1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-04-11 11:31:52 +02:00

Merge pull request #402 from vcmi/feature/multiplayer

Refactoring of all pre-gameplay UI and networking code.
This will break some things, but I'll be able to fix them without constant rebasing.
This commit is contained in:
ArseniyShestakov 2018-04-05 23:29:27 +07:00 committed by GitHub
commit 1a6f456ac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
93 changed files with 8888 additions and 7987 deletions

View File

@ -95,7 +95,7 @@ void CCallback::endTurn()
{
logGlobal->trace("Player %d ended his turn.", player.get().getNum());
EndTurn pack;
sendRequest(&pack); //report that we ended turn
sendRequest(&pack);
}
int CCallback::swapCreatures(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
{
@ -174,7 +174,7 @@ int CBattleCallback::battleMakeAction(BattleAction* action)
return 0;
}
int CBattleCallback::sendRequest(const CPack *request)
int CBattleCallback::sendRequest(const CPackForServer * request)
{
int requestID = cl->sendRequest(request, *player);
if(waitTillRealize)
@ -262,8 +262,8 @@ void CCallback::save( const std::string &fname )
void CCallback::sendMessage(const std::string &mess, const CGObjectInstance * currentObject)
{
ASSERT_IF_CALLED_WITH_PLAYER
PlayerMessage pm(*player, mess, currentObject? currentObject->id : ObjectInstanceID(-1));
sendRequest(&(CPackForClient&)pm);
PlayerMessage pm(mess, currentObject? currentObject->id : ObjectInstanceID(-1));
sendRequest(&pm);
}
void CCallback::buildBoat( const IShipyard *obj )
@ -327,14 +327,6 @@ void CCallback::castSpell(const CGHeroInstance *hero, SpellID spellID, const int
sendRequest(&cas);
}
void CCallback::unregisterAllInterfaces()
{
for (auto& pi : cl->playerint)
pi.second->finish();
cl->playerint.clear();
cl->battleints.clear();
}
int CCallback::mergeOrSwapStacks(const CArmedInstance *s1, const CArmedInstance *s2, SlotID p1, SlotID p2)
{
if(s1->getCreature(p1) == s2->getCreature(p2))

View File

@ -78,12 +78,12 @@ public:
virtual void buildBoat(const IShipyard *obj) = 0;
};
struct CPack;
struct CPackForServer;
class CBattleCallback : public IBattleCallback, public CPlayerBattleCallback
{
protected:
int sendRequest(const CPack *request); //returns requestID (that'll be matched to requestID in PackageApplied)
int sendRequest(const CPackForServer * request); //returns requestID (that'll be matched to requestID in PackageApplied)
CClient *cl;
public:
@ -95,6 +95,7 @@ public:
friend class CClient;
};
class CPlayerInterface;
class CCallback : public CPlayerSpecificInfoCallback, public IGameActionCallback, public CBattleCallback
{
public:
@ -114,8 +115,6 @@ public:
void unregisterGameInterface(std::shared_ptr<IGameEventsReceiver> gameEvents);
void unregisterBattleInterface(std::shared_ptr<IBattleEventsReceiver> battleEvents);
void unregisterAllInterfaces(); //stops delivering information about game events to player interfaces -> can be called ONLY after victory/loss
//commands
bool moveHero(const CGHeroInstance *h, int3 dst, bool transit = false) override; //dst must be free, neighbouring tile (this function can move hero only by one tile)
bool teleportHero(const CGHeroInstance *who, const CGTownInstance *where);

View File

@ -20,6 +20,11 @@ GENERAL:
- BLOCK_MAGIC_BELOW - allows blocking spells below particular spell level. HotA cape artifact can be implemented with this
- DESTRUCTION - creature ability for killing extra units after hit, configurable
MULTIPLAYER:
* Loading support. Save from single client could be used to load all clients.
* Restart support. All clients will restart together on same server.
* Hotseat mixed with network game. Multiple colors can be controlled by each client.
SPELLS:
* Implemented cumulative effects for spells

View File

@ -172,6 +172,9 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#if defined(_MSC_VER) && (_MSC_VER == 1900 || _MSC_VER == 1910 || _MSC_VER == 1911)
#define BOOST_NO_CXX11_VARIADIC_TEMPLATES //Variadic templates are buggy in VS2015 and VS2017, so turn this off to avoid compile errors
#endif
#if BOOST_VERSION >= 106600
#define BOOST_ASIO_ENABLE_OLD_SERVICES
#endif
#include <boost/algorithm/string.hpp>
#include <boost/any.hpp>

View File

@ -14,8 +14,10 @@
#include "../lib/VCMI_Lib.h"
const CGameInfo * CGI; //game info for general use
const CGameInfo * CGI;
CClientState * CCS = nullptr;
CServerHandler * CSH;
CGameInfo::CGameInfo()
{

View File

@ -9,7 +9,6 @@
*/
#pragma once
#include "../lib/ConstTransitivePtr.h"
class CModHandler;
@ -30,6 +29,7 @@ class CConsoleHandler;
class CCursorHandler;
class CGameState;
class IMainVideoPlayer;
class CServerHandler;
class CMap;

View File

@ -20,7 +20,8 @@
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileStream.h"
#include "CPreGame.h"
#include "mainmenu/CMainMenu.h"
#include "lobby/CSelectionBase.h"
#include "windows/CCastleInterface.h"
#include "../lib/CConsoleHandler.h"
#include "gui/CCursorHandler.h"
@ -54,6 +55,12 @@
#include "../lib/StringConstants.h"
#include "../lib/CPlayerState.h"
#include "gui/CAnimation.h"
#include "../lib/serializer/Connection.h"
#include "CServerHandler.h"
#include <boost/asio.hpp>
#include "mainmenu/CPrologEpilogVideo.h"
#ifdef VCMI_WINDOWS
#include "SDL_syswm.h"
@ -75,7 +82,6 @@ namespace bfs = boost::filesystem;
std::string NAME_AFFIX = "client";
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CGuiHandler GH;
static CClient *client = nullptr;
int preferredDriverIndex = -1;
SDL_Window * mainWindow = nullptr;
@ -91,7 +97,6 @@ SDL_Surface *screen = nullptr, //main screen surface
std::queue<SDL_Event> events;
boost::mutex eventsM;
CondSh<bool> serverAlive(false);
static po::variables_map vm;
//static bool setResolution = false; //set by event handling thread after resolution is adjusted
@ -102,9 +107,6 @@ static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayInde
void dispose();
void playIntro();
static void mainLoop();
//void requestChangingResolution();
void startGame(StartInfo * options, CConnection *serv = nullptr);
void endGame();
#ifndef VCMI_WINDOWS
#ifndef _GNU_SOURCE
@ -113,54 +115,6 @@ void endGame();
#include <getopt.h>
#endif
void startTestMap(const std::string &mapname)
{
StartInfo si;
si.mapname = mapname;
si.mode = StartInfo::NEW_GAME;
for (int i = 0; i < 8; i++)
{
PlayerSettings &pset = si.playerInfos[PlayerColor(i)];
pset.color = PlayerColor(i);
pset.name = CGI->generaltexth->allTexts[468];//Computer
pset.playerID = PlayerSettings::PLAYER_AI;
pset.compOnly = true;
pset.castle = 0;
pset.hero = -1;
pset.heroPortrait = -1;
pset.handicap = PlayerSettings::NO_HANDICAP;
}
while(GH.topInt())
GH.popIntTotally(GH.topInt());
startGame(&si);
}
void startGameFromFile(const bfs::path &fname)
{
StartInfo si;
try //attempt retrieving start info from given file
{
if(fname.empty() || !bfs::exists(fname))
throw std::runtime_error("Startfile \"" + fname.string() + "\" does not exist!");
CLoadFile out(fname);
if (!out.sfile || !*out.sfile)
throw std::runtime_error("Cannot read from startfile \"" + fname.string() +"\"!");
out >> si;
}
catch(std::exception &e)
{
logGlobal->error("Failed to start from the file: %s. Error: %s. Falling back to main menu.", fname, e.what());
GH.curInt = CGPreGame::create();
return;
}
while(GH.topInt())
GH.popIntTotally(GH.topInt());
startGame(&si);
}
void init()
{
CStopWatch tmh, pomtime;
@ -243,15 +197,15 @@ int main(int argc, char * argv[])
("version,v", "display version information and exit")
("disable-shm", "force disable shared memory usage")
("enable-shm-uuid", "use UUID for shared memory identifier")
("start", po::value<bfs::path>(), "starts game from saved StartInfo file")
("testmap", po::value<std::string>(), "")
("testsave", po::value<std::string>(), "")
("spectate,s", "enable spectator interface for AI-only games")
("spectate-ignore-hero", "wont follow heroes on adventure map")
("spectate-hero-speed", po::value<int>(), "hero movement speed on adventure map")
("spectate-battle-speed", po::value<int>(), "battle animation speed for spectator")
("spectate-skip-battle", "skip battles in spectator view")
("spectate-skip-battle-result", "skip battle result window")
("onlyAI", "runs without human player, all players will be default AI")
("onlyAI", "allow to run without human player, all players will be default AI")
("headless", "runs without GUI, implies --onlyAI")
("ai", po::value<std::vector<std::string>>(), "AI to be used for the player, can be specified several times for the consecutive players")
("oneGoodAI", "puts one default AI and the rest will be EmptyAI")
@ -259,12 +213,6 @@ int main(int argc, char * argv[])
("disable-video", "disable video player")
("nointro,i", "skips intro movies")
("donotstartserver,d","do not attempt to start server and just connect to it instead server")
("loadserver","specifies we are the multiplayer server for loaded games")
("loadnumplayers",po::value<int>(),"specifies the number of players connecting to a multiplayer game")
("loadhumanplayerindices",po::value<std::vector<int>>(),"Indexes of human players (0=Red, etc.)")
("loadplayer", po::value<int>(),"specifies which player we are in multiplayer loaded games (0=Red, etc.)")
("loadserverip",po::value<std::string>(),"IP for loaded game server")
("loadserverport",po::value<std::string>(),"port for loaded game server")
("serverport", po::value<si64>(), "override port specified in config file")
("saveprefix", po::value<std::string>(), "prefix for auto save files")
("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
@ -317,6 +265,17 @@ int main(int argc, char * argv[])
session["headless"].Bool() = true;
session["onlyai"].Bool() = true;
}
else if(vm.count("spectate"))
{
session["spectate"].Bool() = true;
session["spectate-ignore-hero"].Bool() = vm.count("spectate-ignore-hero");
session["spectate-skip-battle"].Bool() = vm.count("spectate-skip-battle");
session["spectate-skip-battle-result"].Bool() = vm.count("spectate-skip-battle-result");
if(vm.count("spectate-hero-speed"))
session["spectate-hero-speed"].Integer() = vm["spectate-hero-speed"].as<int>();
if(vm.count("spectate-battle-speed"))
session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as<int>();
}
// Server settings
session["donotstartserver"].Bool() = vm.count("donotstartserver");
@ -441,6 +400,7 @@ int main(int argc, char * argv[])
CCS = new CClientState();
CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
CSH = new CServerHandler();
// Initialize video
#ifdef DISABLE_VIDEO
CCS->videoh = new CEmptyVideoPlayer();
@ -453,15 +413,17 @@ int main(int argc, char * argv[])
logGlobal->info("\tInitializing video: %d ms", pomtime.getDiff());
//initializing audio
CCS->soundh = new CSoundHandler();
CCS->soundh->init();
CCS->soundh->setVolume(settings["general"]["sound"].Float());
CCS->musich = new CMusicHandler();
CCS->musich->init();
CCS->musich->setVolume(settings["general"]["music"].Float());
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
if(!settings["session"]["headless"].Bool())
{
//initializing audio
CCS->soundh = new CSoundHandler();
CCS->soundh->init();
CCS->soundh->setVolume(settings["general"]["sound"].Float());
CCS->musich = new CMusicHandler();
CCS->musich->init();
CCS->musich->setVolume(settings["general"]["music"].Float());
logGlobal->info("Initializing screen and sound handling: %d ms", pomtime.getDiff());
}
#ifdef __APPLE__
// Ctrl+click should be treated as a right click on Mac OS X
SDL_SetHint(SDL_HINT_MAC_CTRL_CLICK_EMULATE_RIGHT_CLICK, "1");
@ -500,41 +462,21 @@ int main(int argc, char * argv[])
session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
session["aiSolo"].Bool() = false;
bfs::path fileToStartFrom; //none by default
if(vm.count("start"))
fileToStartFrom = vm["start"].as<bfs::path>();
if(vm.count("testmap"))
{
session["testmap"].String() = vm["testmap"].as<std::string>();
session["onlyai"].Bool() = true;
boost::thread(&CServerHandler::debugStartTest, CSH, session["testmap"].String(), false);
}
session["spectate"].Bool() = vm.count("spectate");
if(session["spectate"].Bool())
else if(vm.count("testsave"))
{
session["spectate-ignore-hero"].Bool() = vm.count("spectate-ignore-hero");
session["spectate-skip-battle"].Bool() = vm.count("spectate-skip-battle");
session["spectate-skip-battle-result"].Bool() = vm.count("spectate-skip-battle-result");
if(vm.count("spectate-hero-speed"))
session["spectate-hero-speed"].Integer() = vm["spectate-hero-speed"].as<int>();
if(vm.count("spectate-battle-speed"))
session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as<int>();
}
if(!session["testmap"].isNull())
{
startTestMap(session["testmap"].String());
session["testsave"].String() = vm["testsave"].as<std::string>();
session["onlyai"].Bool() = true;
boost::thread(&CServerHandler::debugStartTest, CSH, session["testsave"].String(), true);
}
else
{
if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
else
{
if(!fileToStartFrom.empty())
{
logGlobal->warn("Warning: cannot find given file to start from (%s). Falling back to main menu.", fileToStartFrom.string());
}
GH.curInt = CGPreGame::create(); //will set CGP pointer to itself
}
GH.curInt = CMainMenu::create();
}
if(!settings["session"]["headless"].Bool())
@ -614,8 +556,8 @@ void processCommand(const std::string &message)
}
else
{
if(client && client->erm)
client->erm->executeUserCommand(message);
if(CSH->client && CSH->client->erm)
CSH->client->erm->executeUserCommand(message);
std::cout << "erm>";
}
}
@ -665,21 +607,21 @@ void processCommand(const std::string &message)
}
else if(cn=="save")
{
if(!client)
if(!CSH->client)
{
std::cout << "Game in not active";
return;
}
std::string fname;
readed >> fname;
client->save(fname);
CSH->client->save(fname);
}
// else if(cn=="load")
// {
// // TODO: this code should end the running game and manage to call startGame instead
// std::string fname;
// readed >> fname;
// client->loadGame(fname);
// CSH->client->loadGame(fname);
// }
else if(message=="convert txt")
{
@ -861,22 +803,6 @@ void processCommand(const std::string &message)
logGlobal->info("Option %s disabled!", what);
}
}
else if(cn == "sinfo")
{
std::string fname;
readed >> fname;
if(fname.size() && SEL)
{
CSaveFile out(fname);
out << SEL->sInfo;
}
}
else if(cn == "start")
{
std::string fname;
readed >> fname;
startGameFromFile(fname);
}
else if(cn == "unlock")
{
std::string mxname;
@ -935,8 +861,8 @@ void processCommand(const std::string &message)
{
YourTurn yt;
yt.player = player;
yt.daysWithoutCastle = client->getPlayer(player)->daysWithoutCastle;
yt.applyCl(client);
yt.daysWithoutCastle = CSH->client->getPlayer(player)->daysWithoutCastle;
yt.applyCl(CSH->client);
};
Settings session = settings.write["session"];
@ -947,7 +873,7 @@ void processCommand(const std::string &message)
else if(cn == "gosolo")
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
if(!client)
if(!CSH->client)
{
std::cout << "Game in not active";
return;
@ -955,23 +881,23 @@ void processCommand(const std::string &message)
PlayerColor color;
if(session["aiSolo"].Bool())
{
for(auto & elem : client->gameState()->players)
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human)
client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
}
}
else
{
color = LOCPLINT->playerID;
removeGUI();
for(auto & elem : client->gameState()->players)
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human)
{
auto AiToGive = client->aiNameForPlayer(*client->getPlayerSettings(elem.first), false);
auto AiToGive = CSH->client->aiNameForPlayer(*CSH->client->getPlayerSettings(elem.first), false);
logNetwork->info("Player %s will be lead by %s", elem.first, AiToGive);
client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
CSH->client->installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), elem.first);
}
}
GH.totalRedraw();
@ -986,7 +912,7 @@ void processCommand(const std::string &message)
boost::to_lower(colorName);
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
if(!client)
if(!CSH->client)
{
std::cout << "Game in not active";
return;
@ -994,7 +920,7 @@ void processCommand(const std::string &message)
PlayerColor color;
if(LOCPLINT)
color = LOCPLINT->playerID;
for(auto & elem : client->gameState()->players)
for(auto & elem : CSH->client->gameState()->players)
{
if(elem.second.human || (colorName.length() &&
elem.first.getNum() != vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, colorName)))
@ -1003,7 +929,7 @@ void processCommand(const std::string &message)
}
removeGUI();
client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
CSH->client->installNewPlayerInterface(std::make_shared<CPlayerInterface>(elem.first), elem.first);
}
GH.totalRedraw();
if(color != PlayerColor::NEUTRAL)
@ -1290,46 +1216,59 @@ static void handleEvent(SDL_Event & ev)
{
switch(ev.user.code)
{
case FORCE_QUIT:
case EUserEvent::FORCE_QUIT:
{
handleQuit(false);
return;
}
break;
case RETURN_TO_MAIN_MENU:
case EUserEvent::RETURN_TO_MAIN_MENU:
{
endGame();
GH.curInt = CGPreGame::create();
CSH->endGameplay();
GH.curInt = CMainMenu::create();
GH.defActionsDef = 63;
}
break;
case RESTART_GAME:
case EUserEvent::RESTART_GAME:
{
StartInfo si = *client->getStartInfo(true);
si.seedToBeUsed = 0; //server gives new random generator seed if 0
endGame();
startGame(&si);
CSH->sendStartGame();
}
break;
case PREPARE_RESTART_CAMPAIGN:
case EUserEvent::CAMPAIGN_START_SCENARIO:
{
auto si = reinterpret_cast<StartInfo *>(ev.user.data1);
endGame();
startGame(si);
CSH->endGameplay();
GH.curInt = CMainMenu::create();
auto ourCampaign = std::shared_ptr<CCampaignState>(reinterpret_cast<CCampaignState *>(ev.user.data1));
auto & epilogue = ourCampaign->camp->scenarios[ourCampaign->mapsConquered.back()].epilog;
auto finisher = [=]()
{
if(ourCampaign->mapsRemaining.size())
{
CMM->openCampaignLobby(ourCampaign);
}
};
if(epilogue.hasPrologEpilog)
{
GH.pushInt(new CPrologEpilogVideo(epilogue, finisher));
}
else
{
finisher();
}
}
break;
case RETURN_TO_MENU_LOAD:
endGame();
CGPreGame::create();
case EUserEvent::RETURN_TO_MENU_LOAD:
CSH->endGameplay();
CMainMenu::create();
GH.defActionsDef = 63;
CGP->update();
CGP->menu->switchToTab(vstd::find_pos(CGP->menu->menuNameToEntry, "load"));
GH.curInt = CGP;
CMM->update();
CMM->menu->switchToTab(vstd::find_pos(CMM->menu->menuNameToEntry, "load"));
GH.curInt = CMM;
break;
case FULLSCREEN_TOGGLED:
case EUserEvent::FULLSCREEN_TOGGLED:
fullScreenChanged();
break;
case INTERFACE_CHANGED:
case EUserEvent::INTERFACE_CHANGED:
if(LOCPLINT)
LOCPLINT->updateAmbientSounds();
break;
@ -1360,7 +1299,7 @@ static void handleEvent(SDL_Event & ev)
static void mainLoop()
{
SettingsListener resChanged = settings.listen["video"]["fullscreen"];
resChanged([](const JsonNode &newState){ CGuiHandler::pushSDLEvent(SDL_USEREVENT, FULLSCREEN_TOGGLED); });
resChanged([](const JsonNode &newState){ CGuiHandler::pushSDLEvent(SDL_USEREVENT, EUserEvent::FULLSCREEN_TOGGLED); });
inGuiThread.reset(new bool(true));
GH.mainFPSmng->init();
@ -1374,69 +1313,18 @@ static void mainLoop()
handleEvent(ev);
}
CSH->applyPacksOnLobbyScreen();
GH.renderFrame();
}
}
void startGame(StartInfo * options, CConnection *serv)
{
if(!settings["session"]["donotstartserver"].Bool())
{
serverAlive.waitWhileTrue();
serverAlive.setn(true);
}
if(settings["session"]["onlyai"].Bool())
{
auto ais = vm.count("ai") ? vm["ai"].as<std::vector<std::string>>() : std::vector<std::string>();
int i = 0;
for(auto & elem : options->playerInfos)
{
elem.second.playerID = PlayerSettings::PLAYER_AI;
if(i < ais.size())
elem.second.name = ais[i++];
}
}
client = new CClient();
CPlayerInterface::howManyPeople = 0;
switch(options->mode) //new game
{
case StartInfo::NEW_GAME:
case StartInfo::CAMPAIGN:
client->newGame(serv, options);
break;
case StartInfo::LOAD_GAME:
std::string fname = options->mapname;
boost::algorithm::erase_last(fname,".vlgm1");
if(!vm.count("loadplayer"))
client->loadGame(fname);
else
client->loadGame(fname,vm.count("loadserver"),vm.count("loadhumanplayerindices") ? vm["loadhumanplayerindices"].as<std::vector<int>>() : std::vector<int>(),vm.count("loadnumplayers") ? vm["loadnumplayers"].as<int>() : 1,vm["loadplayer"].as<int>(),vm.count("loadserverip") ? vm["loadserverip"].as<std::string>() : "", vm.count("loadserverport") ? vm["loadserverport"].as<ui16>() : CServerHandler::getDefaultPort());
break;
}
{
TLockGuard _(client->connectionHandlerMutex);
client->connectionHandler = make_unique<boost::thread>(&CClient::run, client);
}
}
void endGame()
{
client->endGame();
vstd::clear_pointer(client);
}
void handleQuit(bool ask)
{
auto quitApplication = []()
{
if(client)
endGame();
if(CSH->client)
CSH->endGameplay();
dispose();
vstd::clear_pointer(console);
boost::this_thread::sleep(boost::posix_time::milliseconds(750));
@ -1450,7 +1338,7 @@ void handleQuit(bool ask)
exit(0);
};
if(client && LOCPLINT && ask)
if(CSH->client && LOCPLINT && ask)
{
CCS->curh->changeGraphic(ECursor::ADVENTURE, 0);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[69], quitApplication, 0);

View File

@ -9,7 +9,6 @@
*/
#pragma once
#include <SDL_render.h>
#include "../lib/CondSh.h"
extern SDL_Texture * screenTexture;
@ -20,7 +19,5 @@ extern SDL_Surface *screen; // main screen surface
extern SDL_Surface *screen2; // and hlp surface (used to store not-active interfaces layer)
extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
extern CondSh<bool> serverAlive; //used to prevent game start from executing if server is already running
void removeGUI();
void handleQuit(bool ask = true);

View File

@ -43,6 +43,20 @@ set(client_SRCS
windows/InfoWindows.cpp
windows/QuickRecruitmentWindow.cpp
mainmenu/CMainMenu.cpp
mainmenu/CCampaignScreen.cpp
mainmenu/CreditsScreen.cpp
mainmenu/CPrologEpilogVideo.cpp
lobby/CBonusSelection.cpp
lobby/CSelectionBase.cpp
lobby/CLobbyScreen.cpp
lobby/CSavingScreen.cpp
lobby/CScenarioInfoScreen.cpp
lobby/OptionsTab.cpp
lobby/RandomMapTab.cpp
lobby/SelectionTab.cpp
CBitmapHandler.cpp
CreatureCostBox.cpp
CGameInfo.cpp
@ -51,11 +65,12 @@ set(client_SRCS
CMT.cpp
CMusicHandler.cpp
CPlayerInterface.cpp
CPreGame.cpp
CVideoHandler.cpp
CServerHandler.cpp
Graphics.cpp
mapHandler.cpp
NetPacksClient.cpp
NetPacksLobbyClient.cpp
SDLRWwrapper.cpp
)
@ -100,6 +115,20 @@ set(client_HEADERS
windows/InfoWindows.h
windows/QuickRecruitmentWindow.h
mainmenu/CMainMenu.h
mainmenu/CCampaignScreen.h
mainmenu/CreditsScreen.h
mainmenu/CPrologEpilogVideo.h
lobby/CBonusSelection.h
lobby/CSelectionBase.h
lobby/CLobbyScreen.h
lobby/CSavingScreen.h
lobby/CScenarioInfoScreen.h
lobby/OptionsTab.h
lobby/RandomMapTab.h
lobby/SelectionTab.h
CBitmapHandler.h
CreatureCostBox.h
CGameInfo.h
@ -108,8 +137,8 @@ set(client_HEADERS
CMT.h
CMusicHandler.h
CPlayerInterface.h
CPreGame.h
CVideoHandler.h
CServerHandler.h
Graphics.h
mapHandler.h
resource.h

View File

@ -49,13 +49,16 @@
#include "mapHandler.h"
#include "../lib/CStopWatch.h"
#include "../lib/StartInfo.h"
#include "../lib/CGameState.h"
#include "../lib/CPlayerState.h"
#include "../lib/GameConstants.h"
#include "gui/CGuiHandler.h"
#include "windows/InfoWindows.h"
#include "../lib/UnlockGuard.h"
#include "../lib/CPathfinder.h"
#include <SDL.h>
#include "CServerHandler.h"
// FIXME: only needed for CGameState::mutex
#include "../lib/CGameState.h"
// The macro below is used to mark functions that are called by client when game state changes.
@ -90,8 +93,6 @@ CBattleInterface * CPlayerInterface::battleInt;
enum EMoveState {STOP_MOVE, WAITING_MOVE, CONTINUE_MOVE, DURING_MOVE};
CondSh<EMoveState> stillMoveHero(STOP_MOVE); //used during hero movement
int CPlayerInterface::howManyPeople = 0;
static bool objectBlitOrderSorter(const TerrainTileObject & a, const TerrainTileObject & b)
{
return CMapHandler::compareObjectBlitOrder(a.obj, b.obj);
@ -114,7 +115,6 @@ CPlayerInterface::CPlayerInterface(PlayerColor Player)
logGlobal->trace("\tHuman player interface for player %s being constructed", Player.getStr());
destinationTeleport = ObjectInstanceID();
destinationTeleportPos = int3(-1);
howManyPeople++;
GH.defActionsDef = 0;
LOCPLINT = this;
curAction = nullptr;
@ -139,7 +139,6 @@ CPlayerInterface::~CPlayerInterface()
{
CCS->soundh->ambientStopAllChannels();
logGlobal->trace("\tHuman player interface for player %s being destructed", playerID.getStr());
//howManyPeople--;
delete showingDialog;
delete cingconsole;
if (LOCPLINT == this)
@ -148,9 +147,7 @@ CPlayerInterface::~CPlayerInterface()
void CPlayerInterface::init(std::shared_ptr<CCallback> CB)
{
cb = CB;
if (!towns.size() && !wanderingHeroes.size())
initializeHeroTownList();
initializeHeroTownList();
// always recreate advmap interface to avoid possible memory-corruption bugs
if (adventureInt)
@ -170,7 +167,7 @@ void CPlayerInterface::yourTurn()
std::string prefix = settings["session"]["saveprefix"].String();
if (firstCall)
{
if (howManyPeople == 1)
if(CSH->howManyPlayerInterfaces() == 1)
adventureInt->setPlayer(playerID);
autosaveCount = getLastIndex(prefix + "Autosave_");
@ -192,7 +189,7 @@ void CPlayerInterface::yourTurn()
if (adventureInt->player != playerID)
adventureInt->setPlayer(playerID);
if (howManyPeople > 1) //hot seat message
if (CSH->howManyPlayerInterfaces() > 1) //hot seat message
{
adventureInt->startHotSeatWait(playerID);
@ -1581,45 +1578,20 @@ void CPlayerInterface::objectPropertyChanged(const SetObjectProperty * sop)
void CPlayerInterface::initializeHeroTownList()
{
std::vector<const CGHeroInstance*> allHeroes = cb->getHeroesInfo();
/*
std::vector <const CGHeroInstance *> newWanderingHeroes;
//applying current heroes order to new heroes info
int j;
for (int i = 0; i < wanderingHeroes.size(); i++)
if ((j = vstd::find_pos(allHeroes, wanderingHeroes[i])) >= 0)
if (!allHeroes[j]->inTownGarrison)
{
newWanderingHeroes += allHeroes[j];
allHeroes -= allHeroes[j];
}
//all the rest of new heroes go the end of the list
wanderingHeroes.clear();
wanderingHeroes = newWanderingHeroes;
newWanderingHeroes.clear();*/
for (auto & allHeroe : allHeroes)
if (!allHeroe->inTownGarrison)
wanderingHeroes.push_back(allHeroe);
std::vector<const CGTownInstance*> allTowns = cb->getTownsInfo();
/*
std::vector<const CGTownInstance*> newTowns;
for (int i = 0; i < towns.size(); i++)
if ((j = vstd::find_pos(allTowns, towns[i])) >= 0)
if(!wanderingHeroes.size())
{
std::vector<const CGHeroInstance*> heroes = cb->getHeroesInfo();
for(auto & hero : heroes)
{
newTowns += allTowns[j];
allTowns -= allTowns[j];
if(!hero->inTownGarrison)
wanderingHeroes.push_back(hero);
}
}
towns.clear();
towns = newTowns;
newTowns.clear();*/
for (auto & allTown : allTowns)
towns.push_back(allTown);
if(!towns.size())
towns = cb->getTownsInfo();
if (adventureInt)
if(adventureInt)
adventureInt->updateNextHero(nullptr);
}
@ -1728,7 +1700,7 @@ void CPlayerInterface::update()
return;
//if there are any waiting dialogs, show them
if ((howManyPeople <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
if ((CSH->howManyPlayerInterfaces() <= 1 || makingTurn) && !dialogs.empty() && !showingDialog->get())
{
showingDialog->set(true);
GH.pushInt(dialogs.front());
@ -2227,37 +2199,27 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
waitForAllDialogs(); //wait till all dialogs are displayed and closed
}
--howManyPeople;
if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
if(CSH->howManyPlayerInterfaces() == 1 && !settings["session"]["spectate"].Bool()) //all human players eliminated
{
if (adventureInt)
if(adventureInt)
{
GH.terminate_cond->setn(true);
adventureInt->deactivate();
if (GH.topInt() == adventureInt)
GH.popInt(adventureInt);
delete adventureInt;
adventureInt = nullptr;
vstd::clear_pointer(adventureInt);
}
}
if (cb->getStartInfo()->mode == StartInfo::CAMPAIGN)
if (victoryLossCheckResult.victory() && LOCPLINT == this)
{
// if you lose the campaign go back to the main menu
// campaign wins are handled in proposeNextMission
if (victoryLossCheckResult.loss()) requestReturningToMainMenu();
// end game if current human player has won
requestReturningToMainMenu(true);
}
else
else if(CSH->howManyPlayerInterfaces() == 1 && !settings["session"]["spectate"].Bool())
{
if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
{
requestReturningToMainMenu();
}
else if (victoryLossCheckResult.victory() && LOCPLINT == this) // end game if current human player has won
{
requestReturningToMainMenu();
}
//all human players eliminated
requestReturningToMainMenu(false);
}
if (GH.curInt == this) GH.curInt = nullptr;
@ -2378,7 +2340,7 @@ void CPlayerInterface::acceptTurn()
}
waitWhileDialog();
if (howManyPeople > 1)
if(CSH->howManyPlayerInterfaces() > 1)
adventureInt->startTurn();
adventureInt->heroList.update();
@ -2573,11 +2535,14 @@ void CPlayerInterface::showShipyardDialogOrProblemPopup(const IShipyard *obj)
showShipyardDialog(obj);
}
void CPlayerInterface::requestReturningToMainMenu()
void CPlayerInterface::requestReturningToMainMenu(bool won)
{
sendCustomEvent(RETURN_TO_MAIN_MENU);
CSH->state = EClientState::DISCONNECTING;
CCS->soundh->ambientStopAllChannels();
cb->unregisterAllInterfaces();
if(won && cb->getStartInfo()->campState)
CSH->startCampaignScenario(cb->getStartInfo()->campState);
else
sendCustomEvent(EUserEvent::RETURN_TO_MAIN_MENU);
}
void CPlayerInterface::sendCustomEvent( int code )
@ -2672,7 +2637,7 @@ void CPlayerInterface::playerStartsTurn(PlayerColor player)
GH.popInts(1);
}
if (howManyPeople == 1)
if(CSH->howManyPlayerInterfaces() == 1)
{
GH.curInt = this;
adventureInt->startTurn();
@ -2696,7 +2661,7 @@ void CPlayerInterface::waitForAllDialogs(bool unlockPim)
void CPlayerInterface::proposeLoadingGame()
{
showYesNoDialog(CGI->generaltexth->allTexts[68], [this](){ sendCustomEvent(RETURN_TO_MENU_LOAD); }, 0, false);
showYesNoDialog(CGI->generaltexth->allTexts[68], [this](){ sendCustomEvent(EUserEvent::RETURN_TO_MENU_LOAD); }, 0, false);
}
CPlayerInterface::SpellbookLastSetting::SpellbookLastSetting()

View File

@ -59,19 +59,6 @@ namespace boost
class recursive_mutex;
}
enum
{
/*CHANGE_SCREEN_RESOLUTION = 1,*/
RETURN_TO_MAIN_MENU = 2,
//STOP_CLIENT = 3,
RESTART_GAME = 4,
RETURN_TO_MENU_LOAD,
FULLSCREEN_TOGGLED,
PREPARE_RESTART_CAMPAIGN,
FORCE_QUIT, //quit client without question
INTERFACE_CHANGED
};
/// Central class for managing user interface logic
class CPlayerInterface : public CGameInterface, public IUpdateable
{
@ -88,7 +75,6 @@ public:
int firstCall; // -1 - just loaded game; 1 - just started game; 0 otherwise
int autosaveCount;
static const int SAVES_COUNT = 5;
static int howManyPeople;
CCastleInterface * castleInt; //nullptr if castle window isn't opened
static CBattleInterface * battleInt; //nullptr if no battle
@ -247,7 +233,7 @@ public:
void acceptTurn(); //used during hot seat after your turn message is close
void tryDiggging(const CGHeroInstance *h);
void showShipyardDialogOrProblemPopup(const IShipyard *obj); //obj may be town or shipyard;
void requestReturningToMainMenu();
void requestReturningToMainMenu(bool won);
void sendCustomEvent(int code);
void proposeLoadingGame();

File diff suppressed because it is too large Load Diff

View File

@ -1,650 +0,0 @@
/*
* CPreGame.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 "../lib/StartInfo.h"
#include "../lib/FunctionList.h"
#include "../lib/mapping/CMapInfo.h"
#include "../lib/rmg/CMapGenerator.h"
#include "windows/CWindowObject.h"
class CMapInfo;
class CMusicHandler;
class CMapHeader;
class CCampaignHeader;
class CTextInput;
class CCampaign;
class CGStatusBar;
class CTextBox;
class CCampaignState;
class CConnection;
class JsonNode;
class CMapGenOptions;
class CRandomMapTab;
struct CPackForSelectionScreen;
struct PlayerInfo;
class CMultiLineLabel;
class CToggleButton;
class CToggleGroup;
class CTabbedInt;
class IImage;
class CAnimation;
class CAnimImage;
class CButton;
class CLabel;
class CSlider;
namespace boost{ class thread; class recursive_mutex;}
enum ESortBy{_playerAm, _size, _format, _name, _viccon, _loscon, _numOfMaps, _fileName}; //_numOfMaps is for campaigns
/// Class which handles map sorting by different criteria
class mapSorter
{
public:
ESortBy sortBy;
bool operator()(const CMapInfo *aaa, const CMapInfo *bbb);
mapSorter(ESortBy es):sortBy(es){};
};
/// The main menu screens listed in the EState enum
class CMenuScreen : public CIntObject
{
const JsonNode& config;
CTabbedInt *tabs;
CPicture * background;
std::vector<CPicture*> images;
CIntObject *createTab(size_t index);
public:
std::vector<std::string> menuNameToEntry;
enum EState { //where are we?
mainMenu, newGame, loadGame, campaignMain, saveGame, scenarioInfo, campaignList
};
enum EGameMode {
SINGLE_PLAYER = 0, MULTI_HOT_SEAT, MULTI_NETWORK_HOST, MULTI_NETWORK_GUEST, SINGLE_CAMPAIGN
};
CMenuScreen(const JsonNode& configNode);
void showAll(SDL_Surface * to) override;
void show(SDL_Surface * to) override;
void activate() override;
void deactivate() override;
void switchToTab(size_t index);
};
class CMenuEntry : public CIntObject
{
std::vector<CPicture*> images;
std::vector<CButton*> buttons;
CButton* createButton(CMenuScreen* parent, const JsonNode& button);
public:
CMenuEntry(CMenuScreen* parent, const JsonNode &config);
};
class CreditsScreen : public CIntObject
{
int positionCounter;
CMultiLineLabel* credits;
public:
CreditsScreen();
void show(SDL_Surface * to) override;
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
};
/// Implementation of the chat box
class CChatBox : public CIntObject
{
public:
CTextBox *chatHistory;
CTextInput *inputBox;
CChatBox(const Rect &rect);
void keyPressed(const SDL_KeyboardEvent & key) override;
void addNewMessage(const std::string &text);
};
class InfoCard : public CIntObject
{
CAnimImage * victory, * loss, *sizes;
std::shared_ptr<CAnimation> sFlags;
public:
CPicture *bg;
bool network;
bool chatOn; //if chat is shown, then description is hidden
CTextBox *mapDescription;
CChatBox *chat;
CPicture *playerListBg;
CToggleGroup *difficulty;
void changeSelection(const CMapInfo *to);
void showAll(SDL_Surface * to) override;
void clickRight(tribool down, bool previousState) override;
void showTeamsPopup();
void toggleChat();
void setChat(bool activateChat);
InfoCard(bool Network = false);
~InfoCard();
};
/// The selection tab which is shown at the map selection screen
class SelectionTab : public CIntObject
{
private:
std::shared_ptr<CAnimation> formatIcons;
void parseMaps(const std::unordered_set<ResourceID> &files);
void parseGames(const std::unordered_set<ResourceID> &files, CMenuScreen::EGameMode gameMode);
std::unordered_set<ResourceID> getFiles(std::string dirURI, int resType);
void parseCampaigns(const std::unordered_set<ResourceID> & files );
CMenuScreen::EState tabType;
public:
int positions; //how many entries (games/maps) can be shown
CPicture *bg; //general bg image
CSlider *slider;
std::vector<CMapInfo> allItems;
std::vector<CMapInfo*> curItems;
size_t selectionPos;
std::function<void(CMapInfo *)> onSelect;
ESortBy sortingBy;
ESortBy generalSortingBy;
bool ascending;
CTextInput *txt;
void filter(int size, bool selectFirst = false); //0 - all
void select(int position); //position: <0 - positions> position on the screen
void selectAbs(int position); //position: absolute position in curItems vector
int getPosition(int x, int y); //convert mouse coords to entry position; -1 means none
void sliderMove(int slidPos);
void sortBy(int criteria);
void sort();
void printMaps(SDL_Surface *to);
int getLine();
void selectFName(std::string fname);
const CMapInfo * getSelectedMapInfo() const;
void showAll(SDL_Surface * to) override;
void clickLeft(tribool down, bool previousState) override;
void keyPressed(const SDL_KeyboardEvent & key) override;
void onDoubleClick() override;
SelectionTab(CMenuScreen::EState Type, const std::function<void(CMapInfo *)> &OnSelect, CMenuScreen::EGameMode GameMode = CMenuScreen::SINGLE_PLAYER);
~SelectionTab();
};
/// The options tab which is shown at the map selection phase.
class OptionsTab : public CIntObject
{
CPicture *bg;
public:
enum SelType {TOWN, HERO, BONUS};
struct CPlayerSettingsHelper
{
const PlayerSettings & settings;
const SelType type;
CPlayerSettingsHelper(const PlayerSettings & settings, SelType type):
settings(settings),
type(type)
{}
/// visible image settings
size_t getImageIndex();
std::string getImageName();
std::string getName(); /// name visible in options dialog
std::string getTitle(); /// title in popup box
std::string getSubtitle(); /// popup box subtitle
std::string getDescription();/// popup box description, not always present
};
class CPregameTooltipBox : public CWindowObject, public CPlayerSettingsHelper
{
void genHeader();
void genTownWindow();
void genHeroWindow();
void genBonusWindow();
public:
CPregameTooltipBox(CPlayerSettingsHelper & helper);
};
struct SelectedBox : public CIntObject, public CPlayerSettingsHelper //img with current town/hero/bonus
{
CAnimImage * image;
CLabel *subtitle;
SelectedBox(Point position, PlayerSettings & settings, SelType type);
void clickRight(tribool down, bool previousState) override;
void update();
};
struct PlayerOptionsEntry : public CIntObject
{
PlayerInfo &pi;
PlayerSettings &s;
CPicture *bg;
CButton *btns[6]; //left and right for town, hero, bonus
CButton *flag;
SelectedBox *town;
SelectedBox *hero;
SelectedBox *bonus;
enum {HUMAN_OR_CPU, HUMAN, CPU} whoCanPlay;
PlayerOptionsEntry(OptionsTab *owner, PlayerSettings &S);
void selectButtons(); //hides unavailable buttons
void showAll(SDL_Surface * to) override;
void update();
};
CSlider *turnDuration;
std::set<int> usedHeroes;
struct PlayerToRestore
{
PlayerColor color;
int id;
void reset() { id = -1; color = PlayerColor::CANNOT_DETERMINE; }
PlayerToRestore(){ reset(); }
} playerToRestore;
std::map<PlayerColor, PlayerOptionsEntry *> entries; //indexed by color
void nextCastle(PlayerColor player, int dir); //dir == -1 or +1
void nextHero(PlayerColor player, int dir); //dir == -1 or +1
void nextBonus(PlayerColor player, int dir); //dir == -1 or +1
void setTurnLength(int npos);
void flagPressed(PlayerColor player);
void recreate();
OptionsTab();
~OptionsTab();
void showAll(SDL_Surface * to) override;
int nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir );
bool canUseThisHero(PlayerColor player, int ID );
};
/// The random map tab shows options for generating a random map.
class CRandomMapTab : public CIntObject
{
public:
CRandomMapTab();
void showAll(SDL_Surface * to) override;
void updateMapInfo();
CFunctionList<void (const CMapInfo *)> & getMapInfoChanged();
const CMapInfo * getMapInfo() const;
const CMapGenOptions & getMapGenOptions() const;
void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
private:
void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const;
void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const;
void deactivateButtonsFrom(CToggleGroup * group, int startId);
void validatePlayersCnt(int playersCnt);
void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
std::vector<int> getPossibleMapSizes();
CPicture * bg;
CToggleButton * twoLevelsBtn;
CToggleGroup * mapSizeBtnGroup, * playersCntGroup, * teamsCntGroup, * compOnlyPlayersCntGroup,
* compOnlyTeamsCntGroup, * waterContentGroup, * monsterStrengthGroup;
CButton * showRandMaps;
CMapGenOptions mapGenOptions;
std::unique_ptr<CMapInfo> mapInfo;
CFunctionList<void(const CMapInfo *)> mapInfoChanged;
};
/// Interface for selecting a map.
class ISelectionScreenInfo
{
public:
CMenuScreen::EGameMode gameMode;
CMenuScreen::EState screenType; //new/save/load#Game
const CMapInfo *current;
StartInfo sInfo;
std::map<ui8, std::string> playerNames; // id of player <-> player name; 0 is reserved as ID of AI "players"
ISelectionScreenInfo(const std::map<ui8, std::string> *Names = nullptr);
virtual ~ISelectionScreenInfo();
virtual void update(){};
virtual void propagateOptions() {};
virtual void postRequest(ui8 what, ui8 dir) {};
virtual void postChatMessage(const std::string &txt){};
void setPlayer(PlayerSettings &pset, ui8 player);
void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader);
ui8 getIdOfFirstUnallocatedPlayer(); //returns 0 if none
bool isGuest() const;
bool isHost() const;
};
/// The actual map selection screen which consists of the options and selection tab
class CSelectionScreen : public CIntObject, public ISelectionScreenInfo
{
bool bordered;
public:
CPicture *bg; //general bg image
InfoCard *card;
OptionsTab *opt;
CRandomMapTab * randMapTab;
CButton *start, *back;
SelectionTab *sel;
CIntObject *curTab;
boost::thread *serverHandlingThread;
boost::recursive_mutex *mx;
std::list<CPackForSelectionScreen *> upcomingPacks; //protected by mx
CConnection *serv; //connection to server, used in MP mode
bool ongoingClosing;
ui8 myNameID; //used when networking - otherwise all player are "mine"
CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EGameMode GameMode = CMenuScreen::SINGLE_PLAYER, const std::map<ui8, std::string> * Names = nullptr, const std::string & Address = "", const ui16 Port = 0);
~CSelectionScreen();
void toggleTab(CIntObject *tab);
void changeSelection(const CMapInfo *to);
void startCampaign();
void startScenario();
void difficultyChange(int to);
void handleConnection();
void processPacks();
void setSInfo(const StartInfo &si);
void update() override;
void propagateOptions() override;
void postRequest(ui8 what, ui8 dir) override;
void postChatMessage(const std::string &txt) override;
void propagateNames();
void showAll(SDL_Surface *to) override;
};
/// Save game screen
class CSavingScreen : public CSelectionScreen
{
public:
const CMapInfo *ourGame;
CSavingScreen(bool hotseat = false);
~CSavingScreen();
};
/// Scenario information screen shown during the game (thus not really a "pre-game" but fits here anyway)
class CScenarioInfo : public CIntObject, public ISelectionScreenInfo
{
public:
CButton *back;
InfoCard *card;
OptionsTab *opt;
CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *startInfo);
~CScenarioInfo();
};
/// Multiplayer mode
class CMultiMode : public CIntObject
{
public:
CPicture *bg;
CTextInput *txt;
CButton *btns[7]; //0 - hotseat, 6 - cancel
CGStatusBar *bar;
CMultiMode();
void openHotseat();
void hostTCP();
void joinTCP();
};
/// Hot seat player window
class CHotSeatPlayers : public CIntObject
{
CPicture *bg;
CTextBox *title;
CTextInput* txt[8];
CButton *ok, *cancel;
CGStatusBar *bar;
void onChange(std::string newText);
void enterSelectionScreen();
public:
CHotSeatPlayers(const std::string &firstPlayer);
};
class CPrologEpilogVideo : public CWindowObject
{
CCampaignScenario::SScenarioPrologEpilog spe;
int positionCounter;
int voiceSoundHandle;
std::function<void()> exitCb;
CMultiLineLabel * text;
public:
CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback);
void clickLeft(tribool down, bool previousState) override;
void show(SDL_Surface * to) override;
};
/// Campaign screen where you can choose one out of three starting bonuses
class CBonusSelection : public CIntObject
{
public:
CBonusSelection(const std::string & campaignFName);
CBonusSelection(std::shared_ptr<CCampaignState> _ourCampaign);
~CBonusSelection();
void showAll(SDL_Surface * to) override;
void show(SDL_Surface * to) override;
private:
struct SCampPositions
{
std::string campPrefix;
int colorSuffixLength;
struct SRegionDesc
{
std::string infix;
int xpos, ypos;
};
std::vector<SRegionDesc> regions;
};
class CRegion : public CIntObject
{
CBonusSelection * owner;
SDL_Surface* graphics[3]; //[0] - not selected, [1] - selected, [2] - striped
bool accessible; //false if region should be striped
bool selectable; //true if region should be selectable
int myNumber; //number of region
public:
std::string rclickText;
CRegion(CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber);
~CRegion();
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
void show(SDL_Surface * to) override;
};
void init();
void loadPositionsOfGraphics();
void updateStartButtonState(int selected = -1); //-1 -- no bonus is selected
void updateBonusSelection();
void updateCampaignState();
// Event handlers
void goBack();
void startMap();
void restartMap();
void selectMap(int mapNr, bool initialSelect);
void selectBonus(int id);
void increaseDifficulty();
void decreaseDifficulty();
// GUI components
SDL_Surface * background;
CButton * startB, * restartB, * backB;
CTextBox * campaignDescription, * mapDescription;
std::vector<SCampPositions> campDescriptions;
std::vector<CRegion *> regions;
CRegion * highlightedRegion;
CToggleGroup * bonuses;
std::array<CAnimImage *, 5> diffPics; //pictures of difficulties, user-selectable (or not if campaign locks this)
CButton * diffLb, * diffRb; //buttons for changing difficulty
CAnimImage * sizes;//icons of map sizes
std::shared_ptr<CAnimation> sFlags;
// Data
std::shared_ptr<CCampaignState> ourCampaign;
int selectedMap;
boost::optional<int> selectedBonus;
StartInfo startInfo;
std::unique_ptr<CMapHeader> ourHeader;
};
/// Campaign selection screen
class CCampaignScreen : public CIntObject
{
public:
enum CampaignStatus {DEFAULT = 0, ENABLED, DISABLED, COMPLETED}; // the status of the campaign
private:
/// A button which plays a video when you move the mouse cursor over it
class CCampaignButton : public CIntObject
{
private:
CLabel *hoverLabel;
CampaignStatus status;
std::string campFile; // the filename/resourcename of the campaign
std::string video; // the resource name of the video
std::string hoverText;
void clickLeft(tribool down, bool previousState) override;
void hover(bool on) override;
public:
CCampaignButton(const JsonNode &config );
void show(SDL_Surface * to) override;
};
std::vector<CCampaignButton*> campButtons;
std::vector<CPicture*> images;
CButton* createExitButton(const JsonNode& button);
public:
enum CampaignSet {ROE, AB, SOD, WOG};
CCampaignScreen(const JsonNode &config);
void showAll(SDL_Surface *to) override;
};
/// Manages the configuration of pregame GUI elements like campaign screen, main menu, loading screen,...
class CGPreGameConfig
{
public:
static CGPreGameConfig & get();
const JsonNode & getConfig() const;
const JsonNode & getCampaigns() const;
private:
CGPreGameConfig();
const JsonNode campaignSets;
const JsonNode config;
};
/// Handles background screen, loads graphics for victory/loss condition and random town or hero selection
class CGPreGame : public CIntObject, public IUpdateable
{
void loadGraphics();
void disposeGraphics();
CGPreGame(); //Use createIfNotPresent
public:
CMenuScreen * menu;
std::shared_ptr<CAnimation> victoryIcons, lossIcons;
~CGPreGame();
void update() override;
void openSel(CMenuScreen::EState type, CMenuScreen::EGameMode gameMode = CMenuScreen::SINGLE_PLAYER);
void openCampaignScreen(std::string name);
static CGPreGame * create();
void removeFromGui();
static void showLoadingScreen(std::function<void()> loader);
};
class CLoadingScreen : public CWindowObject
{
boost::thread loadingThread;
std::string getBackground();
public:
CLoadingScreen(std::function<void()> loader);
~CLoadingScreen();
void showAll(SDL_Surface *to) override;
};
/// Simple window to enter the server's address.
class CSimpleJoinScreen : public CIntObject
{
CPicture * bg;
CTextBox * title;
CButton * ok, * cancel;
CGStatusBar * bar;
CTextInput * address;
CTextInput * port;
void enterSelectionScreen(CMenuScreen::EGameMode mode);
void onChange(const std::string & newText);
public:
CSimpleJoinScreen(CMenuScreen::EGameMode mode);
};
extern ISelectionScreenInfo *SEL;
extern CGPreGame *CGP;

674
client/CServerHandler.cpp Normal file
View File

@ -0,0 +1,674 @@
/*
* CServerHandler.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 "CServerHandler.h"
#include "Client.h"
#include "CGameInfo.h"
#include "CPlayerInterface.h"
#include "gui/CGuiHandler.h"
#include "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h"
#include "mainmenu/CMainMenu.h"
#ifndef VCMI_ANDROID
#include "../lib/Interprocess.h"
#endif
#include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CThreadHelper.h"
#include "../lib/NetPacks.h"
#include "../lib/StartInfo.h"
#include "../lib/VCMIDirs.h"
#include "../lib/mapping/CCampaignHandler.h"
#include "../lib/mapping/CMap.h"
#include "../lib/mapping/CMapInfo.h"
#include "../lib/mapObjects/MiscObjects.h"
#include "../lib/rmg/CMapGenOptions.h"
#include "../lib/registerTypes/RegisterTypes.h"
#include "../lib/serializer/Connection.h"
#include "../lib/serializer/CMemorySerializer.h"
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
template<typename T> class CApplyOnLobby;
class CBaseForLobbyApply
{
public:
virtual bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const = 0;
virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const = 0;
virtual ~CBaseForLobbyApply(){};
template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
{
return new CApplyOnLobby<U>();
}
};
template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
{
public:
bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
{
T * ptr = static_cast<T *>(pack);
logNetwork->trace("\tImmidiately apply on lobby: %s", typeList.getTypeInfo(ptr)->name());
return ptr->applyOnLobbyHandler(handler);
}
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override
{
T * ptr = static_cast<T *>(pack);
logNetwork->trace("\tApply on lobby from queue: %s", typeList.getTypeInfo(ptr)->name());
ptr->applyOnLobbyScreen(lobby, handler);
}
};
template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
{
public:
bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
{
logGlobal->error("Cannot apply plain CPack!");
assert(0);
return false;
}
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override
{
logGlobal->error("Cannot apply plain CPack!");
assert(0);
}
};
extern std::string NAME;
CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr)
{
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
applier = std::make_shared<CApplier<CBaseForLobbyApply>>();
registerTypesLobbyPacks(*applier);
}
void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names)
{
hostClientId = -1;
state = EClientState::NONE;
th = make_unique<CStopWatch>();
packsForLobbyScreen.clear();
c.reset();
si.reset(new StartInfo());
playerNames.clear();
si->difficulty = 1;
si->mode = mode;
myNames.clear();
if(names && !names->empty()) //if have custom set of player names - use it
myNames = *names;
else
myNames.push_back(settings["general"]["playerName"].String());
#ifndef VCMI_ANDROID
shm.reset();
if(!settings["session"]["disable-shm"].Bool())
{
std::string sharedMemoryName = "vcmi_memory";
if(settings["session"]["enable-shm-uuid"].Bool())
{
//used or automated testing when multiple clients start simultaneously
sharedMemoryName += "_" + uuid;
}
try
{
shm = std::make_shared<SharedMemory>(sharedMemoryName, true);
}
catch(...)
{
shm.reset();
logNetwork->error("Cannot open interprocess memory. Continue without it...");
}
}
#endif
}
void CServerHandler::startLocalServerAndConnect()
{
if(threadRunLocalServer)
threadRunLocalServer->join();
th->update();
#ifdef VCMI_ANDROID
CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);
#else
threadRunLocalServer = std::make_shared<boost::thread>(&CServerHandler::threadRunServer, this); //runs server executable;
#endif
logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff());
th->update();
#ifndef VCMI_ANDROID
if(shm)
shm->sr->waitTillReady();
#else
logNetwork->info("waiting for server");
while(!androidTestServerReadyFlag.load())
{
logNetwork->info("still waiting...");
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
}
logNetwork->info("waiting for server finished...");
androidTestServerReadyFlag = false;
#endif
logNetwork->trace("Waiting for server: %d ms", th->getDiff());
th->update(); //put breakpoint here to attach to server before it does something stupid
#ifndef VCMI_ANDROID
justConnectToServer(settings["server"]["server"].String(), shm ? shm->sr->port : 0);
#else
justConnectToServer(settings["server"]["server"].String());
#endif
logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff());
}
void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port)
{
state = EClientState::CONNECTING;
while(!c && state != EClientState::CONNECTION_CANCELLED)
{
try
{
logNetwork->info("Establishing connection...");
c = std::make_shared<CConnection>(
addr.size() ? addr : settings["server"]["server"].String(),
port ? port : getDefaultPort(),
NAME, uuid);
}
catch(...)
{
logNetwork->error("\nCannot establish connection! Retrying within 1 second");
boost::this_thread::sleep(boost::posix_time::seconds(1));
}
}
if(state == EClientState::CONNECTION_CANCELLED)
logNetwork->info("Connection aborted by player!");
else
c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
}
void CServerHandler::applyPacksOnLobbyScreen()
{
if(!c || !c->handler)
return;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
while(!packsForLobbyScreen.empty())
{
CPackForLobby * pack = packsForLobbyScreen.front();
packsForLobbyScreen.pop_front();
CBaseForLobbyApply * apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier
apply->applyOnLobbyScreen(static_cast<CLobbyScreen *>(SEL), this, pack);
GH.totalRedraw();
delete pack;
}
}
void CServerHandler::stopServerConnection()
{
if(c->handler)
{
while(!c->handler->timed_join(boost::posix_time::milliseconds(50)))
applyPacksOnLobbyScreen();
c->handler->join();
}
}
std::set<PlayerColor> CServerHandler::getHumanColors()
{
return clientHumanColors(c->connectionID);
}
PlayerColor CServerHandler::myFirstColor() const
{
return clientFirstColor(c->connectionID);
}
bool CServerHandler::isMyColor(PlayerColor color) const
{
return isClientColor(c->connectionID, color);
}
ui8 CServerHandler::myFirstId() const
{
return clientFirstId(c->connectionID);
}
bool CServerHandler::isServerLocal() const
{
if(threadRunLocalServer)
return true;
return false;
}
bool CServerHandler::isHost() const
{
return c && hostClientId == c->connectionID;
}
bool CServerHandler::isGuest() const
{
return !c || hostClientId != c->connectionID;
}
ui16 CServerHandler::getDefaultPort()
{
if(settings["session"]["serverport"].Integer())
return settings["session"]["serverport"].Integer();
else
return settings["server"]["port"].Integer();
}
std::string CServerHandler::getDefaultPortStr()
{
return boost::lexical_cast<std::string>(getDefaultPort());
}
void CServerHandler::sendClientConnecting() const
{
LobbyClientConnected lcc;
lcc.uuid = uuid;
lcc.names = myNames;
lcc.mode = si->mode;
sendLobbyPack(lcc);
}
void CServerHandler::sendClientDisconnecting()
{
// FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server
if(state == EClientState::DISCONNECTING)
return;
state = EClientState::DISCONNECTING;
LobbyClientDisconnected lcd;
lcd.clientId = c->connectionID;
logNetwork->info("Connection has been requested to be closed.");
if(isServerLocal())
{
lcd.shutdownServer = true;
logNetwork->info("Sent closing signal to the server");
}
else
{
logNetwork->info("Sent leaving signal to the server");
}
sendLobbyPack(lcd);
}
void CServerHandler::setCampaignState(std::shared_ptr<CCampaignState> newCampaign)
{
state = EClientState::LOBBY_CAMPAIGN;
LobbySetCampaign lsc;
lsc.ourCampaign = newCampaign;
sendLobbyPack(lsc);
}
void CServerHandler::setCampaignMap(int mapId) const
{
if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
return;
LobbySetCampaignMap lscm;
lscm.mapId = mapId;
sendLobbyPack(lscm);
}
void CServerHandler::setCampaignBonus(int bonusId) const
{
if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
return;
LobbySetCampaignBonus lscb;
lscb.bonusId = bonusId;
sendLobbyPack(lscb);
}
void CServerHandler::setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts) const
{
LobbySetMap lsm;
lsm.mapInfo = to;
lsm.mapGenOpts = mapGenOpts;
sendLobbyPack(lsm);
}
void CServerHandler::setPlayer(PlayerColor color) const
{
LobbySetPlayer lsp;
lsp.clickedColor = color;
sendLobbyPack(lsp);
}
void CServerHandler::setPlayerOption(ui8 what, ui8 dir, PlayerColor player) const
{
LobbyChangePlayerOption lcpo;
lcpo.what = what;
lcpo.direction = dir;
lcpo.color = player;
sendLobbyPack(lcpo);
}
void CServerHandler::setDifficulty(int to) const
{
LobbySetDifficulty lsd;
lsd.difficulty = to;
sendLobbyPack(lsd);
}
void CServerHandler::setTurnLength(int npos) const
{
vstd::amin(npos, GameConstants::POSSIBLE_TURNTIME.size() - 1);
LobbySetTurnTime lstt;
lstt.turnTime = GameConstants::POSSIBLE_TURNTIME[npos];
sendLobbyPack(lstt);
}
void CServerHandler::sendMessage(const std::string & txt) const
{
std::istringstream readed;
readed.str(txt);
std::string command;
readed >> command;
if(command == "!passhost")
{
std::string id;
readed >> id;
if(id.length())
{
LobbyChangeHost lch;
lch.newHostConnectionId = boost::lexical_cast<int>(id);
sendLobbyPack(lch);
}
}
else if(command == "!forcep")
{
std::string connectedId, playerColorId;
readed >> connectedId;
readed >> playerColorId;
if(connectedId.length(), playerColorId.length())
{
ui8 connected = boost::lexical_cast<int>(connectedId);
auto color = PlayerColor(boost::lexical_cast<int>(playerColorId));
if(color.isValidPlayer() && playerNames.find(connected) != playerNames.end())
{
LobbyForceSetPlayer lfsp;
lfsp.targetConnectedPlayer = connected;
lfsp.targetPlayerColor = color;
sendLobbyPack(lfsp);
}
}
}
else
{
LobbyChatMessage lcm;
lcm.message = txt;
lcm.playerName = playerNames.find(myFirstId())->second.name;
sendLobbyPack(lcm);
}
}
void CServerHandler::sendGuiAction(ui8 action) const
{
LobbyGuiAction lga;
lga.action = static_cast<LobbyGuiAction::EAction>(action);
sendLobbyPack(lga);
}
void CServerHandler::sendStartGame(bool allowOnlyAI) const
{
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
LobbyStartGame lsg;
sendLobbyPack(lsg);
}
void CServerHandler::startGameplay()
{
client = new CClient();
switch(si->mode)
{
case StartInfo::NEW_GAME:
client->newGame();
break;
case StartInfo::CAMPAIGN:
client->newGame();
break;
case StartInfo::LOAD_GAME:
client->loadGame();
break;
default:
throw std::runtime_error("Invalid mode");
}
// After everything initialized we can accept CPackToClient netpacks
c->enterGameplayConnectionMode(client->gameState());
state = EClientState::GAMEPLAY;
}
void CServerHandler::endGameplay(bool closeConnection)
{
client->endGame();
vstd::clear_pointer(client);
// Game is ending
// Tell the network thread to reach a stable state
CSH->sendClientDisconnecting();
logNetwork->info("Closed connection.");
}
void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)
{
SDL_Event event;
event.type = SDL_USEREVENT;
event.user.code = EUserEvent::CAMPAIGN_START_SCENARIO;
if(cs)
event.user.data1 = CMemorySerializer::deepCopy(*cs.get()).release();
else
event.user.data1 = CMemorySerializer::deepCopy(*si->campState.get()).release();
SDL_PushEvent(&event);
}
int CServerHandler::howManyPlayerInterfaces()
{
int playerInts = 0;
for(auto pint : client->playerint)
{
if(dynamic_cast<CPlayerInterface *>(pint.second.get()))
playerInts++;
}
return playerInts;
}
ui8 CServerHandler::getLoadMode()
{
if(state == EClientState::GAMEPLAY)
{
if(si->campState)
return ELoadMode::CAMPAIGN;
for(auto pn : playerNames)
{
if(pn.second.connection != c->connectionID)
return ELoadMode::MULTI;
}
return ELoadMode::SINGLE;
}
return loadMode;
}
void CServerHandler::debugStartTest(std::string filename, bool save)
{
logGlobal->info("Starting debug test with file: %s", filename);
auto mapInfo = std::make_shared<CMapInfo>();
if(save)
{
resetStateForLobby(StartInfo::LOAD_GAME);
mapInfo->saveInit(ResourceID(filename, EResType::CLIENT_SAVEGAME));
screenType = ESelectionScreen::loadGame;
}
else
{
resetStateForLobby(StartInfo::NEW_GAME);
mapInfo->mapInit(filename);
screenType = ESelectionScreen::newGame;
}
if(settings["session"]["donotstartserver"].Bool())
justConnectToServer("127.0.0.1", 3030);
else
startLocalServerAndConnect();
while(!settings["session"]["headless"].Bool() && !dynamic_cast<CLobbyScreen *>(GH.topInt()))
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
while(!mi || mapInfo->fileURI != CSH->mi->fileURI)
{
setMapInfo(mapInfo);
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
// "Click" on color to remove us from it
setPlayer(myFirstColor());
while(myFirstColor() != PlayerColor::CANNOT_DETERMINE)
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
while(true)
{
try
{
sendStartGame();
break;
}
catch(...)
{
}
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
}
void CServerHandler::threadHandleConnection()
{
setThreadName("CServerHandler::threadHandleConnection");
c->enterLobbyConnectionMode();
try
{
sendClientConnecting();
while(c->connected)
{
while(state == EClientState::STARTING)
boost::this_thread::sleep(boost::posix_time::milliseconds(10));
CPack * pack = c->retrievePack();
if(state == EClientState::DISCONNECTING)
{
// FIXME: server shouldn't really send netpacks after it's tells client to disconnect
// Though currently they'll be delivered and might cause crash.
vstd::clear_pointer(pack);
}
else if(auto lobbyPack = dynamic_cast<CPackForLobby *>(pack))
{
if(applier->getApplier(typeList.getTypeID(pack))->applyOnLobbyHandler(this, pack))
{
if(!settings["session"]["headless"].Bool())
{
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(lobbyPack);
}
}
}
else if(auto clientPack = dynamic_cast<CPackForClient *>(pack))
{
client->handlePack(clientPack);
}
}
}
//catch only asio exceptions
catch(const boost::system::system_error & e)
{
if(state == EClientState::DISCONNECTING)
{
logNetwork->info("Successfully closed connection to server, ending listening thread!");
}
else
{
logNetwork->error("Lost connection to server, ending listening thread!");
logNetwork->error(e.what());
if(client)
{
CGuiHandler::pushSDLEvent(SDL_USEREVENT, EUserEvent::RETURN_TO_MAIN_MENU);
}
else
{
auto lcd = new LobbyClientDisconnected();
lcd->clientId = c->connectionID;
boost::unique_lock<boost::recursive_mutex> lock(*mx);
packsForLobbyScreen.push_back(lcd);
}
}
}
catch(...)
{
handleException();
throw;
}
}
void CServerHandler::threadRunServer()
{
#ifndef VCMI_ANDROID
setThreadName("CServerHandler::threadRunServer");
const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
std::string comm = VCMIDirs::get().serverPath().string()
+ " --port=" + getDefaultPortStr()
+ " --run-by-client"
+ " --uuid=" + uuid;
if(shm)
{
comm += " --enable-shm";
if(settings["session"]["enable-shm-uuid"].Bool())
comm += " --enable-shm-uuid";
}
comm += " > \"" + logName + '\"';
int result = std::system(comm.c_str());
if (result == 0)
{
logNetwork->info("Server closed correctly");
}
else
{
logNetwork->error("Error: server failed to close correctly or crashed!");
logNetwork->error("Check %s for more info", logName);
}
threadRunLocalServer.reset();
#endif
}
void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
{
if(state != EClientState::STARTING)
c->sendPack(&pack);
}

145
client/CServerHandler.h Normal file
View File

@ -0,0 +1,145 @@
/*
* CServerHandler.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 "../lib/CStopWatch.h"
#include "../lib/StartInfo.h"
struct SharedMemory;
class CConnection;
class PlayerColor;
struct StartInfo;
class CMapInfo;
struct ClientPlayer;
struct CPack;
struct CPackForLobby;
class CClient;
template<typename T> class CApplier;
class CBaseForLobbyApply;
// TODO: Add mutex so we can't set CONNECTION_CANCELLED if client already connected, but thread not setup yet
enum class EClientState : ui8
{
NONE = 0,
CONNECTING, // Trying to connect to server
CONNECTION_CANCELLED, // Connection cancelled by player, stop attempts to connect
LOBBY, // Client is connected to lobby
LOBBY_CAMPAIGN, // Client is on scenario bonus selection screen
STARTING, // Gameplay interfaces being created, we pause netpacks retrieving
GAMEPLAY, // In-game, used by some UI
DISCONNECTING // We disconnecting, drop all netpacks
};
class IServerAPI
{
protected:
virtual void sendLobbyPack(const CPackForLobby & pack) const = 0;
public:
virtual ~IServerAPI() {}
virtual void sendClientConnecting() const = 0;
virtual void sendClientDisconnecting() = 0;
virtual void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) = 0;
virtual void setCampaignMap(int mapId) const = 0;
virtual void setCampaignBonus(int bonusId) const = 0;
virtual void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const = 0;
virtual void setPlayer(PlayerColor color) const = 0;
virtual void setPlayerOption(ui8 what, ui8 dir, PlayerColor player) const = 0;
virtual void setDifficulty(int to) const = 0;
virtual void setTurnLength(int npos) const = 0;
virtual void sendMessage(const std::string & txt) const = 0;
virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
};
/// structure to handle running server and connecting to it
class CServerHandler : public IServerAPI, public LobbyInfo
{
std::shared_ptr<CApplier<CBaseForLobbyApply>> applier;
std::shared_ptr<boost::recursive_mutex> mx;
std::list<CPackForLobby *> packsForLobbyScreen; //protected by mx
std::vector<std::string> myNames;
void threadHandleConnection();
void threadRunServer();
void sendLobbyPack(const CPackForLobby & pack) const override;
public:
std::atomic<EClientState> state;
////////////////////
// FIXME: Bunch of crutches to glue it all together
// For starting non-custom campaign and continue to next mission
std::shared_ptr<CCampaignState> campaignStateToSend;
ui8 screenType; // To create lobby UI only after server is setup
ui8 loadMode; // For saves filtering in SelectionTab
////////////////////
std::unique_ptr<CStopWatch> th;
std::shared_ptr<boost::thread> threadRunLocalServer;
std::shared_ptr<CConnection> c;
CClient * client;
CServerHandler();
void resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names = nullptr);
void startLocalServerAndConnect();
void justConnectToServer(const std::string &addr = "", const ui16 port = 0);
void applyPacksOnLobbyScreen();
void stopServerConnection();
// Helpers for lobby state access
std::set<PlayerColor> getHumanColors();
PlayerColor myFirstColor() const;
bool isMyColor(PlayerColor color) const;
ui8 myFirstId() const; // Used by chat only!
bool isServerLocal() const;
bool isHost() const;
bool isGuest() const;
static ui16 getDefaultPort();
static std::string getDefaultPortStr();
// Lobby server API for UI
void sendClientConnecting() const override;
void sendClientDisconnecting() override;
void setCampaignState(std::shared_ptr<CCampaignState> newCampaign) override;
void setCampaignMap(int mapId) const override;
void setCampaignBonus(int bonusId) const override;
void setMapInfo(std::shared_ptr<CMapInfo> to, std::shared_ptr<CMapGenOptions> mapGenOpts = {}) const override;
void setPlayer(PlayerColor color) const override;
void setPlayerOption(ui8 what, ui8 dir, PlayerColor player) const override;
void setDifficulty(int to) const override;
void setTurnLength(int npos) const override;
void sendMessage(const std::string & txt) const override;
void sendGuiAction(ui8 action) const override;
void sendStartGame(bool allowOnlyAI = false) const override;
void startGameplay();
void endGameplay(bool closeConnection = true);
void startCampaignScenario(std::shared_ptr<CCampaignState> cs = {});
// TODO: LobbyState must be updated within game so we should always know how many player interfaces our client handle
int howManyPlayerInterfaces();
ui8 getLoadMode();
void debugStartTest(std::string filename, bool save = false);
};
extern CServerHandler * CSH;

File diff suppressed because it is too large Load Diff

View File

@ -9,25 +9,24 @@
*/
#pragma once
#include "../lib/IGameCallback.h"
#include "../lib/battle/BattleAction.h"
#include "../lib/CStopWatch.h"
#include "../lib/int3.h"
#include "../lib/CondSh.h"
#include "../lib/CPathfinder.h"
struct CPack;
struct CPackForServer;
class CCampaignState;
class CBattleCallback;
class IGameEventsReceiver;
class IBattleEventsReceiver;
class CBattleGameInterface;
struct StartInfo;
class CGameState;
class CGameInterface;
class CConnection;
class CCallback;
class BattleAction;
struct SharedMemory;
struct BattleAction;
class CClient;
class CScriptingModule;
struct CPathsInfo;
@ -35,31 +34,8 @@ class BinaryDeserializer;
class BinarySerializer;
namespace boost { class thread; }
/// structure to handle running server and connecting to it
class CServerHandler
{
private:
void callServer(); //calls server via system(), should be called as thread
public:
CStopWatch th;
boost::thread *serverThread; //thread that called system to run server
SharedMemory * shared;
std::string uuid;
bool verbose; //whether to print log msgs
//functions setting up local server
void startServer(); //creates a thread with callServer
void waitForServer(); //waits till server is ready
CConnection * connectToServer(); //connects to server
//////////////////////////////////////////////////////////////////////////
static CConnection * justConnectToServer(const std::string &host = "", const ui16 port = 0); //connects to given host without taking any other actions (like setting up server)
static ui16 getDefaultPort();
static std::string getDefaultPortStr();
CServerHandler(bool runServer = false);
virtual ~CServerHandler();
};
template<typename T> class CApplier;
class CBaseForCLApply;
template<typename T>
class ThreadSafeVector
@ -78,7 +54,7 @@ public:
cond.notify_all();
}
void pushBack(const T &item)
void pushBack(const T & item)
{
TLock lock(mx);
items.push_back(item);
@ -97,14 +73,14 @@ public:
return TLock(mx);
}
void waitWhileContains(const T &item)
void waitWhileContains(const T & item)
{
auto lock = getLock();
while(vstd::contains(items, item))
cond.wait(lock);
}
bool tryRemovingElement(const T&item) //returns false if element was not present
bool tryRemovingElement(const T & item) //returns false if element was not present
{
auto lock = getLock();
auto itr = vstd::find(items, item);
@ -122,105 +98,104 @@ public:
/// Class which handles client - server logic
class CClient : public IGameCallback
{
std::shared_ptr<CApplier<CBaseForCLApply>> applier;
std::unique_ptr<CPathsInfo> pathInfo;
std::map<PlayerColor, std::shared_ptr<boost::thread>> playerActionThreads;
void waitForMoveAndSend(PlayerColor color);
public:
std::map<PlayerColor,std::shared_ptr<CCallback> > callbacks; //callbacks given to player interfaces
std::map<PlayerColor,std::shared_ptr<CBattleCallback> > battleCallbacks; //callbacks given to player interfaces
std::map<PlayerColor, std::shared_ptr<CCallback>> callbacks; //callbacks given to player interfaces
std::map<PlayerColor, std::shared_ptr<CBattleCallback>> battleCallbacks; //callbacks given to player interfaces
std::vector<std::shared_ptr<IGameEventsReceiver>> privilegedGameEventReceivers; //scripting modules, spectator interfaces
std::vector<std::shared_ptr<IBattleEventsReceiver>> privilegedBattleEventReceivers; //scripting modules, spectator interfaces
std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;
std::map<PlayerColor,std::vector<std::shared_ptr<IGameEventsReceiver>>> additionalPlayerInts;
std::map<PlayerColor,std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
bool hotSeat;
CConnection *serv;
std::map<PlayerColor, std::vector<std::shared_ptr<IGameEventsReceiver>>> additionalPlayerInts;
std::map<PlayerColor, std::vector<std::shared_ptr<IBattleEventsReceiver>>> additionalBattleInts;
boost::optional<BattleAction> curbaction;
CScriptingModule *erm;
static ThreadSafeVector<int> waitingRequest;//FIXME: make this normal field (need to join all threads before client destruction)
//void sendRequest(const CPackForServer *request, bool waitForRealization);
CScriptingModule * erm;
CClient();
CClient(CConnection *con, StartInfo *si);
~CClient();
void init();
void newGame(CConnection *con, StartInfo *si); //con - connection to server
void newGame();
void loadGame();
void serialize(BinarySerializer & h, const int version);
void serialize(BinaryDeserializer & h, const int version);
void loadNeutralBattleAI();
void save(const std::string & fname);
void endGame();
void initMapHandler();
void initPlayerInterfaces();
std::string aiNameForPlayer(const PlayerSettings & ps, bool battleAI); //empty means no AI -> human
std::string aiNameForPlayer(bool battleAI);
void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb = false);
void installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback = true);
std::string aiNameForPlayer(const PlayerSettings &ps, bool battleAI); //empty means no AI -> human
std::string aiNameForPlayer(bool battleAI);
void endGame(bool closeConnection = true);
void stopConnection();
void save(const std::string & fname);
void loadGame(const std::string & fname, const bool server = true, const std::vector<int>& humanplayerindices = std::vector<int>(), const int loadnumplayers = 1, int player_ = -1, const std::string & ipaddr = "", const ui16 port = 0);
void run();
void campaignMapFinished( std::shared_ptr<CCampaignState> camp );
void finishCampaign( std::shared_ptr<CCampaignState> camp );
void proposeNextMission(std::shared_ptr<CCampaignState> camp);
static ThreadSafeVector<int> waitingRequest; //FIXME: make this normal field (need to join all threads before client destruction)
void handlePack(CPack * pack); //applies the given pack and deletes it
void commitPackage(CPackForClient * pack) override;
int sendRequest(const CPackForServer * request, PlayerColor player); //returns ID given to that request
void battleStarted(const BattleInfo * info);
void commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> battleInt); //will be called as separate thread
void battleFinished();
void startPlayerBattleAction(PlayerColor color);
void stopPlayerBattleAction(PlayerColor color);
void stopAllBattleActions();
void invalidatePaths();
const CPathsInfo * getPathsInfo(const CGHeroInstance *h);
bool terminate; // tell to terminate
std::unique_ptr<boost::thread> connectionHandler; //thread running run() method
boost::mutex connectionHandlerMutex;
//////////////////////////////////////////////////////////////////////////
const CPathsInfo * getPathsInfo(const CGHeroInstance * h);
virtual PlayerColor getLocalPlayer() const override;
//////////////////////////////////////////////////////////////////////////
//not working yet, will be implement somewhen later with support for local-sim-based gameplay
void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> &spells) override {};
friend class CCallback; //handling players actions
friend class CBattleCallback; //handling players actions
void changeSpells(const CGHeroInstance * hero, bool give, const std::set<SpellID> & spells) override {};
bool removeObject(const CGObjectInstance * obj) override {return false;};
void setBlockVis(ObjectInstanceID objid, bool bv) override {};
void setOwner(const CGObjectInstance * obj, PlayerColor owner) override {};
void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs=false) override {};
void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs=false) override {};
void changePrimSkill(const CGHeroInstance * hero, PrimarySkill::PrimarySkill which, si64 val, bool abs = false) override {};
void changeSecSkill(const CGHeroInstance * hero, SecondarySkill which, int val, bool abs = false) override {};
void showBlockingDialog(BlockingDialog *iw) override {};
void showBlockingDialog(BlockingDialog * iw) override {};
void showGarrisonDialog(ObjectInstanceID upobj, ObjectInstanceID hid, bool removableUnits) override {};
void showTeleportDialog(TeleportDialog *iw) override {};
void showTeleportDialog(TeleportDialog * iw) override {};
void showThievesGuildWindow(PlayerColor player, ObjectInstanceID requestingObjId) override {};
void giveResource(PlayerColor player, Res::ERes which, int val) override {};
virtual void giveResources(PlayerColor player, TResources resources) override {};
void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet &creatures, bool remove) override {};
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> &creatures) override {};
bool changeStackType(const StackLocation &sl, const CCreature *c) override {return false;};
bool changeStackCount(const StackLocation &sl, TQuantity count, bool absoluteValue = false) override {return false;};
bool insertNewStack(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;};
bool eraseStack(const StackLocation &sl, bool forceRemoval = false) override{return false;};
bool swapStacks(const StackLocation &sl1, const StackLocation &sl2) override {return false;}
bool addToSlot(const StackLocation &sl, const CCreature *c, TQuantity count) override {return false;}
void tryJoiningArmy(const CArmedInstance *src, const CArmedInstance *dst, bool removeObjWhenFinished, bool allowMerging) override {}
bool moveStack(const StackLocation &src, const StackLocation &dst, TQuantity count = -1) override {return false;}
void giveCreatures(const CArmedInstance * objid, const CGHeroInstance * h, const CCreatureSet & creatures, bool remove) override {};
void takeCreatures(ObjectInstanceID objid, const std::vector<CStackBasicDescriptor> & creatures) override {};
bool changeStackType(const StackLocation & sl, const CCreature * c) override {return false;};
bool changeStackCount(const StackLocation & sl, TQuantity count, bool absoluteValue = false) override {return false;};
bool insertNewStack(const StackLocation & sl, const CCreature * c, TQuantity count) override {return false;};
bool eraseStack(const StackLocation & sl, bool forceRemoval = false) override {return false;};
bool swapStacks(const StackLocation & sl1, const StackLocation & sl2) override {return false;}
bool addToSlot(const StackLocation & sl, const CCreature * c, TQuantity count) override {return false;}
void tryJoiningArmy(const CArmedInstance * src, const CArmedInstance * dst, bool removeObjWhenFinished, bool allowMerging) override {}
bool moveStack(const StackLocation & src, const StackLocation & dst, TQuantity count = -1) override {return false;}
void removeAfterVisit(const CGObjectInstance *object) override {};
void removeAfterVisit(const CGObjectInstance * object) override {};
void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override {};
void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override {};
void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override {};
void removeArtifact(const ArtifactLocation &al) override {};
bool moveArtifact(const ArtifactLocation &al1, const ArtifactLocation &al2) override {return false;};
void giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, ArtifactPosition pos) override {};
void giveHeroArtifact(const CGHeroInstance * h, const CArtifactInstance * a, ArtifactPosition pos) override {};
void putArtifact(const ArtifactLocation & al, const CArtifactInstance * a) override {};
void removeArtifact(const ArtifactLocation & al) override {};
bool moveArtifact(const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;};
void synchronizeArtifactHandlerLists() override {};
void showCompInfo(ShowInInfobox * comp) override {};
void heroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
void stopHeroVisitCastle(const CGTownInstance * obj, const CGHeroInstance * hero) override {};
void startBattlePrimary(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, const CGHeroInstance *hero1, const CGHeroInstance *hero2, bool creatureBank = false, const CGTownInstance *town = nullptr) override {}; //use hero=nullptr for no hero
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance *army1, const CArmedInstance *army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
void startBattlePrimary(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, const CGHeroInstance * hero1, const CGHeroInstance * hero2, bool creatureBank = false, const CGTownInstance * town = nullptr) override {}; //use hero=nullptr for no hero
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, int3 tile, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used
void startBattleI(const CArmedInstance * army1, const CArmedInstance * army2, bool creatureBank = false) override {}; //if any of armies is hero, hero will be used, visitable tile of second obj is place of battle
void setAmount(ObjectInstanceID objid, ui32 val) override {};
bool moveHero(ObjectInstanceID hid, int3 dst, ui8 teleporting, bool transit = false, PlayerColor asker = PlayerColor::NEUTRAL) override {return false;};
void giveHeroBonus(GiveBonus * bonus) override {};
@ -228,37 +203,9 @@ public:
void setManaPoints(ObjectInstanceID hid, int val) override {};
void giveHero(ObjectInstanceID id, PlayerColor player) override {};
void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags) override {};
void sendAndApply(CPackForClient * info) override {};
void sendAndApply(CPackForClient * pack) override {};
void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2) override {};
void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) override {}
void changeFogOfWar(std::unordered_set<int3, ShashInt3> &tiles, PlayerColor player, bool hide) override {}
//////////////////////////////////////////////////////////////////////////
friend class CCallback; //handling players actions
friend class CBattleCallback; //handling players actions
int sendRequest(const CPack *request, PlayerColor player); //returns ID given to that request
void handlePack( CPack * pack ); //applies the given pack and deletes it
void battleStarted(const BattleInfo * info);
void commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> battleInt); //will be called as separate thread
void commitPackage(CPackForClient *pack) override;
//////////////////////////////////////////////////////////////////////////
void serialize(BinarySerializer & h, const int version);
void serialize(BinaryDeserializer & h, const int version);
void serialize(BinarySerializer & h, const int version, const std::set<PlayerColor>& playerIDs);
void serialize(BinaryDeserializer & h, const int version, const std::set<PlayerColor>& playerIDs);
void battleFinished();
void startPlayerBattleAction(PlayerColor color);
void stopPlayerBattleAction(PlayerColor color);
void stopAllBattleActions();
private:
void waitForMoveAndSend(PlayerColor color);
void changeFogOfWar(std::unordered_set<int3, ShashInt3> & tiles, PlayerColor player, bool hide) override {}
};

View File

@ -41,6 +41,7 @@
#include "widgets/MiscWidgets.h"
#include "widgets/AdventureMapClasses.h"
#include "CMT.h"
#include "CServerHandler.h"
// TODO: as Tow suggested these template should all be part of CClient
// This will require rework spectator interface properly though
@ -357,17 +358,6 @@ void RemoveBonus::applyCl(CClient *cl)
}
}
void UpdateCampaignState::applyCl(CClient *cl)
{
cl->stopConnection();
cl->campaignMapFinished(camp);
}
void PrepareForAdvancingCampaign::applyCl(CClient *cl)
{
cl->serv->prepareForSendingHeroes();
}
void RemoveObject::applyFirstCl(CClient *cl)
{
const CGObjectInstance *o = cl->getObj(id);
@ -757,7 +747,7 @@ void PackageApplied::applyCl(CClient *cl)
{
callInterfaceIfPresent(cl, player, &IGameEventsReceiver::requestRealized, this);
if(!CClient::waitingRequest.tryRemovingElement(requestID))
logNetwork->warn("Surprising server message!");
logNetwork->warn("Surprising server message! PackageApplied for unknown requestID!");
}
void SystemMessage::applyCl(CClient *cl)
@ -777,11 +767,13 @@ void PlayerBlocked::applyCl(CClient *cl)
void YourTurn::applyCl(CClient *cl)
{
logNetwork->debug("Server gives turn to %s", player.getStr());
callAllInterfaces(cl, &IGameEventsReceiver::playerStartsTurn, player);
callOnlyThatInterface(cl, player, &CGameInterface::yourTurn);
}
void SaveGame::applyCl(CClient *cl)
void SaveGameClient::applyCl(CClient *cl)
{
const auto stem = FileInfo::GetPathStem(fname);
CResourceHandler::get("local")->createResource(stem.to_string() + ".vcgm1");
@ -798,7 +790,7 @@ void SaveGame::applyCl(CClient *cl)
}
}
void PlayerMessage::applyCl(CClient *cl)
void PlayerMessageClient::applyCl(CClient *cl)
{
logNetwork->debug("Player %s sends a message: %s", player.getStr(), text);

View File

@ -0,0 +1,142 @@
/*
* NetPacksLobbyClient.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 "lobby/CSelectionBase.h"
#include "lobby/CLobbyScreen.h"
#include "lobby/OptionsTab.h"
#include "lobby/RandomMapTab.h"
#include "lobby/SelectionTab.h"
#include "lobby/CBonusSelection.h"
#include "CServerHandler.h"
#include "CGameInfo.h"
#include "gui/CGuiHandler.h"
#include "widgets/Buttons.h"
#include "widgets/TextControls.h"
#include "../lib/CConfigHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/NetPacksLobby.h"
#include "../lib/serializer/Connection.h"
bool LobbyClientConnected::applyOnLobbyHandler(CServerHandler * handler)
{
// Check if it's LobbyClientConnected for our client
if(uuid == handler->c->uuid)
{
handler->c->connectionID = clientId;
if(!settings["session"]["headless"].Bool())
GH.pushInt(new CLobbyScreen(static_cast<ESelectionScreen>(handler->screenType)));
handler->state = EClientState::LOBBY;
return true;
}
return false;
}
void LobbyClientConnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
if(uuid == handler->c->uuid)
{
}
}
bool LobbyClientDisconnected::applyOnLobbyHandler(CServerHandler * handler)
{
if(clientId != c->connectionID)
return false;
handler->stopServerConnection();
return true;
}
void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
GH.popIntTotally(lobby);
}
void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
if(lobby)
{
lobby->card->chat->addNewMessage(playerName + ": " + message);
lobby->card->setChat(true);
if(lobby->buttonChat)
lobby->buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
}
}
void LobbyGuiAction::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
if(!handler->isGuest())
return;
switch(action)
{
case NO_TAB:
lobby->toggleTab(lobby->curTab);
break;
case OPEN_OPTIONS:
lobby->toggleTab(lobby->tabOpt);
break;
case OPEN_SCENARIO_LIST:
lobby->toggleTab(lobby->tabSel);
break;
case OPEN_RANDOM_MAP_OPTIONS:
lobby->toggleTab(lobby->tabRand);
break;
}
}
bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
{
if(handler->state == EClientState::GAMEPLAY)
{
handler->endGameplay(false);
}
handler->state = EClientState::STARTING;
if(handler->si->mode != StartInfo::LOAD_GAME)
{
handler->si = initializedStartInfo;
}
if(settings["session"]["headless"].Bool())
handler->startGameplay();
return true;
}
void LobbyStartGame::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
CMM->showLoadingScreen(std::bind(&CServerHandler::startGameplay, handler));
}
bool LobbyUpdateState::applyOnLobbyHandler(CServerHandler * handler)
{
hostChanged = state.hostClientId != handler->hostClientId;
static_cast<LobbyState &>(*handler) = state;
return true;
}
void LobbyUpdateState::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
if(!lobby->bonusSel && handler->si->campState && handler->state == EClientState::LOBBY_CAMPAIGN)
{
lobby->bonusSel = new CBonusSelection();
GH.pushInt(lobby->bonusSel);
}
if(lobby->bonusSel)
lobby->bonusSel->updateAfterStateChange();
else
lobby->updateAfterStateChange();
if(hostChanged)
lobby->toggleMode(handler->isHost());
}

View File

@ -106,7 +106,7 @@ void CGuiHandler::popInt(IShowActivatable *top)
listInt.front()->activate();
totalRedraw();
pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
}
void CGuiHandler::popIntTotally(IShowActivatable *top)
@ -132,7 +132,7 @@ void CGuiHandler::pushInt(IShowActivatable *newInt)
objsToBlit.push_back(newInt);
totalRedraw();
pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
}
void CGuiHandler::popInts(int howMany)
@ -155,7 +155,7 @@ void CGuiHandler::popInts(int howMany)
}
fakeMouseMove();
pushSDLEvent(SDL_USEREVENT, INTERFACE_CHANGED);
pushSDLEvent(SDL_USEREVENT, EUserEvent::INTERFACE_CHANGED);
}
IShowActivatable * CGuiHandler::topInt()

View File

@ -22,6 +22,20 @@ class IShowable;
enum class EIntObjMouseBtnType;
template <typename T> struct CondSh;
// TODO: event handling need refactoring
enum EUserEvent
{
/*CHANGE_SCREEN_RESOLUTION = 1,*/
RETURN_TO_MAIN_MENU = 2,
//STOP_CLIENT = 3,
RESTART_GAME = 4,
RETURN_TO_MENU_LOAD,
FULLSCREEN_TOGGLED,
CAMPAIGN_START_SCENARIO,
FORCE_QUIT, //quit client without question
INTERFACE_CHANGED
};
// A fps manager which holds game updates at a constant rate
class CFramerateManager
{
@ -119,11 +133,6 @@ public:
extern CGuiHandler GH; //global gui handler
template <typename T> void pushIntT()
{
GH.pushInt(new T());
}
struct SObjectConstruction
{
CIntObject *myObj;
@ -142,5 +151,6 @@ struct SSetCaptureState
#define OBJ_CONSTRUCTION SObjectConstruction obj__i(this)
#define OBJECT_CONSTRUCTION_CAPTURING(actions) defActions = actions; SSetCaptureState obj__i1(true, actions); SObjectConstruction obj__i(this)
#define OBJ_CONSTRUCTION_CAPTURING_ALL defActions = 255; SSetCaptureState obj__i1(true, 255); SObjectConstruction obj__i(this)
#define OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE defActions = 255 - DISPOSE; SSetCaptureState obj__i1(true, 255 - DISPOSE); SObjectConstruction obj__i(this)
#define BLOCK_CAPTURING SSetCaptureState obj__i(false, 0)
#define BLOCK_CAPTURING_DONT_TOUCH_REC_ACTIONS SSetCaptureState obj__i(false, GH.defActionsDef)

View File

@ -20,6 +20,8 @@ const SDL_Color Colors::YELLOW = { 229, 215, 123, 0 };
const SDL_Color Colors::WHITE = { 255, 243, 222, 0 };
const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 };
const SDL_Color Colors::GREEN = { 0, 255, 0, 0 };
const SDL_Color Colors::ORANGE = { 232, 184, 32, 0 };
const SDL_Color Colors::BRIGHT_YELLOW = { 242, 226, 110, 0 };
const SDL_Color Colors::DEFAULT_KEY_COLOR = {0, 255, 255, 0};
void SDL_UpdateRect(SDL_Surface *surface, int x, int y, int w, int h)

View File

@ -89,6 +89,12 @@ public:
/** green color used for in-game console */
static const SDL_Color GREEN;
/** the h3 orange color, used for blocked buttons */
static const SDL_Color ORANGE;
/** the h3 bright yellow color, used for selection border */
static const SDL_Color BRIGHT_YELLOW;
/** default key color for all 8 & 24 bit graphics */
static const SDL_Color DEFAULT_KEY_COLOR;
};

View File

@ -0,0 +1,534 @@
/*
* CBonusSelection.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 "CBonusSelection.h"
#include "CSelectionBase.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../mainmenu/CPrologEpilogVideo.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CArtHandler.h"
#include "../../lib/CBuildingHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CSkillHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/StartInfo.h"
#include "../../lib/mapping/CCampaignHandler.h"
#include "../../lib/mapping/CMapService.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
std::shared_ptr<CCampaignState> CBonusSelection::getCampaign()
{
return CSH->si->campState;
}
CBonusSelection::CBonusSelection()
: CWindowObject(BORDERED)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
static const std::string bgNames[] =
{
"E1_BG.BMP", "G2_BG.BMP", "E2_BG.BMP", "G1_BG.BMP", "G3_BG.BMP", "N1_BG.BMP",
"S1_BG.BMP", "BR_BG.BMP", "IS_BG.BMP", "KR_BG.BMP", "NI_BG.BMP", "TA_BG.BMP", "AR_BG.BMP", "HS_BG.BMP",
"BB_BG.BMP", "NB_BG.BMP", "EL_BG.BMP", "RN_BG.BMP", "UA_BG.BMP", "SP_BG.BMP"
};
loadPositionsOfGraphics();
setBackground(bgNames[getCampaign()->camp->header.mapVersion]);
panelBackground = std::make_shared<CPicture>("CAMPBRF.BMP", 456, 6);
buttonStart = std::make_shared<CButton>(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), SDLK_RETURN);
buttonRestart = std::make_shared<CButton>(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN);
buttonBack = std::make_shared<CButton>(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE);
campaignName = std::make_shared<CLabel>(481, 28, FONT_BIG, EAlignment::TOPLEFT, Colors::YELLOW, CSH->si->getCampaignName());
iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 735, 26);
labelCampaignDescription = std::make_shared<CLabel>(481, 63, FONT_SMALL, EAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
campaignDescription = std::make_shared<CTextBox>(getCampaign()->camp->header.description, Rect(480, 86, 286, 117), 1);
mapName = std::make_shared<CLabel>(481, 219, FONT_BIG, EAlignment::TOPLEFT, Colors::YELLOW, CSH->mi->getName());
labelMapDescription = std::make_shared<CLabel>(481, 253, FONT_SMALL, EAlignment::TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
mapDescription = std::make_shared<CTextBox>("", Rect(480, 280, 286, 117), 1);
labelChooseBonus = std::make_shared<CLabel>(511, 432, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[71]);
groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
flagbox = std::make_shared<CFlagBox>(Rect(486, 407, 335, 23));
std::vector<std::string> difficulty;
boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" "));
labelDifficulty = std::make_shared<CLabel>(689, 432, FONT_MEDIUM, EAlignment::TOPLEFT, Colors::WHITE, difficulty.back());
for(size_t b = 0; b < difficultyIcons.size(); ++b)
{
difficultyIcons[b] = std::make_shared<CAnimImage>("GSPBUT" + boost::lexical_cast<std::string>(b + 3) + ".DEF", 0, 0, 709, 455);
}
if(getCampaign()->camp->header.difficultyChoosenByPlayer)
{
buttonDifficultyLeft = std::make_shared<CButton>(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
buttonDifficultyRight = std::make_shared<CButton>(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
}
for(int g = 0; g < getCampaign()->camp->scenarios.size(); ++g)
{
if(getCampaign()->camp->conquerable(g))
regions.push_back(std::make_shared<CRegion>(g, true, true, campDescriptions[getCampaign()->camp->header.mapVersion]));
else if(getCampaign()->camp->scenarios[g].conquered) //display as striped
regions.push_back(std::make_shared<CRegion>(g, false, false, campDescriptions[getCampaign()->camp->header.mapVersion]));
}
}
void CBonusSelection::loadPositionsOfGraphics()
{
const JsonNode config(ResourceID("config/campaign_regions.json"));
int idx = 0;
for(const JsonNode & campaign : config["campaign_regions"].Vector())
{
SCampPositions sc;
sc.campPrefix = campaign["prefix"].String();
sc.colorSuffixLength = campaign["color_suffix_length"].Float();
for(const JsonNode & desc : campaign["desc"].Vector())
{
SCampPositions::SRegionDesc rd;
rd.infix = desc["infix"].String();
rd.xpos = desc["x"].Float();
rd.ypos = desc["y"].Float();
sc.regions.push_back(rd);
}
campDescriptions.push_back(sc);
idx++;
}
assert(idx == CGI->generaltexth->campaignMapNames.size());
}
void CBonusSelection::createBonusesIcons()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
groupBonuses = std::make_shared<CToggleGroup>(std::bind(&IServerAPI::setCampaignBonus, CSH, _1));
static const char * bonusPics[] =
{
"SPELLBON.DEF", // Spell
"TWCRPORT.DEF", // Monster
"", // Building - BO*.BMP
"ARTIFBON.DEF", // Artifact
"SPELLBON.DEF", // Spell scroll
"PSKILBON.DEF", // Primary skill
"SSKILBON.DEF", // Secondary skill
"BORES.DEF", // Resource
"PORTRAITSLARGE", // Hero HPL*.BMP
"PORTRAITSLARGE"
// Player - CREST58.DEF
};
for(int i = 0; i < bonDescs.size(); i++)
{
std::string picName = bonusPics[bonDescs[i].type];
size_t picNumber = bonDescs[i].info2;
std::string desc;
switch(bonDescs[i].type)
{
case CScenarioTravel::STravelBonus::SPELL:
desc = CGI->generaltexth->allTexts[715];
boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
break;
case CScenarioTravel::STravelBonus::MONSTER:
picNumber = bonDescs[i].info2 + 2;
desc = CGI->generaltexth->allTexts[717];
boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
break;
case CScenarioTravel::STravelBonus::BUILDING:
{
int faction = -1;
for(auto & elem : CSH->si->playerInfos)
{
if(elem.second.isControlledByHuman())
{
faction = elem.second.castle;
break;
}
}
assert(faction != -1);
BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
picName = graphics->ERMUtoPicture[faction][buildID];
picNumber = -1;
if(vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
break;
}
case CScenarioTravel::STravelBonus::ARTIFACT:
desc = CGI->generaltexth->allTexts[715];
boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
break;
case CScenarioTravel::STravelBonus::SPELL_SCROLL:
desc = CGI->generaltexth->allTexts[716];
boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
break;
case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
{
int leadingSkill = -1;
std::vector<std::pair<int, int>> toPrint; //primary skills to be listed <num, val>
const ui8 * ptr = reinterpret_cast<const ui8 *>(&bonDescs[i].info2);
for(int g = 0; g < GameConstants::PRIMARY_SKILLS; ++g)
{
if(leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
{
leadingSkill = g;
}
if(ptr[g] != 0)
{
toPrint.push_back(std::make_pair(g, ptr[g]));
}
}
picNumber = leadingSkill;
desc = CGI->generaltexth->allTexts[715];
std::string substitute; //text to be printed instead of %s
for(int v = 0; v < toPrint.size(); ++v)
{
substitute += boost::lexical_cast<std::string>(toPrint[v].second);
substitute += " " + CGI->generaltexth->primarySkillNames[toPrint[v].first];
if(v != toPrint.size() - 1)
{
substitute += ", ";
}
}
boost::algorithm::replace_first(desc, "%s", substitute);
break;
}
case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
desc = CGI->generaltexth->allTexts[718];
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
boost::algorithm::replace_first(desc, "%s", CGI->skillh->skillName(bonDescs[i].info2)); //skill name
picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
break;
case CScenarioTravel::STravelBonus::RESOURCE:
{
int serialResID = 0;
switch(bonDescs[i].info1)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
serialResID = bonDescs[i].info1;
break;
case 0xFD: //wood + ore
serialResID = 7;
break;
case 0xFE: //rare resources
serialResID = 8;
break;
}
picNumber = serialResID;
desc = CGI->generaltexth->allTexts[717];
boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info2));
std::string replacement;
if(serialResID <= 6)
{
replacement = CGI->generaltexth->restypes[serialResID];
}
else
{
replacement = CGI->generaltexth->allTexts[714 + serialResID];
}
boost::algorithm::replace_first(desc, "%s", replacement);
break;
}
case CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO:
{
auto superhero = getCampaign()->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
if(!superhero)
logGlobal->warn("No superhero! How could it be transferred?");
picNumber = superhero ? superhero->portrait : 0;
desc = CGI->generaltexth->allTexts[719];
boost::algorithm::replace_first(desc, "%s", getCampaign()->camp->scenarios[bonDescs[i].info2].scenarioName);
break;
}
case CScenarioTravel::STravelBonus::HERO:
desc = CGI->generaltexth->allTexts[718];
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
if(bonDescs[i].info2 == 0xFFFF)
{
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name
picNumber = -1;
picName = "CBONN1A3.BMP";
}
else
{
boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name);
}
break;
}
CToggleButton * bonusButton = new CToggleButton(Point(475 + i * 68, 455), "", CButton::tooltip(desc, desc));
if(picNumber != -1)
picName += ":" + boost::lexical_cast<std::string>(picNumber);
auto anim = std::make_shared<CAnimation>();
anim->setCustom(picName, 0);
bonusButton->setImage(anim);
if(CSH->campaignBonus == i)
bonusButton->setBorderColor(Colors::BRIGHT_YELLOW);
groupBonuses->addToggle(i, bonusButton);
}
if(vstd::contains(getCampaign()->chosenCampaignBonuses, CSH->campaignMap))
{
groupBonuses->setSelected(getCampaign()->chosenCampaignBonuses[CSH->campaignMap]);
}
}
void CBonusSelection::updateAfterStateChange()
{
if(CSH->state != EClientState::GAMEPLAY)
{
buttonRestart->disable();
buttonStart->enable();
if(!getCampaign()->mapsConquered.empty())
buttonBack->block(true);
else
buttonBack->block(false);
}
else
{
buttonStart->disable();
buttonRestart->enable();
buttonBack->block(false);
}
if(CSH->campaignBonus == -1)
{
buttonStart->block(getCampaign()->camp->scenarios[CSH->campaignMap].travelOptions.bonusesToChoose.size());
}
else if(buttonStart->isBlocked())
{
buttonStart->block(false);
}
for(auto region : regions)
region->updateState();
if(!CSH->mi)
return;
iconsMapSizes->setFrame(CSH->mi->getMapSizeIconId());
mapDescription->setText(CSH->mi->getDescription());
for(size_t i = 0; i < difficultyIcons.size(); i++)
{
if(i == CSH->si->difficulty)
difficultyIcons[i]->enable();
else
difficultyIcons[i]->disable();
}
flagbox->recreate();
createBonusesIcons();
}
void CBonusSelection::goBack()
{
if(CSH->state != EClientState::GAMEPLAY)
{
GH.popInts(2);
}
else
{
GH.popIntTotally(this);
}
// TODO: we can actually only pop bonus selection interface for custom campaigns
// Though this would require clearing CLobbyScreen::bonusSel pointer when poping this interface
/*
else
{
GH.popIntTotally(this);
CSH->state = EClientState::LOBBY;
}
*/
}
void CBonusSelection::startMap()
{
auto showPrologVideo = [=]()
{
auto exitCb = [=]()
{
logGlobal->info("Starting scenario %d", CSH->campaignMap);
CSH->sendStartGame();
};
const CCampaignScenario & scenario = getCampaign()->camp->scenarios[CSH->campaignMap];
if(scenario.prolog.hasPrologEpilog)
{
GH.pushInt(new CPrologEpilogVideo(scenario.prolog, exitCb));
}
else
{
exitCb();
}
};
if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game
{
GH.popInt(this);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
{
GH.curInt = CMainMenu::create();
showPrologVideo();
}, 0);
}
else
{
showPrologVideo();
}
}
void CBonusSelection::restartMap()
{
GH.popInt(this);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
{
CSH->startCampaignScenario();
}, 0);
}
void CBonusSelection::increaseDifficulty()
{
CSH->setDifficulty(CSH->si->difficulty + 1);
}
void CBonusSelection::decreaseDifficulty()
{
CSH->setDifficulty(CSH->si->difficulty - 1);
}
CBonusSelection::CRegion::CRegion(int id, bool accessible, bool selectable, const SCampPositions & campDsc)
: CIntObject(LCLICK | RCLICK), idOfMapAndRegion(id), accessible(accessible), selectable(selectable)
{
OBJ_CONSTRUCTION;
static const std::string colors[2][8] =
{
{"R", "B", "N", "G", "O", "V", "T", "P"},
{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}
};
const SCampPositions::SRegionDesc & desc = campDsc.regions[idOfMapAndRegion];
pos.x += desc.xpos;
pos.y += desc.ypos;
std::string prefix = campDsc.campPrefix + desc.infix + "_";
std::string suffix = colors[campDsc.colorSuffixLength - 1][CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionColor];
graphicsNotSelected = std::make_shared<CPicture>(prefix + "En" + suffix + ".BMP");
graphicsNotSelected->disable();
graphicsSelected = std::make_shared<CPicture>(prefix + "Se" + suffix + ".BMP");
graphicsSelected->disable();
graphicsStriped = std::make_shared<CPicture>(prefix + "Co" + suffix + ".BMP");
graphicsStriped->disable();
pos.w = graphicsNotSelected->bg->w;
pos.h = graphicsNotSelected->bg->h;
}
void CBonusSelection::CRegion::updateState()
{
if(!accessible)
{
graphicsNotSelected->disable();
graphicsSelected->disable();
graphicsStriped->enable();
}
else if(CSH->campaignMap == idOfMapAndRegion)
{
graphicsNotSelected->disable();
graphicsSelected->enable();
graphicsStriped->disable();
}
else
{
graphicsNotSelected->enable();
graphicsSelected->disable();
graphicsStriped->disable();
}
}
void CBonusSelection::CRegion::clickLeft(tribool down, bool previousState)
{
//select if selectable & clicked inside our graphic
if(indeterminate(down))
return;
if(!down && selectable && !CSDL_Ext::isTransparent(*graphicsNotSelected, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y))
{
CSH->setCampaignMap(idOfMapAndRegion);
}
}
void CBonusSelection::CRegion::clickRight(tribool down, bool previousState)
{
// FIXME: For some reason "down" is only ever contain indeterminate_value
auto text = CSH->si->campState->camp->scenarios[idOfMapAndRegion].regionText;
if(!CSDL_Ext::isTransparent(*graphicsNotSelected, GH.current->motion.x - pos.x, GH.current->motion.y - pos.y) && text.size())
{
CRClickPopup::createAndPush(text);
}
}

View File

@ -0,0 +1,93 @@
/*
* CBonusSelection.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 SDL_Surface;
class CCampaignState;
class CButton;
class CTextBox;
class CToggleGroup;
class CAnimImage;
class CLabel;
class CFlagBox;
/// Campaign screen where you can choose one out of three starting bonuses
class CBonusSelection : public CWindowObject
{
public:
std::shared_ptr<CCampaignState> getCampaign();
CBonusSelection();
struct SCampPositions
{
std::string campPrefix;
int colorSuffixLength;
struct SRegionDesc
{
std::string infix;
int xpos, ypos;
};
std::vector<SRegionDesc> regions;
};
class CRegion
: public CIntObject
{
CBonusSelection * owner;
std::shared_ptr<CPicture> graphicsNotSelected;
std::shared_ptr<CPicture> graphicsSelected;
std::shared_ptr<CPicture> graphicsStriped;
int idOfMapAndRegion;
bool accessible; // false if region should be striped
bool selectable; // true if region should be selectable
public:
CRegion(int id, bool accessible, bool selectable, const SCampPositions & campDsc);
void updateState();
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
};
void loadPositionsOfGraphics();
void createBonusesIcons();
void updateAfterStateChange();
// Event handlers
void goBack();
void startMap();
void restartMap();
void increaseDifficulty();
void decreaseDifficulty();
std::shared_ptr<CPicture> panelBackground;
std::shared_ptr<CButton> buttonStart;
std::shared_ptr<CButton> buttonRestart;
std::shared_ptr<CButton> buttonBack;
std::shared_ptr<CLabel> campaignName;
std::shared_ptr<CLabel> labelCampaignDescription;
std::shared_ptr<CTextBox> campaignDescription;
std::shared_ptr<CLabel> mapName;
std::shared_ptr<CLabel> labelMapDescription;
std::shared_ptr<CTextBox> mapDescription;
std::vector<SCampPositions> campDescriptions;
std::vector<std::shared_ptr<CRegion>> regions;
std::shared_ptr<CFlagBox> flagbox;
std::shared_ptr<CLabel> labelChooseBonus;
std::shared_ptr<CToggleGroup> groupBonuses;
std::shared_ptr<CLabel> labelDifficulty;
std::array<std::shared_ptr<CAnimImage>, 5> difficultyIcons;
std::shared_ptr<CButton> buttonDifficultyLeft;
std::shared_ptr<CButton> buttonDifficultyRight;
std::shared_ptr<CAnimImage> iconsMapSizes;
};

View File

@ -0,0 +1,201 @@
/*
* CLobbyScreen.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 "CLobbyScreen.h"
#include "CBonusSelection.h"
#include "SelectionTab.h"
#include "RandomMapTab.h"
#include "OptionsTab.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/Buttons.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h"
#include "../CGameInfo.h"
#include "../../lib/NetPacksLobby.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/mapping/CCampaignHandler.h"
#include "../../lib/rmg/CMapGenOptions.h"
CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
: CSelectionBase(screenType), bonusSel(nullptr)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
tabSel = std::make_shared<SelectionTab>(screenType);
curTab = tabSel;
auto initLobby = [&]()
{
tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, CSH, _1, nullptr);
buttonSelect = std::make_shared<CButton>(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s);
buttonSelect->addCallback([&]()
{
toggleTab(tabSel);
CSH->setMapInfo(tabSel->getSelectedMapInfo());
});
buttonOptions = std::make_shared<CButton>(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), SDLK_a);
};
buttonChat = std::make_shared<CButton>(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), SDLK_h);
buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL);
switch(screenType)
{
case ESelectionScreen::newGame:
{
tabOpt = std::make_shared<OptionsTab>();
tabRand = std::make_shared<RandomMapTab>();
tabRand->mapInfoChanged += std::bind(&IServerAPI::setMapInfo, CSH, _1, _2);
buttonRMG = std::make_shared<CButton>(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r);
buttonRMG->addCallback([&]()
{
toggleTab(tabRand);
tabRand->updateMapInfoByHost(); // TODO: This is only needed to force-update mapInfo in CSH when tab is opened
});
card->iconDifficulty->addCallback(std::bind(&IServerAPI::setDifficulty, CSH, _1));
buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), SDLK_b);
initLobby();
break;
}
case ESelectionScreen::loadGame:
{
tabOpt = std::make_shared<OptionsTab>();
buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CLobbyScreen::startScenario, this, false), SDLK_l);
initLobby();
break;
}
case ESelectionScreen::campaignList:
tabSel->callOnSelect = std::bind(&IServerAPI::setMapInfo, CSH, _1, nullptr);
buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CLobbyScreen::startCampaign, this), SDLK_b);
break;
}
buttonStart->assignedKeys.insert(SDLK_RETURN);
buttonBack = std::make_shared<CButton>(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], [&](){CSH->sendClientDisconnecting(); GH.popIntTotally(this);}, SDLK_ESCAPE);
}
CLobbyScreen::~CLobbyScreen()
{
// TODO: For now we always destroy whole lobby when leaving bonus selection screen
if(CSH->state == EClientState::LOBBY_CAMPAIGN)
CSH->sendClientDisconnecting();
}
void CLobbyScreen::toggleTab(std::shared_ptr<CIntObject> tab)
{
if(tab == curTab)
CSH->sendGuiAction(LobbyGuiAction::NO_TAB);
else if(tab == tabOpt)
CSH->sendGuiAction(LobbyGuiAction::OPEN_OPTIONS);
else if(tab == tabSel)
CSH->sendGuiAction(LobbyGuiAction::OPEN_SCENARIO_LIST);
else if(tab == tabRand)
CSH->sendGuiAction(LobbyGuiAction::OPEN_RANDOM_MAP_OPTIONS);
CSelectionBase::toggleTab(tab);
}
void CLobbyScreen::startCampaign()
{
if(CSH->mi)
{
auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(CSH->mi->fileURI));
CSH->setCampaignState(ourCampaign);
}
}
void CLobbyScreen::startScenario(bool allowOnlyAI)
{
try
{
CSH->sendStartGame(allowOnlyAI);
buttonStart->block(true);
}
catch(ExceptionMapMissing & e)
{
}
catch(ExceptionNoHuman & e)
{
// You must position yourself prior to starting the game.
CInfoWindow::showYesNoDialog(std::ref(CGI->generaltexth->allTexts[530]), nullptr, 0, std::bind(&CLobbyScreen::startScenario, this, true), false, PlayerColor(1));
}
catch(ExceptionNoTemplate & e)
{
GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[751]));
}
catch(...)
{
}
}
void CLobbyScreen::toggleMode(bool host)
{
tabSel->toggleMode();
buttonStart->block(!host);
if(screenType == ESelectionScreen::campaignList)
return;
auto buttonColor = host ? Colors::WHITE : Colors::ORANGE;
buttonSelect->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, buttonColor);
buttonOptions->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, buttonColor);
if(buttonRMG)
{
buttonRMG->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, buttonColor);
buttonRMG->block(!host);
}
buttonSelect->block(!host);
buttonOptions->block(!host);
if(CSH->mi)
tabOpt->recreate();
}
void CLobbyScreen::toggleChat()
{
card->toggleChat();
if(card->showChat)
buttonChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
else
buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL);
}
void CLobbyScreen::updateAfterStateChange()
{
if(CSH->mi && tabOpt)
tabOpt->recreate();
card->changeSelection();
if(card->iconDifficulty)
card->iconDifficulty->setSelected(CSH->si->difficulty);
if(curTab == tabRand && CSH->si->mapGenOptions)
tabRand->setMapGenOptions(CSH->si->mapGenOptions);
}
const StartInfo * CLobbyScreen::getStartInfo()
{
return CSH->si.get();
}
const CMapInfo * CLobbyScreen::getMapInfo()
{
return CSH->mi.get();
}

View File

@ -0,0 +1,35 @@
/*
* CLobbyScreen.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 "CSelectionBase.h"
class CBonusSelection;
class CLobbyScreen : public CSelectionBase
{
public:
std::shared_ptr<CButton> buttonChat;
CLobbyScreen(ESelectionScreen type);
~CLobbyScreen();
void toggleTab(std::shared_ptr<CIntObject> tab) override;
void startCampaign();
void startScenario(bool allowOnlyAI = false);
void toggleMode(bool host);
void toggleChat();
void updateAfterStateChange();
const CMapInfo * getMapInfo() override;
const StartInfo * getStartInfo() override;
CBonusSelection * bonusSel;
};

View File

@ -0,0 +1,97 @@
/*
* CSavingScreen.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 "CSavingScreen.h"
#include "SelectionTab.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/Buttons.h"
#include "../widgets/TextControls.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/StartInfo.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/mapping/CMapInfo.h"
CSavingScreen::CSavingScreen()
: CSelectionBase(ESelectionScreen::saveGame)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
center(pos);
// TODO: we should really use std::shared_ptr for passing StartInfo around.
localSi = new StartInfo(*LOCPLINT->cb->getStartInfo());
localMi = new CMapInfo();
localMi->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));
tabSel = std::make_shared<SelectionTab>(screenType);
curTab = tabSel;
tabSel->toggleMode();
tabSel->callOnSelect = std::bind(&CSavingScreen::changeSelection, this, _1);
buttonStart = std::make_shared<CButton>(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSavingScreen::saveGame, this), SDLK_s);
buttonStart->assignedKeys.insert(SDLK_RETURN);
}
CSavingScreen::~CSavingScreen()
{
vstd::clear_pointer(localMi);
}
const CMapInfo * CSavingScreen::getMapInfo()
{
return localMi;
}
const StartInfo * CSavingScreen::getStartInfo()
{
return localSi;
}
void CSavingScreen::changeSelection(std::shared_ptr<CMapInfo> to)
{
if(localMi == to.get())
return;
localMi = to.get();
localSi = localMi->scenarioOptionsOfSave;
card->changeSelection();
}
void CSavingScreen::saveGame()
{
if(!(tabSel && tabSel->inputName && tabSel->inputName->text.size()))
return;
std::string path = "Saves/" + tabSel->inputName->text;
auto overWrite = [&]() -> void
{
Settings lastSave = settings.write["general"]["lastSave"];
lastSave->String() = path;
LOCPLINT->cb->save(path);
GH.popIntTotally(this);
};
if(CResourceHandler::get("local")->existsResource(ResourceID(path, EResType::CLIENT_SAVEGAME)))
{
std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
boost::algorithm::replace_first(hlp, "%s", tabSel->inputName->text);
LOCPLINT->showYesNoDialog(hlp, overWrite, 0, false);
}
else
{
overWrite();
}
}

View File

@ -0,0 +1,32 @@
/*
* CSavingScreen.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 "CSelectionBase.h"
class CSelectionBase;
struct StartInfo;
class CMapInfo;
class CSavingScreen : public CSelectionBase
{
public:
const StartInfo * localSi;
CMapInfo * localMi;
CSavingScreen();
~CSavingScreen();
void changeSelection(std::shared_ptr<CMapInfo> to);
void saveGame();
const CMapInfo * getMapInfo() override;
const StartInfo * getStartInfo() override;
};

View File

@ -0,0 +1,59 @@
/*
* CScenarioInfoScreen.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 "CScenarioInfoScreen.h"
#include "OptionsTab.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/Buttons.h"
#include "../../CCallback.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/StartInfo.h"
#include "../../lib/mapping/CMapInfo.h"
CScenarioInfoScreen::CScenarioInfoScreen()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
localSi = new StartInfo(*LOCPLINT->cb->getStartInfo());
localMi = new CMapInfo();
localMi->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));
screenType = ESelectionScreen::scenarioInfo;
card = std::make_shared<InfoCard>();
opt = std::make_shared<OptionsTab>();
opt->recActions = UPDATE | SHOWALL;
opt->recreate();
card->changeSelection();
card->iconDifficulty->setSelected(getCurrentDifficulty());
buttonBack = std::make_shared<CButton>(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
}
CScenarioInfoScreen::~CScenarioInfoScreen()
{
vstd::clear_pointer(localSi);
vstd::clear_pointer(localMi);
}
const CMapInfo * CScenarioInfoScreen::getMapInfo()
{
return localMi;
}
const StartInfo * CScenarioInfoScreen::getStartInfo()
{
return localSi;
}

View File

@ -0,0 +1,30 @@
/*
* CScenarioInfoScreen.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 "CSelectionBase.h"
/// Scenario information screen shown during the game
class CScenarioInfoScreen : public CIntObject, public ISelectionScreenInfo
{
public:
std::shared_ptr<CButton> buttonBack;
std::shared_ptr<InfoCard> card;
std::shared_ptr<OptionsTab> opt;
const StartInfo * localSi;
CMapInfo * localMi;
CScenarioInfoScreen();
~CScenarioInfoScreen();
const CMapInfo * getMapInfo() override;
const StartInfo * getStartInfo() override;
};

View File

@ -0,0 +1,407 @@
/*
* CSelectionBase.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 "CSelectionBase.h"
#include "CBonusSelection.h"
#include "CLobbyScreen.h"
#include "OptionsTab.h"
#include "RandomMapTab.h"
#include "SelectionTab.h"
#include "../../CCallback.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../lib/NetPacksLobby.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/serializer/Connection.h"
ISelectionScreenInfo::ISelectionScreenInfo(ESelectionScreen ScreenType)
: screenType(ScreenType)
{
assert(!SEL);
SEL = this;
}
ISelectionScreenInfo::~ISelectionScreenInfo()
{
assert(SEL == this);
SEL = nullptr;
}
int ISelectionScreenInfo::getCurrentDifficulty()
{
return getStartInfo()->difficulty;
}
PlayerInfo ISelectionScreenInfo::getPlayerInfo(int color)
{
return getMapInfo()->mapHeader->players[color];
}
CSelectionBase::CSelectionBase(ESelectionScreen type)
: CWindowObject(BORDERED | SHADOW_DISABLED), ISelectionScreenInfo(type)
{
CMainMenu::create(); //we depend on its graphics
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
IShowActivatable::type = BLOCK_ADV_HOTKEYS;
pos.w = 762;
pos.h = 584;
if(screenType == ESelectionScreen::campaignList)
{
setBackground("CamCust.bmp");
}
else
{
const JsonVector & bgNames = CMainMenuConfig::get().getConfig()["game-select"].Vector();
setBackground(RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())->String());
}
pos = background->center();
card = std::make_shared<InfoCard>();
buttonBack = std::make_shared<CButton>(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
}
void CSelectionBase::toggleTab(std::shared_ptr<CIntObject> tab)
{
if(curTab && curTab->active)
{
curTab->deactivate();
curTab->recActions = 0;
}
if(curTab != tab)
{
tab->recActions = 255 - DISPOSE;
tab->activate();
curTab = tab;
}
else
{
curTab.reset();
}
GH.totalRedraw();
}
InfoCard::InfoCard()
: showChat(true)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
CIntObject::type |= REDRAW_PARENT;
pos.x += 393;
pos.y += 6;
labelSaveDate = std::make_shared<CLabel>(158, 19, FONT_SMALL, TOPLEFT, Colors::WHITE);
mapName = std::make_shared<CLabel>(26, 39, FONT_BIG, TOPLEFT, Colors::YELLOW);
Rect descriptionRect(26, 149, 320, 115);
mapDescription = std::make_shared<CTextBox>("", descriptionRect, 1);
playerListBg = std::make_shared<CPicture>("CHATPLUG.bmp", 16, 276);
chat = std::make_shared<CChatBox>(Rect(26, 132, 340, 132));
if(SEL->screenType == ESelectionScreen::campaignList)
{
labelCampaignDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[38]);
}
else
{
background = std::make_shared<CPicture>("GSELPOP1.bmp", 0, 0);
parent->addChild(background.get());
auto it = vstd::find(parent->children, this); //our position among parent children
parent->children.insert(it, background.get()); //put BG before us
parent->children.pop_back();
pos.w = background->pos.w;
pos.h = background->pos.h;
iconsMapSizes = std::make_shared<CAnimImage>("SCNRMPSZ", 4, 0, 318, 22); //let it be custom size (frame 4) by default
iconDifficulty = std::make_shared<CToggleGroup>(0);
{
static const char * difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"};
for(int i = 0; i < 5; i++)
{
auto button = new CToggleButton(Point(110 + i * 32, 450), difButns[i], CGI->generaltexth->zelp[24 + i]);
iconDifficulty->addToggle(i, button);
if(SEL->screenType != ESelectionScreen::newGame)
button->block(true);
}
}
flagbox = std::make_shared<CFlagBox>(Rect(24, 400, 335, 23));
labelMapDiff = std::make_shared<CLabel>(33, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[494]);
labelPlayerDifficulty = std::make_shared<CLabel>(133, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[492] + ":");
labelRating = std::make_shared<CLabel>(290, 430, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[218] + ":");
labelScenarioName = std::make_shared<CLabel>(26, 22, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[495]);
labelScenarioDescription = std::make_shared<CLabel>(26, 132, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[496]);
labelVictoryCondition = std::make_shared<CLabel>(26, 283, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[497]);
labelLossCondition = std::make_shared<CLabel>(26, 339, FONT_SMALL, TOPLEFT, Colors::YELLOW, CGI->generaltexth->allTexts[498]);
iconsVictoryCondition = std::make_shared<CAnimImage>("SCNRVICT", 0, 0, 24, 302);
iconsLossCondition = std::make_shared<CAnimImage>("SCNRLOSS", 0, 0, 24, 359);
labelVictoryConditionText = std::make_shared<CLabel>(60, 307, FONT_SMALL, TOPLEFT, Colors::WHITE);
labelLossConditionText = std::make_shared<CLabel>(60, 366, FONT_SMALL, TOPLEFT, Colors::WHITE);
labelDifficulty = std::make_shared<CLabel>(62, 472, FONT_SMALL, CENTER, Colors::WHITE);
labelDifficultyPercent = std::make_shared<CLabel>(311, 472, FONT_SMALL, CENTER, Colors::WHITE);
labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
}
setChat(false);
}
void InfoCard::changeSelection()
{
if(!SEL->getMapInfo())
return;
labelSaveDate->setText(SEL->getMapInfo()->date);
mapName->setText(SEL->getMapInfo()->getName());
mapDescription->setText(SEL->getMapInfo()->getDescription());
mapDescription->label->scrollTextTo(0);
if(mapDescription->slider)
mapDescription->slider->moveToMin();
if(SEL->screenType == ESelectionScreen::campaignList)
return;
iconsMapSizes->setFrame(SEL->getMapInfo()->getMapSizeIconId());
const CMapHeader * header = SEL->getMapInfo()->mapHeader.get();
iconsVictoryCondition->setFrame(header->victoryIconIndex);
labelVictoryConditionText->setText(header->victoryMessage);
iconsLossCondition->setFrame(header->defeatIconIndex);
labelLossConditionText->setText(header->defeatMessage);
flagbox->recreate();
labelDifficulty->setText(CGI->generaltexth->arraytxt[142 + SEL->getMapInfo()->mapHeader->difficulty]);
iconDifficulty->setSelected(SEL->getCurrentDifficulty());
const std::array<std::string, 5> difficultyPercent = {"80%", "100%", "130%", "160%", "200%"};
labelDifficultyPercent->setText(difficultyPercent[SEL->getCurrentDifficulty()]);
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
// FIXME: We recreate them each time because CLabelGroup don't use smart pointers
labelGroupPlayersAssigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
labelGroupPlayersUnassigned = std::make_shared<CLabelGroup>(FONT_SMALL, TOPLEFT, Colors::WHITE);
if(!showChat)
{
labelGroupPlayersAssigned->disable();
labelGroupPlayersUnassigned->disable();
}
for(auto & p : CSH->playerNames)
{
const auto pset = CSH->si->getPlayersSettings(p.first);
int pid = p.first;
if(pset)
{
auto name = boost::str(boost::format("%s (%d-%d %s)") % p.second.name % p.second.connection % pid % pset->color.getStr());
labelGroupPlayersAssigned->add(24, 285 + labelGroupPlayersAssigned->currentSize()*graphics->fonts[FONT_SMALL]->getLineHeight(), name);
}
else
{
auto name = boost::str(boost::format("%s (%d-%d)") % p.second.name % p.second.connection % pid);
labelGroupPlayersUnassigned->add(193, 285 + labelGroupPlayersUnassigned->currentSize()*graphics->fonts[FONT_SMALL]->getLineHeight(), name);
}
}
}
void InfoCard::toggleChat()
{
setChat(!showChat);
}
void InfoCard::setChat(bool activateChat)
{
if(showChat == activateChat)
return;
if(activateChat)
{
if(SEL->screenType == ESelectionScreen::campaignList)
{
labelCampaignDescription->disable();
}
else
{
labelScenarioDescription->disable();
labelVictoryCondition->disable();
labelLossCondition->disable();
iconsVictoryCondition->disable();
labelVictoryConditionText->disable();
iconsLossCondition->disable();
labelLossConditionText->disable();
labelGroupPlayersAssigned->enable();
labelGroupPlayersUnassigned->enable();
}
mapDescription->disable();
chat->enable();
playerListBg->enable();
}
else
{
mapDescription->enable();
chat->disable();
playerListBg->disable();
if(SEL->screenType == ESelectionScreen::campaignList)
{
labelCampaignDescription->enable();
}
else
{
labelScenarioDescription->enable();
labelVictoryCondition->enable();
labelLossCondition->enable();
iconsVictoryCondition->enable();
iconsLossCondition->enable();
labelVictoryConditionText->enable();
labelLossConditionText->enable();
labelGroupPlayersAssigned->disable();
labelGroupPlayersUnassigned->disable();
}
}
showChat = activateChat;
GH.totalRedraw();
}
CChatBox::CChatBox(const Rect & rect)
: CIntObject(KEYBOARD | TEXTINPUT)
{
OBJ_CONSTRUCTION;
pos += rect;
captureAllKeys = true;
type |= REDRAW_PARENT;
const int height = graphics->fonts[FONT_SMALL]->getLineHeight();
inputBox = std::make_shared<CTextInput>(Rect(0, rect.h - height, rect.w, height));
inputBox->removeUsedEvents(KEYBOARD);
chatHistory = std::make_shared<CTextBox>("", Rect(0, 0, rect.w, rect.h - height), 1);
chatHistory->label->color = Colors::GREEN;
}
void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->text.size())
{
CSH->sendMessage(inputBox->text);
inputBox->setText("");
}
else
inputBox->keyPressed(key);
}
void CChatBox::addNewMessage(const std::string & text)
{
CCS->soundh->playSound("CHAT");
chatHistory->setText(chatHistory->label->text + text + "\n");
if(chatHistory->slider)
chatHistory->slider->moveToMax();
}
CFlagBox::CFlagBox(const Rect & rect)
: CIntObject(RCLICK)
{
pos += rect;
pos.w = rect.w;
pos.h = rect.h;
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
labelAllies = std::make_shared<CLabel>(0, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[390] + ":");
labelEnemies = std::make_shared<CLabel>(133, 0, FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE, CGI->generaltexth->allTexts[391] + ":");
iconsTeamFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
iconsTeamFlags->preload();
}
void CFlagBox::recreate()
{
flagsAllies.clear();
flagsEnemies.clear();
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
const int alliesX = 5 + labelAllies->getWidth();
const int enemiesX = 5 + 133 + labelEnemies->getWidth();
for(auto i = CSH->si->playerInfos.cbegin(); i != CSH->si->playerInfos.cend(); i++)
{
auto flag = std::make_shared<CAnimImage>(iconsTeamFlags, i->first.getNum(), 0);
if(i->first == CSH->myFirstColor() || CSH->getPlayerTeamId(i->first) == CSH->getPlayerTeamId(CSH->myFirstColor()))
{
flag->moveTo(Point(pos.x + alliesX + flagsAllies.size()*flag->pos.w, pos.y));
flagsAllies.push_back(flag);
}
else
{
flag->moveTo(Point(pos.x + enemiesX + flagsEnemies.size()*flag->pos.w, pos.y));
flagsEnemies.push_back(flag);
}
}
}
void CFlagBox::clickRight(tribool down, bool previousState)
{
if(down && SEL->getMapInfo())
GH.pushInt(new CFlagBoxTooltipBox(iconsTeamFlags));
}
CFlagBox::CFlagBoxTooltipBox::CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons)
: CWindowObject(BORDERED | RCLICK_POPUP | SHADOW_DISABLED, "DIBOXBCK")
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.w = 256;
pos.h = 90 + 50 * SEL->getMapInfo()->mapHeader->howManyTeams;
labelTeamAlignment = std::make_shared<CLabel>(128, 30, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[657]);
labelGroupTeams = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
for(int i = 0; i < SEL->getMapInfo()->mapHeader->howManyTeams; i++)
{
std::vector<ui8> flags;
labelGroupTeams->add(128, 65 + 50 * i, boost::str(boost::format(CGI->generaltexth->allTexts[656]) % (i+1)));
for(int j = 0; j < PlayerColor::PLAYER_LIMIT_I; j++)
{
if((SEL->getPlayerInfo(j).canHumanPlay || SEL->getPlayerInfo(j).canComputerPlay)
&& SEL->getPlayerInfo(j).team == TeamID(i))
{
flags.push_back(j);
}
}
int curx = 128 - 9 * flags.size();
for(auto & flag : flags)
{
iconsFlags.push_back(std::make_shared<CAnimImage>(icons, flag, 0, curx, 75 + 50 * i));
curx += 18;
}
}
background->scaleTo(Point(pos.w, pos.h));
center();
}

View File

@ -0,0 +1,146 @@
/*
* CSelectionBase.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 "../mainmenu/CMainMenu.h"
class CButton;
class CTextBox;
class CTextInput;
class CAnimImage;
class CToggleGroup;
class RandomMapTab;
class OptionsTab;
class SelectionTab;
class InfoCard;
class CChatBox;
class CMapInfo;
struct StartInfo;
struct PlayerInfo;
class CLabel;
class CFlagBox;
class CLabelGroup;
class ISelectionScreenInfo
{
public:
ESelectionScreen screenType;
ISelectionScreenInfo(ESelectionScreen ScreenType = ESelectionScreen::unknown);
virtual ~ISelectionScreenInfo();
virtual const CMapInfo * getMapInfo() = 0;
virtual const StartInfo * getStartInfo() = 0;
virtual int getCurrentDifficulty();
virtual PlayerInfo getPlayerInfo(int color);
};
/// The actual map selection screen which consists of the options and selection tab
class CSelectionBase : public CWindowObject, public ISelectionScreenInfo
{
public:
std::shared_ptr<InfoCard> card;
std::shared_ptr<CButton> buttonSelect;
std::shared_ptr<CButton> buttonRMG;
std::shared_ptr<CButton> buttonOptions;
std::shared_ptr<CButton> buttonStart;
std::shared_ptr<CButton> buttonBack;
std::shared_ptr<SelectionTab> tabSel;
std::shared_ptr<OptionsTab> tabOpt;
std::shared_ptr<RandomMapTab> tabRand;
std::shared_ptr<CIntObject> curTab;
CSelectionBase(ESelectionScreen type);
virtual void toggleTab(std::shared_ptr<CIntObject> tab);
};
class InfoCard : public CIntObject
{
std::shared_ptr<CPicture> playerListBg;
std::shared_ptr<CPicture> background;
std::shared_ptr<CAnimImage> iconsVictoryCondition;
std::shared_ptr<CAnimImage> iconsLossCondition;
std::shared_ptr<CAnimImage> iconsMapSizes;
std::shared_ptr<CLabel> labelSaveDate;
std::shared_ptr<CLabel> labelScenarioName;
std::shared_ptr<CLabel> labelScenarioDescription;
std::shared_ptr<CLabel> labelVictoryCondition;
std::shared_ptr<CLabel> labelLossCondition;
std::shared_ptr<CLabel> labelMapDiff;
std::shared_ptr<CLabel> labelPlayerDifficulty;
std::shared_ptr<CLabel> labelRating;
std::shared_ptr<CLabel> labelCampaignDescription;
std::shared_ptr<CLabel> mapName;
std::shared_ptr<CTextBox> mapDescription;
std::shared_ptr<CLabel> labelDifficulty;
std::shared_ptr<CLabel> labelDifficultyPercent;
std::shared_ptr<CLabel> labelVictoryConditionText;
std::shared_ptr<CLabel> labelLossConditionText;
std::shared_ptr<CLabelGroup> labelGroupPlayersAssigned;
std::shared_ptr<CLabelGroup> labelGroupPlayersUnassigned;
public:
bool showChat;
std::shared_ptr<CChatBox> chat;
std::shared_ptr<CFlagBox> flagbox;
std::shared_ptr<CToggleGroup> iconDifficulty;
InfoCard();
void changeSelection();
void toggleChat();
void setChat(bool activateChat);
};
class CChatBox : public CIntObject
{
public:
std::shared_ptr<CTextBox> chatHistory;
std::shared_ptr<CTextInput> inputBox;
CChatBox(const Rect & rect);
void keyPressed(const SDL_KeyboardEvent & key) override;
void addNewMessage(const std::string & text);
};
class CFlagBox : public CIntObject
{
std::shared_ptr<CAnimation> iconsTeamFlags;
std::shared_ptr<CLabel> labelAllies;
std::shared_ptr<CLabel> labelEnemies;
std::vector<std::shared_ptr<CAnimImage>> flagsAllies;
std::vector<std::shared_ptr<CAnimImage>> flagsEnemies;
public:
CFlagBox(const Rect & rect);
void recreate();
void clickRight(tribool down, bool previousState) override;
void showTeamsPopup();
class CFlagBoxTooltipBox : public CWindowObject
{
std::shared_ptr<CLabel> labelTeamAlignment;
std::shared_ptr<CLabelGroup> labelGroupTeams;
std::vector<std::shared_ptr<CAnimImage>> iconsFlags;
public:
CFlagBoxTooltipBox(std::shared_ptr<CAnimation> icons);
};
};
extern ISelectionScreenInfo * SEL;

537
client/lobby/OptionsTab.cpp Normal file
View File

@ -0,0 +1,537 @@
/*
* OptionsTab.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 "CSelectionBase.h"
#include "OptionsTab.h"
#include "../CBitmapHandler.h"
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../lib/NetPacksLobby.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CArtHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/mapping/CMap.h"
#include "../../lib/mapping/CMapInfo.h"
OptionsTab::OptionsTab()
{
recActions = 0;
OBJ_CONSTRUCTION;
background = std::make_shared<CPicture>("ADVOPTBK", 0, 6);
pos = background->pos;
labelTitle = std::make_shared<CLabel>(222, 30, FONT_BIG, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[515]);
labelSubTitle = std::make_shared<CMultiLineLabel>(Rect(60, 44, 320, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[516]);
labelPlayerNameAndHandicap = std::make_shared<CMultiLineLabel>(Rect(58, 86, 100, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[517]);
labelStartingTown = std::make_shared<CMultiLineLabel>(Rect(163, 86, 70, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[518]);
labelStartingHero = std::make_shared<CMultiLineLabel>(Rect(239, 86, 70, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[519]);
labelStartingBonus = std::make_shared<CMultiLineLabel>(Rect(315, 86, 70, graphics->fonts[EFonts::FONT_SMALL]->getLineHeight()*2), EFonts::FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[520]);
if(SEL->screenType == ESelectionScreen::newGame || SEL->screenType == ESelectionScreen::loadGame || SEL->screenType == ESelectionScreen::scenarioInfo)
{
sliderTurnDuration = std::make_shared<CSlider>(Point(55, 551), 194, std::bind(&IServerAPI::setTurnLength, CSH, _1), 1, GameConstants::POSSIBLE_TURNTIME.size(), GameConstants::POSSIBLE_TURNTIME.size(), true, CSlider::BLUE);
labelPlayerTurnDuration = std::make_shared<CLabel>(222, 538, FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[521]);
labelTurnDurationValue = std::make_shared<CLabel>(319, 559, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
}
}
void OptionsTab::recreate()
{
entries.clear();
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
for(auto & pInfo : SEL->getStartInfo()->playerInfos)
{
entries.insert(std::make_pair(pInfo.first, std::make_shared<PlayerOptionsEntry>(pInfo.second)));
}
if(sliderTurnDuration)
{
sliderTurnDuration->moveTo(vstd::find_pos(GameConstants::POSSIBLE_TURNTIME, SEL->getStartInfo()->turnTime));
labelTurnDurationValue->setText(CGI->generaltexth->turnDurations[sliderTurnDuration->getValue()]);
}
}
size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
{
enum EBonusSelection //frames of bonuses file
{
WOOD_ORE = 0, CRYSTAL = 1, GEM = 2,
MERCURY = 3, SULFUR = 5, GOLD = 8,
ARTIFACT = 9, RANDOM = 10,
WOOD = 0, ORE = 0, MITHRIL = 10, // resources unavailable in bonuses file
TOWN_RANDOM = 38, TOWN_NONE = 39, // Special frames in ITPA
HERO_RANDOM = 163, HERO_NONE = 164 // Special frames in PortraitsSmall
};
switch(type)
{
case TOWN:
switch(settings.castle)
{
case PlayerSettings::NONE:
return TOWN_NONE;
case PlayerSettings::RANDOM:
return TOWN_RANDOM;
default:
return CGI->townh->factions[settings.castle]->town->clientInfo.icons[true][false] + 2;
}
case HERO:
switch(settings.hero)
{
case PlayerSettings::NONE:
return HERO_NONE;
case PlayerSettings::RANDOM:
return HERO_RANDOM;
default:
{
if(settings.heroPortrait >= 0)
return settings.heroPortrait;
return CGI->heroh->heroes[settings.hero]->imageIndex;
}
}
case BONUS:
{
switch(settings.bonus)
{
case PlayerSettings::RANDOM:
return RANDOM;
case PlayerSettings::ARTIFACT:
return ARTIFACT;
case PlayerSettings::GOLD:
return GOLD;
case PlayerSettings::RESOURCE:
{
switch(CGI->townh->factions[settings.castle]->town->primaryRes)
{
case Res::WOOD_AND_ORE:
return WOOD_ORE;
case Res::WOOD:
return WOOD;
case Res::MERCURY:
return MERCURY;
case Res::ORE:
return ORE;
case Res::SULFUR:
return SULFUR;
case Res::CRYSTAL:
return CRYSTAL;
case Res::GEMS:
return GEM;
case Res::GOLD:
return GOLD;
case Res::MITHRIL:
return MITHRIL;
}
}
}
}
}
return 0;
}
std::string OptionsTab::CPlayerSettingsHelper::getImageName()
{
switch(type)
{
case OptionsTab::TOWN:
return "ITPA";
case OptionsTab::HERO:
return "PortraitsSmall";
case OptionsTab::BONUS:
return "SCNRSTAR";
}
return "";
}
std::string OptionsTab::CPlayerSettingsHelper::getName()
{
switch(type)
{
case TOWN:
{
switch(settings.castle)
{
case PlayerSettings::NONE:
return CGI->generaltexth->allTexts[523];
case PlayerSettings::RANDOM:
return CGI->generaltexth->allTexts[522];
default:
return CGI->townh->factions[settings.castle]->name;
}
}
case HERO:
{
switch(settings.hero)
{
case PlayerSettings::NONE:
return CGI->generaltexth->allTexts[523];
case PlayerSettings::RANDOM:
return CGI->generaltexth->allTexts[522];
default:
{
if(!settings.heroName.empty())
return settings.heroName;
return CGI->heroh->heroes[settings.hero]->name;
}
}
}
case BONUS:
{
switch(settings.bonus)
{
case PlayerSettings::RANDOM:
return CGI->generaltexth->allTexts[522];
default:
return CGI->generaltexth->arraytxt[214 + settings.bonus];
}
}
}
return "";
}
std::string OptionsTab::CPlayerSettingsHelper::getTitle()
{
switch(type)
{
case OptionsTab::TOWN:
return (settings.castle < 0) ? CGI->generaltexth->allTexts[103] : CGI->generaltexth->allTexts[80];
case OptionsTab::HERO:
return (settings.hero < 0) ? CGI->generaltexth->allTexts[101] : CGI->generaltexth->allTexts[77];
case OptionsTab::BONUS:
{
switch(settings.bonus)
{
case PlayerSettings::RANDOM:
return CGI->generaltexth->allTexts[86]; //{Random Bonus}
case PlayerSettings::ARTIFACT:
return CGI->generaltexth->allTexts[83]; //{Artifact Bonus}
case PlayerSettings::GOLD:
return CGI->generaltexth->allTexts[84]; //{Gold Bonus}
case PlayerSettings::RESOURCE:
return CGI->generaltexth->allTexts[85]; //{Resource Bonus}
}
}
}
return "";
}
std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
{
switch(type)
{
case TOWN:
return getName();
case HERO:
{
if(settings.hero >= 0)
return getName() + " - " + CGI->heroh->heroes[settings.hero]->heroClass->name;
return getName();
}
case BONUS:
{
switch(settings.bonus)
{
case PlayerSettings::GOLD:
return CGI->generaltexth->allTexts[87]; //500-1000
case PlayerSettings::RESOURCE:
{
switch(CGI->townh->factions[settings.castle]->town->primaryRes)
{
case Res::MERCURY:
return CGI->generaltexth->allTexts[694];
case Res::SULFUR:
return CGI->generaltexth->allTexts[695];
case Res::CRYSTAL:
return CGI->generaltexth->allTexts[692];
case Res::GEMS:
return CGI->generaltexth->allTexts[693];
case Res::WOOD_AND_ORE:
return CGI->generaltexth->allTexts[89]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
}
}
}
}
}
return "";
}
std::string OptionsTab::CPlayerSettingsHelper::getDescription()
{
switch(type)
{
case TOWN:
return CGI->generaltexth->allTexts[104];
case HERO:
return CGI->generaltexth->allTexts[102];
case BONUS:
{
switch(settings.bonus)
{
case PlayerSettings::RANDOM:
return CGI->generaltexth->allTexts[94]; //Gold, wood and ore, or an artifact is randomly chosen as your starting bonus
case PlayerSettings::ARTIFACT:
return CGI->generaltexth->allTexts[90]; //An artifact is randomly chosen and equipped to your starting hero
case PlayerSettings::GOLD:
return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
case PlayerSettings::RESOURCE:
{
switch(CGI->townh->factions[settings.castle]->town->primaryRes)
{
case Res::MERCURY:
return CGI->generaltexth->allTexts[690];
case Res::SULFUR:
return CGI->generaltexth->allTexts[691];
case Res::CRYSTAL:
return CGI->generaltexth->allTexts[688];
case Res::GEMS:
return CGI->generaltexth->allTexts[689];
case Res::WOOD_AND_ORE:
return CGI->generaltexth->allTexts[93]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
}
}
}
}
}
return "";
}
OptionsTab::CPlayerOptionTooltipBox::CPlayerOptionTooltipBox(CPlayerSettingsHelper & helper)
: CWindowObject(BORDERED | RCLICK_POPUP), CPlayerSettingsHelper(helper)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
int value = PlayerSettings::NONE;
switch(CPlayerSettingsHelper::type)
{
break;
case TOWN:
value = settings.castle;
break;
case HERO:
value = settings.hero;
break;
case BONUS:
value = settings.bonus;
}
if(value == PlayerSettings::RANDOM)
genBonusWindow();
else if(CPlayerSettingsHelper::type == BONUS)
genBonusWindow();
else if(CPlayerSettingsHelper::type == HERO)
genHeroWindow();
else if(CPlayerSettingsHelper::type == TOWN)
genTownWindow();
center();
}
void OptionsTab::CPlayerOptionTooltipBox::genHeader()
{
backgroundTexture = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
updateShadow();
labelTitle = std::make_shared<CLabel>(pos.w / 2 + 8, 21, FONT_MEDIUM, CENTER, Colors::YELLOW, getTitle());
labelSubTitle = std::make_shared<CLabel>(pos.w / 2, 88, FONT_SMALL, CENTER, Colors::WHITE, getSubtitle());
image = std::make_shared<CAnimImage>(getImageName(), getImageIndex(), 0, pos.w / 2 - 24, 45);
}
void OptionsTab::CPlayerOptionTooltipBox::genTownWindow()
{
pos = Rect(0, 0, 228, 290);
genHeader();
labelAssociatedCreatures = std::make_shared<CLabel>(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
std::vector<CComponent *> components;
const CTown * town = CGI->townh->factions[settings.castle]->town;
for(auto & elem : town->creatures)
{
if(!elem.empty())
components.push_back(new CComponent(CComponent::creature, elem.front(), 0, CComponent::tiny));
}
boxAssociatedCreatures = std::make_shared<CComponentBox>(components, Rect(10, 140, pos.w - 20, 140));
}
void OptionsTab::CPlayerOptionTooltipBox::genHeroWindow()
{
pos = Rect(0, 0, 292, 226);
genHeader();
labelHeroSpeciality = std::make_shared<CLabel>(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
imageSpeciality = std::make_shared<CAnimImage>("UN44", CGI->heroh->heroes[settings.hero]->imageIndex, 0, pos.w / 2 - 22, 134);
labelSpecialityName = std::make_shared<CLabel>(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, CGI->heroh->heroes[settings.hero]->specName);
}
void OptionsTab::CPlayerOptionTooltipBox::genBonusWindow()
{
pos = Rect(0, 0, 228, 162);
genHeader();
textBonusDescription = std::make_shared<CTextBox>(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, CENTER, Colors::WHITE);
}
OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
: CIntObject(RCLICK, position), CPlayerSettingsHelper(settings, type)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
image = std::make_shared<CAnimImage>(getImageName(), getImageIndex());
subtitle = std::make_shared<CLabel>(23, 39, FONT_TINY, CENTER, Colors::WHITE, getName());
pos = image->pos;
}
void OptionsTab::SelectedBox::update()
{
image->setFrame(getImageIndex());
subtitle->setText(getName());
}
void OptionsTab::SelectedBox::clickRight(tribool down, bool previousState)
{
if(down)
{
// cases when we do not need to display a message
if(settings.castle == -2 && CPlayerSettingsHelper::type == TOWN)
return;
if(settings.hero == -2 && !SEL->getPlayerInfo(settings.color.getNum()).hasCustomMainHero() && CPlayerSettingsHelper::type == HERO)
return;
GH.pushInt(new CPlayerOptionTooltipBox(*this));
}
}
OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S)
: pi(SEL->getPlayerInfo(S.color.getNum())), s(S)
{
OBJ_CONSTRUCTION;
defActions |= SHARE_POS;
int serial = 0;
for(int g = 0; g < s.color.getNum(); ++g)
{
auto itred = SEL->getPlayerInfo(g);
if(itred.canComputerPlay || itred.canHumanPlay)
serial++;
}
pos.x += 54;
pos.y += 122 + serial * 50;
assert(CSH->mi && CSH->mi->mapHeader);
const PlayerInfo & p = SEL->getPlayerInfo(s.color.getNum());
assert(p.canComputerPlay || p.canHumanPlay); //someone must be able to control this player
if(p.canHumanPlay && p.canComputerPlay)
whoCanPlay = HUMAN_OR_CPU;
else if(p.canComputerPlay)
whoCanPlay = CPU;
else
whoCanPlay = HUMAN;
static const char * flags[] =
{
"AOFLGBR.DEF", "AOFLGBB.DEF", "AOFLGBY.DEF", "AOFLGBG.DEF",
"AOFLGBO.DEF", "AOFLGBP.DEF", "AOFLGBT.DEF", "AOFLGBS.DEF"
};
static const char * bgs[] =
{
"ADOPRPNL.bmp", "ADOPBPNL.bmp", "ADOPYPNL.bmp", "ADOPGPNL.bmp",
"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"
};
background = std::make_shared<CPicture>(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
labelPlayerName = std::make_shared<CLabel>(55, 10, EFonts::FONT_SMALL, EAlignment::CENTER, Colors::WHITE, s.name);
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, graphics->fonts[EFonts::FONT_TINY]->getLineHeight()*2), EFonts::FONT_TINY, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
if(SEL->screenType == ESelectionScreen::newGame)
{
buttonTownLeft = std::make_shared<CButton>(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, -1, s.color));
buttonTownRight = std::make_shared<CButton>(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::TOWN, +1, s.color));
buttonHeroLeft = std::make_shared<CButton>(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, -1, s.color));
buttonHeroRight = std::make_shared<CButton>(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::HERO, +1, s.color));
buttonBonusLeft = std::make_shared<CButton>(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, -1, s.color));
buttonBonusRight = std::make_shared<CButton>(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&IServerAPI::setPlayerOption, CSH, LobbyChangePlayerOption::BONUS, +1, s.color));
}
hideUnavailableButtons();
if(SEL->screenType != ESelectionScreen::scenarioInfo && SEL->getPlayerInfo(s.color.getNum()).canHumanPlay)
{
flag = std::make_shared<CButton>(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], std::bind(&IServerAPI::setPlayer, CSH, s.color));
flag->hoverable = true;
flag->block(CSH->isGuest());
}
else
flag = nullptr;
town = std::make_shared<SelectedBox>(Point(119, 2), s, TOWN);
hero = std::make_shared<SelectedBox>(Point(195, 2), s, HERO);
bonus = std::make_shared<SelectedBox>(Point(271, 2), s, BONUS);
}
void OptionsTab::PlayerOptionsEntry::hideUnavailableButtons()
{
if(!buttonTownLeft)
return;
const bool foreignPlayer = CSH->isGuest() && !CSH->isMyColor(s.color);
if((pi.allowedFactions.size() < 2 && !pi.isFactionRandom) || foreignPlayer)
{
buttonTownLeft->disable();
buttonTownRight->disable();
}
else
{
buttonTownLeft->enable();
buttonTownRight->enable();
}
if((pi.defaultHero() != -1 || s.castle < 0) //fixed hero
|| foreignPlayer) //or not our player
{
buttonHeroLeft->disable();
buttonHeroRight->disable();
}
else
{
buttonHeroLeft->enable();
buttonHeroRight->enable();
}
if(foreignPlayer)
{
buttonBonusLeft->disable();
buttonBonusRight->disable();
}
else
{
buttonBonusLeft->enable();
buttonBonusRight->enable();
}
}

127
client/lobby/OptionsTab.h Normal file
View File

@ -0,0 +1,127 @@
/*
* OptionsTab.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 "../../lib/StartInfo.h"
#include "../../lib/mapping/CMap.h"
class CSlider;
class CLabel;
class CMultiLineLabel;
class CFilledTexture;
class CAnimImage;
class CComponentBox;
/// The options tab which is shown at the map selection phase.
class OptionsTab : public CIntObject
{
std::shared_ptr<CPicture> background;
std::shared_ptr<CLabel> labelTitle;
std::shared_ptr<CMultiLineLabel> labelSubTitle;
std::shared_ptr<CMultiLineLabel> labelPlayerNameAndHandicap;
std::shared_ptr<CMultiLineLabel> labelStartingTown;
std::shared_ptr<CMultiLineLabel> labelStartingHero;
std::shared_ptr<CMultiLineLabel> labelStartingBonus;
std::shared_ptr<CLabel> labelPlayerTurnDuration;
std::shared_ptr<CLabel> labelTurnDurationValue;
public:
enum SelType
{
TOWN,
HERO,
BONUS
};
struct CPlayerSettingsHelper
{
const PlayerSettings & settings;
const SelType type;
CPlayerSettingsHelper(const PlayerSettings & settings, SelType type)
: settings(settings), type(type)
{}
/// visible image settings
size_t getImageIndex();
std::string getImageName();
std::string getName(); /// name visible in options dialog
std::string getTitle(); /// title in popup box
std::string getSubtitle(); /// popup box subtitle
std::string getDescription(); /// popup box description, not always present
};
class CPlayerOptionTooltipBox : public CWindowObject, public CPlayerSettingsHelper
{
std::shared_ptr<CFilledTexture> backgroundTexture;
std::shared_ptr<CLabel> labelTitle;
std::shared_ptr<CLabel> labelSubTitle;
std::shared_ptr<CAnimImage> image;
std::shared_ptr<CLabel> labelAssociatedCreatures;
std::shared_ptr<CComponentBox> boxAssociatedCreatures;
std::shared_ptr<CLabel> labelHeroSpeciality;
std::shared_ptr<CAnimImage> imageSpeciality;
std::shared_ptr<CLabel> labelSpecialityName;
std::shared_ptr<CTextBox> textBonusDescription;
void genHeader();
void genTownWindow();
void genHeroWindow();
void genBonusWindow();
public:
CPlayerOptionTooltipBox(CPlayerSettingsHelper & helper);
};
/// Image with current town/hero/bonus
struct SelectedBox : public CIntObject, public CPlayerSettingsHelper
{
std::shared_ptr<CAnimImage> image;
std::shared_ptr<CLabel> subtitle;
SelectedBox(Point position, PlayerSettings & settings, SelType type);
void clickRight(tribool down, bool previousState) override;
void update();
};
struct PlayerOptionsEntry : public CIntObject
{
PlayerInfo pi;
PlayerSettings s;
std::shared_ptr<CLabel> labelPlayerName;
std::shared_ptr<CMultiLineLabel> labelWhoCanPlay;
std::shared_ptr<CPicture> background;
std::shared_ptr<CButton> buttonTownLeft;
std::shared_ptr<CButton> buttonTownRight;
std::shared_ptr<CButton> buttonHeroLeft;
std::shared_ptr<CButton> buttonHeroRight;
std::shared_ptr<CButton> buttonBonusLeft;
std::shared_ptr<CButton> buttonBonusRight;
std::shared_ptr<CButton> flag;
std::shared_ptr<SelectedBox> town;
std::shared_ptr<SelectedBox> hero;
std::shared_ptr<SelectedBox> bonus;
enum {HUMAN_OR_CPU, HUMAN, CPU} whoCanPlay;
PlayerOptionsEntry(const PlayerSettings & S);
void hideUnavailableButtons();
};
std::shared_ptr<CSlider> sliderTurnDuration;
std::map<PlayerColor, std::shared_ptr<PlayerOptionsEntry>> entries;
OptionsTab();
void recreate();
};

View File

@ -0,0 +1,300 @@
/*
* RandomMapTab.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 "RandomMapTab.h"
#include "CSelectionBase.h"
#include "../CGameInfo.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/rmg/CMapGenOptions.h"
RandomMapTab::RandomMapTab()
{
recActions = 0;
mapGenOptions = std::make_shared<CMapGenOptions>();
OBJ_CONSTRUCTION;
background = std::make_shared<CPicture>("RANMAPBK", 0, 6);
labelHeadlineBig = std::make_shared<CLabel>(222, 36, FONT_BIG, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[738]);
labelHeadlineSmall = std::make_shared<CLabel>(222, 56, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[739]);
labelMapSize = std::make_shared<CLabel>(104, 97, FONT_SMALL, EAlignment::CENTER, Colors::WHITE, CGI->generaltexth->allTexts[752]);
groupMapSize = std::make_shared<CToggleGroup>(0);
groupMapSize->pos.y += 81;
groupMapSize->pos.x += 158;
const std::vector<std::string> mapSizeBtns = {"RANSIZS", "RANSIZM", "RANSIZL", "RANSIZX"};
addButtonsToGroup(groupMapSize.get(), mapSizeBtns, 0, 3, 47, 198);
groupMapSize->setSelected(1);
groupMapSize->addCallback([&](int btnId)
{
auto mapSizeVal = getPossibleMapSizes();
mapGenOptions->setWidth(mapSizeVal[btnId]);
mapGenOptions->setHeight(mapSizeVal[btnId]);
updateMapInfoByHost();
});
buttonTwoLevels = std::make_shared<CToggleButton>(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
buttonTwoLevels->setSelected(true);
buttonTwoLevels->addCallback([&](bool on)
{
mapGenOptions->setHasTwoLevels(on);
updateMapInfoByHost();
});
labelGroupForOptions = std::make_shared<CLabelGroup>(FONT_SMALL, EAlignment::TOPLEFT, Colors::WHITE);
// Create number defs list
std::vector<std::string> numberDefs;
for(int i = 0; i <= 8; ++i)
{
numberDefs.push_back("RANNUM" + boost::lexical_cast<std::string>(i));
}
const int NUMBERS_WIDTH = 32;
const int BTNS_GROUP_LEFT_MARGIN = 67;
labelGroupForOptions->add(68, 133, CGI->generaltexth->allTexts[753]);
groupMaxPlayers = std::make_shared<CToggleGroup>(0);
groupMaxPlayers->pos.y += 153;
groupMaxPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupMaxPlayers.get(), numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
groupMaxPlayers->addCallback([&](int btnId)
{
mapGenOptions->setPlayerCount(btnId);
deactivateButtonsFrom(groupMaxTeams.get(), btnId);
deactivateButtonsFrom(groupCompOnlyPlayers.get(), btnId);
validatePlayersCnt(btnId);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 199, CGI->generaltexth->allTexts[754]);
groupMaxTeams = std::make_shared<CToggleGroup>(0);
groupMaxTeams->pos.y += 219;
groupMaxTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupMaxTeams.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
groupMaxTeams->addCallback([&](int btnId)
{
mapGenOptions->setTeamCount(btnId);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 265, CGI->generaltexth->allTexts[755]);
groupCompOnlyPlayers = std::make_shared<CToggleGroup>(0);
groupCompOnlyPlayers->pos.y += 285;
groupCompOnlyPlayers->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupCompOnlyPlayers.get(), numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
groupCompOnlyPlayers->addCallback([&](int btnId)
{
mapGenOptions->setCompOnlyPlayerCount(btnId);
deactivateButtonsFrom(groupCompOnlyTeams.get(), (btnId == 0 ? 1 : btnId));
validateCompOnlyPlayersCnt(btnId);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 331, CGI->generaltexth->allTexts[756]);
groupCompOnlyTeams = std::make_shared<CToggleGroup>(0);
groupCompOnlyTeams->pos.y += 351;
groupCompOnlyTeams->pos.x += BTNS_GROUP_LEFT_MARGIN;
addButtonsWithRandToGroup(groupCompOnlyTeams.get(), numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
deactivateButtonsFrom(groupCompOnlyTeams.get(), 1);
groupCompOnlyTeams->addCallback([&](int btnId)
{
mapGenOptions->setCompOnlyTeamCount(btnId);
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 398, CGI->generaltexth->allTexts[757]);
const int WIDE_BTN_WIDTH = 85;
groupWaterContent = std::make_shared<CToggleGroup>(0);
groupWaterContent->pos.y += 419;
groupWaterContent->pos.x += BTNS_GROUP_LEFT_MARGIN;
const std::vector<std::string> waterContentBtns = {"RANNONE", "RANNORM", "RANISLD"};
addButtonsWithRandToGroup(groupWaterContent.get(), waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
groupWaterContent->addCallback([&](int btnId)
{
mapGenOptions->setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
updateMapInfoByHost();
});
labelGroupForOptions->add(68, 465, CGI->generaltexth->allTexts[758]);
groupMonsterStrength = std::make_shared<CToggleGroup>(0);
groupMonsterStrength->pos.y += 485;
groupMonsterStrength->pos.x += BTNS_GROUP_LEFT_MARGIN;
const std::vector<std::string> monsterStrengthBtns = {"RANWEAK", "RANNORM", "RANSTRG"};
addButtonsWithRandToGroup(groupMonsterStrength.get(), monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251);
groupMonsterStrength->addCallback([&](int btnId)
{
if(btnId < 0)
mapGenOptions->setMonsterStrength(EMonsterStrength::RANDOM);
else
mapGenOptions->setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4
updateMapInfoByHost();
});
buttonShowRandomMaps = std::make_shared<CButton>(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
updateMapInfoByHost();
}
void RandomMapTab::updateMapInfoByHost()
{
if(CSH->isGuest())
return;
// Generate header info
mapInfo = std::make_shared<CMapInfo>();
mapInfo->isRandomMap = true;
mapInfo->mapHeader = make_unique<CMapHeader>();
mapInfo->mapHeader->version = EMapFormat::SOD;
mapInfo->mapHeader->name = CGI->generaltexth->allTexts[740];
mapInfo->mapHeader->description = CGI->generaltexth->allTexts[741];
mapInfo->mapHeader->difficulty = 1; // Normal
mapInfo->mapHeader->height = mapGenOptions->getHeight();
mapInfo->mapHeader->width = mapGenOptions->getWidth();
mapInfo->mapHeader->twoLevel = mapGenOptions->getHasTwoLevels();
// Generate player information
mapInfo->mapHeader->players.clear();
int playersToGen = PlayerColor::PLAYER_LIMIT_I;
if(mapGenOptions->getPlayerCount() != CMapGenOptions::RANDOM_SIZE)
playersToGen = mapGenOptions->getPlayerCount();
mapInfo->mapHeader->howManyTeams = playersToGen;
for(int i = 0; i < playersToGen; ++i)
{
PlayerInfo player;
player.isFactionRandom = true;
player.canComputerPlay = true;
if(mapGenOptions->getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE && i >= mapGenOptions->getHumanOnlyPlayerCount())
{
player.canHumanPlay = false;
}
else
{
player.canHumanPlay = true;
}
player.team = TeamID(i);
player.hasMainTown = true;
player.generateHeroAtMainTown = true;
mapInfo->mapHeader->players.push_back(player);
}
mapInfoChanged(mapInfo, mapGenOptions);
}
void RandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
{
groupMapSize->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
buttonTwoLevels->setSelected(opts->getHasTwoLevels());
groupMaxPlayers->setSelected(opts->getPlayerCount());
groupMaxTeams->setSelected(opts->getTeamCount());
groupCompOnlyPlayers->setSelected(opts->getCompOnlyPlayerCount());
groupCompOnlyTeams->setSelected(opts->getCompOnlyTeamCount());
groupWaterContent->setSelected(opts->getWaterContent());
groupMonsterStrength->setSelected(opts->getMonsterStrength());
}
void RandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const
{
addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex);
// Buttons are relative to button group, TODO better solution?
SObjectConstruction obj__i(group);
const std::string RANDOM_DEF = "RANRAND";
group->addToggle(CMapGenOptions::RANDOM_SIZE, new CToggleButton(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
group->setSelected(CMapGenOptions::RANDOM_SIZE);
}
void RandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const
{
// Buttons are relative to button group, TODO better solution?
SObjectConstruction obj__i(group);
int cnt = nEnd - nStart + 1;
for(int i = 0; i < cnt; ++i)
{
auto button = new CToggleButton(Point(i * btnWidth, 0), defs[i + nStart], CGI->generaltexth->zelp[helpStartIndex + i]);
// For blocked state we should use pressed image actually
button->setImageOrder(0, 1, 1, 3);
group->addToggle(i + nStart, button);
}
}
void RandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
{
logGlobal->debug("Blocking buttons from %d", startId);
for(auto toggle : group->buttons)
{
if(auto button = dynamic_cast<CToggleButton *>(toggle.second))
{
if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
{
button->block(false);
}
else
{
button->block(true);
}
}
}
}
void RandomMapTab::validatePlayersCnt(int playersCnt)
{
if(playersCnt == CMapGenOptions::RANDOM_SIZE)
{
return;
}
if(mapGenOptions->getTeamCount() >= playersCnt)
{
mapGenOptions->setTeamCount(playersCnt - 1);
groupMaxTeams->setSelected(mapGenOptions->getTeamCount());
}
if(mapGenOptions->getCompOnlyPlayerCount() >= playersCnt)
{
mapGenOptions->setCompOnlyPlayerCount(playersCnt - 1);
groupCompOnlyPlayers->setSelected(mapGenOptions->getCompOnlyPlayerCount());
}
validateCompOnlyPlayersCnt(mapGenOptions->getCompOnlyPlayerCount());
}
void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
{
if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
{
return;
}
if(mapGenOptions->getCompOnlyTeamCount() >= compOnlyPlayersCnt)
{
int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
mapGenOptions->setCompOnlyTeamCount(compOnlyTeamCount);
updateMapInfoByHost();
groupCompOnlyTeams->setSelected(compOnlyTeamCount);
}
}
std::vector<int> RandomMapTab::getPossibleMapSizes()
{
return {CMapHeader::MAP_SIZE_SMALL, CMapHeader::MAP_SIZE_MIDDLE, CMapHeader::MAP_SIZE_LARGE, CMapHeader::MAP_SIZE_XLARGE};
}

View File

@ -0,0 +1,58 @@
/*
* RandomMapTab.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 "CSelectionBase.h"
#include "../../lib/FunctionList.h"
class CMapGenOptions;
class CToggleButton;
class CLabel;
class CLabelGroup;
class RandomMapTab : public CIntObject
{
public:
RandomMapTab();
void updateMapInfoByHost();
void setMapGenOptions(std::shared_ptr<CMapGenOptions> opts);
CFunctionList<void(std::shared_ptr<CMapInfo>, std::shared_ptr<CMapGenOptions>)> mapInfoChanged;
private:
void addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex, int helpRandIndex) const;
void addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int startIndex, int endIndex, int btnWidth, int helpStartIndex) const;
void deactivateButtonsFrom(CToggleGroup * group, int startId);
void validatePlayersCnt(int playersCnt);
void validateCompOnlyPlayersCnt(int compOnlyPlayersCnt);
std::vector<int> getPossibleMapSizes();
std::shared_ptr<CPicture> background;
std::shared_ptr<CLabel> labelHeadlineBig;
std::shared_ptr<CLabel> labelHeadlineSmall;
std::shared_ptr<CLabel> labelMapSize;
std::shared_ptr<CToggleGroup> groupMapSize;
std::shared_ptr<CToggleButton> buttonTwoLevels;
std::shared_ptr<CLabelGroup> labelGroupForOptions;
std::shared_ptr<CToggleGroup> groupMaxPlayers;
std::shared_ptr<CToggleGroup> groupMaxTeams;
std::shared_ptr<CToggleGroup> groupCompOnlyPlayers;
std::shared_ptr<CToggleGroup> groupCompOnlyTeams;
std::shared_ptr<CToggleGroup> groupWaterContent;
std::shared_ptr<CToggleGroup> groupMonsterStrength;
std::shared_ptr<CButton> buttonShowRandomMaps;
std::shared_ptr<CMapGenOptions> mapGenOptions;
std::shared_ptr<CMapInfo> mapInfo;
};

View File

@ -0,0 +1,664 @@
/*
* SelectionTab.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 "SelectionTab.h"
#include "CSelectionBase.h"
#include "CLobbyScreen.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../../CCallback.h"
#include "../../lib/NetPacksLobby.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CModHandler.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/mapping/CMapInfo.h"
#include "../../lib/serializer/Connection.h"
bool mapSorter::operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb)
{
auto a = aaa->mapHeader.get();
auto b = bbb->mapHeader.get();
if(a && b) //if we are sorting scenarios
{
switch(sortBy)
{
case _format: //by map format (RoE, WoG, etc)
return (a->version < b->version);
break;
case _loscon: //by loss conditions
return (a->defeatMessage < b->defeatMessage);
break;
case _playerAm: //by player amount
int playerAmntB, humenPlayersB, playerAmntA, humenPlayersA;
playerAmntB = humenPlayersB = playerAmntA = humenPlayersA = 0;
for(int i = 0; i < 8; i++)
{
if(a->players[i].canHumanPlay)
{
playerAmntA++;
humenPlayersA++;
}
else if(a->players[i].canComputerPlay)
{
playerAmntA++;
}
if(b->players[i].canHumanPlay)
{
playerAmntB++;
humenPlayersB++;
}
else if(b->players[i].canComputerPlay)
{
playerAmntB++;
}
}
if(playerAmntB != playerAmntA)
return (playerAmntA < playerAmntB);
else
return (humenPlayersA < humenPlayersB);
break;
case _size: //by size of map
return (a->width < b->width);
break;
case _viccon: //by victory conditions
return (a->victoryMessage < b->victoryMessage);
break;
case _name: //by name
return boost::ilexicographical_compare(a->name, b->name);
case _fileName: //by filename
return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI);
default:
return boost::ilexicographical_compare(a->name, b->name);
}
}
else //if we are sorting campaigns
{
switch(sortBy)
{
case _numOfMaps: //by number of maps in campaign
return CGI->generaltexth->campaignRegionNames[aaa->campaignHeader->mapVersion].size() <
CGI->generaltexth->campaignRegionNames[bbb->campaignHeader->mapVersion].size();
break;
case _name: //by name
return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
default:
return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
}
}
}
SelectionTab::SelectionTab(ESelectionScreen Type)
: CIntObject(LCLICK | WHEEL | KEYBOARD | DOUBLECLICK), callOnSelect(nullptr), tabType(Type), selectionPos(0), sortModeAscending(true)
{
OBJ_CONSTRUCTION;
if(tabType != ESelectionScreen::campaignList)
{
sortingBy = _format;
background = std::make_shared<CPicture>("SCSELBCK.bmp", 0, 6);
pos = background->pos;
inputName = std::make_shared<CTextInput>(Rect(32, 539, 350, 20), Point(-32, -25), "GSSTRIP.bmp", 0);
inputName->filters += CTextInput::filenameFilter;
labelMapSizes = std::make_shared<CLabel>(87, 62, FONT_SMALL, EAlignment::CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[510]);
int sizes[] = {36, 72, 108, 144, 0};
const char * filterIconNmes[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
for(int i = 0; i < 5; i++)
buttonsSortBy.push_back(std::make_shared<CButton>(Point(158 + 47 * i, 46), filterIconNmes[i], CGI->generaltexth->zelp[54 + i], std::bind(&SelectionTab::filter, this, sizes[i], true)));
int xpos[] = {23, 55, 88, 121, 306, 339};
const char * sortIconNames[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
for(int i = 0; i < 6; i++)
{
ESortBy criteria = (ESortBy)i;
if(criteria == _name)
criteria = generalSortingBy;
buttonsSortBy.push_back(std::make_shared<CButton>(Point(xpos[i], 86), sortIconNames[i], CGI->generaltexth->zelp[107 + i], std::bind(&SelectionTab::sortBy, this, criteria)));
}
}
int positionsToShow = 18;
std::string tabTitle;
switch(tabType)
{
case ESelectionScreen::newGame:
generalSortingBy = ESortBy::_name;
tabTitle = CGI->generaltexth->arraytxt[229];
break;
case ESelectionScreen::loadGame:
generalSortingBy = ESortBy::_fileName;
tabTitle = CGI->generaltexth->arraytxt[230];
break;
case ESelectionScreen::saveGame:
positionsToShow = 16;
generalSortingBy = ESortBy::_fileName;
tabTitle = CGI->generaltexth->arraytxt[231];
break;
case ESelectionScreen::campaignList:
generalSortingBy = ESortBy::_name;
tabTitle = CGI->generaltexth->allTexts[726];
type |= REDRAW_PARENT; // we use parent background so we need to make sure it's will be redrawn too
pos.w = parent->pos.w;
pos.h = parent->pos.h;
pos.x += 3;
pos.y += 6;
buttonsSortBy.push_back(std::make_shared<CButton>(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)));
buttonsSortBy.push_back(std::make_shared<CButton>(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)));
break;
default:
assert(0);
break;
}
iconsMapFormats = std::make_shared<CAnimation>("SCSELC.DEF");
iconsMapFormats->preload();
iconsVictoryCondition = std::make_shared<CAnimation>("SCNRVICT.DEF");
iconsVictoryCondition->preload();
iconsLossCondition = std::make_shared<CAnimation>("SCNRLOSS.DEF");
iconsLossCondition->preload();
for(int i = 0; i < positionsToShow; i++)
listItems.push_back(std::make_shared<ListItem>(Point(30, 129 + i * 25), iconsMapFormats, iconsVictoryCondition, iconsLossCondition));
labelTabTitle = std::make_shared<CLabel>(205, 28, FONT_MEDIUM, EAlignment::CENTER, Colors::YELLOW, tabTitle);
slider = std::make_shared<CSlider>(Point(372, 86), tabType != ESelectionScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positionsToShow, curItems.size(), 0, false, CSlider::BLUE);
filter(0);
}
void SelectionTab::toggleMode()
{
if(CSH->isGuest())
{
allItems.clear();
curItems.clear();
if(slider)
slider->block(true);
}
else
{
switch(tabType)
{
case ESelectionScreen::newGame:
inputName->disable();
parseMaps(getFiles("Maps/", EResType::MAP));
break;
case ESelectionScreen::loadGame:
inputName->disable();
parseSaves(getFiles("Saves/", EResType::CLIENT_SAVEGAME));
break;
case ESelectionScreen::saveGame:
parseSaves(getFiles("Saves/", EResType::CLIENT_SAVEGAME));
inputName->enable();
restoreLastSelection();
break;
case ESelectionScreen::campaignList:
parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN));
break;
default:
assert(0);
break;
}
if(slider)
{
slider->block(false);
filter(0);
}
if(CSH->campaignStateToSend)
{
CSH->setCampaignState(CSH->campaignStateToSend);
CSH->campaignStateToSend.reset();
}
else
{
restoreLastSelection();
}
}
slider->setAmount(curItems.size());
updateListItems();
redraw();
}
void SelectionTab::clickLeft(tribool down, bool previousState)
{
if(down)
{
int line = getLine();
if(line != -1)
select(line);
}
}
void SelectionTab::keyPressed(const SDL_KeyboardEvent & key)
{
if(key.state != SDL_PRESSED)
return;
int moveBy = 0;
switch(key.keysym.sym)
{
case SDLK_UP:
moveBy = -1;
break;
case SDLK_DOWN:
moveBy = +1;
break;
case SDLK_PAGEUP:
moveBy = -listItems.size() + 1;
break;
case SDLK_PAGEDOWN:
moveBy = +listItems.size() - 1;
break;
case SDLK_HOME:
select(-slider->getValue());
return;
case SDLK_END:
select(curItems.size() - slider->getValue());
return;
default:
return;
}
select(selectionPos - slider->getValue() + moveBy);
}
void SelectionTab::onDoubleClick()
{
if(getLine() != -1) //double clicked scenarios list
{
(static_cast<CLobbyScreen *>(parent))->buttonStart->clickLeft(false, true);
}
}
// A new size filter (Small, Medium, ...) has been selected. Populate
// selMaps with the relevant data.
void SelectionTab::filter(int size, bool selectFirst)
{
curItems.clear();
if(tabType == ESelectionScreen::campaignList)
{
for(auto elem : allItems)
curItems.push_back(elem);
}
else
{
for(auto elem : allItems)
{
if(elem->mapHeader && elem->mapHeader->version && (!size || elem->mapHeader->width == size))
curItems.push_back(elem);
}
}
if(curItems.size())
{
slider->block(false);
slider->setAmount(curItems.size());
sort();
if(selectFirst)
{
slider->moveTo(0);
callOnSelect(curItems[0]);
selectAbs(0);
}
}
else
{
slider->block(true);
if(callOnSelect)
callOnSelect(nullptr);
}
}
void SelectionTab::sortBy(int criteria)
{
if(criteria == sortingBy)
{
sortModeAscending = !sortModeAscending;
}
else
{
sortingBy = (ESortBy)criteria;
sortModeAscending = true;
}
sort();
selectAbs(0);
}
void SelectionTab::sort()
{
if(sortingBy != generalSortingBy)
std::stable_sort(curItems.begin(), curItems.end(), mapSorter(generalSortingBy));
std::stable_sort(curItems.begin(), curItems.end(), mapSorter(sortingBy));
if(!sortModeAscending)
std::reverse(curItems.begin(), curItems.end());
updateListItems();
redraw();
}
void SelectionTab::select(int position)
{
if(!curItems.size())
return;
// New selection. py is the index in curItems.
int py = position + slider->getValue();
vstd::amax(py, 0);
vstd::amin(py, curItems.size() - 1);
selectionPos = py;
if(position < 0)
slider->moveBy(position);
else if(position >= listItems.size())
slider->moveBy(position - listItems.size() + 1);
rememberCurrentSelection();
if(inputName && inputName->active)
{
auto filename = *CResourceHandler::get("local")->getResourceName(ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
inputName->setText(filename.stem().string());
}
updateListItems();
if(callOnSelect)
callOnSelect(curItems[py]);
}
void SelectionTab::selectAbs(int position)
{
select(position - slider->getValue());
}
void SelectionTab::sliderMove(int slidPos)
{
if(!slider)
return; // ignore spurious call when slider is being created
updateListItems();
redraw();
}
void SelectionTab::updateListItems()
{
// elemIdx is the index of the maps or saved game to display on line 0
// slider->capacity contains the number of available screen lines
// slider->positionsAmnt is the number of elements after filtering
int elemIdx = slider->getValue();
for(auto item : listItems)
{
if(elemIdx < curItems.size())
{
item->updateItem(curItems[elemIdx], elemIdx == selectionPos);
elemIdx++;
}
else
{
item->updateItem();
}
}
}
int SelectionTab::getLine()
{
int line = -1;
Point clickPos(GH.current->button.x, GH.current->button.y);
clickPos = clickPos - pos.topLeft();
// Ignore clicks on save name area
int maxPosY;
if(tabType == ESelectionScreen::saveGame)
maxPosY = 516;
else
maxPosY = 564;
if(clickPos.y > 115 && clickPos.y < maxPosY && clickPos.x > 22 && clickPos.x < 371)
{
line = (clickPos.y - 115) / 25; //which line
}
return line;
}
void SelectionTab::selectFileName(std::string fname)
{
boost::to_upper(fname);
for(int i = curItems.size() - 1; i >= 0; i--)
{
if(curItems[i]->fileURI == fname)
{
slider->moveTo(i);
selectAbs(i);
return;
}
}
selectAbs(0);
}
std::shared_ptr<CMapInfo> SelectionTab::getSelectedMapInfo() const
{
return curItems.empty() ? nullptr : curItems[selectionPos];
}
void SelectionTab::rememberCurrentSelection()
{
// TODO: this can be more elegant
if(tabType == ESelectionScreen::newGame)
{
Settings lastMap = settings.write["general"]["lastMap"];
lastMap->String() = getSelectedMapInfo()->fileURI;
}
else if(tabType == ESelectionScreen::loadGame)
{
Settings lastSave = settings.write["general"]["lastSave"];
lastSave->String() = getSelectedMapInfo()->fileURI;
}
else if(tabType == ESelectionScreen::campaignList)
{
Settings lastCampaign = settings.write["general"]["lastCampaign"];
lastCampaign->String() = getSelectedMapInfo()->fileURI;
}
}
void SelectionTab::restoreLastSelection()
{
switch(tabType)
{
case ESelectionScreen::newGame:
selectFileName(settings["general"]["lastMap"].String());
break;
case ESelectionScreen::campaignList:
selectFileName(settings["general"]["lastCampaign"].String());
break;
case ESelectionScreen::loadGame:
case ESelectionScreen::saveGame:
selectFileName(settings["general"]["lastSave"].String());
}
}
void SelectionTab::parseMaps(const std::unordered_set<ResourceID> & files)
{
logGlobal->debug("Parsing %d maps", files.size());
allItems.clear();
for(auto & file : files)
{
try
{
auto mapInfo = std::make_shared<CMapInfo>();
mapInfo->mapInit(file.getName());
// ignore unsupported map versions (e.g. WoG maps without WoG)
// but accept VCMI maps
if((mapInfo->mapHeader->version >= EMapFormat::VCMI) || (mapInfo->mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float()))
allItems.push_back(mapInfo);
}
catch(std::exception & e)
{
logGlobal->error("Map %s is invalid. Message: %s", file.getName(), e.what());
}
}
}
void SelectionTab::parseSaves(const std::unordered_set<ResourceID> & files)
{
for(auto & file : files)
{
try
{
auto mapInfo = std::make_shared<CMapInfo>();
mapInfo->saveInit(file);
// Filter out other game modes
bool isCampaign = mapInfo->scenarioOptionsOfSave->mode == StartInfo::CAMPAIGN;
bool isMultiplayer = mapInfo->amountOfHumanPlayersInSave > 1;
switch(CSH->getLoadMode())
{
case ELoadMode::SINGLE:
if(isMultiplayer || isCampaign)
mapInfo->mapHeader.reset();
break;
case ELoadMode::CAMPAIGN:
if(!isCampaign)
mapInfo->mapHeader.reset();
break;
default:
if(!isMultiplayer)
mapInfo->mapHeader.reset();
break;
}
allItems.push_back(mapInfo);
}
catch(const std::exception & e)
{
logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what());
}
}
}
void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> & files)
{
allItems.reserve(files.size());
for(auto & file : files)
{
auto info = std::make_shared<CMapInfo>();
//allItems[i].date = std::asctime(std::localtime(&files[i].date));
info->fileURI = file.getName();
info->campaignInit();
allItems.push_back(info);
}
}
std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
{
boost::to_upper(dirURI);
CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount)
{
return boost::algorithm::starts_with(mount, dirURI);
});
std::unordered_set<ResourceID> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
{
return ident.getType() == resType && boost::algorithm::starts_with(ident.getName(), dirURI);
});
return ret;
}
SelectionTab::ListItem::ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss)
: CIntObject(LCLICK, position)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
labelName = std::make_shared<CLabel>(184, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelName->setAutoRedraw(false);
labelAmountOfPlayers = std::make_shared<CLabel>(8, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelAmountOfPlayers->setAutoRedraw(false);
labelNumberOfCampaignMaps = std::make_shared<CLabel>(8, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelNumberOfCampaignMaps->setAutoRedraw(false);
labelMapSizeLetter = std::make_shared<CLabel>(41, 0, FONT_SMALL, EAlignment::CENTER, Colors::WHITE);
labelMapSizeLetter->setAutoRedraw(false);
// FIXME: This -12 should not be needed, but for some reason CAnimImage displaced otherwise
iconFormat = std::make_shared<CAnimImage>(iconsFormats, 0, 0, 59, -12);
iconVictoryCondition = std::make_shared<CAnimImage>(iconsVictory, 0, 0, 277, -12);
iconLossCondition = std::make_shared<CAnimImage>(iconsLoss, 0, 0, 310, -12);
}
void SelectionTab::ListItem::updateItem(std::shared_ptr<CMapInfo> info, bool selected)
{
if(!info)
{
labelAmountOfPlayers->disable();
labelMapSizeLetter->disable();
iconFormat->disable();
iconVictoryCondition->disable();
iconLossCondition->disable();
labelNumberOfCampaignMaps->disable();
labelName->disable();
return;
}
auto color = selected ? Colors::YELLOW : Colors::WHITE;
if(info->campaignHeader)
{
labelAmountOfPlayers->disable();
labelMapSizeLetter->disable();
iconFormat->disable();
iconVictoryCondition->disable();
iconLossCondition->disable();
labelNumberOfCampaignMaps->enable();
std::ostringstream ostr(std::ostringstream::out);
ostr << CGI->generaltexth->campaignRegionNames[info->campaignHeader->mapVersion].size();
labelNumberOfCampaignMaps->setText(ostr.str());
labelNumberOfCampaignMaps->setColor(color);
}
else
{
labelNumberOfCampaignMaps->disable();
std::ostringstream ostr(std::ostringstream::out);
ostr << info->amountOfPlayersOnMap << "/" << info->amountOfHumanControllablePlayers;
labelAmountOfPlayers->enable();
labelAmountOfPlayers->setText(ostr.str());
labelAmountOfPlayers->setColor(color);
labelMapSizeLetter->enable();
labelMapSizeLetter->setText(info->getMapSizeName());
labelMapSizeLetter->setColor(color);
iconFormat->enable();
iconFormat->setFrame(info->getMapSizeFormatIconId().first, info->getMapSizeFormatIconId().second);
iconVictoryCondition->enable();
iconVictoryCondition->setFrame(info->mapHeader->victoryIconIndex, 0);
iconLossCondition->enable();
iconLossCondition->setFrame(info->mapHeader->defeatIconIndex, 0);
}
labelName->enable();
labelName->setText(info->getNameForList());
labelName->setColor(color);
}

View File

@ -0,0 +1,98 @@
/*
* SelectionTab.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 "CSelectionBase.h"
class CSlider;
class CLabel;
enum ESortBy
{
_playerAm, _size, _format, _name, _viccon, _loscon, _numOfMaps, _fileName
}; //_numOfMaps is for campaigns
/// Class which handles map sorting by different criteria
class mapSorter
{
public:
ESortBy sortBy;
bool operator()(const std::shared_ptr<CMapInfo> aaa, const std::shared_ptr<CMapInfo> bbb);
mapSorter(ESortBy es) : sortBy(es){};
};
class SelectionTab : public CIntObject
{
struct ListItem : public CIntObject
{
std::shared_ptr<CLabel> labelAmountOfPlayers;
std::shared_ptr<CLabel> labelNumberOfCampaignMaps;
std::shared_ptr<CLabel> labelMapSizeLetter;
std::shared_ptr<CAnimImage> iconFormat;
std::shared_ptr<CAnimImage> iconVictoryCondition;
std::shared_ptr<CAnimImage> iconLossCondition;
std::shared_ptr<CLabel> labelName;
ListItem(Point position, std::shared_ptr<CAnimation> iconsFormats, std::shared_ptr<CAnimation> iconsVictory, std::shared_ptr<CAnimation> iconsLoss);
void updateItem(std::shared_ptr<CMapInfo> info = {}, bool selected = false);
};
std::vector<std::shared_ptr<ListItem>> listItems;
std::shared_ptr<CAnimation> iconsMapFormats;
// FIXME: CSelectionBase use them too!
std::shared_ptr<CAnimation> iconsVictoryCondition;
std::shared_ptr<CAnimation> iconsLossCondition;
public:
std::vector<std::shared_ptr<CMapInfo>> allItems;
std::vector<std::shared_ptr<CMapInfo>> curItems;
size_t selectionPos;
std::function<void(std::shared_ptr<CMapInfo>)> callOnSelect;
ESortBy sortingBy;
ESortBy generalSortingBy;
bool sortModeAscending;
std::shared_ptr<CTextInput> inputName;
SelectionTab(ESelectionScreen Type);
void toggleMode();
void clickLeft(tribool down, bool previousState) override;
void keyPressed(const SDL_KeyboardEvent & key) override;
void onDoubleClick() override;
void filter(int size, bool selectFirst = false); //0 - all
void sortBy(int criteria);
void sort();
void select(int position); //position: <0 - positions> position on the screen
void selectAbs(int position); //position: absolute position in curItems vector
void sliderMove(int slidPos);
void updateListItems();
int getLine();
void selectFileName(std::string fname);
std::shared_ptr<CMapInfo> getSelectedMapInfo() const;
void rememberCurrentSelection();
void restoreLastSelection();
private:
std::shared_ptr<CPicture> background;
std::shared_ptr<CSlider> slider;
std::vector<std::shared_ptr<CButton>> buttonsSortBy;
std::shared_ptr<CLabel> labelTabTitle;
std::shared_ptr<CLabel> labelMapSizes;
ESelectionScreen tabType;
void parseMaps(const std::unordered_set<ResourceID> & files);
void parseSaves(const std::unordered_set<ResourceID> & files);
void parseCampaigns(const std::unordered_set<ResourceID> & files);
std::unordered_set<ResourceID> getFiles(std::string dirURI, int resType);
};

View File

@ -0,0 +1,156 @@
/*
* CCampaignScreen.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 "../mainmenu/CMainMenu.h"
#include "CCampaignScreen.h"
#include "../CGameInfo.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../CPlayerInterface.h"
#include "../CServerHandler.h"
#include "../gui/CAnimation.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/GUIClasses.h"
#include "../windows/InfoWindows.h"
#include "../windows/CWindowObject.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CArtHandler.h"
#include "../../lib/CBuildingHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/CSkillHandler.h"
#include "../../lib/CTownHandler.h"
#include "../../lib/CHeroHandler.h"
#include "../../lib/CCreatureHandler.h"
#include "../../lib/mapping/CCampaignHandler.h"
#include "../../lib/mapping/CMapService.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
CCampaignScreen::CCampaignScreen(const JsonNode & config)
: CWindowObject(BORDERED)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
for(const JsonNode & node : config["images"].Vector())
images.push_back(CMainMenu::createPicture(node));
if(!images.empty())
{
images[0]->center(); // move background to center
moveTo(images[0]->pos.topLeft()); // move everything else to center
images[0]->moveTo(pos.topLeft()); // restore moved twice background
pos = images[0]->pos; // fix height\width of this window
}
if(!config["exitbutton"].isNull())
{
buttonBack = createExitButton(config["exitbutton"]);
buttonBack->hoverable = true;
}
for(const JsonNode & node : config["items"].Vector())
campButtons.push_back(std::make_shared<CCampaignButton>(node));
}
std::shared_ptr<CButton> CCampaignScreen::createExitButton(const JsonNode & button)
{
std::pair<std::string, std::string> help;
if(!button["help"].isNull() && button["help"].Float() > 0)
help = CGI->generaltexth->zelp[button["help"].Float()];
std::function<void()> close = std::bind(&CGuiHandler::popIntTotally, &GH, this);
return std::make_shared<CButton>(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float());
}
CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode & config)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.x += config["x"].Float();
pos.y += config["y"].Float();
pos.w = 200;
pos.h = 116;
campFile = config["file"].String();
video = config["video"].String();
status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
CCampaignHeader header = CCampaignHandler::getHeader(campFile);
hoverText = header.name;
if(status != CCampaignScreen::DISABLED)
{
addUsedEvents(LCLICK | HOVER);
graphicsImage = std::make_shared<CPicture>(config["image"].String());
hoverLabel = std::make_shared<CLabel>(pos.w / 2, pos.h + 20, FONT_MEDIUM, CENTER, Colors::YELLOW, "");
parent->addChild(hoverLabel.get());
}
if(status == CCampaignScreen::COMPLETED)
graphicsCompleted = std::make_shared<CPicture>("CAMPCHK");
}
void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
{
if(status == CCampaignScreen::DISABLED)
return;
CIntObject::show(to);
// Play the campaign button video when the mouse cursor is placed over the button
if(hovered)
{
if(CCS->videoh->fname != video)
CCS->videoh->open(video);
CCS->videoh->update(pos.x, pos.y, to, true, false); // plays sequentially frame by frame, starts at the beginning when the video is over
}
else if(CCS->videoh->fname == video) // When you got out of the bounds of the button then close the video
{
CCS->videoh->close();
redraw();
}
}
void CCampaignScreen::CCampaignButton::clickLeft(tribool down, bool previousState)
{
if(down)
{
CCS->videoh->close();
CMainMenu::openCampaignLobby(campFile);
}
}
void CCampaignScreen::CCampaignButton::hover(bool on)
{
if(hoverLabel)
{
if(on)
hoverLabel->setText(hoverText); // Shows the name of the campaign when you get into the bounds of the button
else
hoverLabel->setText(" ");
}
}

View File

@ -0,0 +1,55 @@
/*
* CCampaignScreen.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
class CLabel;
class CPicture;
class CButton;
class SDL_Surface;
class JsonNode;
class CCampaignScreen : public CWindowObject
{
public:
enum CampaignStatus {DEFAULT = 0, ENABLED, DISABLED, COMPLETED}; // the status of the campaign
private:
/// A button which plays a video when you move the mouse cursor over it
class CCampaignButton : public CIntObject
{
private:
std::shared_ptr<CLabel> hoverLabel;
std::shared_ptr<CPicture> graphicsImage;
std::shared_ptr<CPicture> graphicsCompleted;
CampaignStatus status;
std::string campFile; // the filename/resourcename of the campaign
std::string video; // the resource name of the video
std::string hoverText;
void clickLeft(tribool down, bool previousState) override;
void hover(bool on) override;
public:
CCampaignButton(const JsonNode & config);
void show(SDL_Surface * to) override;
};
std::vector<std::shared_ptr<CCampaignButton>> campButtons;
std::vector<std::shared_ptr<CPicture>> images;
std::shared_ptr<CButton> buttonBack;
std::shared_ptr<CButton> createExitButton(const JsonNode & button);
public:
enum CampaignSet {ROE, AB, SOD, WOG};
CCampaignScreen(const JsonNode & config);
};

View File

@ -0,0 +1,566 @@
/*
* CMainMenu.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 "CMainMenu.h"
#include "CCampaignScreen.h"
#include "CreditsScreen.h"
#include "../lobby/CBonusSelection.h"
#include "../lobby/CSelectionBase.h"
#include "../lobby/CLobbyScreen.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/filesystem/CCompressedStream.h"
#include "../gui/SDL_Extensions.h"
#include "../gui/CCursorHandler.h"
#include "../CGameInfo.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/JsonNode.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../Graphics.h"
#include "../../lib/serializer/Connection.h"
#include "../../lib/serializer/CTypeList.h"
#include "../../lib/VCMIDirs.h"
#include "../../lib/mapping/CMap.h"
#include "../windows/GUIClasses.h"
#include "../CPlayerInterface.h"
#include "../../CCallback.h"
#include "../CMessage.h"
#include "../CBitmapHandler.h"
#include "../Client.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CAnimation.h"
#include "../widgets/CComponent.h"
#include "../widgets/Buttons.h"
#include "../widgets/MiscWidgets.h"
#include "../widgets/ObjectLists.h"
#include "../widgets/TextControls.h"
#include "../windows/InfoWindows.h"
#include "../CServerHandler.h"
#include "../../lib/CStopWatch.h"
#include "../../lib/NetPacksLobby.h"
#include "../../lib/CThreadHelper.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/GameConstants.h"
#include "../../lib/CRandomGenerator.h"
#include "../../lib/CondSh.h"
#include "../../lib/mapping/CCampaignHandler.h"
namespace fs = boost::filesystem;
CMainMenu * CMM = nullptr;
ISelectionScreenInfo * SEL;
static void do_quit()
{
SDL_Event event;
event.quit.type = SDL_QUIT;
SDL_PushEvent(&event);
}
CMenuScreen::CMenuScreen(const JsonNode & configNode)
: CWindowObject(BORDERED), config(configNode)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
background = std::make_shared<CPicture>(config["background"].String());
if(config["scalable"].Bool())
{
if(background->bg->format->palette)
background->convertToScreenBPP();
background->scaleTo(Point(screen->w, screen->h));
}
pos = background->center();
for(const JsonNode & node : config["items"].Vector())
menuNameToEntry.push_back(node["name"].String());
for(const JsonNode & node : config["images"].Vector())
images.push_back(CMainMenu::createPicture(node));
//Hardcoded entry
menuNameToEntry.push_back("credits");
tabs = std::make_shared<CTabbedInt>(std::bind(&CMenuScreen::createTab, this, _1), CTabbedInt::DestroyFunc());
tabs->type |= REDRAW_PARENT;
}
CIntObject * CMenuScreen::createTab(size_t index)
{
if(config["items"].Vector().size() == index)
return new CreditsScreen();
return new CMenuEntry(this, config["items"].Vector()[index]);
}
void CMenuScreen::show(SDL_Surface * to)
{
if(!config["video"].isNull())
CCS->videoh->update(config["video"]["x"].Float() + pos.x, config["video"]["y"].Float() + pos.y, to, true, false);
CIntObject::show(to);
}
void CMenuScreen::activate()
{
CCS->musich->playMusic("Music/MainMenu", true);
if(!config["video"].isNull())
CCS->videoh->open(config["video"]["name"].String());
CIntObject::activate();
}
void CMenuScreen::deactivate()
{
if(!config["video"].isNull())
CCS->videoh->close();
CIntObject::deactivate();
}
void CMenuScreen::switchToTab(size_t index)
{
tabs->setActive(index);
}
//funciton for std::string -> std::function conversion for main menu
static std::function<void()> genCommand(CMenuScreen * menu, std::vector<std::string> menuType, const std::string & string)
{
static const std::vector<std::string> commandType = {"to", "campaigns", "start", "load", "exit", "highscores"};
static const std::vector<std::string> gameType = {"single", "multi", "campaign", "tutorial"};
std::list<std::string> commands;
boost::split(commands, string, boost::is_any_of("\t "));
if(!commands.empty())
{
size_t index = std::find(commandType.begin(), commandType.end(), commands.front()) - commandType.begin();
commands.pop_front();
if(index > 3 || !commands.empty())
{
switch(index)
{
case 0: //to - switch to another tab, if such tab exists
{
size_t index2 = std::find(menuType.begin(), menuType.end(), commands.front()) - menuType.begin();
if(index2 != menuType.size())
return std::bind(&CMenuScreen::switchToTab, menu, index2);
break;
}
case 1: //open campaign selection window
{
return std::bind(&CMainMenu::openCampaignScreen, CMM, commands.front());
break;
}
case 2: //start
{
switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
{
case 0:
return std::bind(CMainMenu::openLobby, ESelectionScreen::newGame, true, nullptr, ELoadMode::NONE);
case 1:
return []() { GH.pushInt(new CMultiMode(ESelectionScreen::newGame)); };
case 2:
return std::bind(CMainMenu::openLobby, ESelectionScreen::campaignList, true, nullptr, ELoadMode::NONE);
case 3:
return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent *> *)nullptr, false, PlayerColor(1));
}
break;
}
case 3: //load
{
switch(std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
{
case 0:
return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::SINGLE);
case 1:
return []() { GH.pushInt(new CMultiMode(ESelectionScreen::loadGame)); };
case 2:
return std::bind(CMainMenu::openLobby, ESelectionScreen::loadGame, true, nullptr, ELoadMode::CAMPAIGN);
case 3:
return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent *> *)nullptr, false, PlayerColor(1));
}
}
break;
case 4: //exit
{
return std::bind(CInfoWindow::showYesNoDialog, std::ref(CGI->generaltexth->allTexts[69]), (const std::vector<CComponent *> *)nullptr, do_quit, 0, false, PlayerColor(1));
}
break;
case 5: //highscores
{
return std::bind(CInfoWindow::showInfoDialog, "Sorry, high scores menu is not implemented yet\n", (const std::vector<CComponent *> *)nullptr, false, PlayerColor(1));
}
}
}
}
logGlobal->error("Failed to parse command: %s", string);
return std::function<void()>();
}
std::shared_ptr<CButton> CMenuEntry::createButton(CMenuScreen * parent, const JsonNode & button)
{
std::function<void()> command = genCommand(parent, parent->menuNameToEntry, button["command"].String());
std::pair<std::string, std::string> help;
if(!button["help"].isNull() && button["help"].Float() > 0)
help = CGI->generaltexth->zelp[button["help"].Float()];
int posx = button["x"].Float();
if(posx < 0)
posx = pos.w + posx;
int posy = button["y"].Float();
if(posy < 0)
posy = pos.h + posy;
return std::make_shared<CButton>(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float());
}
CMenuEntry::CMenuEntry(CMenuScreen * parent, const JsonNode & config)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
type |= REDRAW_PARENT;
pos = parent->pos;
for(const JsonNode & node : config["images"].Vector())
images.push_back(CMainMenu::createPicture(node));
for(const JsonNode & node : config["buttons"].Vector())
{
buttons.push_back(createButton(parent, node));
buttons.back()->hoverable = true;
buttons.back()->type |= REDRAW_PARENT;
}
}
CMainMenuConfig::CMainMenuConfig()
: campaignSets(JsonNode(ResourceID("config/campaignSets.json"))), config(JsonNode(ResourceID("config/mainmenu.json")))
{
}
CMainMenuConfig & CMainMenuConfig::get()
{
static CMainMenuConfig config;
return config;
}
const JsonNode & CMainMenuConfig::getConfig() const
{
return config;
}
const JsonNode & CMainMenuConfig::getCampaigns() const
{
return campaignSets;
}
CMainMenu::CMainMenu()
{
pos.w = screen->w;
pos.h = screen->h;
GH.defActionsDef = 63;
CMM = this;
menu = new CMenuScreen(CMainMenuConfig::get().getConfig()["window"]);
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
backgroundAroundMenu = std::make_shared<CFilledTexture>("DIBOXBCK", pos);
}
CMainMenu::~CMainMenu()
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
if(CMM == this)
CMM = nullptr;
if(GH.curInt == this)
GH.curInt = nullptr;
}
void CMainMenu::update()
{
if(CMM != this) //don't update if you are not a main interface
return;
if(GH.listInt.empty())
{
GH.pushInt(this);
GH.pushInt(menu);
menu->switchToTab(0);
}
// Handles mouse and key input
GH.updateTime();
GH.handleEvents();
// check for null othervice crash on finishing a campaign
// /FIXME: find out why GH.listInt is empty to begin with
if(GH.topInt() != nullptr)
GH.topInt()->show(screen);
}
void CMainMenu::openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode)
{
CSH->resetStateForLobby(screenType == ESelectionScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME, names);
CSH->screenType = screenType;
CSH->loadMode = loadMode;
GH.pushInt(new CSimpleJoinScreen(host));
}
void CMainMenu::openCampaignLobby(const std::string & campaignFileName)
{
auto ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFileName));
openCampaignLobby(ourCampaign);
}
void CMainMenu::openCampaignLobby(std::shared_ptr<CCampaignState> campaign)
{
CSH->resetStateForLobby(StartInfo::CAMPAIGN);
CSH->screenType = ESelectionScreen::campaignList;
CSH->campaignStateToSend = campaign;
GH.pushInt(new CSimpleJoinScreen());
}
void CMainMenu::openCampaignScreen(std::string name)
{
if(vstd::contains(CMainMenuConfig::get().getCampaigns().Struct(), name))
{
GH.pushInt(new CCampaignScreen(CMainMenuConfig::get().getCampaigns()[name]));
return;
}
logGlobal->error("Unknown campaign set: %s", name);
}
CMainMenu * CMainMenu::create()
{
if(!CMM)
CMM = new CMainMenu();
GH.terminate_cond->set(false);
return CMM;
}
void CMainMenu::removeFromGui()
{
//remove everything but main menu and background
GH.popInts(GH.listInt.size() - 2);
GH.popInt(GH.topInt()); //remove main menu
GH.popInt(GH.topInt()); //remove background
}
void CMainMenu::showLoadingScreen(std::function<void()> loader)
{
if(GH.listInt.size() && GH.listInt.front() == CMM)
CMM->removeFromGui();
GH.pushInt(new CLoadingScreen(loader));
}
std::shared_ptr<CPicture> CMainMenu::createPicture(const JsonNode & config)
{
return std::make_shared<CPicture>(config["name"].String(), config["x"].Float(), config["y"].Float());
}
CMultiMode::CMultiMode(ESelectionScreen ScreenType)
: screenType(ScreenType)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
background = std::make_shared<CPicture>("MUPOPUP.bmp");
background->convertToScreenBPP(); //so we could draw without problems
blitAt(CPicture("MUMAP.bmp"), 16, 77, *background);
pos = background->center(); //center, window has size of bg graphic
statusBar = std::make_shared<CGStatusBar>(new CPicture(Rect(7, 465, 440, 18), 0)); //226, 472
playerName = std::make_shared<CTextInput>(Rect(19, 436, 334, 16), *background);
playerName->setText(settings["general"]["playerName"].String());
playerName->cb += std::bind(&CMultiMode::onNameChange, this, _1);
buttonHotseat = std::make_shared<CButton>(Point(373, 78), "MUBHOT.DEF", CGI->generaltexth->zelp[266], std::bind(&CMultiMode::hostTCP, this));
buttonHost = std::make_shared<CButton>(Point(373, 78 + 57 * 1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), std::bind(&CMultiMode::hostTCP, this));
buttonJoin = std::make_shared<CButton>(Point(373, 78 + 57 * 2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), std::bind(&CMultiMode::joinTCP, this));
buttonCancel = std::make_shared<CButton>(Point(373, 424), "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&]() { GH.popIntTotally(this);}, SDLK_ESCAPE);
}
void CMultiMode::hostTCP()
{
GH.popIntTotally(this);
GH.pushInt(new CMultiPlayers(settings["general"]["playerName"].String(), screenType, true, ELoadMode::MULTI));
}
void CMultiMode::joinTCP()
{
GH.popIntTotally(this);
GH.pushInt(new CMultiPlayers(settings["general"]["playerName"].String(), screenType, false, ELoadMode::MULTI));
}
void CMultiMode::onNameChange(std::string newText)
{
Settings name = settings.write["general"]["playerName"];
name->String() = newText;
}
CMultiPlayers::CMultiPlayers(const std::string & firstPlayer, ESelectionScreen ScreenType, bool Host, ELoadMode LoadMode)
: loadMode(LoadMode), screenType(ScreenType), host(Host)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
background = std::make_shared<CPicture>("MUHOTSEA.bmp");
pos = background->center(); //center, window has size of bg graphic
std::string text = CGI->generaltexth->allTexts[446];
boost::replace_all(text, "\t", "\n");
textTitle = std::make_shared<CTextBox>(text, Rect(25, 20, 315, 50), 0, FONT_BIG, CENTER, Colors::WHITE); //HOTSEAT Please enter names
for(int i = 0; i < inputNames.size(); i++)
{
inputNames[i] = std::make_shared<CTextInput>(Rect(60, 85 + i * 30, 280, 16), *background);
inputNames[i]->cb += std::bind(&CMultiPlayers::onChange, this, _1);
}
buttonOk = std::make_shared<CButton>(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CMultiPlayers::enterSelectionScreen, this), SDLK_RETURN);
buttonCancel = std::make_shared<CButton>(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
statusBar = std::make_shared<CGStatusBar>(new CPicture(Rect(7, 381, 348, 18), 0)); //226, 472
inputNames[0]->setText(firstPlayer, true);
inputNames[0]->giveFocus();
}
void CMultiPlayers::onChange(std::string newText)
{
size_t namesCount = 0;
for(auto & elem : inputNames)
if(!elem->text.empty())
namesCount++;
}
void CMultiPlayers::enterSelectionScreen()
{
std::vector<std::string> names;
for(auto name : inputNames)
{
if(name->text.length())
names.push_back(name->text);
}
Settings name = settings.write["general"]["playerName"];
name->String() = names[0];
CMainMenu::openLobby(screenType, host, &names, loadMode);
}
CSimpleJoinScreen::CSimpleJoinScreen(bool host)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
background = std::make_shared<CPicture>("MUDIALOG.bmp"); // address background
pos = background->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212)
textTitle = std::make_shared<CTextBox>("", Rect(20, 20, 205, 50), 0, FONT_BIG, CENTER, Colors::WHITE);
inputAddress = std::make_shared<CTextInput>(Rect(25, 68, 175, 16), *background.get());
inputPort = std::make_shared<CTextInput>(Rect(25, 115, 175, 16), *background.get());
if(host && !settings["session"]["donotstartserver"].Bool())
{
textTitle->setText("Connecting...");
boost::thread(&CSimpleJoinScreen::connectThread, this, "", 0);
}
else
{
textTitle->setText("Enter address:");
inputAddress->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
inputPort->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
inputPort->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
buttonOk = std::make_shared<CButton>(Point(26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::connectToServer, this), SDLK_RETURN);
inputAddress->giveFocus();
}
inputAddress->setText(settings["server"]["server"].String(), true);
inputPort->setText(CServerHandler::getDefaultPortStr(), true);
buttonCancel = std::make_shared<CButton>(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CSimpleJoinScreen::leaveScreen, this), SDLK_ESCAPE);
statusBar = std::make_shared<CGStatusBar>(new CPicture(Rect(7, 186, 218, 18), 0));
}
void CSimpleJoinScreen::connectToServer()
{
textTitle->setText("Connecting...");
buttonOk->block(true);
boost::thread(&CSimpleJoinScreen::connectThread, this, inputAddress->text, boost::lexical_cast<ui16>(inputPort->text));
}
void CSimpleJoinScreen::leaveScreen()
{
if(CSH->state == EClientState::CONNECTING)
{
textTitle->setText("Closing...");
CSH->state = EClientState::CONNECTION_CANCELLED;
}
else if(GH.listInt.size() && GH.listInt.front() == this)
{
GH.popIntTotally(this);
}
}
void CSimpleJoinScreen::onChange(const std::string & newText)
{
buttonOk->block(inputAddress->text.empty() || inputPort->text.empty());
}
void CSimpleJoinScreen::connectThread(const std::string addr, const ui16 port)
{
setThreadName("CSimpleJoinScreen::connectThread");
if(!addr.length())
CSH->startLocalServerAndConnect();
else
CSH->justConnectToServer(addr, port);
if(GH.listInt.size() && GH.listInt.front() == this)
{
GH.popIntTotally(this);
}
}
CLoadingScreen::CLoadingScreen(std::function<void()> loader)
: CWindowObject(BORDERED, getBackground()), loadingThread(loader)
{
CCS->musich->stopMusic(5000);
}
CLoadingScreen::~CLoadingScreen()
{
loadingThread.join();
}
void CLoadingScreen::showAll(SDL_Surface * to)
{
Rect rect(0, 0, to->w, to->h);
SDL_FillRect(to, &rect, 0);
CWindowObject::showAll(to);
}
std::string CLoadingScreen::getBackground()
{
const auto & conf = CMainMenuConfig::get().getConfig()["loading"].Vector();
if(conf.empty())
{
return "loadbar";
}
else
{
return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String();
}
}

183
client/mainmenu/CMainMenu.h Normal file
View File

@ -0,0 +1,183 @@
/*
* CMainMenu.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/JsonNode.h"
class CCampaignState;
class CTextInput;
class CGStatusBar;
class CTextBox;
class CTabbedInt;
class CAnimation;
class CButton;
class CFilledTexture;
// TODO: Find new location for these enums
enum ESelectionScreen : ui8 {
unknown = 0, newGame, loadGame, saveGame, scenarioInfo, campaignList
};
enum ELoadMode : ui8
{
NONE = 0, SINGLE, MULTI, CAMPAIGN
};
/// The main menu screens listed in the EState enum
class CMenuScreen : public CWindowObject
{
const JsonNode & config;
std::shared_ptr<CTabbedInt> tabs;
std::shared_ptr<CPicture> background;
std::vector<std::shared_ptr<CPicture>> images;
CIntObject * createTab(size_t index);
public:
std::vector<std::string> menuNameToEntry;
CMenuScreen(const JsonNode & configNode);
void show(SDL_Surface * to) override;
void activate() override;
void deactivate() override;
void switchToTab(size_t index);
};
class CMenuEntry : public CIntObject
{
std::vector<std::shared_ptr<CPicture>> images;
std::vector<std::shared_ptr<CButton>> buttons;
std::shared_ptr<CButton> createButton(CMenuScreen * parent, const JsonNode & button);
public:
CMenuEntry(CMenuScreen * parent, const JsonNode & config);
};
/// Multiplayer mode
class CMultiMode : public CIntObject
{
public:
ESelectionScreen screenType;
std::shared_ptr<CPicture> background;
std::shared_ptr<CTextInput> playerName;
std::shared_ptr<CButton> buttonHotseat;
std::shared_ptr<CButton> buttonHost;
std::shared_ptr<CButton> buttonJoin;
std::shared_ptr<CButton> buttonCancel;
std::shared_ptr<CGStatusBar> statusBar;
CMultiMode(ESelectionScreen ScreenType);
void hostTCP();
void joinTCP();
void onNameChange(std::string newText);
};
/// Hot seat player window
class CMultiPlayers : public CIntObject
{
bool host;
ELoadMode loadMode;
ESelectionScreen screenType;
std::shared_ptr<CPicture> background;
std::shared_ptr<CTextBox> textTitle;
std::array<std::shared_ptr<CTextInput>, 8> inputNames;
std::shared_ptr<CButton> buttonOk;
std::shared_ptr<CButton> buttonCancel;
std::shared_ptr<CGStatusBar> statusBar;
void onChange(std::string newText);
void enterSelectionScreen();
public:
CMultiPlayers(const std::string & firstPlayer, ESelectionScreen ScreenType, bool Host, ELoadMode LoadMode);
};
/// Manages the configuration of pregame GUI elements like campaign screen, main menu, loading screen,...
class CMainMenuConfig
{
public:
static CMainMenuConfig & get();
const JsonNode & getConfig() const;
const JsonNode & getCampaigns() const;
private:
CMainMenuConfig();
const JsonNode campaignSets;
const JsonNode config;
};
/// Handles background screen, loads graphics for victory/loss condition and random town or hero selection
class CMainMenu : public CIntObject, public IUpdateable
{
std::shared_ptr<CFilledTexture> backgroundAroundMenu;
CMainMenu(); //Use CMainMenu::create
public:
CMenuScreen * menu;
~CMainMenu();
void update() override;
static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
static void openCampaignLobby(const std::string & campaignFileName);
static void openCampaignLobby(std::shared_ptr<CCampaignState> campaign);
void openCampaignScreen(std::string name);
static CMainMenu * create();
void removeFromGui();
static void showLoadingScreen(std::function<void()> loader);
static std::shared_ptr<CPicture> createPicture(const JsonNode & config);
};
/// Simple window to enter the server's address.
class CSimpleJoinScreen : public CIntObject
{
std::shared_ptr<CPicture> background;
std::shared_ptr<CTextBox> textTitle;
std::shared_ptr<CButton> buttonOk;
std::shared_ptr<CButton> buttonCancel;
std::shared_ptr<CGStatusBar> statusBar;
std::shared_ptr<CTextInput> inputAddress;
std::shared_ptr<CTextInput> inputPort;
void connectToServer();
void leaveScreen();
void onChange(const std::string & newText);
void connectThread(const std::string addr = "", const ui16 inputPort = 0);
public:
CSimpleJoinScreen(bool host = true);
};
class CLoadingScreen : public CWindowObject
{
boost::thread loadingThread;
std::string getBackground();
public:
CLoadingScreen(std::function<void()> loader);
~CLoadingScreen();
void showAll(SDL_Surface * to) override;
};
extern CMainMenu * CMM;

View File

@ -0,0 +1,64 @@
/*
* CPrologEpilogVideo.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 "CPrologEpilogVideo.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CVideoHandler.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/TextControls.h"
#include "../../lib/mapping/CCampaignHandler.h"
CPrologEpilogVideo::CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback)
: CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), exitCb(callback)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
addUsedEvents(LCLICK);
pos = center(Rect(0, 0, 800, 600));
updateShadow();
CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo));
CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true);
// MPTODO: Custom campaign crashing on this?
// voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));
text = std::make_shared<CMultiLineLabel>(Rect(100, 500, 600, 100), EFonts::FONT_BIG, CENTER, Colors::METALLIC_GOLD, spe.prologText);
text->scrollTextTo(-100);
}
void CPrologEpilogVideo::show(SDL_Surface * to)
{
CSDL_Ext::fillRectBlack(to, &pos);
//BUG: some videos are 800x600 in size while some are 800x400
//VCMI should center them in the middle of the screen. Possible but needs modification
//of video player API which I'd like to avoid until we'll get rid of Windows-specific player
CCS->videoh->update(pos.x, pos.y, to, true, false);
//move text every 5 calls/frames; seems to be good enough
++positionCounter;
if(positionCounter % 5 == 0)
text->scrollTextBy(1);
else
text->showAll(to); // blit text over video, if needed
if(text->textSize.y + 100 < positionCounter / 5)
clickLeft(false, false);
}
void CPrologEpilogVideo::clickLeft(tribool down, bool previousState)
{
GH.popInt(this);
CCS->soundh->stopSound(voiceSoundHandle);
exitCb();
}

View File

@ -0,0 +1,31 @@
/*
* CPrologEpilogVideo.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/mapping/CCampaignHandler.h"
class CMultiLineLabel;
class SDL_Surface;
class CPrologEpilogVideo : public CWindowObject
{
CCampaignScenario::SScenarioPrologEpilog spe;
int positionCounter;
int voiceSoundHandle;
std::function<void()> exitCb;
std::shared_ptr<CMultiLineLabel> text;
public:
CPrologEpilogVideo(CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback);
void clickLeft(tribool down, bool previousState) override;
void show(SDL_Surface * to) override;
};

View File

@ -0,0 +1,59 @@
/*
* CreditsScreen.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 "CreditsScreen.h"
#include "../mainmenu/CMainMenu.h"
#include "../gui/CGuiHandler.h"
#include "../widgets/TextControls.h"
#include "../widgets/ObjectLists.h"
#include "../../lib/filesystem/Filesystem.h"
CreditsScreen::CreditsScreen()
: positionCounter(0)
{
addUsedEvents(LCLICK | RCLICK);
type |= REDRAW_PARENT;
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.w = CMM->menu->pos.w;
pos.h = CMM->menu->pos.h;
auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
std::string text((char *)textFile.first.get(), textFile.second);
size_t firstQuote = text.find('\"') + 1;
text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote);
credits = std::make_shared<CMultiLineLabel>(Rect(pos.w - 350, 0, 350, 600), FONT_CREDITS, CENTER, Colors::WHITE, text);
credits->scrollTextTo(-600); // move all text below the screen
}
void CreditsScreen::show(SDL_Surface * to)
{
CIntObject::show(to);
positionCounter++;
if(positionCounter % 2 == 0)
credits->scrollTextBy(1);
//end of credits, close this screen
if(credits->textSize.y + 600 < positionCounter / 2)
clickRight(false, false);
}
void CreditsScreen::clickLeft(tribool down, bool previousState)
{
clickRight(down, previousState);
}
void CreditsScreen::clickRight(tribool down, bool previousState)
{
CTabbedInt * menu = dynamic_cast<CTabbedInt *>(parent);
assert(menu);
menu->setActive(0);
}

View File

@ -0,0 +1,27 @@
/*
* CreditsScreen.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 CMultiLineLabel;
class SDL_Surface;
class CreditsScreen : public CIntObject
{
int positionCounter;
std::shared_ptr<CMultiLineLabel> credits;
public:
CreditsScreen();
void show(SDL_Surface * to) override;
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
};

View File

@ -18,7 +18,7 @@
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../CPreGame.h"
#include "../mainmenu/CMainMenu.h"
#include "../Graphics.h"
#include "../CMessage.h"

View File

@ -63,6 +63,11 @@ std::string CLabel::getText()
return text;
}
void CLabel::setAutoRedraw(bool value)
{
autoRedraw = value;
}
void CLabel::setText(const std::string &Txt)
{
text = Txt;
@ -75,6 +80,23 @@ void CLabel::setText(const std::string &Txt)
}
}
void CLabel::setColor(const SDL_Color & Color)
{
color = Color;
if(autoRedraw)
{
if(bg || !parent)
redraw();
else
parent->redraw();
}
}
size_t CLabel::getWidth()
{
return graphics->fonts[font]->getStringWidth(visibleText());;
}
CMultiLineLabel::CMultiLineLabel(Rect position, EFonts Font, EAlignment Align, const SDL_Color &Color, const std::string &Text):
CLabel(position.x, position.y, Font, Align, Color, Text),
visibleSize(0, 0, position.w, position.h)
@ -244,7 +266,12 @@ CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color):
void CLabelGroup::add(int x, int y, const std::string &text)
{
OBJ_CONSTRUCTION_CAPTURING_ALL;
new CLabel(x, y, font, align, color, text);
labels.push_back(new CLabel(x, y, font, align, color, text));
}
size_t CLabelGroup::currentSize() const
{
return labels.size();
}
CTextBox::CTextBox(std::string Text, const Rect &rect, int SliderStyle, EFonts Font, EAlignment Align, const SDL_Color &Color):

View File

@ -47,7 +47,10 @@ public:
bool autoRedraw; //whether control will redraw itself on setTxt
std::string getText();
virtual void setAutoRedraw(bool option);
virtual void setText(const std::string &Txt);
virtual void setColor(const SDL_Color & Color);
size_t getWidth();
CLabel(int x=0, int y=0, EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT,
const SDL_Color &Color = Colors::WHITE, const std::string &Text = "");
@ -64,6 +67,7 @@ class CLabelGroup : public CIntObject
public:
CLabelGroup(EFonts Font = FONT_SMALL, EAlignment Align = TOPLEFT, const SDL_Color &Color = Colors::WHITE);
void add(int x=0, int y=0, const std::string &text = "");
size_t currentSize() const;
};
/// Multi-line label that can display multiple lines of text

View File

@ -22,7 +22,10 @@
#include "../CMessage.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../CPreGame.h"
#include "../mainmenu/CMainMenu.h"
#include "../lobby/CBonusSelection.h"
#include "../lobby/CSavingScreen.h"
#include "../lobby/CScenarioInfoScreen.h"
#include "../Graphics.h"
#include "../mapHandler.h"
@ -47,6 +50,7 @@
#include "../../lib/mapping/CMap.h"
#include "../../lib/UnlockGuard.h"
#include "../../lib/VCMI_Lib.h"
#include "../../lib/StartInfo.h"
#ifdef _MSC_VER
#pragma warning (disable : 4355)
@ -935,7 +939,8 @@ void CAdvMapInt::activate()
}
minimap.activate();
terrain.activate();
LOCPLINT->cingconsole->activate();
if(LOCPLINT)
LOCPLINT->cingconsole->activate();
GH.fakeMouseMove(); //to restore the cursor
}
@ -1215,7 +1220,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
return;
case SDLK_s:
if(isActive() && key.type == SDL_KEYUP)
GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
GH.pushInt(new CSavingScreen());
return;
case SDLK_d:
{
@ -1235,7 +1240,7 @@ void CAdvMapInt::keyPressed(const SDL_KeyboardEvent & key)
if(isActive() && LOCPLINT->ctrlPressed())
{
LOCPLINT->showYesNoDialog("Are you sure you want to restart game?",
[](){ LOCPLINT->sendCustomEvent(RESTART_GAME); },
[](){ LOCPLINT->sendCustomEvent(EUserEvent::RESTART_GAME); },
[](){}, true);
}
return;
@ -1947,14 +1952,13 @@ CAdventureOptions::CAdventureOptions():
void CAdventureOptions::showScenarioInfo()
{
auto campState = LOCPLINT->cb->getStartInfo()->campState;
if(campState)
if(LOCPLINT->cb->getStartInfo()->campState)
{
GH.pushInt(new CBonusSelection(campState));
GH.pushInt(new CBonusSelection());
}
else
{
GH.pushInt(new CScenarioInfo(LOCPLINT->cb->getMapHeader(), LOCPLINT->cb->getStartInfo()));
GH.pushInt(new CScenarioInfoScreen());
}
}

View File

@ -21,10 +21,10 @@
#include "../CMessage.h"
#include "../CMusicHandler.h"
#include "../CPlayerInterface.h"
#include "../CPreGame.h"
#include "../CVideoHandler.h"
#include "../Graphics.h"
#include "../mapHandler.h"
#include "../CServerHandler.h"
#include "../battle/CBattleInterfaceClasses.h"
#include "../battle/CBattleInterface.h"
@ -38,6 +38,8 @@
#include "../widgets/MiscWidgets.h"
#include "../windows/InfoWindows.h"
#include "../lobby/CSavingScreen.h"
#include "../../CCallback.h"
#include "../lib/mapObjects/CGHeroInstance.h"
@ -487,6 +489,13 @@ CSystemOptionsWindow::CSystemOptionsWindow():
restart = new CButton (Point(246, 357), "SORSTRT", CGI->generaltexth->zelp[323], [&](){ brestartf(); }, SDLK_r);
restart->setImageOrder(1, 0, 2, 3);
if(CSH->isGuest())
{
load->block(true);
save->block(true);
restart->block(true);
}
mainMenu = new CButton (Point(357, 357), "SOMAIN.DEF", CGI->generaltexth->zelp[320], [&](){ bmainmenuf(); }, SDLK_m);
mainMenu->setImageOrder(1, 0, 2, 3);
@ -593,7 +602,7 @@ void CSystemOptionsWindow::setGameRes(int index)
void CSystemOptionsWindow::bquitf()
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, FORCE_QUIT); }, 0);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, EUserEvent::FORCE_QUIT); }, 0);
}
void CSystemOptionsWindow::breturnf()
@ -603,7 +612,7 @@ void CSystemOptionsWindow::breturnf()
void CSystemOptionsWindow::bmainmenuf()
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, RETURN_TO_MAIN_MENU); }, 0);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[578], [this](){ closeAndPushEvent(SDL_USEREVENT, EUserEvent::RETURN_TO_MAIN_MENU); }, 0);
}
void CSystemOptionsWindow::bloadf()
@ -615,12 +624,12 @@ void CSystemOptionsWindow::bloadf()
void CSystemOptionsWindow::bsavef()
{
GH.popIntTotally(this);
GH.pushInt(new CSavingScreen(CPlayerInterface::howManyPeople > 1));
GH.pushInt(new CSavingScreen());
}
void CSystemOptionsWindow::brestartf()
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this](){ closeAndPushEvent(SDL_USEREVENT, RESTART_GAME); }, 0);
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [this](){ closeAndPushEvent(SDL_USEREVENT, EUserEvent::RESTART_GAME); }, 0);
}
void CSystemOptionsWindow::closeAndPushEvent(int eventType, int code)

View File

@ -46,6 +46,18 @@
"saveRandomMaps" : {
"type" : "boolean",
"default" : false
},
"lastMap" : {
"type":"string",
"default" : "Maps/Arrogance"
},
"lastSave" : {
"type":"string",
"default" : "NEWGAME"
},
"lastCampaign" : {
"type":"string",
"default" : ""
}
}
},

View File

@ -64,8 +64,6 @@ public:
}
};
static CApplier<CBaseForGSApply> *applierGs = nullptr;
void MetaString::getLocalString(const std::pair<ui8,ui32> &txt, std::string &dst) const
{
int type = txt.first, ser = txt.second;
@ -673,9 +671,9 @@ int CGameState::getDate(Date::EDateType mode) const
CGameState::CGameState()
{
gs = this;
applierGs = new CApplier<CBaseForGSApply>();
registerTypesClientPacks1(*applierGs);
registerTypesClientPacks2(*applierGs);
applier = std::make_shared<CApplier<CBaseForGSApply>>();
registerTypesClientPacks1(*applier);
registerTypesClientPacks2(*applier);
//objCaller = new CObjectCallersHandler();
globalEffects.setDescription("Global effects");
globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
@ -686,10 +684,6 @@ CGameState::~CGameState()
{
map.dellNull();
curB.dellNull();
//delete scenarioOps; //TODO: fix for loading ind delete
//delete initialOpts;
delete applierGs;
//delete objCaller;
for(auto ptr : hpool.heroesPool) // clean hero pool
ptr.second.dellNull();
@ -709,7 +703,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
initNewGame(mapService, allowSavingRandomMap);
break;
case StartInfo::CAMPAIGN:
initCampaign(mapService);
initCampaign();
break;
default:
logGlobal->error("Wrong mode: %d", static_cast<int>(scenarioOps->mode));
@ -765,6 +759,13 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
}
}
void CGameState::updateOnLoad(StartInfo * si)
{
scenarioOps->playerInfos = si->playerInfos;
for(auto & i : si->playerInfos)
gs->players[i.first].human = i.second.isControlledByHuman();
}
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap)
{
if(scenarioOps->createRandomMap())
@ -815,7 +816,7 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
playerSettings.compOnly = !playerInfo.canHumanPlay;
playerSettings.team = playerInfo.team;
playerSettings.castle = playerInfo.defaultCastle();
if(playerSettings.playerID == PlayerSettings::PLAYER_AI && playerSettings.name.empty())
if(playerSettings.isControlledByAI() && playerSettings.name.empty())
{
playerSettings.name = VLC->generaltexth->allTexts[468];
}
@ -837,19 +838,10 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
}
}
void CGameState::initCampaign(const IMapService * mapService)
void CGameState::initCampaign()
{
logGlobal->info("Open campaign map file: %d", scenarioOps->campState->currentMap.get());
auto campaign = scenarioOps->campState;
assert(vstd::contains(campaign->camp->mapPieces, *scenarioOps->campState->currentMap));
std::string scenarioName = scenarioOps->mapname.substr(0, scenarioOps->mapname.find('.'));
boost::to_lower(scenarioName);
scenarioName += ':' + boost::lexical_cast<std::string>(*campaign->currentMap);
std::string & mapContent = campaign->camp->mapPieces[*campaign->currentMap];
auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
map = mapService->loadMap(buffer, mapContent.size(), scenarioName).release();
map = scenarioOps->campState->getMap();
}
void CGameState::checkMapChecksum()
@ -962,13 +954,11 @@ void CGameState::initPlayerStates()
for(auto & elem : scenarioOps->playerInfos)
{
PlayerState & p = players[elem.first];
//std::pair<PlayerColor, PlayerState> ins(elem.first,PlayerState());
p.color=elem.first;
p.human = elem.second.playerID;
p.human = elem.second.isControlledByHuman();
p.team = map->players[elem.first.getNum()].team;
teams[p.team].id = p.team;//init team
teams[p.team].players.insert(elem.first);//add player to team
//players.insert(ins);
}
}
@ -1091,13 +1081,26 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
auto bonus = campaignState->getBonusForCurrentMap();
if (bonus && bonus->type == CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO)
{
crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[bonus->info2].crossoverHeroes;
std::vector<CGHeroInstance *> heroes;
for(auto & node : campaignState->camp->scenarios[bonus->info2].crossoverHeroes)
{
auto h = CCampaignState::crossoverDeserialize(node);
heroes.push_back(h);
}
crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
}
else
{
if(!campaignState->mapsConquered.empty())
{
crossoverHeroes.heroesFromPreviousScenario = campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes;
std::vector<CGHeroInstance *> heroes;
for(auto & node : campaignState->camp->scenarios[campaignState->mapsConquered.back()].crossoverHeroes)
{
auto h = CCampaignState::crossoverDeserialize(node);
heroes.push_back(h);
}
crossoverHeroes.heroesFromAnyPreviousScenarios = crossoverHeroes.heroesFromPreviousScenario = heroes;
crossoverHeroes.heroesFromPreviousScenario = heroes;
for(auto mapNr : campaignState->mapsConquered)
{
@ -1108,15 +1111,22 @@ CGameState::CrossoverHeroesList CGameState::getCrossoverHeroesFromPreviousScenar
// remove heroes which didn't reached the end of the scenario, but were available at the start
for(auto hero : lostCrossoverHeroes)
{
vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios,
CGObjectInstanceBySubIdFinder(hero));
// auto hero = CCampaignState::crossoverDeserialize(node);
vstd::erase_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
{
return hero->subID == h->subID;
});
}
// now add heroes which completed the scenario
for(auto hero : scenario.crossoverHeroes)
for(auto node : scenario.crossoverHeroes)
{
auto hero = CCampaignState::crossoverDeserialize(node);
// add new heroes and replace old heroes with newer ones
auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, CGObjectInstanceBySubIdFinder(hero));
auto it = range::find_if(crossoverHeroes.heroesFromAnyPreviousScenarios, [hero](CGHeroInstance * h)
{
return hero->subID == h->subID;
});
if (it != crossoverHeroes.heroesFromAnyPreviousScenarios.end())
{
// replace old hero with newer one
@ -1306,7 +1316,7 @@ void CGameState::initStartingResources()
for(auto it = scenarioOps->playerInfos.cbegin();
it != scenarioOps->playerInfos.cend(); ++it)
{
if(it->second.playerID != PlayerSettings::PLAYER_AI)
if(it->second.isControlledByHuman())
ret.push_back(&it->second);
}
@ -1955,7 +1965,7 @@ PlayerRelations::PlayerRelations CGameState::getPlayerRelations( PlayerColor col
void CGameState::apply(CPack *pack)
{
ui16 typ = typeList.getTypeID(pack);
applierGs->getApplier(typ)->applyOnGS(this,pack);
applier->getApplier(typ)->applyOnGS(this, pack);
}
void CGameState::calculatePaths(const CGHeroInstance *hero, CPathsInfo &out)
@ -2806,7 +2816,7 @@ void CGameState::replaceHeroesPlaceholders(const std::vector<CGameState::Campaig
map->objects[heroToPlace->id.getNum()] = heroToPlace;
map->addBlockVisTiles(heroToPlace);
scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(heroToPlace);
scenarioOps->campState->getCurrentScenario().placedCrossoverHeroes.push_back(CCampaignState::crossoverSerialize(heroToPlace));
}
}

View File

@ -60,6 +60,9 @@ namespace boost
class shared_mutex;
}
template<typename T> class CApplier;
class CBaseForGSApply;
struct DLL_LINKAGE SThievesGuildInfo
{
std::vector<PlayerColor> playerColors; //colors of players that are in-game
@ -153,6 +156,7 @@ public:
virtual ~CGameState();
void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false);
void updateOnLoad(StartInfo * si);
ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
PlayerColor currentPlayer; //ID of player currently having turn
@ -246,7 +250,7 @@ private:
// ----- initialization -----
void initNewGame(const IMapService * mapService, bool allowSavingRandomMap);
void initCampaign(const IMapService * mapService);
void initCampaign();
void checkMapChecksum();
void initGrailPosition();
void initRandomFactionsForPlayers();
@ -291,6 +295,7 @@ private:
int pickNextHeroType(PlayerColor owner); // picks next free hero type of the H3 hero init sequence -> chosen starting hero, then unused hero type randomly
// ---- data -----
std::shared_ptr<CApplier<CBaseForGSApply>> applier;
CRandomGenerator rand;
friend class CCallback;

View File

@ -74,7 +74,7 @@ set(lib_SRCS
registerTypes/TypesMapObjects1.cpp
registerTypes/TypesMapObjects2.cpp
registerTypes/TypesMapObjects3.cpp
registerTypes/TypesPregamePacks.cpp
registerTypes/TypesLobbyPacks.cpp
registerTypes/TypesServerPacks.cpp
rmg/CMapGenerator.cpp
@ -152,6 +152,7 @@ set(lib_SRCS
JsonNode.cpp
LogicalExpression.cpp
NetPacksLib.cpp
StartInfo.cpp
ResourceSet.cpp
VCMIDirs.cpp
VCMI_Lib.cpp
@ -335,6 +336,7 @@ set(lib_HEADERS
LogicalExpression.h
NetPacksBase.h
NetPacks.h
NetPacksLobby.h
ResourceSet.h
ScopeGuard.h
StartInfo.h

View File

@ -51,6 +51,8 @@ namespace GameConstants
const ui32 BASE_MOVEMENT_COST = 100; //default cost for non-diagonal movement
const int HERO_PORTRAIT_SHIFT = 30;// 2 special frames + some extra portraits
const std::array<int, 11> POSSIBLE_TURNTIME = {1, 2, 4, 6, 8, 10, 15, 20, 25, 30, 0};
}
class CArtifact;

View File

@ -28,6 +28,9 @@
#include "CGameState.h"
#include "mapping/CMap.h"
#include "CPlayerState.h"
#include "CSkillHandler.h"
#include "serializer/Connection.h"
void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
{

View File

@ -91,7 +91,7 @@ public:
virtual void setManaPoints(ObjectInstanceID hid, int val)=0;
virtual void giveHero(ObjectInstanceID id, PlayerColor player)=0;
virtual void changeObjPos(ObjectInstanceID objid, int3 newPos, ui8 flags)=0;
virtual void sendAndApply(CPackForClient * info)=0;
virtual void sendAndApply(CPackForClient * pack) = 0;
virtual void heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)=0; //when two heroes meet on adventure map
virtual void changeFogOfWar(int3 center, ui32 radius, PlayerColor player, bool hide) = 0;
virtual void changeFogOfWar(std::unordered_set<int3, ShashInt3> &tiles, PlayerColor player, bool hide) = 0;

View File

@ -22,16 +22,15 @@
#include "spells/ViewSpellInt.h"
class CCampaignState;
class CClient;
class CGameState;
class CGameHandler;
class CArtifact;
class CSelectionScreen;
class CGObjectInstance;
class CArtifactInstance;
struct StackLocation;
struct ArtSlotInfo;
struct QuestInfo;
class CMapInfo;
struct StartInfo;
class IBattleState;
struct Query : public CPackForClient
@ -438,18 +437,6 @@ struct RemoveBonus : public CPackForClient
}
};
struct UpdateCampaignState : public CPackForClient
{
UpdateCampaignState(){}
std::shared_ptr<CCampaignState> camp;
void applyCl(CClient *cl);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & camp;
}
};
struct SetCommanderProperty : public CPackForClient
{
enum ECommanderProperty {ALIVE, BONUS, SECONDARY_SKILL, EXPERIENCE, SPECIAL_SKILL};
@ -495,16 +482,6 @@ struct AddQuest : public CPackForClient
}
};
struct PrepareForAdvancingCampaign : public CPackForClient
{
PrepareForAdvancingCampaign(){}
void applyCl(CClient *cl);
template <typename Handler> void serialize(Handler &h, const int version)
{
}
};
struct UpdateArtHandlerLists : public CPackForClient
{
UpdateArtHandlerLists(){}
@ -1889,30 +1866,18 @@ struct CommitPackage : public CPackForServer
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & packToCommit;
}
};
struct CloseServer : public CPackForServer
{
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{}
};
struct LeaveGame : public CPackForServer
{
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{}
};
struct EndTurn : public CPackForServer
{
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{}
{
h & static_cast<CPackForServer &>(*this);
}
};
struct DismissHero : public CPackForServer
@ -1924,6 +1889,7 @@ struct DismissHero : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & hid;
}
};
@ -1939,6 +1905,7 @@ struct MoveHero : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & dest;
h & hid;
h & transit;
@ -1956,6 +1923,7 @@ struct CastleTeleportHero : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & dest;
h & hid;
}
@ -1974,6 +1942,7 @@ struct ArrangeStacks : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & what;
h & p1;
h & p2;
@ -1993,6 +1962,7 @@ struct DisbandCreature : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & pos;
h & id;
}
@ -2008,6 +1978,7 @@ struct BuildStructure : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & tid;
h & bid;
}
@ -2033,6 +2004,7 @@ struct RecruitCreatures : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & tid;
h & dst;
h & crid;
@ -2052,6 +2024,7 @@ struct UpgradeCreature : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & pos;
h & id;
h & cid;
@ -2067,6 +2040,7 @@ struct GarrisonHeroSwap : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & tid;
}
};
@ -2079,6 +2053,7 @@ struct ExchangeArtifacts : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & src;
h & dst;
}
@ -2097,6 +2072,7 @@ struct AssembleArtifacts : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & heroID;
h & artifactSlot;
h & assemble;
@ -2114,6 +2090,7 @@ struct BuyArtifact : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & hid;
h & aid;
}
@ -2135,6 +2112,7 @@ struct TradeOnMarketplace : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & marketId;
h & heroId;
h & mode;
@ -2154,6 +2132,7 @@ struct SetFormation : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & hid;
h & formation;
}
@ -2170,6 +2149,7 @@ struct HireHero : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & hid;
h & tid;
h & player;
@ -2184,6 +2164,7 @@ struct BuildBoat : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & objid;
}
@ -2200,6 +2181,7 @@ struct QueryReply : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & qid;
h & player;
h & reply;
@ -2215,6 +2197,7 @@ struct MakeAction : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & ba;
}
};
@ -2228,6 +2211,7 @@ struct MakeCustomAction : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & ba;
}
};
@ -2240,6 +2224,7 @@ struct DigWithHero : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & id;
}
};
@ -2254,6 +2239,7 @@ struct CastAdvSpell : public CPackForServer
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & hid;
h & sid;
h & pos;
@ -2262,43 +2248,73 @@ struct CastAdvSpell : public CPackForServer
/***********************************************************************************************************/
struct SaveGame : public CPackForClient, public CPackForServer
struct SaveGame : public CPackForServer
{
SaveGame(){};
SaveGame(const std::string &Fname) :fname(Fname){};
std::string fname;
void applyCl(CClient *cl);
void applyGs(CGameState *gs){};
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & fname;
}
};
// TODO: Eventually we should re-merge both SaveGame and PlayerMessage
struct SaveGameClient : public CPackForClient
{
SaveGameClient(){};
SaveGameClient(const std::string &Fname) :fname(Fname){};
std::string fname;
void applyCl(CClient *cl);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & fname;
}
};
struct PlayerMessage : public CPackForClient, public CPackForServer
struct PlayerMessage : public CPackForServer
{
PlayerMessage(){};
PlayerMessage(PlayerColor Player, const std::string &Text, ObjectInstanceID obj)
:player(Player),text(Text), currObj(obj)
PlayerMessage(const std::string &Text, ObjectInstanceID obj)
: text(Text), currObj(obj)
{};
void applyCl(CClient *cl);
void applyGs(CGameState *gs){};
bool applyGh(CGameHandler *gh);
PlayerColor player;
std::string text;
ObjectInstanceID currObj; // optional parameter that specifies current object. For cheats :)
template <typename Handler> void serialize(Handler &h, const int version)
{
h & static_cast<CPackForServer &>(*this);
h & text;
h & player;
h & currObj;
}
};
struct PlayerMessageClient : public CPackForClient
{
PlayerMessageClient(){};
PlayerMessageClient(PlayerColor Player, const std::string &Text)
: player(Player), text(Text)
{}
void applyCl(CClient *cl);
PlayerColor player;
std::string text;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & player;
h & text;
}
};
struct CenterView : public CPackForClient
{
CenterView():focusTime(0){};
@ -2315,159 +2331,3 @@ struct CenterView : public CPackForClient
h & focusTime;
}
};
/***********************************************************************************************************/
struct CPackForSelectionScreen : public CPack
{
void apply(CSelectionScreen *selScreen) {} // implemented in CPreGame.cpp
};
class CPregamePackToPropagate : public CPackForSelectionScreen
{};
class CPregamePackToHost : public CPackForSelectionScreen
{};
struct ChatMessage : public CPregamePackToPropagate
{
std::string playerName, message;
void apply(CSelectionScreen *selScreen);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & playerName;
h & message;
}
};
struct QuitMenuWithoutStarting : public CPregamePackToPropagate
{
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{}
};
struct PlayerJoined : public CPregamePackToHost
{
std::string playerName;
ui8 connectionID;
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
h & playerName;
h & connectionID;
}
};
struct ELF_VISIBILITY SelectMap : public CPregamePackToPropagate
{
const CMapInfo *mapInfo;
bool free;//local flag, do not serialize
DLL_LINKAGE SelectMap(const CMapInfo &src);
DLL_LINKAGE SelectMap();
DLL_LINKAGE ~SelectMap();
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
h & mapInfo;
}
};
struct ELF_VISIBILITY UpdateStartOptions : public CPregamePackToPropagate
{
StartInfo *options;
bool free;//local flag, do not serialize
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
DLL_LINKAGE UpdateStartOptions(StartInfo &src);
DLL_LINKAGE UpdateStartOptions();
DLL_LINKAGE ~UpdateStartOptions();
template <typename Handler> void serialize(Handler &h, const int version)
{
h & options;
}
};
struct PregameGuiAction : public CPregamePackToPropagate
{
enum {NO_TAB, OPEN_OPTIONS, OPEN_SCENARIO_LIST, OPEN_RANDOM_MAP_OPTIONS}
action;
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
h & action;
}
};
struct RequestOptionsChange : public CPregamePackToHost
{
enum EWhat {TOWN, HERO, BONUS};
ui8 what;
si8 direction; //-1 or +1
ui8 playerID;
RequestOptionsChange(ui8 What, si8 Dir, ui8 Player)
:what(What), direction(Dir), playerID(Player)
{}
RequestOptionsChange()
:what(0), direction(0), playerID(0)
{}
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
h & what;
h & direction;
h & playerID;
}
};
struct PlayerLeft : public CPregamePackToPropagate
{
ui8 playerID;
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
h & playerID;
}
};
struct PlayersNames : public CPregamePackToPropagate
{
public:
std::map<ui8, std::string> playerNames;
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
h & playerNames;
}
};
struct StartWithCurrentSettings : public CPregamePackToPropagate
{
public:
void apply(CSelectionScreen *selScreen); //that functions are implemented in CPreGame.cpp
template <typename Handler> void serialize(Handler &h, const int version)
{
//h & playerNames;
}
};

View File

@ -20,7 +20,6 @@ class CArmedInstance;
class CArtifactSet;
class CBonusSystemNode;
struct ArtSlotInfo;
class BattleInfo;
#include "ConstTransitivePtr.h"
#include "GameConstants.h"
@ -28,7 +27,9 @@ class BattleInfo;
struct DLL_LINKAGE CPack
{
CPack() {};
std::shared_ptr<CConnection> c; // Pointer to connection that pack received from
CPack() : c(nullptr) {};
virtual ~CPack() {};
template <typename Handler> void serialize(Handler &h, const int version)
@ -54,12 +55,11 @@ struct CPackForClient : public CPack
struct CPackForServer : public CPack
{
PlayerColor player;
CConnection *c;
mutable PlayerColor player;
mutable si32 requestID;
CGameState* GS(CGameHandler *gh);
CPackForServer():
player(PlayerColor::NEUTRAL),
c(nullptr)
player(PlayerColor::NEUTRAL)
{
}
@ -69,6 +69,12 @@ struct CPackForServer : public CPack
return false;
}
template <typename Handler> void serialize(Handler &h, const int version)
{
h & player;
h & requestID;
}
protected:
void throwNotAllowedAction();
void throwOnWrongOwner(CGameHandler * gh, ObjectInstanceID id);

View File

@ -56,40 +56,6 @@ DLL_LINKAGE void SetSecSkill::applyGs(CGameState *gs)
hero->setSecSkillLevel(which, val, abs);
}
DLL_LINKAGE SelectMap::SelectMap(const CMapInfo &src)
{
mapInfo = &src;
free = false;
}
DLL_LINKAGE SelectMap::SelectMap()
{
mapInfo = nullptr;
free = true;
}
DLL_LINKAGE SelectMap::~SelectMap()
{
if(free)
delete mapInfo;
}
DLL_LINKAGE UpdateStartOptions::UpdateStartOptions(StartInfo &src)
{
options = &src;
free = false;
}
DLL_LINKAGE UpdateStartOptions::UpdateStartOptions()
{
options = nullptr;
free = true;
}
DLL_LINKAGE UpdateStartOptions::~UpdateStartOptions()
{
if(free)
delete options;
}
DLL_LINKAGE void SetCommanderProperty::applyGs(CGameState *gs)
{
CCommanderInstance * commander = gs->getHero(heroid)->commander;
@ -346,8 +312,43 @@ DLL_LINKAGE void ChangeObjectVisitors::applyGs(CGameState *gs)
DLL_LINKAGE void PlayerEndsGame::applyGs(CGameState *gs)
{
PlayerState *p = gs->getPlayer(player);
if(victoryLossCheckResult.victory()) p->status = EPlayerStatus::WINNER;
else p->status = EPlayerStatus::LOSER;
if(victoryLossCheckResult.victory())
{
p->status = EPlayerStatus::WINNER;
// TODO: Campaign-specific code might as well go somewhere else
if(p->human && gs->scenarioOps->campState)
{
std::vector<CGHeroInstance *> crossoverHeroes;
for (CGHeroInstance * hero : gs->map->heroesOnMap)
{
if (hero->tempOwner == player)
{
// keep all heroes from the winning player
crossoverHeroes.push_back(hero);
}
else if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID)))
{
// keep hero whether lost or won (like Xeron in AB campaign)
crossoverHeroes.push_back(hero);
}
}
// keep lost heroes which are in heroes pool
for (auto & heroPair : gs->hpool.heroesPool)
{
if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first)))
{
crossoverHeroes.push_back(heroPair.second.get());
}
}
gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
}
}
else
{
p->status = EPlayerStatus::LOSER;
}
}
DLL_LINKAGE void RemoveBonus::applyGs(CGameState *gs)

311
lib/NetPacksLobby.h Normal file
View File

@ -0,0 +1,311 @@
/*
* NetPacksLobby.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 "NetPacksBase.h"
#include "StartInfo.h"
class CCampaignState;
class CLobbyScreen;
class CServerHandler;
class CMapInfo;
struct StartInfo;
class CMapGenOptions;
struct ClientPlayer;
class CVCMIServer;
struct CPackForLobby : public CPack
{
bool checkClientPermissions(CVCMIServer * srv) const
{
return false;
}
bool applyOnServer(CVCMIServer * srv)
{
return true;
}
void applyOnServerAfterAnnounce(CVCMIServer * srv) {}
bool applyOnLobbyHandler(CServerHandler * handler)
{
return true;
}
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler) {}
};
struct CLobbyPackToPropagate : public CPackForLobby
{
};
struct CLobbyPackToServer : public CPackForLobby
{
bool checkClientPermissions(CVCMIServer * srv) const;
void applyOnServerAfterAnnounce(CVCMIServer * srv);
};
struct LobbyClientConnected : public CLobbyPackToPropagate
{
// Set by client before sending pack to server
std::string uuid;
std::vector<std::string> names;
StartInfo::EMode mode;
// Changed by server before announcing pack
int clientId;
int hostClientId;
LobbyClientConnected()
: mode(StartInfo::INVALID), clientId(-1), hostClientId(-1)
{}
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnLobbyHandler(CServerHandler * handler);
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
bool applyOnServer(CVCMIServer * srv);
void applyOnServerAfterAnnounce(CVCMIServer * srv);
template <typename Handler> void serialize(Handler & h, const int version)
{
h & uuid;
h & names;
h & mode;
h & clientId;
h & hostClientId;
}
};
struct LobbyClientDisconnected : public CLobbyPackToPropagate
{
int clientId;
bool shutdownServer;
LobbyClientDisconnected() : shutdownServer(false) {}
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnServer(CVCMIServer * srv);
void applyOnServerAfterAnnounce(CVCMIServer * srv);
bool applyOnLobbyHandler(CServerHandler * handler);
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & clientId;
h & shutdownServer;
}
};
struct LobbyChatMessage : public CLobbyPackToPropagate
{
std::string playerName, message;
bool checkClientPermissions(CVCMIServer * srv) const;
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & playerName;
h & message;
}
};
struct LobbyGuiAction : public CLobbyPackToPropagate
{
enum EAction : ui8 {
NONE, NO_TAB, OPEN_OPTIONS, OPEN_SCENARIO_LIST, OPEN_RANDOM_MAP_OPTIONS
} action;
LobbyGuiAction() : action(NONE) {}
bool checkClientPermissions(CVCMIServer * srv) const;
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & action;
}
};
struct LobbyStartGame : public CLobbyPackToPropagate
{
// Set by server
std::shared_ptr<StartInfo> initializedStartInfo;
LobbyStartGame() : initializedStartInfo(nullptr) {}
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnServer(CVCMIServer * srv);
void applyOnServerAfterAnnounce(CVCMIServer * srv);
bool applyOnLobbyHandler(CServerHandler * handler);
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & initializedStartInfo;
}
};
struct LobbyChangeHost : public CLobbyPackToPropagate
{
int newHostConnectionId;
LobbyChangeHost() : newHostConnectionId(-1) {}
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnServer(CVCMIServer * srv);
bool applyOnServerAfterAnnounce(CVCMIServer * srv);
template <typename Handler> void serialize(Handler & h, const int version)
{
h & newHostConnectionId;
}
};
struct LobbyUpdateState : public CLobbyPackToPropagate
{
LobbyState state;
bool hostChanged; // Used on client-side only
LobbyUpdateState() : hostChanged(false) {}
bool applyOnLobbyHandler(CServerHandler * handler);
void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & state;
}
};
struct LobbySetMap : public CLobbyPackToServer
{
std::shared_ptr<CMapInfo> mapInfo;
std::shared_ptr<CMapGenOptions> mapGenOpts;
LobbySetMap() : mapInfo(nullptr), mapGenOpts(nullptr) {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & mapInfo;
h & mapGenOpts;
}
};
struct LobbySetCampaign : public CLobbyPackToServer
{
std::shared_ptr<CCampaignState> ourCampaign;
LobbySetCampaign() {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & ourCampaign;
}
};
struct LobbySetCampaignMap : public CLobbyPackToServer
{
int mapId;
LobbySetCampaignMap() : mapId(-1) {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & mapId;
}
};
struct LobbySetCampaignBonus : public CLobbyPackToServer
{
int bonusId;
LobbySetCampaignBonus() : bonusId(-1) {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & bonusId;
}
};
struct LobbyChangePlayerOption : public CLobbyPackToServer
{
enum EWhat : ui8 {UNKNOWN, TOWN, HERO, BONUS};
ui8 what;
si8 direction; //-1 or +1
PlayerColor color;
LobbyChangePlayerOption() : what(UNKNOWN), direction(0), color(PlayerColor::CANNOT_DETERMINE) {}
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & what;
h & direction;
h & color;
}
};
struct LobbySetPlayer : public CLobbyPackToServer
{
PlayerColor clickedColor;
LobbySetPlayer() : clickedColor(PlayerColor::CANNOT_DETERMINE){}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & clickedColor;
}
};
struct LobbySetTurnTime : public CLobbyPackToServer
{
ui8 turnTime;
LobbySetTurnTime() : turnTime(0) {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & turnTime;
}
};
struct LobbySetDifficulty : public CLobbyPackToServer
{
ui8 difficulty;
LobbySetDifficulty() : difficulty(0) {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & difficulty;
}
};
struct LobbyForceSetPlayer : public CLobbyPackToServer
{
ui8 targetConnectedPlayer;
PlayerColor targetPlayerColor;
LobbyForceSetPlayer() : targetConnectedPlayer(-1), targetPlayerColor(PlayerColor::CANNOT_DETERMINE) {}
bool applyOnServer(CVCMIServer * srv);
template <typename Handler> void serialize(Handler & h, const int version)
{
h & targetConnectedPlayer;
h & targetPlayerColor;
}
};

195
lib/StartInfo.cpp Normal file
View File

@ -0,0 +1,195 @@
/*
* StartInfo.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 "StartInfo.h"
#include "CGeneralTextHandler.h"
#include "rmg/CMapGenOptions.h"
#include "mapping/CMapInfo.h"
PlayerSettings::PlayerSettings()
: bonus(RANDOM), castle(NONE), hero(RANDOM), heroPortrait(RANDOM), color(0), handicap(NO_HANDICAP), team(0), compOnly(false)
{
}
bool PlayerSettings::isControlledByAI() const
{
return !connectedPlayerIDs.size();
}
bool PlayerSettings::isControlledByHuman() const
{
return connectedPlayerIDs.size();
}
PlayerSettings & StartInfo::getIthPlayersSettings(PlayerColor no)
{
if(playerInfos.find(no) != playerInfos.end())
return playerInfos[no];
logGlobal->error("Cannot find info about player %s. Throwing...", no.getStr());
throw std::runtime_error("Cannot find info about player");
}
const PlayerSettings & StartInfo::getIthPlayersSettings(PlayerColor no) const
{
return const_cast<StartInfo &>(*this).getIthPlayersSettings(no);
}
PlayerSettings * StartInfo::getPlayersSettings(const ui8 connectedPlayerId)
{
for(auto & elem : playerInfos)
{
if(vstd::contains(elem.second.connectedPlayerIDs, connectedPlayerId))
return &elem.second;
}
return nullptr;
}
std::string StartInfo::getCampaignName() const
{
if(campState->camp->header.name.length())
return campState->camp->header.name;
else
return VLC->generaltexth->allTexts[508];
}
void LobbyInfo::verifyStateBeforeStart(bool ignoreNoHuman) const
{
if(!mi)
throw ExceptionMapMissing();
//there must be at least one human player before game can be started
std::map<PlayerColor, PlayerSettings>::const_iterator i;
for(i = si->playerInfos.cbegin(); i != si->playerInfos.cend(); i++)
if(i->second.isControlledByHuman())
break;
if(i == si->playerInfos.cend() && !ignoreNoHuman)
throw ExceptionNoHuman();
if(si->mapGenOptions && si->mode == StartInfo::NEW_GAME)
{
if(!si->mapGenOptions->checkOptions())
throw ExceptionNoTemplate();
}
}
bool LobbyInfo::isClientHost(int clientId) const
{
return clientId == hostClientId;
}
std::set<PlayerColor> LobbyInfo::getAllClientPlayers(int clientId)
{
std::set<PlayerColor> players;
for(auto & elem : si->playerInfos)
{
if(isClientHost(clientId) && elem.second.isControlledByAI())
players.insert(elem.first);
for(ui8 id : elem.second.connectedPlayerIDs)
{
if(vstd::contains(getConnectedPlayerIdsForClient(clientId), id))
players.insert(elem.first);
}
}
if(isClientHost(clientId))
players.insert(PlayerColor::NEUTRAL);
return players;
}
std::vector<ui8> LobbyInfo::getConnectedPlayerIdsForClient(int clientId) const
{
std::vector<ui8> ids;
for(auto & pair : playerNames)
{
if(pair.second.connection == clientId)
{
for(auto & elem : si->playerInfos)
{
if(vstd::contains(elem.second.connectedPlayerIDs, pair.first))
ids.push_back(pair.first);
}
}
}
return ids;
}
std::set<PlayerColor> LobbyInfo::clientHumanColors(int clientId)
{
std::set<PlayerColor> players;
for(auto & elem : si->playerInfos)
{
for(ui8 id : elem.second.connectedPlayerIDs)
{
if(vstd::contains(getConnectedPlayerIdsForClient(clientId), id))
{
players.insert(elem.first);
}
}
}
return players;
}
PlayerColor LobbyInfo::clientFirstColor(int clientId) const
{
for(auto & pair : si->playerInfos)
{
if(isClientColor(clientId, pair.first))
return pair.first;
}
return PlayerColor::CANNOT_DETERMINE;
}
bool LobbyInfo::isClientColor(int clientId, PlayerColor color) const
{
if(si->playerInfos.find(color) != si->playerInfos.end())
{
for(ui8 id : si->playerInfos.find(color)->second.connectedPlayerIDs)
{
if(playerNames.find(id) != playerNames.end())
{
if(playerNames.find(id)->second.connection == clientId)
return true;
}
}
}
return false;
}
ui8 LobbyInfo::clientFirstId(int clientId) const
{
for(auto & pair : playerNames)
{
if(pair.second.connection == clientId)
return pair.first;
}
return 0;
}
PlayerInfo & LobbyInfo::getPlayerInfo(int color)
{
return mi->mapHeader->players[color];
}
TeamID LobbyInfo::getPlayerTeamId(PlayerColor color)
{
if(color < PlayerColor::PLAYER_LIMIT)
return getPlayerInfo(color.getNum()).team;
else
return TeamID::NO_TEAM;
}

View File

@ -13,9 +13,13 @@
class CMapGenOptions;
class CCampaignState;
class CMapInfo;
struct PlayerInfo;
class PlayerColor;
class SharedMemory;
/// Struct which describes the name, the color, the starting bonus of a player
struct PlayerSettings
struct DLL_LINKAGE PlayerSettings
{
enum { PLAYER_AI = 0 }; // for use in playerID
@ -39,7 +43,7 @@ struct PlayerSettings
TeamID team;
std::string name;
ui8 playerID; //0 - AI, non-0 serves as player id
std::set<ui8> connectedPlayerIDs; //Empty - AI, or connectrd player ids
bool compOnly; //true if this player is a computer only player; required for RMG
template <typename Handler>
void serialize(Handler &h, const int version)
@ -52,20 +56,30 @@ struct PlayerSettings
h & color;
h & handicap;
h & name;
h & playerID;
if(version < 787)
{
ui8 oldConnectedId = 0;
h & oldConnectedId;
if(oldConnectedId)
{
connectedPlayerIDs.insert(oldConnectedId);
}
}
else
{
h & connectedPlayerIDs;
}
h & team;
h & compOnly;
}
PlayerSettings() : bonus(RANDOM), castle(NONE), hero(RANDOM), heroPortrait(RANDOM),
color(0), handicap(NO_HANDICAP), team(0), playerID(PLAYER_AI), compOnly(false)
{
}
PlayerSettings();
bool isControlledByAI() const;
bool isControlledByHuman() const;
};
/// Struct which describes the difficulty, the turn time,.. of a heroes match.
struct StartInfo
struct DLL_LINKAGE StartInfo
{
enum EMode {NEW_GAME, LOAD_GAME, CAMPAIGN, INVALID = 255};
@ -85,26 +99,12 @@ struct StartInfo
std::shared_ptr<CCampaignState> campState;
PlayerSettings & getIthPlayersSettings(PlayerColor no)
{
if(playerInfos.find(no) != playerInfos.end())
return playerInfos[no];
logGlobal->error("Cannot find info about player %s. Throwing...", no.getStr());
throw std::runtime_error("Cannot find info about player");
}
const PlayerSettings & getIthPlayersSettings(PlayerColor no) const
{
return const_cast<StartInfo&>(*this).getIthPlayersSettings(no);
}
PlayerSettings & getIthPlayersSettings(PlayerColor no);
const PlayerSettings & getIthPlayersSettings(PlayerColor no) const;
PlayerSettings * getPlayersSettings(const ui8 connectedPlayerId);
PlayerSettings *getPlayersSettings(const ui8 nameID)
{
for(auto it=playerInfos.begin(); it != playerInfos.end(); ++it)
if(it->second.playerID == nameID)
return &it->second;
return nullptr;
}
// TODO: Must be client-side
std::string getCampaignName() const;
template <typename Handler>
void serialize(Handler &h, const int version)
@ -127,3 +127,66 @@ struct StartInfo
}
};
struct ClientPlayer
{
int connection;
std::string name;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & connection;
h & name;
}
};
struct LobbyState
{
std::shared_ptr<StartInfo> si;
std::shared_ptr<CMapInfo> mi;
std::map<ui8, ClientPlayer> playerNames; // id of player <-> player name; 0 is reserved as ID of AI "players"
int hostClientId;
// TODO: Campaign-only and we don't really need either of them.
// Before start both go into CCampaignState that is part of StartInfo
int campaignMap;
int campaignBonus;
LobbyState() : si(new StartInfo()), hostClientId(-1), campaignMap(-1), campaignBonus(-1) {}
template <typename Handler> void serialize(Handler &h, const int version)
{
h & si;
h & mi;
h & playerNames;
h & hostClientId;
h & campaignMap;
h & campaignBonus;
}
};
struct DLL_LINKAGE LobbyInfo : public LobbyState
{
boost::mutex stateMutex;
std::string uuid;
std::shared_ptr<SharedMemory> shm;
LobbyInfo() {}
void verifyStateBeforeStart(bool ignoreNoHuman = false) const;
bool isClientHost(int clientId) const;
std::set<PlayerColor> getAllClientPlayers(int clientId);
std::vector<ui8> getConnectedPlayerIdsForClient(int clientId) const;
// Helpers for lobby state access
std::set<PlayerColor> clientHumanColors(int clientId);
PlayerColor clientFirstColor(int clientId) const;
bool isClientColor(int clientId, PlayerColor color) const;
ui8 clientFirstId(int clientId) const; // Used by chat only!
PlayerInfo & getPlayerInfo(int color);
TeamID getPlayerTeamId(PlayerColor color);
};
class ExceptionMapMissing : public std::exception {};
class ExceptionNoHuman : public std::exception {};
class ExceptionNoTemplate : public std::exception {};

View File

@ -39,6 +39,8 @@ public:
class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet, public spells::Caster
{
// We serialize heroes into JSON for crossover
friend class CCampaignState;
public:
//////////////////////////////////////////////////////////////////////////

View File

@ -468,7 +468,7 @@ void CGEvent::onHeroVisit( const CGHeroInstance * h ) const
{
if(!(availableFor & (1 << h->tempOwner.getNum())))
return;
if(cb->getPlayerSettings(h->tempOwner)->playerID)
if(cb->getPlayerSettings(h->tempOwner)->isControlledByHuman())
{
if(humanActivate)
activated(h);

View File

@ -1350,6 +1350,12 @@ void CGTownInstance::afterAddToMap(CMap * map)
map->towns.push_back(this);
}
void CGTownInstance::reset()
{
CGTownInstance::merchantArtifacts.clear();
CGTownInstance::universitySkills.clear();
}
void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler)
{
CGObjectInstance::serializeJsonOwner(handler);

View File

@ -291,6 +291,7 @@ public:
std::string getObjectName() const override;
void afterAddToMap(CMap * map) override;
static void reset();
protected:
void setPropertyDer(ui8 what, ui32 val) override;
void serializeJsonOptions(JsonSerializeFormat & handler) override;

View File

@ -23,8 +23,12 @@
#include "../CHeroHandler.h"
#include "CMapService.h"
#include "CMap.h"
#include "CMapInfo.h"
namespace fs = boost::filesystem;
// For hero crossover
#include "serializer/CSerializer.h"
#include "serializer/JsonDeserializer.h"
#include "serializer/JsonSerializer.h"
CCampaignHeader::CCampaignHeader()
: version(0), mapVersion(0), difficultyChoosenByPlayer(0), music(0), filename(), loadFromLod(0)
@ -373,25 +377,42 @@ bool CCampaignScenario::isNotVoid() const
return mapName.size() > 0;
}
const CGHeroInstance * CCampaignScenario::strongestHero( PlayerColor owner ) const
const CGHeroInstance * CCampaignScenario::strongestHero(PlayerColor owner)
{
using boost::adaptors::filtered;
std::function<bool(CGHeroInstance*)> isOwned = [=](const CGHeroInstance *h){ return h->tempOwner == owner; };
auto ownedHeroes = crossoverHeroes | filtered(isOwned);
std::function<bool(JsonNode & node)> isOwned = [owner](JsonNode & node)
{
auto h = CCampaignState::crossoverDeserialize(node);
bool result = h->tempOwner == owner;
vstd::clear_pointer(h);
return result;
};
auto ownedHeroes = crossoverHeroes | boost::adaptors::filtered(isOwned);
auto i = vstd::maxElementByFun(ownedHeroes,
[](const CGHeroInstance * h) {return h->getHeroStrength();});
return i == ownedHeroes.end() ? nullptr : *i;
auto i = vstd::maxElementByFun(ownedHeroes, [](JsonNode & node)
{
auto h = CCampaignState::crossoverDeserialize(node);
double result = h->getHeroStrength();
vstd::clear_pointer(h);
return result;
});
return i == ownedHeroes.end() ? nullptr : CCampaignState::crossoverDeserialize(*i);
}
std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes() const
std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes()
{
std::vector<CGHeroInstance *> lostCrossoverHeroes;
if(conquered)
{
for(auto hero : placedCrossoverHeroes)
for(auto node2 : placedCrossoverHeroes)
{
auto it = range::find_if(crossoverHeroes, CGObjectInstanceBySubIdFinder(hero));
auto hero = CCampaignState::crossoverDeserialize(node2);
auto it = range::find_if(crossoverHeroes, [hero](JsonNode node)
{
auto h = CCampaignState::crossoverDeserialize(node);
bool result = hero->subID == h->subID;
vstd::clear_pointer(h);
return result;
});
if(it == crossoverHeroes.end())
{
lostCrossoverHeroes.push_back(hero);
@ -401,9 +422,25 @@ std::vector<CGHeroInstance *> CCampaignScenario::getLostCrossoverHeroes() const
return lostCrossoverHeroes;
}
void CCampaignState::setCurrentMapAsConquered( const std::vector<CGHeroInstance*> & heroes )
std::vector<JsonNode> CCampaignScenario::update787(std::vector<CGHeroInstance *> & heroes)
{
camp->scenarios[*currentMap].crossoverHeroes = heroes;
static_assert(MINIMAL_SERIALIZATION_VERSION < 787, "No longer needed CCampaignScenario::update787");
std::vector<JsonNode> heroesNew;
for(auto hero : heroes)
{
heroesNew.push_back(CCampaignState::crossoverSerialize(hero));
}
return heroesNew;
}
void CCampaignState::setCurrentMapAsConquered(const std::vector<CGHeroInstance *> & heroes)
{
camp->scenarios[*currentMap].crossoverHeroes.clear();
for(CGHeroInstance * hero : heroes)
{
camp->scenarios[*currentMap].crossoverHeroes.push_back(crossoverSerialize(hero));
}
mapsConquered.push_back(*currentMap);
mapsRemaining -= *currentMap;
camp->scenarios[*currentMap].conquered = true;
@ -449,6 +486,63 @@ CCampaignState::CCampaignState( std::unique_ptr<CCampaign> _camp ) : camp(std::m
}
}
CMap * CCampaignState::getMap(int scenarioId) const
{
// FIXME: there is certainly better way to handle maps inside campaigns
if(scenarioId == -1)
scenarioId = currentMap.get();
std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
boost::to_lower(scenarioName);
scenarioName += ':' + boost::lexical_cast<std::string>(scenarioId);
std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
CMapService mapService;
return mapService.loadMap(buffer, mapContent.size(), scenarioName).release();
}
std::unique_ptr<CMapHeader> CCampaignState::getHeader(int scenarioId) const
{
if(scenarioId == -1)
scenarioId = currentMap.get();
std::string scenarioName = camp->header.filename.substr(0, camp->header.filename.find('.'));
boost::to_lower(scenarioName);
scenarioName += ':' + boost::lexical_cast<std::string>(scenarioId);
std::string & mapContent = camp->mapPieces.find(scenarioId)->second;
auto buffer = reinterpret_cast<const ui8 *>(mapContent.data());
CMapService mapService;
return mapService.loadMapHeader(buffer, mapContent.size(), scenarioName);
}
std::shared_ptr<CMapInfo> CCampaignState::getMapInfo(int scenarioId) const
{
if(scenarioId == -1)
scenarioId = currentMap.get();
auto mapInfo = std::make_shared<CMapInfo>();
mapInfo->fileURI = camp->header.filename;
mapInfo->mapHeader = getHeader(scenarioId);
mapInfo->countPlayers();
return mapInfo;
}
JsonNode CCampaignState::crossoverSerialize(CGHeroInstance * hero)
{
JsonNode node;
JsonSerializer handler(nullptr, node);
hero->serializeJsonOptions(handler);
return node;
}
CGHeroInstance * CCampaignState::crossoverDeserialize(JsonNode & node)
{
JsonDeserializer handler(nullptr, node);
auto hero = new CGHeroInstance();
hero->ID = Obj::HERO;
hero->serializeJsonOptions(handler);
return hero;
}
std::string CCampaignHandler::prologVideoName(ui8 index)
{
JsonNode config(ResourceID(std::string("CONFIG/campaignMedia"), EResType::TEXT));

View File

@ -14,6 +14,10 @@
struct StartInfo;
class CGHeroInstance;
class CBinaryReader;
class CMap;
class CMapHeader;
class CMapInfo;
class JsonNode;
namespace CampaignVersion
{
@ -134,13 +138,15 @@ public:
CScenarioTravel travelOptions;
std::vector<HeroTypeID> keepHeroes; // contains list of heroes which should be kept for next scenario (doesn't matter if they lost)
std::vector<CGHeroInstance *> crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
std::vector<CGHeroInstance *> placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
std::vector<JsonNode> crossoverHeroes; // contains all heroes with the same state when the campaign scenario was finished
std::vector<JsonNode> placedCrossoverHeroes; // contains all placed crossover heroes defined by hero placeholders when the scenario was started
const CGHeroInstance * strongestHero(PlayerColor owner) const;
void loadPreconditionRegions(ui32 regions);
bool isNotVoid() const;
std::vector<CGHeroInstance *> getLostCrossoverHeroes() const; /// returns a list of crossover heroes which started the scenario, but didn't complete it
// FIXME: due to usage of JsonNode I can't make these methods const
const CGHeroInstance * strongestHero(PlayerColor owner);
std::vector<CGHeroInstance *> getLostCrossoverHeroes(); /// returns a list of crossover heroes which started the scenario, but didn't complete it
std::vector<JsonNode> update787(std::vector<CGHeroInstance *> & heroes);
CCampaignScenario();
@ -157,8 +163,19 @@ public:
h & prolog;
h & epilog;
h & travelOptions;
h & crossoverHeroes;
h & placedCrossoverHeroes;
if(formatVersion < 787)
{
std::vector<CGHeroInstance *> crossoverHeroesOld, placedCrossoverHeroesOld;
h & crossoverHeroesOld;
h & placedCrossoverHeroesOld;
crossoverHeroes = update787(crossoverHeroesOld);
placedCrossoverHeroes = update787(placedCrossoverHeroesOld);
}
else
{
h & crossoverHeroes;
h & placedCrossoverHeroes;
}
h & keepHeroes;
}
};
@ -192,13 +209,19 @@ public:
std::map<ui8, ui8> chosenCampaignBonuses;
//void initNewCampaign(const StartInfo &si);
void setCurrentMapAsConquered(const std::vector<CGHeroInstance*> & heroes);
boost::optional<CScenarioTravel::STravelBonus> getBonusForCurrentMap() const;
const CCampaignScenario & getCurrentScenario() const;
CCampaignScenario & getCurrentScenario();
ui8 currentBonusID() const;
CMap * getMap(int scenarioId = -1) const;
std::unique_ptr<CMapHeader> getHeader(int scenarioId = -1) const;
std::shared_ptr<CMapInfo> getMapInfo(int scenarioId = -1) const;
static JsonNode crossoverSerialize(CGHeroInstance * hero);
static CGHeroInstance * crossoverDeserialize(JsonNode & node);
CCampaignState();
CCampaignState(std::unique_ptr<CCampaign> _camp);
~CCampaignState(){};

View File

@ -184,11 +184,6 @@ bool TerrainTile::isWater() const
return terType == ETerrainType::WATER;
}
const int CMapHeader::MAP_SIZE_SMALL = 36;
const int CMapHeader::MAP_SIZE_MIDDLE = 72;
const int CMapHeader::MAP_SIZE_LARGE = 108;
const int CMapHeader::MAP_SIZE_XLARGE = 144;
void CMapHeader::setupEvents()
{
EventCondition victoryCondition(EventCondition::STANDARD_WIN);
@ -269,6 +264,8 @@ CMap::~CMap()
for(auto quest : quests)
quest.dellNull();
resetStaticData();
}
void CMap::removeBlockVisTiles(CGObjectInstance * obj, bool total)
@ -647,3 +644,11 @@ CMapEditManager * CMap::getEditManager()
if(!editManager) editManager = make_unique<CMapEditManager>(this);
return editManager.get();
}
void CMap::resetStaticData()
{
CGKeys::reset();
CGMagi::reset();
CGObelisk::reset();
CGTownInstance::reset();
}

View File

@ -288,10 +288,11 @@ class DLL_LINKAGE CMapHeader
{
void setupEvents();
public:
static const int MAP_SIZE_SMALL;
static const int MAP_SIZE_MIDDLE;
static const int MAP_SIZE_LARGE;
static const int MAP_SIZE_XLARGE;
static const int MAP_SIZE_SMALL = 36;
static const int MAP_SIZE_MIDDLE = 72;
static const int MAP_SIZE_LARGE = 108;
static const int MAP_SIZE_XLARGE = 144;
CMapHeader();
virtual ~CMapHeader();
@ -382,6 +383,8 @@ public:
/// Sets the victory/loss condition objectives ??
void checkForObjectives();
void resetStaticData();
ui32 checksum;
std::vector<Rumor> rumors;
std::vector<DisposedHero> disposedHeroes;

View File

@ -15,54 +15,23 @@
#include "../GameConstants.h"
#include "CMapService.h"
void CMapInfo::countPlayers()
{
actualHumanPlayers = playerAmnt = humanPlayers = 0;
for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
{
if(mapHeader->players[i].canHumanPlay)
{
playerAmnt++;
humanPlayers++;
}
else if(mapHeader->players[i].canComputerPlay)
{
playerAmnt++;
}
}
#include "../filesystem/Filesystem.h"
#include "../serializer/CMemorySerializer.h"
#include "../CGeneralTextHandler.h"
#include "../rmg/CMapGenOptions.h"
#include "../CCreatureHandler.h"
#include "../CHeroHandler.h"
#include "../CModHandler.h"
if(scenarioOpts)
for (auto i = scenarioOpts->playerInfos.cbegin(); i != scenarioOpts->playerInfos.cend(); i++)
if(i->second.playerID != PlayerSettings::PLAYER_AI)
actualHumanPlayers++;
}
CMapInfo::CMapInfo() : scenarioOpts(nullptr), playerAmnt(0), humanPlayers(0),
actualHumanPlayers(0), isRandomMap(false)
CMapInfo::CMapInfo()
: scenarioOptionsOfSave(nullptr), amountOfPlayersOnMap(0), amountOfHumanControllablePlayers(0), amountOfHumanPlayersInSave(0), isRandomMap(false)
{
}
#define STEAL(x) x = std::move(tmp.x)
CMapInfo::CMapInfo(CMapInfo && tmp):
scenarioOpts(nullptr), playerAmnt(0), humanPlayers(0),
actualHumanPlayers(0), isRandomMap(false)
{
std::swap(scenarioOpts, tmp.scenarioOpts);
STEAL(mapHeader);
STEAL(campaignHeader);
STEAL(fileURI);
STEAL(date);
STEAL(playerAmnt);
STEAL(humanPlayers);
STEAL(actualHumanPlayers);
STEAL(isRandomMap);
}
CMapInfo::~CMapInfo()
{
vstd::clear_pointer(scenarioOpts);
vstd::clear_pointer(scenarioOptionsOfSave);
}
void CMapInfo::mapInit(const std::string & fname)
@ -73,23 +42,143 @@ void CMapInfo::mapInit(const std::string & fname)
countPlayers();
}
void CMapInfo::saveInit(ResourceID file)
{
CLoadFile lf(*CResourceHandler::get()->getResourceName(file), MINIMAL_SERIALIZATION_VERSION);
lf.checkMagicBytes(SAVEGAME_MAGIC);
mapHeader = make_unique<CMapHeader>();
lf >> *(mapHeader.get()) >> scenarioOptionsOfSave;
fileURI = file.getName();
countPlayers();
std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file));
date = std::asctime(std::localtime(&time));
// We absolutely not need this data for lobby and server will read it from save
// FIXME: actually we don't want them in CMapHeader!
mapHeader->triggeredEvents.clear();
}
void CMapInfo::campaignInit()
{
campaignHeader = std::unique_ptr<CCampaignHeader>(new CCampaignHeader(CCampaignHandler::getHeader(fileURI)));
}
CMapInfo & CMapInfo::operator=(CMapInfo &&tmp)
void CMapInfo::countPlayers()
{
STEAL(mapHeader);
STEAL(campaignHeader);
STEAL(scenarioOpts);
STEAL(fileURI);
STEAL(date);
STEAL(playerAmnt);
STEAL(humanPlayers);
STEAL(actualHumanPlayers);
STEAL(isRandomMap);
return *this;
for(int i=0; i<PlayerColor::PLAYER_LIMIT_I; i++)
{
if(mapHeader->players[i].canHumanPlay)
{
amountOfPlayersOnMap++;
amountOfHumanControllablePlayers++;
}
else if(mapHeader->players[i].canComputerPlay)
{
amountOfPlayersOnMap++;
}
}
if(scenarioOptionsOfSave)
for (auto i = scenarioOptionsOfSave->playerInfos.cbegin(); i != scenarioOptionsOfSave->playerInfos.cend(); i++)
if(i->second.isControlledByHuman())
amountOfHumanPlayersInSave++;
}
#undef STEAL
std::string CMapInfo::getName() const
{
if(campaignHeader && campaignHeader->name.length())
return campaignHeader->name;
else if(mapHeader && mapHeader->name.length())
return mapHeader->name;
else
return VLC->generaltexth->allTexts[508];
}
std::string CMapInfo::getNameForList() const
{
if(scenarioOptionsOfSave)
{
// TODO: this could be handled differently
std::vector<std::string> path;
boost::split(path, fileURI, boost::is_any_of("\\/"));
return path[path.size()-1];
}
else
{
return getName();
}
}
std::string CMapInfo::getDescription() const
{
if(campaignHeader)
return campaignHeader->description;
else
return mapHeader->description;
}
int CMapInfo::getMapSizeIconId() const
{
if(!mapHeader)
return 4;
switch(mapHeader->width)
{
case CMapHeader::MAP_SIZE_SMALL:
return 0;
case CMapHeader::MAP_SIZE_MIDDLE:
return 1;
case CMapHeader::MAP_SIZE_LARGE:
return 2;
case CMapHeader::MAP_SIZE_XLARGE:
return 3;
default:
return 4;
}
}
std::pair<int, int> CMapInfo::getMapSizeFormatIconId() const
{
int frame = -1, group = 0;
switch(mapHeader->version)
{
case EMapFormat::ROE:
frame = 0;
break;
case EMapFormat::AB:
frame = 1;
break;
case EMapFormat::SOD:
frame = 2;
break;
case EMapFormat::WOG:
frame = 3;
break;
case EMapFormat::VCMI:
frame = 0;
group = 1;
break;
default:
// Unknown version. Be safe and ignore that map
//logGlobal->warn("Warning: %s has wrong version!", currentItem->fileURI);
break;
}
return std::make_pair(frame, group);
}
std::string CMapInfo::getMapSizeName() const
{
switch(mapHeader->width)
{
case CMapHeader::MAP_SIZE_SMALL:
return "S";
case CMapHeader::MAP_SIZE_MIDDLE:
return "M";
case CMapHeader::MAP_SIZE_LARGE:
return "L";
case CMapHeader::MAP_SIZE_XLARGE:
return "XL";
default:
return "C";
}
}

View File

@ -31,34 +31,41 @@ class DLL_LINKAGE CMapInfo
public:
std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
std::unique_ptr<CCampaignHeader> campaignHeader; //may be nullptr if scenario
StartInfo * scenarioOpts; //options with which scenario has been started (used only with saved games)
StartInfo * scenarioOptionsOfSave; // Options with which scenario has been started (used only with saved games)
std::string fileURI;
std::string date;
int playerAmnt; //players in map
int humanPlayers; //players ALLOWED to be controlled by human
int actualHumanPlayers; // >1 if multiplayer game
bool isRandomMap; // true if the map will be created randomly, false if not
int amountOfPlayersOnMap;
int amountOfHumanControllablePlayers;
int amountOfHumanPlayersInSave;
bool isRandomMap;
CMapInfo();
CMapInfo(CMapInfo && tmp);
virtual ~CMapInfo();
CMapInfo &operator=(CMapInfo &&other);
void mapInit(const std::string & fname);
void saveInit(ResourceID file);
void campaignInit();
void countPlayers();
// TODO: Those must be on client-side
std::string getName() const;
std::string getNameForList() const;
std::string getDescription() const;
int getMapSizeIconId() const;
std::pair<int, int> getMapSizeFormatIconId() const;
std::string getMapSizeName() const;
template <typename Handler> void serialize(Handler &h, const int Version)
{
h & mapHeader;
h & campaignHeader;
h & scenarioOpts;
h & scenarioOptionsOfSave;
h & fileURI;
h & date;
h & playerAmnt;
h & humanPlayers;
h & actualHumanPlayers;
h & amountOfPlayersOnMap;
h & amountOfHumanControllablePlayers;
h & amountOfHumanPlayersInSave;
h & isRandomMap;
}
};

View File

@ -44,7 +44,7 @@ DEFINE_EXTERNAL_METHOD(registerTypesMapObjects2)
DEFINE_EXTERNAL_METHOD(registerTypesClientPacks1)
DEFINE_EXTERNAL_METHOD(registerTypesClientPacks2)
DEFINE_EXTERNAL_METHOD(registerTypesServerPacks)
DEFINE_EXTERNAL_METHOD(registerTypesPregamePacks)
DEFINE_EXTERNAL_METHOD(registerTypesLobbyPacks)
template void registerTypes<BinaryDeserializer>(BinaryDeserializer & s);
template void registerTypes<BinarySerializer>(BinarySerializer & s);

View File

@ -10,6 +10,7 @@
#pragma once
#include "../NetPacks.h"
#include "../NetPacksLobby.h"
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CPlayerState.h"
@ -234,8 +235,6 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, ChangeObjPos>();
s.template registerType<CPackForClient, PlayerEndsGame>();
s.template registerType<CPackForClient, RemoveBonus>();
s.template registerType<CPackForClient, UpdateCampaignState>();
s.template registerType<CPackForClient, PrepareForAdvancingCampaign>();
s.template registerType<CPackForClient, UpdateArtHandlerLists>();
s.template registerType<CPackForClient, UpdateMapEvents>();
s.template registerType<CPackForClient, UpdateCastleEvents>();
@ -312,16 +311,14 @@ void registerTypesClientPacks2(Serializer &s)
s.template registerType<CArtifactOperationPack, AssembledArtifact>();
s.template registerType<CArtifactOperationPack, DisassembledArtifact>();
s.template registerType<CPackForClient, SaveGame>();
s.template registerType<CPackForClient, PlayerMessage>();
s.template registerType<CPackForClient, SaveGameClient>();
s.template registerType<CPackForClient, PlayerMessageClient>();
}
template<typename Serializer>
void registerTypesServerPacks(Serializer &s)
{
s.template registerType<CPack, CPackForServer>();
s.template registerType<CPackForServer, CloseServer>();
s.template registerType<CPackForServer, LeaveGame>();
s.template registerType<CPackForServer, EndTurn>();
s.template registerType<CPackForServer, DismissHero>();
s.template registerType<CPackForServer, MoveHero>();
@ -351,23 +348,34 @@ void registerTypesServerPacks(Serializer &s)
}
template<typename Serializer>
void registerTypesPregamePacks(Serializer &s)
void registerTypesLobbyPacks(Serializer &s)
{
s.template registerType<CPack, CPackForSelectionScreen>();
s.template registerType<CPackForSelectionScreen, CPregamePackToPropagate>();
s.template registerType<CPackForSelectionScreen, CPregamePackToHost>();
s.template registerType<CPack, CPackForLobby>();
s.template registerType<CPackForLobby, CLobbyPackToPropagate>();
s.template registerType<CPackForLobby, CLobbyPackToServer>();
s.template registerType<CPregamePackToPropagate, ChatMessage>();
s.template registerType<CPregamePackToPropagate, QuitMenuWithoutStarting>();
s.template registerType<CPregamePackToPropagate, SelectMap>();
s.template registerType<CPregamePackToPropagate, UpdateStartOptions>();
s.template registerType<CPregamePackToPropagate, PregameGuiAction>();
s.template registerType<CPregamePackToPropagate, PlayerLeft>();
s.template registerType<CPregamePackToPropagate, PlayersNames>();
s.template registerType<CPregamePackToPropagate, StartWithCurrentSettings>();
// Any client can sent
s.template registerType<CLobbyPackToPropagate, LobbyClientConnected>();
s.template registerType<CLobbyPackToPropagate, LobbyClientDisconnected>();
s.template registerType<CLobbyPackToPropagate, LobbyChatMessage>();
// Only host client send
s.template registerType<CLobbyPackToPropagate, LobbyGuiAction>();
s.template registerType<CLobbyPackToPropagate, LobbyStartGame>();
s.template registerType<CLobbyPackToPropagate, LobbyChangeHost>();
// Only server send
s.template registerType<CLobbyPackToPropagate, LobbyUpdateState>();
s.template registerType<CPregamePackToHost, PlayerJoined>();
s.template registerType<CPregamePackToHost, RequestOptionsChange>();
// For client with permissions
s.template registerType<CLobbyPackToServer, LobbyChangePlayerOption>();
// Only for host client
s.template registerType<CLobbyPackToServer, LobbySetMap>();
s.template registerType<CLobbyPackToServer, LobbySetCampaign>();
s.template registerType<CLobbyPackToServer, LobbySetCampaignMap>();
s.template registerType<CLobbyPackToServer, LobbySetCampaignBonus>();
s.template registerType<CLobbyPackToServer, LobbySetPlayer>();
s.template registerType<CLobbyPackToServer, LobbySetTurnTime>();
s.template registerType<CLobbyPackToServer, LobbySetDifficulty>();
s.template registerType<CLobbyPackToServer, LobbyForceSetPlayer>();
}
template<typename Serializer>
@ -379,7 +387,7 @@ void registerTypes(Serializer &s)
registerTypesClientPacks1(s);
registerTypesClientPacks2(s);
registerTypesServerPacks(s);
registerTypesPregamePacks(s);
registerTypesLobbyPacks(s);
}
#ifndef INSTANTIATE_REGISTER_TYPES_HERE

View File

@ -1,37 +1,37 @@
/*
* TypesPregamePacks.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 "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CGameState.h"
#include "../mapping/CMap.h"
#include "../CModHandler.h"
#include "../mapObjects/CObjectHandler.h"
#include "../CCreatureHandler.h"
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"
#include "../mapObjects/CObjectClassesHandler.h"
#include "../rmg/CMapGenOptions.h"
#include "../serializer/BinaryDeserializer.h"
#include "../serializer/BinarySerializer.h"
#include "../serializer/CTypeList.h"
template void registerTypesPregamePacks<BinaryDeserializer>(BinaryDeserializer & s);
template void registerTypesPregamePacks<BinarySerializer>(BinarySerializer & s);
template void registerTypesPregamePacks<CTypeList>(CTypeList & s);
/*
* TypesLobbyPacks.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 "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CGameState.h"
#include "../mapping/CMap.h"
#include "../CModHandler.h"
#include "../mapObjects/CObjectHandler.h"
#include "../CCreatureHandler.h"
#include "../VCMI_Lib.h"
#include "../CArtHandler.h"
#include "../CHeroHandler.h"
#include "../spells/CSpellHandler.h"
#include "../CTownHandler.h"
#include "../mapping/CCampaignHandler.h"
#include "../NetPacks.h"
#include "../mapObjects/CObjectClassesHandler.h"
#include "../rmg/CMapGenOptions.h"
#include "../serializer/BinaryDeserializer.h"
#include "../serializer/BinarySerializer.h"
#include "../serializer/CTypeList.h"
template void registerTypesLobbyPacks<BinaryDeserializer>(BinaryDeserializer & s);
template void registerTypesLobbyPacks<BinarySerializer>(BinarySerializer & s);
template void registerTypesLobbyPacks<CTypeList>(CTypeList & s);

View File

@ -17,7 +17,8 @@
#include "../VCMI_Lib.h"
#include "../CTownHandler.h"
CMapGenOptions::CMapGenOptions() : width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(false),
CMapGenOptions::CMapGenOptions()
: width(CMapHeader::MAP_SIZE_MIDDLE), height(CMapHeader::MAP_SIZE_MIDDLE), hasTwoLevels(true),
playerCount(RANDOM_SIZE), teamCount(RANDOM_SIZE), compOnlyPlayerCount(RANDOM_SIZE), compOnlyTeamCount(RANDOM_SIZE), humanPlayersCount(0),
waterContent(EWaterContent::RANDOM), monsterStrength(EMonsterStrength::RANDOM), mapTemplate(nullptr)
{

View File

@ -12,7 +12,7 @@
#include "../ConstTransitivePtr.h"
#include "../GameConstants.h"
const ui32 SERIALIZATION_VERSION = 786;
const ui32 SERIALIZATION_VERSION = 787;
const ui32 MINIMAL_SERIALIZATION_VERSION = 753;
const std::string SAVEGAME_MAGIC = "VCMISVG";

View File

@ -14,9 +14,6 @@
#include "../mapping/CMap.h"
#include "../CGameState.h"
#if BOOST_VERSION >= 106600
#define BOOST_ASIO_ENABLE_OLD_SERVICES
#endif
#include <boost/asio.hpp>
using namespace boost;
@ -35,8 +32,9 @@ using namespace boost::asio::ip;
void CConnection::init()
{
boost::asio::ip::tcp::no_delay option(true);
socket->set_option(option);
socket->set_option(boost::asio::ip::tcp::no_delay(true));
socket->set_option(boost::asio::socket_base::send_buffer_size(4194304));
socket->set_option(boost::asio::socket_base::receive_buffer_size(4194304));
enableSmartPointerSerialization();
disableStackSendingByID();
@ -50,25 +48,21 @@ void CConnection::init()
connected = true;
std::string pom;
//we got connection
oser & std::string("Aiya!\n") & name & myEndianess; //identify ourselves
iser & pom & pom & contactEndianess;
logNetwork->info("Established connection with %s", pom);
wmx = new boost::mutex();
rmx = new boost::mutex();
oser & std::string("Aiya!\n") & name & uuid & myEndianess; //identify ourselves
iser & pom & pom & contactUuid & contactEndianess;
logNetwork->info("Established connection with %s. UUID: %s", pom, contactUuid);
mutexRead = std::make_shared<boost::mutex>();
mutexWrite = std::make_shared<boost::mutex>();
handler = nullptr;
receivedStop = sendStop = false;
static int cid = 1;
connectionID = cid++;
iser.fileVersion = SERIALIZATION_VERSION;
}
CConnection::CConnection(std::string host, ui16 port, std::string Name)
:iser(this), oser(this), io_service(new asio::io_service), name(Name)
CConnection::CConnection(std::string host, ui16 port, std::string Name, std::string UUID)
: iser(this), oser(this), io_service(std::make_shared<asio::io_service>()), connectionID(0), name(Name), uuid(UUID)
{
int i;
boost::system::error_code error = asio::error::host_not_found;
socket = new tcp::socket(*io_service);
socket = std::make_shared<tcp::socket>(*io_service);
tcp::resolver resolver(*io_service);
tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
if(error)
@ -114,25 +108,23 @@ connerror1:
logNetwork->error(error.message());
else
logNetwork->error("No error info. ");
delete io_service;
//delete socket;
throw std::runtime_error("Can't establish connection :(");
}
CConnection::CConnection(TSocket * Socket, std::string Name )
:iser(this), oser(this), socket(Socket),io_service(&Socket->get_io_service()), name(Name)//, send(this), rec(this)
CConnection::CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID)
: iser(this), oser(this), socket(Socket), io_service(&Socket->get_io_service()), connectionID(0), name(Name), uuid(UUID)
{
init();
}
CConnection::CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name)
: iser(this), oser(this), name(Name)//, send(this), rec(this)
CConnection::CConnection(std::shared_ptr<TAcceptor> acceptor, std::shared_ptr<boost::asio::io_service> Io_service, std::string Name, std::string UUID)
: iser(this), oser(this), connectionID(0), name(Name), uuid(UUID)
{
boost::system::error_code error = asio::error::host_not_found;
socket = new tcp::socket(*io_service);
socket = std::make_shared<tcp::socket>(*io_service);
acceptor->accept(*socket,error);
if (error)
{
logNetwork->error("Error on accepting: %s", error.message());
delete socket;
socket.reset();
throw std::runtime_error("Can't establish connection :(");
}
init();
@ -171,12 +163,7 @@ CConnection::~CConnection()
if(handler)
handler->join();
delete handler;
close();
delete io_service;
delete wmx;
delete rmx;
}
template<class T>
@ -193,7 +180,7 @@ void CConnection::close()
if(socket)
{
socket->close();
vstd::clear_pointer(socket);
socket.reset();
}
}
@ -202,11 +189,6 @@ bool CConnection::isOpen() const
return socket && connected;
}
bool CConnection::isHost() const
{
return connectionID == 1;
}
void CConnection::reportState(vstd::CLoggerBase * out)
{
out->debug("CConnection");
@ -219,19 +201,26 @@ void CConnection::reportState(vstd::CLoggerBase * out)
CPack * CConnection::retrievePack()
{
CPack *ret = nullptr;
boost::unique_lock<boost::mutex> lock(*rmx);
logNetwork->trace("Listening... ");
iser & ret;
logNetwork->trace("\treceived server message of type %s", (ret? typeid(*ret).name() : "nullptr"));
return ret;
CPack * pack = nullptr;
boost::unique_lock<boost::mutex> lock(*mutexRead);
iser & pack;
logNetwork->trace("Received CPack of type %s", (pack ? typeid(*pack).name() : "nullptr"));
if(pack == nullptr)
{
logNetwork->error("Received a nullptr CPack! You should check whether client and server ABI matches.");
}
else
{
pack->c = this->shared_from_this();
}
return pack;
}
void CConnection::sendPackToServer(const CPack &pack, PlayerColor player, ui32 requestID)
void CConnection::sendPack(const CPack * pack)
{
boost::unique_lock<boost::mutex> lock(*wmx);
logNetwork->trace("Sending to server a pack of type %s", typeid(pack).name());
oser & player & requestID & &pack; //packs has to be sent as polymorphic pointers!
boost::unique_lock<boost::mutex> lock(*mutexWrite);
logNetwork->trace("Sending a pack of type %s", typeid(*pack).name());
oser & pack;
}
void CConnection::disableStackSendingByID()
@ -254,16 +243,7 @@ void CConnection::enableSmartPointerSerialization()
iser.smartPointerSerialization = oser.smartPointerSerialization = true;
}
void CConnection::prepareForSendingHeroes()
{
iser.loadedPointers.clear();
oser.savedPointers.clear();
disableSmartVectorMemberSerialization();
enableSmartPointerSerialization();
disableStackSendingByID();
}
void CConnection::enterPregameConnectionMode()
void CConnection::enterLobbyConnectionMode()
{
iser.loadedPointers.clear();
oser.savedPointers.clear();
@ -271,6 +251,13 @@ void CConnection::enterPregameConnectionMode()
disableSmartPointerSerialization();
}
void CConnection::enterGameplayConnectionMode(CGameState * gs)
{
enableStackSendingByID();
disableSmartPointerSerialization();
addStdVecItems(gs);
}
void CConnection::disableSmartVectorMemberSerialization()
{
CSerializer::smartVectorMembersSerialization = false;
@ -283,7 +270,7 @@ void CConnection::enableSmartVectorMemberSerializatoin()
std::string CConnection::toString() const
{
boost::format fmt("Connection with %s (ID: %d)");
fmt % name % connectionID;
return fmt.str();
boost::format fmt("Connection with %s (ID: %d UUID: %s)");
fmt % name % connectionID % uuid;
return fmt.str();
}

View File

@ -47,7 +47,7 @@ typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::so
/// Main class for network communication
/// Allows establishing connection and bidirectional read-write
class DLL_LINKAGE CConnection
: public IBinaryReader, public IBinaryWriter
: public IBinaryReader, public IBinaryWriter, public std::enable_shared_from_this<CConnection>
{
CConnection();
@ -60,31 +60,31 @@ public:
BinaryDeserializer iser;
BinarySerializer oser;
boost::mutex *rmx, *wmx; // read/write mutexes
TSocket * socket;
std::shared_ptr<boost::mutex> mutexRead;
std::shared_ptr<boost::mutex> mutexWrite;
std::shared_ptr<TSocket> socket;
bool connected;
bool myEndianess, contactEndianess; //true if little endian, if endianness is different we'll have to revert received multi-byte vars
boost::asio::io_service *io_service;
std::string contactUuid;
std::shared_ptr<boost::asio::io_service> io_service;
std::string name; //who uses this connection
std::string uuid;
int connectionID;
boost::thread *handler;
std::shared_ptr<boost::thread> handler;
bool receivedStop, sendStop;
CConnection(std::string host, ui16 port, std::string Name);
CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name);
CConnection(TSocket * Socket, std::string Name); //use immediately after accepting connection into socket
CConnection(std::string host, ui16 port, std::string Name, std::string UUID);
CConnection(std::shared_ptr<TAcceptor> acceptor, std::shared_ptr<boost::asio::io_service> Io_service, std::string Name, std::string UUID);
CConnection(std::shared_ptr<TSocket> Socket, std::string Name, std::string UUID); //use immediately after accepting connection into socket
void close();
bool isOpen() const;
bool isHost() const;
template<class T>
CConnection &operator&(const T&);
virtual ~CConnection();
CPack * retrievePack(); //gets from server next pack (allocates it with new)
void sendPackToServer(const CPack &pack, PlayerColor player, ui32 requestID);
CPack * retrievePack();
void sendPack(const CPack * pack);
void disableStackSendingByID();
void enableStackSendingByID();
@ -93,8 +93,8 @@ public:
void disableSmartVectorMemberSerialization();
void enableSmartVectorMemberSerializatoin();
void prepareForSendingHeroes(); //disables sending vectorized, enables smart pointer serialization, clears saved/loaded ptr cache
void enterPregameConnectionMode();
void enterLobbyConnectionMode();
void enterGameplayConnectionMode(CGameState * gs);
std::string toString() const;

View File

@ -60,7 +60,7 @@ class DLL_LINKAGE PacketSender
{
public:
virtual ~PacketSender(){};
virtual void sendAndApply(CPackForClient * info) const = 0;
virtual void sendAndApply(CPackForClient * pack) const = 0;
virtual void complain(const std::string & problem) const = 0;
};

View File

@ -50,7 +50,6 @@
#ifndef _MSC_VER
#include <boost/thread/xtime.hpp>
#endif
extern std::atomic<bool> serverShuttingDown;
#define COMPLAIN_RET_IF(cond, txt) do {if (cond){complain(txt); return;}} while(0)
#define COMPLAIN_RET_FALSE_IF(cond, txt) do {if (cond){complain(txt); return false;}} while(0)
@ -62,7 +61,7 @@ class ServerSpellCastEnvironment : public SpellCastEnvironment
public:
ServerSpellCastEnvironment(CGameHandler * gh);
~ServerSpellCastEnvironment() = default;
void sendAndApply(CPackForClient * info) const override;
void sendAndApply(CPackForClient * pack) const override;
CRandomGenerator & getRandomGenerator() const override;
void complain(const std::string & problem) const override;
const CMap * getMap() const override;
@ -177,7 +176,7 @@ template <typename T> class CApplyOnGH;
class CBaseForGHApply
{
public:
virtual bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const =0;
virtual bool applyOnGH(CGameHandler * gh, void * pack) const =0;
virtual ~CBaseForGHApply(){}
template<typename U> static CBaseForGHApply *getApplier(const U * t=nullptr)
{
@ -188,11 +187,9 @@ public:
template <typename T> class CApplyOnGH : public CBaseForGHApply
{
public:
bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const override
bool applyOnGH(CGameHandler * gh, void * pack) const override
{
T *ptr = static_cast<T*>(pack);
ptr->c = c;
ptr->player = player;
try
{
return ptr->applyGh(gh);
@ -212,7 +209,7 @@ template <>
class CApplyOnGH<CPack> : public CBaseForGHApply
{
public:
bool applyOnGH(CGameHandler *gh, CConnection *c, void *pack, PlayerColor player) const override
bool applyOnGH(CGameHandler * gh, void * pack) const override
{
logGlobal->error("Cannot apply on GH plain CPack!");
assert(0);
@ -220,8 +217,6 @@ public:
}
};
static CApplier<CBaseForGHApply> *applier = nullptr;
static inline double distance(int3 a, int3 b)
{
return std::sqrt((double)(a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
@ -1194,114 +1189,64 @@ void CGameHandler::applyBattleEffects(BattleAttack & bat, std::shared_ptr<battle
fireShield.push_back(std::make_pair(def, fireShieldDamage));
}
}
void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &c)
{
setThreadName("CGameHandler::handleConnection");
auto handleDisconnection = [&](const std::exception & e)
void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
{
for(auto playerConns : connections)
{
boost::unique_lock<boost::mutex> lock(*c.wmx);
assert(!c.connected); //make sure that connection has been marked as broken
logGlobal->error(e.what());
conns -= &c;
for(auto playerConn : connections)
for(auto conn : playerConns.second)
{
if(!serverShuttingDown && playerConn.second == &c)
if(lobby->state != EServerState::SHUTDOWN && conn == c)
{
vstd::erase_if_present(playerConns.second, conn);
if(playerConns.second.size())
continue;
PlayerCheated pc;
pc.player = playerConn.first;
pc.player = playerConns.first;
pc.losingCheatCode = true;
sendAndApply(&pc);
checkVictoryLossConditionsForPlayer(playerConn.first);
checkVictoryLossConditionsForPlayer(playerConns.first);
}
}
}
}
void CGameHandler::handleReceivedPack(CPackForServer * pack)
{
//prepare struct informing that action was applied
auto sendPackageResponse = [&](bool succesfullyApplied)
{
PackageApplied applied;
applied.player = pack->player;
applied.result = succesfullyApplied;
applied.packType = typeList.getTypeID(pack);
applied.requestID = pack->requestID;
pack->c->sendPack(&applied);
};
try
CBaseForGHApply * apply = applier->getApplier(typeList.getTypeID(pack)); //and appropriate applier object
if(isBlockedByQueries(pack, pack->player))
{
while(1)//server should never shut connection first //was: while(!end2)
{
CPack *pack = nullptr;
PlayerColor player = PlayerColor::NEUTRAL;
si32 requestID = -999;
int packType = 0;
{
boost::unique_lock<boost::mutex> lock(*c.rmx);
if(!c.connected)
throw clientDisconnectedException();
c >> player >> requestID >> pack; //get the package
if (!pack)
{
logGlobal->error("Received a null package marked as request %d from player %d", requestID, player);
}
else
{
packType = typeList.getTypeID(pack); //get the id of type
logGlobal->trace("Received client message (request %d by player %d (%s)) of type with ID=%d (%s).\n",
requestID, player, player.getStr(), packType, typeid(*pack).name());
}
}
//prepare struct informing that action was applied
auto sendPackageResponse = [&](bool succesfullyApplied)
{
//dont reply to disconnected client
//TODO: this must be implemented as option of CPackForServer
if(dynamic_cast<LeaveGame *>(pack) || dynamic_cast<CloseServer *>(pack))
return;
PackageApplied applied;
applied.player = player;
applied.result = succesfullyApplied;
applied.packType = packType;
applied.requestID = requestID;
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &applied;
};
CBaseForGHApply *apply = applier->getApplier(packType); //and appropriate applier object
if(isBlockedByQueries(pack, player))
{
sendPackageResponse(false);
}
else if (apply)
{
const bool result = apply->applyOnGH(this, &c, pack, player);
if (result)
logGlobal->trace("Message %s successfully applied!", typeid(*pack).name());
else
complain((boost::format("Got false in applying %s... that request must have been fishy!")
% typeid(*pack).name()).str());
sendPackageResponse(true);
}
else
{
logGlobal->error("Message cannot be applied, cannot find applier (unregistered type)!");
sendPackageResponse(false);
}
vstd::clear_pointer(pack);
}
sendPackageResponse(false);
}
catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
else if(apply)
{
handleDisconnection(e);
const bool result = apply->applyOnGH(this, pack);
if(result)
logGlobal->trace("Message %s successfully applied!", typeid(*pack).name());
else
complain((boost::format("Got false in applying %s... that request must have been fishy!")
% typeid(*pack).name()).str());
sendPackageResponse(true);
}
catch(clientDisconnectedException & e)
else
{
handleDisconnection(e);
}
catch(...)
{
serverShuttingDown = true;
handleException();
throw;
logGlobal->error("Message cannot be applied, cannot find applier (unregistered type)!");
sendPackageResponse(false);
}
logGlobal->error("Ended handling connection");
vstd::clear_pointer(pack);
}
int CGameHandler::moveStack(int stack, BattleHex dest)
@ -1569,12 +1514,12 @@ int CGameHandler::moveStack(int stack, BattleHex dest)
return ret;
}
CGameHandler::CGameHandler()
CGameHandler::CGameHandler(CVCMIServer * lobby)
: lobby(lobby)
{
QID = 1;
//gs = nullptr;
IObjectInterface::cb = this;
applier = new CApplier<CBaseForGHApply>();
applier = std::make_shared<CApplier<CBaseForGHApply>>();
registerTypesServerPacks(*applier);
visitObjectAfterVictory = false;
@ -1584,8 +1529,6 @@ CGameHandler::CGameHandler()
CGameHandler::~CGameHandler()
{
delete spellEnv;
delete applier;
applier = nullptr;
delete gs;
}
@ -1988,16 +1931,9 @@ void CGameHandler::run(bool resume)
LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
using namespace boost::posix_time;
for (CConnection *cc : conns)
for (auto cc : lobby->connections)
{
if (!resume)
{
(*cc) << gs->initialOpts; // gs->scenarioOps
}
std::set<PlayerColor> players;
(*cc) >> players; //how many players will be handled at that client
auto players = lobby->getAllClientPlayers(cc->connectionID);
std::stringstream sbuffer;
sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
for (PlayerColor color : players)
@ -2005,30 +1941,15 @@ void CGameHandler::run(bool resume)
sbuffer << color << " ";
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
if(!color.isSpectator()) // there can be more than one spectator
connections[color] = cc;
connections[color].insert(cc);
}
}
logGlobal->info(sbuffer.str());
cc->addStdVecItems(gs);
cc->enableStackSendingByID();
cc->disableSmartPointerSerialization();
}
for (auto & elem : conns)
{
std::set<PlayerColor> pom;
for (auto j = connections.cbegin(); j!=connections.cend();j++)
if (j->second == elem)
pom.insert(j->first);
boost::thread(std::bind(&CGameHandler::handleConnection,this,pom,std::ref(*elem)));
}
auto playerTurnOrder = generatePlayerTurnOrder();
while(!serverShuttingDown)
while(lobby->state == EServerState::GAMEPLAY)
{
if (!resume) newTurn();
@ -2069,12 +1990,14 @@ void CGameHandler::run(bool resume)
//wait till turn is done
boost::unique_lock<boost::mutex> lock(states.mx);
while(states.players.at(playerColor).makingTurn && !serverShuttingDown)
while(states.players.at(playerColor).makingTurn && lobby->state == EServerState::GAMEPLAY)
{
static time_duration p = milliseconds(100);
states.cv.timed_wait(lock, p);
}
}
if(lobby->state != EServerState::GAMEPLAY)
break;
}
}
//additional check that game is not finished
@ -2084,11 +2007,9 @@ void CGameHandler::run(bool resume)
if (gs->players[player].status == EPlayerStatus::INGAME)
activePlayer = true;
}
if (!activePlayer)
serverShuttingDown = true;
if(!activePlayer)
lobby->state = EServerState::GAMEPLAY_ENDED;
}
while(conns.size() && (*conns.begin())->isOpen())
boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
}
std::list<PlayerColor> CGameHandler::generatePlayerTurnOrder() const
@ -2619,12 +2540,12 @@ void CGameHandler::changeSpells(const CGHeroInstance * hero, bool give, const st
sendAndApply(&cs);
}
void CGameHandler::sendMessageTo(CConnection &c, const std::string &message)
void CGameHandler::sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message)
{
SystemMessage sm;
sm.text = message;
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &sm;
boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
*(c.get()) << &sm;
}
void CGameHandler::giveHeroBonus(GiveBonus * bonus)
@ -2773,59 +2694,59 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
}
}
void CGameHandler::sendToAllClients(CPackForClient * info)
void CGameHandler::sendToAllClients(CPackForClient * pack)
{
logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
for (auto & elem : conns)
logNetwork->trace("\tSending to all clients: %s", typeid(*pack).name());
for (auto c : lobby->connections)
{
if(!elem->isOpen())
if(!c->isOpen())
continue;
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
*elem << info;
c->sendPack(pack);
}
}
void CGameHandler::sendAndApply(CPackForClient * info)
void CGameHandler::sendAndApply(CPackForClient * pack)
{
sendToAllClients(info);
gs->apply(info);
sendToAllClients(pack);
gs->apply(pack);
logNetwork->trace("\tApplied on gs: %s", typeid(*pack).name());
}
void CGameHandler::applyAndSend(CPackForClient * info)
void CGameHandler::applyAndSend(CPackForClient * pack)
{
gs->apply(info);
sendToAllClients(info);
gs->apply(pack);
sendToAllClients(pack);
}
void CGameHandler::sendAndApply(CGarrisonOperationPack * info)
void CGameHandler::sendAndApply(CGarrisonOperationPack * pack)
{
sendAndApply(static_cast<CPackForClient*>(info));
sendAndApply(static_cast<CPackForClient *>(pack));
checkVictoryLossConditionsForAll();
}
void CGameHandler::sendAndApply(SetResources * info)
void CGameHandler::sendAndApply(SetResources * pack)
{
sendAndApply(static_cast<CPackForClient*>(info));
checkVictoryLossConditionsForPlayer(info->player);
sendAndApply(static_cast<CPackForClient *>(pack));
checkVictoryLossConditionsForPlayer(pack->player);
}
void CGameHandler::sendAndApply(NewStructures * info)
void CGameHandler::sendAndApply(NewStructures * pack)
{
sendAndApply(static_cast<CPackForClient*>(info));
checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
sendAndApply(static_cast<CPackForClient *>(pack));
checkVictoryLossConditionsForPlayer(getTown(pack->tid)->tempOwner);
}
void CGameHandler::save(const std::string & filename)
{
logGlobal->info("Saving to %s", filename);
logGlobal->info("Loading from %s", filename);
const auto stem = FileInfo::GetPathStem(filename);
const auto savefname = stem.to_string() + ".vsgm1";
CResourceHandler::get("local")->createResource(savefname);
{
logGlobal->info("Ordering clients to serialize...");
SaveGame sg(savefname);
SaveGameClient sg(savefname);
sendToAllClients(&sg);
}
@ -2845,34 +2766,26 @@ void CGameHandler::save(const std::string & filename)
}
}
void CGameHandler::close()
void CGameHandler::load(const std::string & filename)
{
logGlobal->info("We have been requested to close.");
serverShuttingDown = true;
logGlobal->info("Loading from %s", filename);
const auto stem = FileInfo::GetPathStem(filename);
for (auto & elem : conns)
try
{
if(!elem->isOpen())
continue;
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
elem->close();
elem->connected = false;
}
}
void CGameHandler::playerLeftGame(int cid)
{
for (auto & elem : conns)
{
if(elem->isOpen() && elem->connectionID == cid)
{
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
elem->close();
elem->connected = false;
break;
CLoadFile lf(*CResourceHandler::get("local")->getResourceName(ResourceID(stem.to_string(), EResType::SERVER_SAVEGAME)), MINIMAL_SERIALIZATION_VERSION);
loadCommonState(lf);
logGlobal->info("Loading server state");
lf >> *this;
}
logGlobal->info("Game has been successfully loaded!");
}
catch(std::exception &e)
{
logGlobal->error("Failed to load game: %s", e.what());
}
gs->updateOnLoad(lobby->si.get());
}
bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
@ -3014,11 +2927,11 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
return true;
}
PlayerColor CGameHandler::getPlayerAt(CConnection *c) const
PlayerColor CGameHandler::getPlayerAt(std::shared_ptr<CConnection> c) const
{
std::set<PlayerColor> all;
for (auto i=connections.cbegin(); i!=connections.cend(); i++)
if (i->second == c)
if(vstd::contains(i->second, c))
all.insert(i->first);
switch(all.size())
@ -4567,7 +4480,7 @@ bool CGameHandler::makeBattleAction(BattleAction &ba)
void CGameHandler::playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj)
{
bool cheated = true;
PlayerMessage temp_message(player, message, ObjectInstanceID(-1)); // don't inform other client on selected object
PlayerMessageClient temp_message(player, message);
sendAndApply(&temp_message);
std::vector<std::string> cheat;
@ -5304,51 +5217,9 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
}
}
if (p->human)
if(p->human)
{
serverShuttingDown = true;
if (gs->scenarioOps->campState)
{
std::vector<CGHeroInstance *> crossoverHeroes;
for (CGHeroInstance * hero : gs->map->heroesOnMap)
{
if (hero->tempOwner == player)
{
// keep all heroes from the winning player
crossoverHeroes.push_back(hero);
}
else if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(hero->subID)))
{
// keep hero whether lost or won (like Xeron in AB campaign)
crossoverHeroes.push_back(hero);
}
}
// keep lost heroes which are in heroes pool
for (auto & heroPair : gs->hpool.heroesPool)
{
if (vstd::contains(gs->scenarioOps->campState->getCurrentScenario().keepHeroes, HeroTypeID(heroPair.first)))
{
crossoverHeroes.push_back(heroPair.second.get());
}
}
gs->scenarioOps->campState->setCurrentMapAsConquered(crossoverHeroes);
//Request clients to change connection mode
PrepareForAdvancingCampaign pfac;
sendAndApply(&pfac);
//Change connection mode
if (getPlayer(player)->human && getStartInfo()->campState)
{
for (auto connection : conns)
connection->prepareForSendingHeroes();
}
UpdateCampaignState ucs;
ucs.camp = gs->scenarioOps->campState;
sendAndApply(&ucs);
}
lobby->state = EServerState::GAMEPLAY_ENDED;
}
}
else
@ -6856,9 +6727,9 @@ ServerSpellCastEnvironment::ServerSpellCastEnvironment(CGameHandler * gh): gh(gh
}
void ServerSpellCastEnvironment::sendAndApply(CPackForClient * info) const
void ServerSpellCastEnvironment::sendAndApply(CPackForClient * pack) const
{
gh->sendAndApply(info);
gh->sendAndApply(pack);
}
CRandomGenerator & ServerSpellCastEnvironment::getRandomGenerator() const

View File

@ -30,6 +30,9 @@ class IMarket;
class SpellCastEnvironment;
template<typename T> class CApplier;
class CBaseForGHApply;
struct PlayerStatus
{
bool makingTurn;
@ -74,6 +77,8 @@ struct CasualtiesAfterBattle
class CGameHandler : public IGameCallback, CBattleInfoCallback
{
CVCMIServer * lobby;
std::shared_ptr<CApplier<CBaseForGHApply>> applier;
public:
using FireShieldInfo = std::vector<std::pair<const CStack *, int64_t>>;
//use enums as parameters, because doMove(sth, true, false, true) is not readable
@ -81,9 +86,8 @@ public:
enum EVisitDest {VISIT_DEST, DONT_VISIT_DEST};
enum ELEaveTile {LEAVING_TILE, REMAINING_ON_TILE};
std::map<PlayerColor, CConnection*> connections; //player color -> connection to client with interface of that player
std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player
PlayerStatuses states; //player color -> player state
std::set<CConnection*> conns;
//queries stuff
boost::recursive_mutex gsm;
@ -111,7 +115,7 @@ public:
void setupBattle(int3 tile, const CArmedInstance *armies[2], const CGHeroInstance *heroes[2], bool creatureBank, const CGTownInstance *town);
void setBattleResult(BattleResult::EResult resultType, int victoriusSide);
CGameHandler();
CGameHandler(CVCMIServer * lobby);
~CGameHandler();
//////////////////////////////////////////////////////////////////////////
@ -189,8 +193,9 @@ public:
void commitPackage(CPackForClient *pack) override;
void init(StartInfo *si);
void handleConnection(std::set<PlayerColor> players, CConnection &c);
PlayerColor getPlayerAt(CConnection *c) const;
void handleClientDisconnection(std::shared_ptr<CConnection> c);
void handleReceivedPack(CPackForServer * pack);
PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;
void playerMessage(PlayerColor player, const std::string &message, ObjectInstanceID currObj);
void updateGateState();
@ -225,8 +230,8 @@ public:
bool disbandCreature( ObjectInstanceID id, SlotID pos );
bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player);
void save(const std::string &fname);
void close();
void playerLeftGame(int cid);
void load(const std::string &fname);
void handleTimeEvents();
void handleTownEvents(CGTownInstance *town, NewTurn &n);
bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true
@ -248,13 +253,13 @@ public:
}
void sendMessageToAll(const std::string &message);
void sendMessageTo(CConnection &c, const std::string &message);
void sendToAllClients(CPackForClient * info);
void sendAndApply(CPackForClient * info) override;
void applyAndSend(CPackForClient * info);
void sendAndApply(CGarrisonOperationPack * info);
void sendAndApply(SetResources * info);
void sendAndApply(NewStructures * info);
void sendMessageTo(std::shared_ptr<CConnection> c, const std::string &message);
void sendToAllClients(CPackForClient * pack);
void sendAndApply(CPackForClient * pack) override;
void applyAndSend(CPackForClient * pack);
void sendAndApply(CGarrisonOperationPack * pack);
void sendAndApply(SetResources * pack);
void sendAndApply(NewStructures * pack);
struct FinishingBattleHelper
{
@ -308,10 +313,6 @@ private:
void checkVictoryLossConditionsForAll();
};
class clientDisconnectedException : public std::exception
{
};
class ExceptionNotAllowedAction : public std::exception
{

View File

@ -8,6 +8,7 @@ set(server_SRCS
CQuery.cpp
CVCMIServer.cpp
NetPacksServer.cpp
NetPacksLobbyServer.cpp
)
set(server_HEADERS

File diff suppressed because it is too large Load Diff

View File

@ -1,112 +1,105 @@
/*
* CVCMIServer.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 <boost/program_options.hpp>
class CMapInfo;
class CConnection;
struct CPackForSelectionScreen;
class CGameHandler;
struct SharedMemory;
namespace boost
{
namespace asio
{
namespace ip
{
class tcp;
}
#if BOOST_VERSION >= 106600 // Boost version >= 1.66
class io_context;
typedef io_context io_service;
#else
class io_service;
#endif
template <typename Protocol> class stream_socket_service;
template <typename Protocol,typename StreamSocketService>
class basic_stream_socket;
template <typename Protocol> class socket_acceptor_service;
template <typename Protocol,typename SocketAcceptorService>
class basic_socket_acceptor;
}
};
typedef boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::socket_acceptor_service<boost::asio::ip::tcp> > TAcceptor;
typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::stream_socket_service<boost::asio::ip::tcp> > TSocket;
class CVCMIServer
{
ui16 port;
boost::asio::io_service *io;
TAcceptor * acceptor;
SharedMemory * shared;
CConnection *firstConnection;
public:
CVCMIServer();
~CVCMIServer();
void start();
std::shared_ptr<CGameHandler> initGhFromHostingConnection(CConnection &c);
void newGame();
void loadGame();
void newPregame();
#ifdef VCMI_ANDROID
static void create();
#endif
};
struct StartInfo;
class CPregameServer
{
public:
CConnection *host;
int listeningThreads;
std::set<CConnection *> connections;
std::list<CPackForSelectionScreen*> toAnnounce;
boost::recursive_mutex mx;
TAcceptor *acceptor;
TSocket *upcomingConnection;
const CMapInfo *curmap;
StartInfo *curStartInfo;
CPregameServer(CConnection *Host, TAcceptor *Acceptor = nullptr);
~CPregameServer();
void run();
void processPack(CPackForSelectionScreen * pack);
void handleConnection(CConnection *cpc);
void connectionAccepted(const boost::system::error_code& ec);
void initConnection(CConnection *c);
void start_async_accept();
enum { INVALID, RUNNING, ENDING_WITHOUT_START, ENDING_AND_STARTING_GAME
} state;
void announceTxt(const std::string &txt, const std::string &playerName = "system");
void announcePack(const CPackForSelectionScreen &pack);
void sendPack(CConnection * pc, const CPackForSelectionScreen & pack);
void startListeningThread(CConnection * pc);
};
extern boost::program_options::variables_map cmdLineOptions;
/*
* CVCMIServer.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 "../lib/serializer/Connection.h"
#include "../lib/StartInfo.h"
#include <boost/program_options.hpp>
class CMapInfo;
struct CPackForLobby;
class CGameHandler;
struct SharedMemory;
struct StartInfo;
struct LobbyInfo;
class PlayerSettings;
class PlayerColor;
template<typename T> class CApplier;
class CBaseForServerApply;
class CBaseForGHApply;
enum class EServerState : ui8
{
LOBBY,
GAMEPLAY,
GAMEPLAY_ENDED,
SHUTDOWN
};
class CVCMIServer : public LobbyInfo
{
std::atomic<bool> restartGameplay; // FIXME: this is just a hack
std::shared_ptr<boost::asio::io_service> io;
std::shared_ptr<TAcceptor> acceptor;
std::shared_ptr<TSocket> upcomingConnection;
std::list<CPackForLobby *> announceQueue;
boost::recursive_mutex mx;
std::shared_ptr<CApplier<CBaseForServerApply>> applier;
public:
std::shared_ptr<CGameHandler> gh;
std::atomic<EServerState> state;
ui16 port;
boost::program_options::variables_map cmdLineOptions;
std::set<std::shared_ptr<CConnection>> connections;
std::atomic<int> currentClientId;
std::atomic<ui8> currentPlayerId;
std::shared_ptr<CConnection> hostClient;
CVCMIServer(boost::program_options::variables_map & opts);
~CVCMIServer();
void run();
void prepareToStartGame();
void startGameImmidiately();
void startAsyncAccept();
void connectionAccepted(const boost::system::error_code & ec);
void threadHandleClient(std::shared_ptr<CConnection> c);
void threadAnnounceLobby();
void handleReceivedPack(CPackForLobby * pack);
void announcePack(CPackForLobby * pack);
bool passHost(int toConnectionId);
void announceTxt(const std::string & txt, const std::string & playerName = "system");
void addToAnnounceQueue(CPackForLobby * pack);
void setPlayerConnectedId(PlayerSettings & pset, ui8 player) const;
void updateStartInfoOnMapChange(std::shared_ptr<CMapInfo> mapInfo, std::shared_ptr<CMapGenOptions> mapGenOpt = {});
void clientConnected(std::shared_ptr<CConnection> c, std::vector<std::string> & names, std::string uuid, StartInfo::EMode mode);
void clientDisconnected(std::shared_ptr<CConnection> c);
void updateAndPropagateLobbyState();
// Work with LobbyInfo
void setPlayer(PlayerColor clickedColor);
void optionNextHero(PlayerColor player, int dir); //dir == -1 or +1
int nextAllowedHero(PlayerColor player, int min, int max, int incl, int dir);
bool canUseThisHero(PlayerColor player, int ID);
std::vector<int> getUsedHeroes();
void optionNextBonus(PlayerColor player, int dir); //dir == -1 or +1
void optionNextCastle(PlayerColor player, int dir); //dir == -1 or +
// Campaigns
void setCampaignMap(int mapId);
void setCampaignBonus(int bonusId);
ui8 getIdOfFirstUnallocatedPlayer() const;
#ifdef VCMI_ANDROID
static void create();
#endif
};

View File

@ -0,0 +1,250 @@
/*
* NetPacksLobbyServer.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 "CVCMIServer.h"
#include "CGameHandler.h"
#include "../lib/NetPacksLobby.h"
#include "../lib/serializer/Connection.h"
#include "../lib/StartInfo.h"
// Campaigns
#include "../lib/mapping/CCampaignHandler.h"
#include "../lib/mapping/CMapService.h"
#include "../lib/mapping/CMapInfo.h"
bool CLobbyPackToServer::checkClientPermissions(CVCMIServer * srv) const
{
return srv->isClientHost(c->connectionID);
}
void CLobbyPackToServer::applyOnServerAfterAnnounce(CVCMIServer * srv)
{
// Propogate options after every CLobbyPackToServer
srv->updateAndPropagateLobbyState();
}
bool LobbyClientConnected::checkClientPermissions(CVCMIServer * srv) const
{
return true;
}
bool LobbyClientConnected::applyOnServer(CVCMIServer * srv)
{
srv->clientConnected(c, names, uuid, mode);
// Server need to pass some data to newly connected client
clientId = c->connectionID;
mode = srv->si->mode;
hostClientId = srv->hostClientId;
return true;
}
void LobbyClientConnected::applyOnServerAfterAnnounce(CVCMIServer * srv)
{
// FIXME: we need to avoid senting something to client that not yet get answer for LobbyClientConnected
// Until UUID set we only pass LobbyClientConnected to this client
c->uuid = uuid;
srv->updateAndPropagateLobbyState();
}
bool LobbyClientDisconnected::checkClientPermissions(CVCMIServer * srv) const
{
if(clientId != c->connectionID)
return false;
if(shutdownServer)
{
if(!srv->cmdLineOptions.count("run-by-client"))
return false;
if(c->uuid != srv->cmdLineOptions["uuid"].as<std::string>())
return false;
}
return true;
}
bool LobbyClientDisconnected::applyOnServer(CVCMIServer * srv)
{
srv->clientDisconnected(c);
return true;
}
void LobbyClientDisconnected::applyOnServerAfterAnnounce(CVCMIServer * srv)
{
if(c->isOpen())
{
boost::unique_lock<boost::mutex> lock(*c->mutexWrite);
c->close();
c->connected = false;
}
if(shutdownServer)
{
logNetwork->info("Client requested shutdown, server will close itself...");
srv->state = EServerState::SHUTDOWN;
return;
}
else if(srv->connections.empty())
{
logNetwork->error("Last connection lost, server will close itself...");
srv->state = EServerState::SHUTDOWN;
}
else if(c == srv->hostClient)
{
auto ph = new LobbyChangeHost();
auto newHost = *RandomGeneratorUtil::nextItem(srv->connections, CRandomGenerator::getDefault());
ph->newHostConnectionId = newHost->connectionID;
srv->addToAnnounceQueue(ph);
}
srv->updateAndPropagateLobbyState();
}
bool LobbyChatMessage::checkClientPermissions(CVCMIServer * srv) const
{
return true;
}
bool LobbySetMap::applyOnServer(CVCMIServer * srv)
{
srv->updateStartInfoOnMapChange(mapInfo, mapGenOpts);
return true;
}
bool LobbySetCampaign::applyOnServer(CVCMIServer * srv)
{
srv->si->mapname = ourCampaign->camp->header.filename;
srv->si->mode = StartInfo::CAMPAIGN;
srv->si->campState = ourCampaign;
srv->si->turnTime = 0;
bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap);
for(int i = 0; i < ourCampaign->camp->scenarios.size(); i++)
{
if(ourCampaign->camp->conquerable(i))
{
if(!isCurrentMapConquerable || (isCurrentMapConquerable && i == *ourCampaign->currentMap))
{
srv->setCampaignMap(i);
}
}
}
return true;
}
bool LobbySetCampaignMap::applyOnServer(CVCMIServer * srv)
{
srv->setCampaignMap(mapId);
return true;
}
bool LobbySetCampaignBonus::applyOnServer(CVCMIServer * srv)
{
srv->setCampaignBonus(bonusId);
return true;
}
bool LobbyGuiAction::checkClientPermissions(CVCMIServer * srv) const
{
return srv->isClientHost(c->connectionID);
}
bool LobbyStartGame::checkClientPermissions(CVCMIServer * srv) const
{
return srv->isClientHost(c->connectionID);
}
bool LobbyStartGame::applyOnServer(CVCMIServer * srv)
{
try
{
srv->verifyStateBeforeStart(true);
}
catch(...)
{
return false;
}
// Server will prepare gamestate and we announce StartInfo to clients
srv->prepareToStartGame();
initializedStartInfo = std::make_shared<StartInfo>(*srv->gh->getStartInfo(true));
return true;
}
void LobbyStartGame::applyOnServerAfterAnnounce(CVCMIServer * srv)
{
srv->startGameImmidiately();
}
bool LobbyChangeHost::checkClientPermissions(CVCMIServer * srv) const
{
return srv->isClientHost(c->connectionID);
}
bool LobbyChangeHost::applyOnServer(CVCMIServer * srv)
{
return true;
}
bool LobbyChangeHost::applyOnServerAfterAnnounce(CVCMIServer * srv)
{
return srv->passHost(newHostConnectionId);
}
bool LobbyChangePlayerOption::checkClientPermissions(CVCMIServer * srv) const
{
if(srv->isClientHost(c->connectionID))
return true;
if(vstd::contains(srv->getAllClientPlayers(c->connectionID), color))
return true;
return false;
}
bool LobbyChangePlayerOption::applyOnServer(CVCMIServer * srv)
{
switch(what)
{
case TOWN:
srv->optionNextCastle(color, direction);
break;
case HERO:
srv->optionNextHero(color, direction);
break;
case BONUS:
srv->optionNextBonus(color, direction);
break;
}
return true;
}
bool LobbySetPlayer::applyOnServer(CVCMIServer * srv)
{
srv->setPlayer(clickedColor);
return true;
}
bool LobbySetTurnTime::applyOnServer(CVCMIServer * srv)
{
srv->si->turnTime = turnTime;
return true;
}
bool LobbySetDifficulty::applyOnServer(CVCMIServer * srv)
{
srv->si->difficulty = vstd::abetween(difficulty, 0, 4);
return true;
}
bool LobbyForceSetPlayer::applyOnServer(CVCMIServer * srv)
{
srv->si->playerInfos[targetPlayerColor].connectedPlayerIDs.insert(targetConnectedPlayer);
return true;
}

View File

@ -31,7 +31,7 @@ void CPackForServer::throwNotAllowedAction()
if(c)
{
SystemMessage temp_message("You are not allowed to perform this action!");
*c << &temp_message;
c->sendPack(&temp_message);
}
logNetwork->error("Player is not allowed to perform this action!");
throw ExceptionNotAllowedAction();
@ -45,7 +45,7 @@ void CPackForServer::wrongPlayerMessage(CGameHandler * gh, PlayerColor expectedp
if(c)
{
SystemMessage temp_message(oss.str());
*c << &temp_message;
c->sendPack(&temp_message);
}
}
@ -92,18 +92,6 @@ bool CommitPackage::applyGh(CGameHandler * gh)
return true;
}
bool CloseServer::applyGh(CGameHandler * gh)
{
gh->close();
return true;
}
bool LeaveGame::applyGh(CGameHandler * gh)
{
gh->playerLeftGame(c->connectionID);
return true;
}
bool EndTurn::applyGh(CGameHandler * gh)
{
PlayerColor player = GS(gh)->currentPlayer;
@ -292,7 +280,7 @@ bool QueryReply::applyGh(CGameHandler * gh)
auto playerToConnection = gh->connections.find(player);
if(playerToConnection == gh->connections.end())
throwAndCompain(gh, "No such player!");
if(playerToConnection->second != c)
if(!vstd::contains(playerToConnection->second, c))
throwAndCompain(gh, "Message came from wrong connection!");
if(qid == QueryID(-1))
throwAndCompain(gh, "Cannot answer the query with id -1!");
@ -312,17 +300,18 @@ bool MakeAction::applyGh(CGameHandler * gh)
if(ba.actionType != EActionType::WALK && ba.actionType != EActionType::END_TACTIC_PHASE
&& ba.actionType != EActionType::RETREAT && ba.actionType != EActionType::SURRENDER)
throwNotAllowedAction();
if(gh->connections[b->sides[b->tacticsSide].color] != c)
if(!vstd::contains(gh->connections[b->sides[b->tacticsSide].color], c))
throwNotAllowedAction();
}
else
{
auto active = b->battleActiveUnit();
if(!active) throwNotAllowedAction();
if(!active)
throwNotAllowedAction();
auto unitOwner = b->battleGetOwner(active);
if(gh->connections[unitOwner] != c) throwNotAllowedAction();
if(!vstd::contains(gh->connections[unitOwner], c))
throwNotAllowedAction();
}
return gh->makeBattleAction(ba);
}
@ -337,7 +326,7 @@ bool MakeCustomAction::applyGh(CGameHandler * gh)
if(!active)
throwNotAllowedAction();
auto unitOwner = b->battleGetOwner(active);
if(gh->connections[unitOwner] != c)
if(!vstd::contains(gh->connections[unitOwner], c))
throwNotAllowedAction();
if(ba.actionType != EActionType::HERO_SPELL)
throwNotAllowedAction();
@ -373,7 +362,7 @@ bool PlayerMessage::applyGh(CGameHandler * gh)
if(!player.isSpectator()) // TODO: clearly not a great way to verify permissions
{
throwOnWrongPlayer(gh, player);
if(gh->getPlayerAt(c) != player)
if(gh->getPlayerAt(this->c) != player)
throwNotAllowedAction();
}
gh->playerMessage(player, text, currObj);

View File

@ -44,9 +44,9 @@ public:
IObjectInterface::cb = nullptr;
}
void sendAndApply(CPackForClient * info) const override
void sendAndApply(CPackForClient * pack) const override
{
gameState->apply(info);
gameState->apply(pack);
}
void complain(const std::string & problem) const
@ -108,7 +108,7 @@ public:
PlayerSettings & pset = si.playerInfos[PlayerColor(i)];
pset.color = PlayerColor(i);
pset.playerID = i;
pset.connectedPlayerIDs.insert(i);
pset.name = "Player";
pset.castle = pinfo.defaultCastle();

View File

@ -32,7 +32,7 @@ void GameCallbackMock::commitPackage(CPackForClient * pack)
sendAndApply(pack);
}
void GameCallbackMock::sendAndApply(CPackForClient * info)
void GameCallbackMock::sendAndApply(CPackForClient * pack)
{
upperCallback->sendAndApply(info);
upperCallback->sendAndApply(pack);
}

View File

@ -86,7 +86,7 @@ public:
///useful callback methods
void commitPackage(CPackForClient * pack) override;
void sendAndApply(CPackForClient * info) override;
void sendAndApply(CPackForClient * pack) override;
private:
const UpperCallback * upperCallback;
};