/* * CPreGame.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 "CPreGame.h" #include "../lib/filesystem/Filesystem.h" #include "../lib/filesystem/CCompressedStream.h" #include "../lib/CStopWatch.h" #include "gui/SDL_Extensions.h" #include "CGameInfo.h" #include "gui/CCursorHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CSkillHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/mapping/CCampaignHandler.h" #include "../lib/CCreatureHandler.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 "../lib/spells/CSpellHandler.h" /*for campaign bonuses*/ #include "../lib/CArtHandler.h" /*for campaign bonuses*/ #include "../lib/CBuildingHandler.h" /*for campaign bonuses*/ #include "CBitmapHandler.h" #include "Client.h" #include "../lib/NetPacks.h" #include "../lib/registerTypes//RegisterTypes.h" #include "../lib/CThreadHelper.h" #include "../lib/CConfigHandler.h" #include "../lib/GameConstants.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 "../lib/mapping/CMapService.h" #include "../lib/CRandomGenerator.h" #include "../lib/CondSh.h" namespace fs = boost::filesystem; void startGame(StartInfo * options, CConnection *serv = nullptr); void endGame(); CGPreGame * CGP = nullptr; ISelectionScreenInfo *SEL; static PlayerColor playerColor; //if more than one player - applies to the first /** * Stores the current name of the savegame. * * TODO better solution for auto-selection when saving already saved games. * -> CSelectionScreen should be divided into CLoadGameScreen, CSaveGameScreen,... * The name of the savegame can then be stored non-statically in CGameState and * passed separately to CSaveGameScreen. */ static std::string saveGameName; struct EvilHlpStruct { CConnection *serv; StartInfo *sInfo; void reset() { // vstd::clear_pointer(serv); vstd::clear_pointer(sInfo); } } startingInfo; static void do_quit() { SDL_Event event; event.quit.type = SDL_QUIT; SDL_PushEvent(&event); } static CMapInfo *mapInfoFromGame() { auto ret = new CMapInfo(); ret->mapHeader = std::unique_ptr(new CMapHeader(*LOCPLINT->cb->getMapHeader())); return ret; } static void setPlayersFromGame() { playerColor = LOCPLINT->playerID; } static void swapPlayers(PlayerSettings &a, PlayerSettings &b) { std::swap(a.playerID, b.playerID); std::swap(a.name, b.name); if(a.playerID == 1) playerColor = a.color; else if(b.playerID == 1) playerColor = b.color; } void setPlayer(PlayerSettings &pset, ui8 player, const std::map &playerNames) { if(vstd::contains(playerNames, player)) pset.name = playerNames.find(player)->second; else pset.name = CGI->generaltexth->allTexts[468];//Computer pset.playerID = player; if(player == playerNames.begin()->first) playerColor = pset.color; } void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr & mapHeader, const std::map &playerNames) { sInfo.playerInfos.clear(); if(!mapHeader.get()) { return; } sInfo.mapname = filename; playerColor = PlayerColor::NEUTRAL; auto namesIt = playerNames.cbegin(); for (int i = 0; i < mapHeader->players.size(); i++) { const PlayerInfo &pinfo = mapHeader->players[i]; //neither computer nor human can play - no player if (!(pinfo.canHumanPlay || pinfo.canComputerPlay)) continue; PlayerSettings &pset = sInfo.playerInfos[PlayerColor(i)]; pset.color = PlayerColor(i); if(pinfo.canHumanPlay && namesIt != playerNames.cend()) { setPlayer(pset, namesIt++->first, playerNames); } else { setPlayer(pset, 0, playerNames); if(!pinfo.canHumanPlay) { pset.compOnly = true; } } pset.castle = pinfo.defaultCastle(); pset.hero = pinfo.defaultHero(); if(pset.hero != PlayerSettings::RANDOM && pinfo.hasCustomMainHero()) { pset.hero = pinfo.mainCustomHeroId; pset.heroName = pinfo.mainCustomHeroName; pset.heroPortrait = pinfo.mainCustomHeroPortrait; } pset.handicap = PlayerSettings::NO_HANDICAP; } } template class CApplyOnPG; class CBaseForPGApply { public: virtual void applyOnPG(CSelectionScreen *selScr, void *pack) const =0; virtual ~CBaseForPGApply(){}; template static CBaseForPGApply *getApplier(const U * t=nullptr) { return new CApplyOnPG(); } }; template class CApplyOnPG : public CBaseForPGApply { public: void applyOnPG(CSelectionScreen *selScr, void *pack) const override { T *ptr = static_cast(pack); ptr->apply(selScr); } }; template <> class CApplyOnPG : public CBaseForPGApply { public: void applyOnPG(CSelectionScreen *selScr, void *pack) const override { logGlobal->error("Cannot apply on PG plain CPack!"); assert(0); } }; static CApplier *applier = nullptr; static CPicture* createPicture(const JsonNode& config) { return new CPicture(config["name"].String(), config["x"].Float(), config["y"].Float()); } CMenuScreen::CMenuScreen(const JsonNode& configNode): config(configNode) { OBJ_CONSTRUCTION_CAPTURING_ALL; background = new 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(createPicture(node)); //Hardcoded entry menuNameToEntry.push_back("credits"); tabs = new 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::showAll(SDL_Surface * to) { CIntObject::showAll(to); if (pos.h != to->h || pos.w != to->w) CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15); } 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 genCommand(CMenuScreen* menu, std::vector menuType, const std::string &string) { static const std::vector commandType = {"to", "campaigns", "start", "load", "exit", "highscores"}; static const std::vector gameType = {"single", "multi", "campaign", "tutorial"}; std::list 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) { break; 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(&CGPreGame::openCampaignScreen, CGP, commands.front()); } break; case 2://start { switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin()) { case 0: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::newGame, CMenuScreen::SINGLE_PLAYER); case 1: return &pushIntT; case 2: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::campaignList, CMenuScreen::SINGLE_PLAYER); //TODO: start tutorial case 3: return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector*)nullptr, false, PlayerColor(1)); } } break; case 3://load { switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin()) { case 0: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_PLAYER); case 1: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::MULTI_HOT_SEAT); case 2: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_CAMPAIGN); //TODO: load tutorial case 3: return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector*)nullptr, false, PlayerColor(1)); } } break; case 4://exit { return std::bind(CInfoWindow::showYesNoDialog, std::ref(CGI->generaltexth->allTexts[69]), (const std::vector*)nullptr, do_quit, 0, false, PlayerColor(1)); } break; case 5://highscores { //TODO: high scores return std::bind(CInfoWindow::showInfoDialog, "Sorry, high scores menu is not implemented yet\n", (const std::vector*)nullptr, false, PlayerColor(1)); } } } } logGlobal->error("Failed to parse command: %s", string); return std::function(); } CButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button) { std::function command = genCommand(parent, parent->menuNameToEntry, button["command"].String()); std::pair 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 new CButton(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float()); } CMenuEntry::CMenuEntry(CMenuScreen* parent, const JsonNode &config) { OBJ_CONSTRUCTION_CAPTURING_ALL; type |= REDRAW_PARENT; pos = parent->pos; for(const JsonNode& node : config["images"].Vector()) images.push_back(createPicture(node)); for(const JsonNode& node : config["buttons"].Vector()) { buttons.push_back(createButton(parent, node)); buttons.back()->hoverable = true; buttons.back()->type |= REDRAW_PARENT; } } CreditsScreen::CreditsScreen(): positionCounter(0) { addUsedEvents(LCLICK | RCLICK); type |= REDRAW_PARENT; OBJ_CONSTRUCTION_CAPTURING_ALL; pos.w = CGP->menu->pos.w; pos.h = CGP->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 = new 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(parent); assert(menu); menu->setActive(0); } CGPreGameConfig & CGPreGameConfig::get() { static CGPreGameConfig config; return config; } const JsonNode & CGPreGameConfig::getConfig() const { return config; } const JsonNode & CGPreGameConfig::getCampaigns() const { return campaignSets; } CGPreGameConfig::CGPreGameConfig() : campaignSets(JsonNode(ResourceID("config/campaignSets.json"))), config(JsonNode(ResourceID("config/mainmenu.json"))) { } CGPreGame::CGPreGame() { pos.w = screen->w; pos.h = screen->h; GH.defActionsDef = 63; CGP = this; menu = new CMenuScreen(CGPreGameConfig::get().getConfig()["window"]); loadGraphics(); } CGPreGame::~CGPreGame() { boost::unique_lock lock(*CPlayerInterface::pim); disposeGraphics(); if(CGP == this) CGP = nullptr; if(GH.curInt == this) GH.curInt = nullptr; } void CGPreGame::openSel(CMenuScreen::EState screenType, CMenuScreen::EGameMode gameMode) { GH.pushInt(new CSelectionScreen(screenType, gameMode)); } void CGPreGame::loadGraphics() { OBJ_CONSTRUCTION_CAPTURING_ALL; new CFilledTexture("DIBOXBCK", pos); victoryIcons = std::make_shared("SCNRVICT.DEF"); victoryIcons->load(); lossIcons = std::make_shared("SCNRLOSS.DEF"); lossIcons->load(); } void CGPreGame::disposeGraphics() { victoryIcons->unload(); lossIcons->unload(); } void CGPreGame::update() { if(CGP != 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); } if(SEL) SEL->update(); // 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 CGPreGame::openCampaignScreen(std::string name) { if (vstd::contains(CGPreGameConfig::get().getCampaigns().Struct(), name)) { GH.pushInt(new CCampaignScreen(CGPreGameConfig::get().getCampaigns()[name])); return; } logGlobal->error("Unknown campaign set: %s", name); } CGPreGame *CGPreGame::create() { if(!CGP) CGP = new CGPreGame(); GH.terminate_cond->set(false); return CGP; } void CGPreGame::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 } CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EGameMode GameMode, const std::map * Names, const std::string & Address, const ui16 Port) : ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex), serv(nullptr), ongoingClosing(false), myNameID(255) { CGPreGame::create(); //we depend on its graphics screenType = Type; gameMode = GameMode; OBJ_CONSTRUCTION_CAPTURING_ALL; bool network = (isGuest() || isHost()); CServerHandler *sh = nullptr; if(isHost()) { sh = new CServerHandler(); sh->startServer(); } IShowActivatable::type = BLOCK_ADV_HOTKEYS; pos.w = 762; pos.h = 584; if(Type == CMenuScreen::saveGame) { bordered = false; center(pos); } else if(Type == CMenuScreen::campaignList) { bordered = false; bg = new CPicture("CamCust.bmp", 0, 0); pos = bg->center(); } else { bordered = true; //load random background const JsonVector & bgNames = CGPreGameConfig::get().getConfig()["game-select"].Vector(); bg = new CPicture(RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())->String(), 0, 0); pos = bg->center(); } sInfo.difficulty = 1; current = nullptr; sInfo.mode = (Type == CMenuScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME); sInfo.turnTime = 0; curTab = nullptr; card = new InfoCard(network); //right info card if (screenType == CMenuScreen::campaignList) { opt = nullptr; randMapTab = nullptr; } else { opt = new OptionsTab(); //scenario options tab opt->recActions = DISPOSE; randMapTab = new CRandomMapTab(); randMapTab->getMapInfoChanged() += std::bind(&CSelectionScreen::changeSelection, this, _1); randMapTab->recActions = DISPOSE; } sel = new SelectionTab(screenType, std::bind(&CSelectionScreen::changeSelection, this, _1), gameMode); //scenario selection tab sel->recActions = DISPOSE; switch(screenType) { case CMenuScreen::newGame: { SDL_Color orange = {232, 184, 32, 0}; SDL_Color overlayColor = isGuest() ? orange : Colors::WHITE; card->difficulty->addCallback(std::bind(&CSelectionScreen::difficultyChange, this, _1)); card->difficulty->setSelected(1); CButton * select = new CButton(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s); select->addCallback([&]() { toggleTab(sel); changeSelection(sel->getSelectedMapInfo()); }); select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, overlayColor); CButton *opts = new CButton(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CSelectionScreen::toggleTab, this, opt), SDLK_a); opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, overlayColor); CButton * randomBtn = new CButton(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r); randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, overlayColor); randomBtn->addCallback([&]() { toggleTab(randMapTab); changeSelection(randMapTab->getMapInfo()); }); start = new CButton(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_b); if(network) { CButton *hideChat = new CButton(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&InfoCard::toggleChat, card), SDLK_h); hideChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL); if(isGuest()) { select->block(true); opts->block(true); randomBtn->block(true); start->block(true); } } } break; case CMenuScreen::loadGame: sel->recActions = 255; start = new CButton(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_l); break; case CMenuScreen::saveGame: sel->recActions = 255; start = new CButton(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_s); break; case CMenuScreen::campaignList: sel->recActions = 255; start = new CButton(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CSelectionScreen::startCampaign, this), SDLK_b); break; } start->assignedKeys.insert(SDLK_RETURN); back = new CButton(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE); if(network) { if(isHost()) { assert(playerNames.size() == 1 && vstd::contains(playerNames, 1)); //TODO hot-seat/network combo if(settings["session"]["donotstartserver"].Bool()) serv = CServerHandler::justConnectToServer(Address, Port); else serv = sh->connectToServer(); *serv << (ui8) 4; myNameID = 1; } else serv = CServerHandler::justConnectToServer(Address, Port); serv->enterPregameConnectionMode(); *serv << playerNames.begin()->second; if(isGuest()) { const CMapInfo *map; *serv >> myNameID >> map; serv->connectionID = myNameID; changeSelection(map); } else if(current) { SelectMap sm(*current); *serv << &sm; UpdateStartOptions uso(sInfo); *serv << &uso; } applier = new CApplier(); registerTypesPregamePacks(*applier); serverHandlingThread = new boost::thread(&CSelectionScreen::handleConnection, this); } delete sh; } CSelectionScreen::~CSelectionScreen() { ongoingClosing = true; if(serv) { assert(serverHandlingThread); QuitMenuWithoutStarting qmws; *serv << &qmws; // while(!serverHandlingThread->timed_join(boost::posix_time::milliseconds(50))) // processPacks(); serverHandlingThread->join(); delete serverHandlingThread; } playerColor = PlayerColor::CANNOT_DETERMINE; playerNames.clear(); assert(!serv); vstd::clear_pointer(applier); delete mx; } void CSelectionScreen::toggleTab(CIntObject *tab) { if(isHost() && serv) { PregameGuiAction pga; if(tab == curTab) pga.action = PregameGuiAction::NO_TAB; else if(tab == opt) pga.action = PregameGuiAction::OPEN_OPTIONS; else if(tab == sel) pga.action = PregameGuiAction::OPEN_SCENARIO_LIST; else if(tab == randMapTab) pga.action = PregameGuiAction::OPEN_RANDOM_MAP_OPTIONS; *serv << &pga; } if(curTab && curTab->active) { curTab->deactivate(); curTab->recActions = DISPOSE; } if(curTab != tab) { tab->recActions = 255; tab->activate(); curTab = tab; } else { curTab = nullptr; } GH.totalRedraw(); } void CSelectionScreen::changeSelection(const CMapInfo * to) { if(current == to) return; if(isGuest()) vstd::clear_pointer(current); current = to; if(to && (screenType == CMenuScreen::loadGame || screenType == CMenuScreen::saveGame)) SEL->sInfo.difficulty = to->scenarioOpts->difficulty; if(screenType != CMenuScreen::campaignList) { std::unique_ptr emptyHeader; if(to) updateStartInfo(to->fileURI, sInfo, to->mapHeader); else updateStartInfo("", sInfo, emptyHeader); if(screenType == CMenuScreen::newGame) { if(to && to->isRandomMap) { sInfo.mapGenOptions = std::shared_ptr(new CMapGenOptions(randMapTab->getMapGenOptions())); } else { sInfo.mapGenOptions.reset(); } } } card->changeSelection(to); if(screenType != CMenuScreen::campaignList) { opt->recreate(); } if(isHost() && serv) { SelectMap sm(*to); *serv << &sm; UpdateStartOptions uso(sInfo); *serv << &uso; } } void CSelectionScreen::startCampaign() { if (SEL->current) GH.pushInt(new CBonusSelection(SEL->current->fileURI)); } void CSelectionScreen::startScenario() { if(screenType == CMenuScreen::newGame) { //there must be at least one human player before game can be started std::map::const_iterator i; for(i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++) if(i->second.playerID != PlayerSettings::PLAYER_AI) break; if(i == SEL->sInfo.playerInfos.cend()) { GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[530])); //You must position yourself prior to starting the game. return; } } if(isHost()) { start->block(true); StartWithCurrentSettings swcs; *serv << &swcs; ongoingClosing = true; return; } if(screenType != CMenuScreen::saveGame) { if(!current) return; if(sInfo.mapGenOptions) { //copy settings from interface to actual options. TODO: refactor, it used to have no effect at all -.- sInfo.mapGenOptions = std::shared_ptr(new CMapGenOptions(randMapTab->getMapGenOptions())); // Update player settings for RMG for(const auto & psetPair : sInfo.playerInfos) { const auto & pset = psetPair.second; sInfo.mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle); if(pset.playerID != PlayerSettings::PLAYER_AI) { sInfo.mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN); } } if(!sInfo.mapGenOptions->checkOptions()) { GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[751])); return; } } saveGameName.clear(); if(screenType == CMenuScreen::loadGame) { saveGameName = sInfo.mapname; } auto si = new StartInfo(sInfo); CGP->removeFromGui(); CGP->showLoadingScreen(std::bind(&startGame, si, (CConnection *)nullptr)); } else { if(!(sel && sel->txt && sel->txt->text.size())) return; saveGameName = "Saves/" + sel->txt->text; CFunctionList overWrite; overWrite += std::bind(&CCallback::save, LOCPLINT->cb.get(), saveGameName); overWrite += std::bind(&CGuiHandler::popIntTotally, &GH, this); if(CResourceHandler::get("local")->existsResource(ResourceID(saveGameName, EResType::CLIENT_SAVEGAME))) { std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite? boost::algorithm::replace_first(hlp, "%s", sel->txt->text); LOCPLINT->showYesNoDialog(hlp, overWrite, 0, false); } else overWrite(); } } void CSelectionScreen::difficultyChange( int to ) { assert(screenType == CMenuScreen::newGame); sInfo.difficulty = to; propagateOptions(); redraw(); } void CSelectionScreen::handleConnection() { setThreadName("CSelectionScreen::handleConnection"); try { assert(serv); while(serv) { CPackForSelectionScreen *pack = nullptr; *serv >> pack; logNetwork->trace("Received a pack of type %s", typeid(*pack).name()); assert(pack); if(QuitMenuWithoutStarting *endingPack = dynamic_cast(pack)) { endingPack->apply(this); } else if(StartWithCurrentSettings *endingPack = dynamic_cast(pack)) { endingPack->apply(this); } else { boost::unique_lock lll(*mx); upcomingPacks.push_back(pack); } } } catch(int i) { if(i != 666) throw; } catch(...) { handleException(); throw; } } void CSelectionScreen::setSInfo(const StartInfo &si) { std::map::const_iterator i; for(i = si.playerInfos.cbegin(); i != si.playerInfos.cend(); i++) { if(i->second.playerID == myNameID) { playerColor = i->first; break; } } if(i == si.playerInfos.cend()) //not found playerColor = PlayerColor::CANNOT_DETERMINE; sInfo = si; if(current) opt->recreate(); //will force to recreate using current sInfo card->difficulty->setSelected(si.difficulty); if(curTab == randMapTab) randMapTab->setMapGenOptions(si.mapGenOptions); GH.totalRedraw(); } void CSelectionScreen::processPacks() { boost::unique_lock lll(*mx); while(!upcomingPacks.empty()) { CPackForSelectionScreen *pack = upcomingPacks.front(); upcomingPacks.pop_front(); CBaseForPGApply *apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier apply->applyOnPG(this, pack); delete pack; } } void CSelectionScreen::update() { if(serverHandlingThread) processPacks(); } void CSelectionScreen::propagateOptions() { if(isHost() && serv) { UpdateStartOptions ups(sInfo); *serv << &ups; } } void CSelectionScreen::postRequest(ui8 what, ui8 dir) { if(!isGuest() || !serv) return; RequestOptionsChange roc(what, dir, myNameID); *serv << &roc; } void CSelectionScreen::postChatMessage(const std::string &txt) { assert(serv); ChatMessage cm; cm.message = txt; cm.playerName = sInfo.getPlayersSettings(myNameID)->name; *serv << &cm; } void CSelectionScreen::propagateNames() { PlayersNames pn; pn.playerNames = playerNames; *serv << &pn; } void CSelectionScreen::showAll(SDL_Surface *to) { CIntObject::showAll(to); if (bordered && (pos.h != to->h || pos.w != to->w)) CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15); } // 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 == CMenuScreen::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); onSelect(curItems[0]); selectAbs(0); } } else { slider->block(true); onSelect(nullptr); } } std::unordered_set 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 ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident) { return ident.getType() == resType && boost::algorithm::starts_with(ident.getName(), dirURI); }); return ret; } void SelectionTab::parseMaps(const std::unordered_set &files) { logGlobal->debug("Parsing %d maps", files.size()); allItems.clear(); for(auto & file : files) { try { CMapInfo mapInfo; 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(std::move(mapInfo)); } catch(std::exception & e) { logGlobal->error("Map %s is invalid. Message: %s", file.getName(), e.what()); } } } void SelectionTab::parseGames(const std::unordered_set &files, CMenuScreen::EGameMode gameMode) { for(auto & file : files) { try { CLoadFile lf(*CResourceHandler::get()->getResourceName(file), MINIMAL_SERIALIZATION_VERSION); lf.checkMagicBytes(SAVEGAME_MAGIC); // ui8 sign[8]; // lf >> sign; // if(std::memcmp(sign,"VCMISVG",7)) // { // throw std::runtime_error("not a correct savefile!"); // } // Create the map info object CMapInfo mapInfo; mapInfo.mapHeader = make_unique(); mapInfo.scenarioOpts = nullptr;//to be created by serialiser lf >> *(mapInfo.mapHeader.get()) >> mapInfo.scenarioOpts; mapInfo.fileURI = file.getName(); mapInfo.countPlayers(); std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file)); mapInfo.date = std::asctime(std::localtime(&time)); // Filter out other game modes bool isCampaign = mapInfo.scenarioOpts->mode == StartInfo::CAMPAIGN; bool isMultiplayer = mapInfo.actualHumanPlayers > 1; switch(gameMode) { case CMenuScreen::SINGLE_PLAYER: if(isMultiplayer || isCampaign) mapInfo.mapHeader.reset(); break; case CMenuScreen::SINGLE_CAMPAIGN: if(!isCampaign) mapInfo.mapHeader.reset(); break; default: if(!isMultiplayer) mapInfo.mapHeader.reset(); break; } allItems.push_back(std::move(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 &files ) { allItems.reserve(files.size()); for (auto & file : files) { CMapInfo info; //allItems[i].date = std::asctime(std::localtime(&files[i].date)); info.fileURI = file.getName(); info.campaignInit(); allItems.push_back(std::move(info)); } } SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function &OnSelect, CMenuScreen::EGameMode GameMode) :bg(nullptr), onSelect(OnSelect) { OBJ_CONSTRUCTION; selectionPos = 0; addUsedEvents(LCLICK | WHEEL | KEYBOARD | DOUBLECLICK); slider = nullptr; txt = nullptr; tabType = Type; if (Type != CMenuScreen::campaignList) { bg = new CPicture("SCSELBCK.bmp", 0, 6); pos = bg->pos; } else { bg = nullptr; //use background from parent 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; } if(GameMode == CMenuScreen::MULTI_NETWORK_GUEST) { positions = 18; } else { switch(tabType) { case CMenuScreen::newGame: parseMaps(getFiles("Maps/", EResType::MAP)); positions = 18; break; case CMenuScreen::loadGame: case CMenuScreen::saveGame: parseGames(getFiles("Saves/", EResType::CLIENT_SAVEGAME), GameMode); if(tabType == CMenuScreen::loadGame) { positions = 18; } else { positions = 16; } if(tabType == CMenuScreen::saveGame) { txt = new CTextInput(Rect(32, 539, 350, 20), Point(-32, -25), "GSSTRIP.bmp", 0); txt->filters += CTextInput::filenameFilter; } break; case CMenuScreen::campaignList: parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN)); positions = 18; break; default: assert(0); break; } } generalSortingBy = (tabType == CMenuScreen::loadGame || tabType == CMenuScreen::saveGame) ? _fileName : _name; if (tabType != CMenuScreen::campaignList) { //size filter buttons { int sizes[] = {36, 72, 108, 144, 0}; const char * names[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"}; for(int i = 0; i < 5; i++) new CButton(Point(158 + 47*i, 46), names[i], CGI->generaltexth->zelp[54+i], std::bind(&SelectionTab::filter, this, sizes[i], true)); } //sort buttons buttons { int xpos[] = {23, 55, 88, 121, 306, 339}; const char * names[] = {"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; new CButton(Point(xpos[i], 86), names[i], CGI->generaltexth->zelp[107+i], std::bind(&SelectionTab::sortBy, this, criteria)); } } } else { //sort by buttons new CButton(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)); //by num of maps new CButton(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)); //by name } slider = new CSlider(Point(372, 86), tabType != CMenuScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, CSlider::BLUE); slider->addUsedEvents(WHEEL); formatIcons = std::make_shared("SCSELC.DEF"); formatIcons->load(); sortingBy = _format; ascending = true; filter(0); //select(0); switch(tabType) { case CMenuScreen::newGame: logGlobal->error(settings["session"]["lastMap"].String()); if(settings["session"]["lastMap"].isNull()) selectFName("Maps/Arrogance"); else selectFName(settings["session"]["lastMap"].String()); break; case CMenuScreen::campaignList: select(0); break; case CMenuScreen::loadGame: case CMenuScreen::saveGame: if(saveGameName.empty()) { if(tabType == CMenuScreen::saveGame) txt->setText("NEWGAME"); else select(0); } else { selectFName(saveGameName); } } } SelectionTab::~SelectionTab() { formatIcons->unload(); } void SelectionTab::sortBy( int criteria ) { if(criteria == sortingBy) { ascending = !ascending; } else { sortingBy = (ESortBy)criteria; ascending = 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(!ascending) std::reverse(curItems.begin(), curItems.end()); 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 >= positions) slider->moveBy(position - positions + 1); if(tabType == CMenuScreen::newGame) { Settings lastMap = settings.write["session"]["lastMap"]; lastMap->String() = getSelectedMapInfo()->fileURI; } if(txt) { auto filename = *CResourceHandler::get("local")->getResourceName( ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME)); txt->setText(filename.stem().string()); } onSelect(curItems[py]); } void SelectionTab::selectAbs( int position ) { select(position - slider->getValue()); } int SelectionTab::getPosition( int x, int y ) { return -1; } void SelectionTab::sliderMove( int slidPos ) { if(!slider) return; //ignore spurious call when slider is being created redraw(); } // Display the tab with the scenario names // // 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 void SelectionTab::printMaps(SDL_Surface *to) { int elemIdx = slider->getValue(); // Display all elements if there's enough space //if(slider->amount < slider->capacity) // elemIdx = 0; SDL_Color itemColor; for (int line = 0; line < positions && elemIdx < curItems.size(); elemIdx++, line++) { CMapInfo *currentItem = curItems[elemIdx]; if(elemIdx == selectionPos) itemColor=Colors::YELLOW; else itemColor=Colors::WHITE; if(tabType != CMenuScreen::campaignList) { //amount of players std::ostringstream ostr(std::ostringstream::out); ostr << currentItem->playerAmnt << "/" << currentItem->humanPlayers; printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to); //map size std::string temp2 = "C"; switch (currentItem->mapHeader->width) { case 36: temp2="S"; break; case 72: temp2="M"; break; case 108: temp2="L"; break; case 144: temp2="XL"; break; } printAtMiddleLoc(temp2, 70, 128 + line * 25, FONT_SMALL, itemColor, to); int frame = -1, group = 0; switch (currentItem->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); continue; } IImage * icon = formatIcons->getImage(frame,group); if(icon) { icon->draw(to, pos.x + 88, pos.y + 117 + line * 25); icon->decreaseRef(); } //victory conditions icon = CGP->victoryIcons->getImage(currentItem->mapHeader->victoryIconIndex,0); if(icon) { icon->draw(to, pos.x + 306, pos.y + 117 + line * 25); icon->decreaseRef(); } //loss conditions icon = CGP->lossIcons->getImage(currentItem->mapHeader->defeatIconIndex,0); if(icon) { icon->draw(to, pos.x + 339, pos.y + 117 + line * 25); icon->decreaseRef(); } } else //if campaign { //number of maps in campaign std::ostringstream ostr(std::ostringstream::out); ostr << CGI->generaltexth->campaignRegionNames[ currentItem->campaignHeader->mapVersion ].size(); printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to); } std::string name; if(tabType == CMenuScreen::newGame) { if (!currentItem->mapHeader->name.length()) currentItem->mapHeader->name = "Unnamed"; name = currentItem->mapHeader->name; } else if(tabType == CMenuScreen::campaignList) { name = currentItem->campaignHeader->name; } else { name = CResourceHandler::get("local")->getResourceName( ResourceID(currentItem->fileURI, EResType::CLIENT_SAVEGAME))->stem().string(); } //print name printAtMiddleLoc(name, 213, 128 + line * 25, FONT_SMALL, itemColor, to); } } void SelectionTab::showAll(SDL_Surface * to) { CIntObject::showAll(to); printMaps(to); std::string title; switch(tabType) { case CMenuScreen::newGame: title = CGI->generaltexth->arraytxt[229]; break; case CMenuScreen::loadGame: title = CGI->generaltexth->arraytxt[230]; break; case CMenuScreen::saveGame: title = CGI->generaltexth->arraytxt[231]; break; case CMenuScreen::campaignList: title = CGI->generaltexth->allTexts[726]; break; } printAtMiddleLoc(title, 205, 28, FONT_MEDIUM, Colors::YELLOW, to); //Select a Scenario to Play if(tabType != CMenuScreen::campaignList) { printAtMiddleLoc(CGI->generaltexth->allTexts[510], 87, 62, FONT_SMALL, Colors::YELLOW, to); //Map sizes } } 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 = -positions+1; break; case SDLK_PAGEDOWN: moveBy = +positions-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 { //act as if start button was pressed (static_cast(parent))->start->clickLeft(false, true); } } 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 == CMenuScreen::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::selectFName( 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); } const CMapInfo * SelectionTab::getSelectedMapInfo() const { return curItems.empty() ? nullptr : curItems[selectionPos]; } CRandomMapTab::CRandomMapTab() { OBJ_CONSTRUCTION; bg = new CPicture("RANMAPBK", 0, 6); // Map Size mapSizeBtnGroup = new CToggleGroup(0); mapSizeBtnGroup->pos.y += 81; mapSizeBtnGroup->pos.x += 158; const std::vector mapSizeBtns = {"RANSIZS", "RANSIZM","RANSIZL","RANSIZX"}; addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198); mapSizeBtnGroup->setSelected(1); mapSizeBtnGroup->addCallback([&](int btnId) { auto mapSizeVal = getPossibleMapSizes(); mapGenOptions.setWidth(mapSizeVal[btnId]); mapGenOptions.setHeight(mapSizeVal[btnId]); if(!SEL->isGuest()) updateMapInfo(); }); // Two levels twoLevelsBtn = new CToggleButton(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]); //twoLevelsBtn->select(true); for now, deactivated twoLevelsBtn->addCallback([&](bool on) { mapGenOptions.setHasTwoLevels(on); if(!SEL->isGuest()) updateMapInfo(); }); // Create number defs list std::vector numberDefs; for(int i = 0; i <= 8; ++i) { numberDefs.push_back("RANNUM" + boost::lexical_cast(i)); } const int NUMBERS_WIDTH = 32; const int BTNS_GROUP_LEFT_MARGIN = 67; // Amount of players playersCntGroup = new CToggleGroup(0); playersCntGroup->pos.y += 153; playersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212); playersCntGroup->addCallback([&](int btnId) { mapGenOptions.setPlayerCount(btnId); deactivateButtonsFrom(teamsCntGroup, btnId); deactivateButtonsFrom(compOnlyPlayersCntGroup, btnId); validatePlayersCnt(btnId); if(!SEL->isGuest()) updateMapInfo(); }); // Amount of teams teamsCntGroup = new CToggleGroup(0); teamsCntGroup->pos.y += 219; teamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222); teamsCntGroup->addCallback([&](int btnId) { mapGenOptions.setTeamCount(btnId); if(!SEL->isGuest()) updateMapInfo(); }); // Computer only players compOnlyPlayersCntGroup = new CToggleGroup(0); compOnlyPlayersCntGroup->pos.y += 285; compOnlyPlayersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232); compOnlyPlayersCntGroup->addCallback([&](int btnId) { mapGenOptions.setCompOnlyPlayerCount(btnId); deactivateButtonsFrom(compOnlyTeamsCntGroup, (btnId == 0 ? 1 : btnId)); validateCompOnlyPlayersCnt(btnId); if(!SEL->isGuest()) updateMapInfo(); }); // Computer only teams compOnlyTeamsCntGroup = new CToggleGroup(0); compOnlyTeamsCntGroup->pos.y += 351; compOnlyTeamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241); deactivateButtonsFrom(compOnlyTeamsCntGroup, 1); compOnlyTeamsCntGroup->addCallback([&](int btnId) { mapGenOptions.setCompOnlyTeamCount(btnId); if(!SEL->isGuest()) updateMapInfo(); }); const int WIDE_BTN_WIDTH = 85; // Water content waterContentGroup = new CToggleGroup(0); waterContentGroup->pos.y += 419; waterContentGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; const std::vector waterContentBtns = {"RANNONE","RANNORM","RANISLD"}; addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246); waterContentGroup->addCallback([&](int btnId) { mapGenOptions.setWaterContent(static_cast(btnId)); }); // Monster strength monsterStrengthGroup = new CToggleGroup(0); monsterStrengthGroup->pos.y += 485; monsterStrengthGroup->pos.x += BTNS_GROUP_LEFT_MARGIN; const std::vector monsterStrengthBtns = {"RANWEAK","RANNORM","RANSTRG"}; addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251); monsterStrengthGroup->addCallback([&](int btnId) { if (btnId < 0) mapGenOptions.setMonsterStrength(EMonsterStrength::RANDOM); else mapGenOptions.setMonsterStrength(static_cast(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4 }); // Show random maps btn showRandMaps = new CButton(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]); // Initialize map info object if(!SEL->isGuest()) updateMapInfo(); } void CRandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector & 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 CRandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector & 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 CRandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId) { logGlobal->debug("Blocking buttons from %d", startId); for(auto toggle : group->buttons) { if (auto button = dynamic_cast(toggle.second)) { if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId) { button->block(false); } else { button->block(true); } } } } void CRandomMapTab::validatePlayersCnt(int playersCnt) { if(playersCnt == CMapGenOptions::RANDOM_SIZE) { return; } if(mapGenOptions.getTeamCount() >= playersCnt) { mapGenOptions.setTeamCount(playersCnt - 1); teamsCntGroup->setSelected(mapGenOptions.getTeamCount()); } if(mapGenOptions.getCompOnlyPlayerCount() >= playersCnt) { mapGenOptions.setCompOnlyPlayerCount(playersCnt - 1); compOnlyPlayersCntGroup->setSelected(mapGenOptions.getCompOnlyPlayerCount()); } validateCompOnlyPlayersCnt(mapGenOptions.getCompOnlyPlayerCount()); } void CRandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt) { if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE) { return; } if(mapGenOptions.getCompOnlyTeamCount() >= compOnlyPlayersCnt) { int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1; mapGenOptions.setCompOnlyTeamCount(compOnlyTeamCount); compOnlyTeamsCntGroup->setSelected(compOnlyTeamCount); } } std::vector CRandomMapTab::getPossibleMapSizes() { return {CMapHeader::MAP_SIZE_SMALL,CMapHeader::MAP_SIZE_MIDDLE,CMapHeader::MAP_SIZE_LARGE,CMapHeader::MAP_SIZE_XLARGE}; } void CRandomMapTab::showAll(SDL_Surface * to) { CIntObject::showAll(to); // Headline printAtMiddleLoc(CGI->generaltexth->allTexts[738], 222, 36, FONT_BIG, Colors::YELLOW, to); printAtMiddleLoc(CGI->generaltexth->allTexts[739], 222, 56, FONT_SMALL, Colors::WHITE, to); // Map size printAtMiddleLoc(CGI->generaltexth->allTexts[752], 104, 97, FONT_SMALL, Colors::WHITE, to); // Players cnt printAtLoc(CGI->generaltexth->allTexts[753], 68, 133, FONT_SMALL, Colors::WHITE, to); // Teams cnt printAtLoc(CGI->generaltexth->allTexts[754], 68, 199, FONT_SMALL, Colors::WHITE, to); // Computer only players cnt printAtLoc(CGI->generaltexth->allTexts[755], 68, 265, FONT_SMALL, Colors::WHITE, to); // Computer only teams cnt printAtLoc(CGI->generaltexth->allTexts[756], 68, 331, FONT_SMALL, Colors::WHITE, to); // Water content printAtLoc(CGI->generaltexth->allTexts[757], 68, 398, FONT_SMALL, Colors::WHITE, to); // Monster strength printAtLoc(CGI->generaltexth->allTexts[758], 68, 465, FONT_SMALL, Colors::WHITE, to); } void CRandomMapTab::updateMapInfo() { // Generate header info mapInfo = make_unique(); mapInfo->isRandomMap = true; mapInfo->mapHeader = make_unique(); 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.get()); } CFunctionList & CRandomMapTab::getMapInfoChanged() { return mapInfoChanged; } const CMapInfo * CRandomMapTab::getMapInfo() const { return mapInfo.get(); } const CMapGenOptions & CRandomMapTab::getMapGenOptions() const { return mapGenOptions; } void CRandomMapTab::setMapGenOptions(std::shared_ptr opts) { mapSizeBtnGroup->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth())); twoLevelsBtn->setSelected(opts->getHasTwoLevels()); playersCntGroup->setSelected(opts->getPlayerCount()); teamsCntGroup->setSelected(opts->getTeamCount()); compOnlyPlayersCntGroup->setSelected(opts->getCompOnlyPlayerCount()); compOnlyTeamsCntGroup->setSelected(opts->getCompOnlyTeamCount()); waterContentGroup->setSelected(opts->getWaterContent()); monsterStrengthGroup->setSelected(opts->getMonsterStrength()); } CChatBox::CChatBox(const Rect &rect) { OBJ_CONSTRUCTION; pos += rect; addUsedEvents(KEYBOARD | TEXTINPUT); captureAllKeys = true; type |= REDRAW_PARENT; const int height = graphics->fonts[FONT_SMALL]->getLineHeight(); inputBox = new CTextInput(Rect(0, rect.h - height, rect.w, height)); inputBox->removeUsedEvents(KEYBOARD); chatHistory = new 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()) { SEL->postChatMessage(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(); } InfoCard::InfoCard( bool Network ) : sizes(nullptr), bg(nullptr), network(Network), chatOn(false), chat(nullptr), playerListBg(nullptr), difficulty(nullptr) { OBJ_CONSTRUCTION_CAPTURING_ALL; CIntObject::type |= REDRAW_PARENT; pos.x += 393; pos.y += 6; addUsedEvents(RCLICK); mapDescription = nullptr; Rect descriptionRect(26, 149, 320, 115); mapDescription = new CTextBox("", descriptionRect, 1); if(SEL->screenType == CMenuScreen::campaignList) { CSelectionScreen *ss = static_cast(parent); mapDescription->addChild(new CPicture(*ss->bg, descriptionRect + Point(-393, 0)), true); //move subpicture bg to our description control (by default it's our (Infocard) child) } else { bg = new CPicture("GSELPOP1.bmp", 0, 0); parent->addChild(bg); auto it = vstd::find(parent->children, this); //our position among parent children parent->children.insert(it, bg); //put BG before us parent->children.pop_back(); pos.w = bg->pos.w; pos.h = bg->pos.h; sizes = new CAnimImage("SCNRMPSZ", 4, 0, 318, 22);//let it be custom size (frame 4) by default sizes->recActions &= ~(SHOWALL | UPDATE);//explicit draw sFlags = std::make_shared("ITGFLAGS.DEF"); sFlags->load(); difficulty = new 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]); difficulty->addToggle(i, button); if(SEL->screenType != CMenuScreen::newGame) button->block(true); } } if(network) { playerListBg = new CPicture("CHATPLUG.bmp", 16, 276); chat = new CChatBox(Rect(26, 132, 340, 132)); chatOn = true; mapDescription->disable(); } } victory = new CAnimImage("SCNRVICT",0, 0, 24, 302); victory->recActions &= ~(SHOWALL | UPDATE);//explicit draw loss = new CAnimImage("SCNRLOSS", 0, 0, 24, 359); loss->recActions &= ~(SHOWALL | UPDATE);//explicit draw } InfoCard::~InfoCard() { if(sFlags) sFlags->unload(); } void InfoCard::showAll(SDL_Surface * to) { CIntObject::showAll(to); //blit texts if(SEL->screenType != CMenuScreen::campaignList) { printAtLoc(CGI->generaltexth->allTexts[390] + ":", 24, 400, FONT_SMALL, Colors::WHITE, to); //Allies printAtLoc(CGI->generaltexth->allTexts[391] + ":", 190, 400, FONT_SMALL, Colors::WHITE, to); //Enemies printAtLoc(CGI->generaltexth->allTexts[494], 33, 430, FONT_SMALL, Colors::YELLOW, to);//"Map Diff:" printAtLoc(CGI->generaltexth->allTexts[492] + ":", 133,430, FONT_SMALL, Colors::YELLOW, to); //player difficulty printAtLoc(CGI->generaltexth->allTexts[218] + ":", 290,430, FONT_SMALL, Colors::YELLOW, to); //"Rating:" printAtLoc(CGI->generaltexth->allTexts[495], 26, 22, FONT_SMALL, Colors::YELLOW, to); //Scenario Name: if(!chatOn) { printAtLoc(CGI->generaltexth->allTexts[496], 26, 132, FONT_SMALL, Colors::YELLOW, to); //Scenario Description: printAtLoc(CGI->generaltexth->allTexts[497], 26, 283, FONT_SMALL, Colors::YELLOW, to); //Victory Condition: printAtLoc(CGI->generaltexth->allTexts[498], 26, 339, FONT_SMALL, Colors::YELLOW, to); //Loss Condition: } else //players list { std::map playerNames = SEL->playerNames; int playerSoFar = 0; for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++) { if(i->second.playerID != PlayerSettings::PLAYER_AI) { printAtLoc(i->second.name, 24, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to); playerNames.erase(i->second.playerID); } } playerSoFar = 0; for (auto i = playerNames.cbegin(); i != playerNames.cend(); i++) { printAtLoc(i->second, 193, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to); } } } if(SEL->current) { if(SEL->screenType != CMenuScreen::campaignList) { if(!chatOn) { CMapHeader * header = SEL->current->mapHeader.get(); //victory conditions printAtLoc(header->victoryMessage, 60, 307, FONT_SMALL, Colors::WHITE, to); victory->setFrame(header->victoryIconIndex); victory->showAll(to); //loss conditoins printAtLoc(header->defeatMessage, 60, 366, FONT_SMALL, Colors::WHITE, to); loss->setFrame(header->defeatIconIndex); loss->showAll(to); } //difficulty assert(SEL->current->mapHeader->difficulty <= 4); std::string &diff = CGI->generaltexth->arraytxt[142 + SEL->current->mapHeader->difficulty]; printAtMiddleLoc(diff, 62, 472, FONT_SMALL, Colors::WHITE, to); //selecting size icon switch (SEL->current->mapHeader->width) { case 36: sizes->setFrame(0); break; case 72: sizes->setFrame(1); break; case 108: sizes->setFrame(2); break; case 144: sizes->setFrame(3); break; default: sizes->setFrame(4); break; } sizes->showAll(to); if(SEL->screenType == CMenuScreen::loadGame) printToLoc((static_cast(SEL->current))->date,308,34, FONT_SMALL, Colors::WHITE, to); //print flags int fx = 34 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]); int ex = 200 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]); TeamID myT; if(playerColor < PlayerColor::PLAYER_LIMIT) myT = SEL->current->mapHeader->players[playerColor.getNum()].team; else myT = TeamID::NO_TEAM; for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++) { int *myx = ((i->first == playerColor || SEL->current->mapHeader->players[i->first.getNum()].team == myT) ? &fx : &ex); IImage * flag = sFlags->getImage(i->first.getNum(),0); flag->draw(to, pos.x + *myx, pos.y + 399); *myx += flag->width(); flag->decreaseRef(); } std::string tob; switch (SEL->sInfo.difficulty) { case 0: tob="80%"; break; case 1: tob="100%"; break; case 2: tob="130%"; break; case 3: tob="160%"; break; case 4: tob="200%"; break; } printAtMiddleLoc(tob, 311, 472, FONT_SMALL, Colors::WHITE, to); } //blit description std::string name; if (SEL->screenType == CMenuScreen::campaignList) { name = SEL->current->campaignHeader->name; } else { name = SEL->current->mapHeader->name; } //name if (name.length()) printAtLoc(name, 26, 39, FONT_BIG, Colors::YELLOW, to); else printAtLoc("Unnamed", 26, 39, FONT_BIG, Colors::YELLOW, to); } } void InfoCard::changeSelection( const CMapInfo *to ) { if(to && mapDescription) { if (SEL->screenType == CMenuScreen::campaignList) mapDescription->setText(to->campaignHeader->description); else mapDescription->setText(to->mapHeader->description); mapDescription->label->scrollTextTo(0); if (mapDescription->slider) mapDescription->slider->moveToMin(); if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) { //difficulty->block(true); difficulty->setSelected(SEL->sInfo.difficulty); } } redraw(); } void InfoCard::clickRight( tribool down, bool previousState ) { static const Rect flagArea(19, 397, 335, 23); if(SEL->current && down && SEL->current && isItInLoc(flagArea, GH.current->motion.x, GH.current->motion.y)) showTeamsPopup(); } void InfoCard::showTeamsPopup() { SDL_Surface *bmp = CMessage::drawDialogBox(256, 90 + 50 * SEL->current->mapHeader->howManyTeams); graphics->fonts[FONT_MEDIUM]->renderTextCenter(bmp, CGI->generaltexth->allTexts[657], Colors::YELLOW, Point(128, 30)); for(int i = 0; i < SEL->current->mapHeader->howManyTeams; i++) { std::vector flags; std::string hlp = CGI->generaltexth->allTexts[656]; //Team %d hlp.replace(hlp.find("%d"), 2, boost::lexical_cast(i+1)); graphics->fonts[FONT_SMALL]->renderTextCenter(bmp, hlp, Colors::WHITE, Point(128, 65 + 50 * i)); for(int j = 0; j < PlayerColor::PLAYER_LIMIT_I; j++) if((SEL->current->mapHeader->players[j].canHumanPlay || SEL->current->mapHeader->players[j].canComputerPlay) && SEL->current->mapHeader->players[j].team == TeamID(i)) flags.push_back(j); int curx = 128 - 9*flags.size(); for(auto & flag : flags) { IImage * icon = sFlags->getImage(flag,0); icon->draw(bmp, curx, 75 + 50*i); icon->decreaseRef(); curx += 18; } } GH.pushInt(new CInfoPopup(bmp, true)); } void InfoCard::toggleChat() { setChat(!chatOn); } void InfoCard::setChat(bool activateChat) { if(chatOn == activateChat) return; assert(active); if(activateChat) { mapDescription->disable(); chat->enable(); playerListBg->enable(); } else { mapDescription->enable(); chat->disable(); playerListBg->disable(); } chatOn = activateChat; GH.totalRedraw(); } OptionsTab::OptionsTab(): turnDuration(nullptr) { OBJ_CONSTRUCTION; bg = new CPicture("ADVOPTBK", 0, 6); pos = bg->pos; if(SEL->screenType == CMenuScreen::newGame) turnDuration = new CSlider(Point(55, 551), 194, std::bind(&OptionsTab::setTurnLength, this, _1), 1, 11, 11, true, CSlider::BLUE); } OptionsTab::~OptionsTab() { } void OptionsTab::showAll(SDL_Surface * to) { CIntObject::showAll(to); printAtMiddleLoc(CGI->generaltexth->allTexts[515], 222, 30, FONT_BIG, Colors::YELLOW, to); printAtMiddleWBLoc(CGI->generaltexth->allTexts[516], 222, 68, FONT_SMALL, 300, Colors::WHITE, to); //Select starting options, handicap, and name for each player in the game. printAtMiddleWBLoc(CGI->generaltexth->allTexts[517], 107, 110, FONT_SMALL, 100, Colors::YELLOW, to); //Player Name Handicap Type printAtMiddleWBLoc(CGI->generaltexth->allTexts[518], 197, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Town printAtMiddleWBLoc(CGI->generaltexth->allTexts[519], 273, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Hero printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Bonus printAtMiddleLoc(CGI->generaltexth->allTexts[521], 222, 538, FONT_SMALL, Colors::YELLOW, to); // Player Turn Duration if (turnDuration) printAtMiddleLoc(CGI->generaltexth->turnDurations[turnDuration->getValue()], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value } void OptionsTab::nextCastle( PlayerColor player, int dir ) { if(SEL->isGuest()) { SEL->postRequest(RequestOptionsChange::TOWN, dir); return; } PlayerSettings &s = SEL->sInfo.playerInfos[player]; si16 &cur = s.castle; auto & allowed = SEL->current->mapHeader->players[s.color.getNum()].allowedFactions; if (cur == PlayerSettings::NONE) //no change return; if (cur == PlayerSettings::RANDOM) //first/last available { if (dir > 0) cur = *allowed.begin(); //id of first town else cur = *allowed.rbegin(); //id of last town } else // next/previous available { if ( (cur == *allowed.begin() && dir < 0 ) || (cur == *allowed.rbegin() && dir > 0) ) cur = -1; else { assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range auto iter = allowed.find(cur); std::advance(iter, dir); cur = *iter; } } if(s.hero >= 0 && !SEL->current->mapHeader->players[s.color.getNum()].hasCustomMainHero()) // remove hero unless it set to fixed one in map editor { usedHeroes.erase(s.hero); // restore previously selected hero back to available pool s.hero = PlayerSettings::RANDOM; } if(cur < 0 && s.bonus == PlayerSettings::RESOURCE) s.bonus = PlayerSettings::RANDOM; entries[player]->selectButtons(); SEL->propagateOptions(); entries[player]->update(); redraw(); } void OptionsTab::nextHero( PlayerColor player, int dir ) { if(SEL->isGuest()) { SEL->postRequest(RequestOptionsChange::HERO, dir); return; } PlayerSettings &s = SEL->sInfo.playerInfos[player]; int old = s.hero; if (s.castle < 0 || s.playerID == PlayerSettings::PLAYER_AI || s.hero == PlayerSettings::NONE) return; if (s.hero == PlayerSettings::RANDOM) // first/last available { int max = CGI->heroh->heroes.size(), min = 0; s.hero = nextAllowedHero(player, min,max,0,dir); } else { if(dir > 0) s.hero = nextAllowedHero(player, s.hero, CGI->heroh->heroes.size(), 1, dir); else s.hero = nextAllowedHero(player, -1, s.hero, 1, dir); // min needs to be -1 -- hero at index 0 would be skipped otherwise } if(old != s.hero) { usedHeroes.erase(old); usedHeroes.insert(s.hero); entries[player]->update(); redraw(); } SEL->propagateOptions(); } int OptionsTab::nextAllowedHero( PlayerColor player, int min, int max, int incl, int dir ) { if(dir>0) { for(int i=min+incl; i<=max-incl; i++) if(canUseThisHero(player, i)) return i; } else { for(int i=max-incl; i>=min+incl; i--) if(canUseThisHero(player, i)) return i; } return -1; } bool OptionsTab::canUseThisHero( PlayerColor player, int ID ) { return CGI->heroh->heroes.size() > ID && SEL->sInfo.playerInfos[player].castle == CGI->heroh->heroes[ID]->heroClass->faction && !vstd::contains(usedHeroes, ID) && SEL->current->mapHeader->allowedHeroes[ID]; } void OptionsTab::nextBonus( PlayerColor player, int dir ) { if(SEL->isGuest()) { SEL->postRequest(RequestOptionsChange::BONUS, dir); return; } PlayerSettings &s = SEL->sInfo.playerInfos[player]; PlayerSettings::Ebonus &ret = s.bonus = static_cast(static_cast(s.bonus) + dir); if (s.hero==PlayerSettings::NONE && !SEL->current->mapHeader->players[s.color.getNum()].heroesNames.size() && ret==PlayerSettings::ARTIFACT) //no hero - can't be artifact { if (dir<0) ret=PlayerSettings::RANDOM; else ret=PlayerSettings::GOLD; } if(ret > PlayerSettings::RESOURCE) ret = PlayerSettings::RANDOM; if(ret < PlayerSettings::RANDOM) ret = PlayerSettings::RESOURCE; if (s.castle==PlayerSettings::RANDOM && ret==PlayerSettings::RESOURCE) //random castle - can't be resource { if (dir<0) ret=PlayerSettings::GOLD; else ret=PlayerSettings::RANDOM; } SEL->propagateOptions(); entries[player]->update(); redraw(); } void OptionsTab::recreate() { for(auto & elem : entries) { delete elem.second; } entries.clear(); usedHeroes.clear(); OBJ_CONSTRUCTION_CAPTURING_ALL; for(auto it = SEL->sInfo.playerInfos.begin(); it != SEL->sInfo.playerInfos.end(); ++it) { entries.insert(std::make_pair(it->first, new PlayerOptionsEntry(this, it->second))); const std::vector &heroes = SEL->current->mapHeader->players[it->first.getNum()].heroesNames; for(auto & heroe : heroes) if(heroe.heroId >= 0)//in VCMI map format heroId = -1 means random hero usedHeroes.insert(heroe.heroId); } } void OptionsTab::setTurnLength( int npos ) { static const int times[] = {1, 2, 4, 6, 8, 10, 15, 20, 25, 30, 0}; vstd::amin(npos, ARRAY_COUNT(times) - 1); SEL->sInfo.turnTime = times[npos]; redraw(); } void OptionsTab::flagPressed( PlayerColor color ) { PlayerSettings &clicked = SEL->sInfo.playerInfos[color]; PlayerSettings *old = nullptr; if(SEL->playerNames.size() == 1) //single player -> just swap { if(color == playerColor) //that color is already selected, no action needed return; old = &SEL->sInfo.playerInfos[playerColor]; swapPlayers(*old, clicked); } else { //identify clicked player int clickedNameID = clicked.playerID; //human is a number of player, zero means AI if(clickedNameID > 0 && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place { PlayerSettings &restPos = SEL->sInfo.playerInfos[playerToRestore.color]; SEL->setPlayer(restPos, playerToRestore.id); playerToRestore.reset(); } int newPlayer; //which player will take clicked position //who will be put here? if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player { newPlayer = SEL->getIdOfFirstUnallocatedPlayer(); if(!newPlayer) //no "free" player -> get just first one newPlayer = SEL->playerNames.begin()->first; } else //human clicked -> take next { auto i = SEL->playerNames.find(clickedNameID); //clicked one i++; //player AFTER clicked one if(i != SEL->playerNames.end()) newPlayer = i->first; else newPlayer = 0; //AI if we scrolled through all players } SEL->setPlayer(clicked, newPlayer); //put player //if that player was somewhere else, we need to replace him with computer if(newPlayer) //not AI { for(auto i = SEL->sInfo.playerInfos.begin(); i != SEL->sInfo.playerInfos.end(); i++) { int curNameID = i->second.playerID; if(i->first != color && curNameID == newPlayer) { assert(i->second.playerID); playerToRestore.color = i->first; playerToRestore.id = newPlayer; SEL->setPlayer(i->second, 0); //set computer old = &i->second; break; } } } } entries[clicked.color]->selectButtons(); if(old) { entries[old->color]->selectButtons(); if(old->hero >= 0) usedHeroes.erase(old->hero); old->hero = entries[old->color]->pi.defaultHero(); entries[old->color]->update(); // update previous frame images in case entries were auto-updated } SEL->propagateOptions(); GH.totalRedraw(); } OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSettings &S) : pi(SEL->current->mapHeader->players[S.color.getNum()]), s(S) { OBJ_CONSTRUCTION; defActions |= SHARE_POS; int serial = 0; for(int g=0; g < s.color.getNum(); ++g) { PlayerInfo &itred = SEL->current->mapHeader->players[g]; if( itred.canComputerPlay || itred.canHumanPlay) serial++; } pos.x += 54; pos.y += 122 + serial*50; 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"}; bg = new CPicture(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true); if(SEL->screenType == CMenuScreen::newGame) { btns[0] = new CButton(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&OptionsTab::nextCastle, owner, s.color, -1)); btns[1] = new CButton(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&OptionsTab::nextCastle, owner, s.color, +1)); btns[2] = new CButton(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&OptionsTab::nextHero, owner, s.color, -1)); btns[3] = new CButton(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&OptionsTab::nextHero, owner, s.color, +1)); btns[4] = new CButton(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&OptionsTab::nextBonus, owner, s.color, -1)); btns[5] = new CButton(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&OptionsTab::nextBonus, owner, s.color, +1)); } else for(auto & elem : btns) elem = nullptr; selectButtons(); assert(SEL->current && SEL->current->mapHeader); const PlayerInfo &p = SEL->current->mapHeader->players[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; if(SEL->screenType != CMenuScreen::scenarioInfo && SEL->current->mapHeader->players[s.color.getNum()].canHumanPlay) { flag = new CButton(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], std::bind(&OptionsTab::flagPressed, owner, s.color)); flag->hoverable = true; flag->block(SEL->isGuest()); } else flag = nullptr; town = new SelectedBox(Point(119, 2), s, TOWN); hero = new SelectedBox(Point(195, 2), s, HERO); bonus = new SelectedBox(Point(271, 2), s, BONUS); } void OptionsTab::PlayerOptionsEntry::showAll(SDL_Surface * to) { CIntObject::showAll(to); printAtMiddleLoc(s.name, 55, 10, FONT_SMALL, Colors::WHITE, to); printAtMiddleWBLoc(CGI->generaltexth->arraytxt[206+whoCanPlay], 28, 39, FONT_TINY, 50, Colors::WHITE, to); } void OptionsTab::PlayerOptionsEntry::update() { town->update(); hero->update(); bonus->update(); } void OptionsTab::PlayerOptionsEntry::selectButtons() { if(!btns[0]) return; if( (pi.defaultCastle() != -1) //fixed tow || (SEL->isGuest() && s.color != playerColor)) //or not our player { btns[0]->disable(); btns[1]->disable(); } else { btns[0]->enable(); btns[1]->enable(); } if( (pi.defaultHero() != -1 || !s.playerID || s.castle < 0) //fixed hero || (SEL->isGuest() && s.color != playerColor))//or not our player { btns[2]->disable(); btns[3]->disable(); } else { btns[2]->enable(); btns[3]->enable(); } if(SEL->isGuest() && s.color != playerColor)//or not our player { btns[4]->disable(); btns[5]->disable(); } else { btns[4]->enable(); btns[5]->enable(); } } 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::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::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::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::CPregameTooltipBox::CPregameTooltipBox(CPlayerSettingsHelper & helper): CWindowObject(BORDERED | RCLICK_POPUP), CPlayerSettingsHelper(helper) { OBJ_CONSTRUCTION_CAPTURING_ALL; 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::CPregameTooltipBox::genHeader() { new CFilledTexture("DIBOXBCK", pos); updateShadow(); new CLabel(pos.w / 2 + 8, 21, FONT_MEDIUM, CENTER, Colors::YELLOW, getTitle()); new CLabel(pos.w / 2, 88, FONT_SMALL, CENTER, Colors::WHITE, getSubtitle()); new CAnimImage(getImageName(), getImageIndex(), 0, pos.w / 2 - 24, 45); } void OptionsTab::CPregameTooltipBox::genTownWindow() { pos = Rect(0, 0, 228, 290); genHeader(); new CLabel(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]); std::vector 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)); } new CComponentBox(components, Rect(10, 140, pos.w - 20, 140)); } void OptionsTab::CPregameTooltipBox::genHeroWindow() { pos = Rect(0, 0, 292, 226); genHeader(); // specialty new CAnimImage("UN44", CGI->heroh->heroes[settings.hero]->imageIndex, 0, pos.w / 2 - 22, 134); new CLabel(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]); new CLabel(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, CGI->heroh->heroes[settings.hero]->specName); } void OptionsTab::CPregameTooltipBox::genBonusWindow() { pos = Rect(0, 0, 228, 162); genHeader(); new 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; image = new CAnimImage(getImageName(), getImageIndex()); subtitle = new 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->current->mapHeader->players[settings.color.getNum()].hasCustomMainHero() && CPlayerSettingsHelper::type == HERO) return; GH.pushInt(new CPregameTooltipBox(*this)); } } CScenarioInfo::CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *startInfo) { OBJ_CONSTRUCTION_CAPTURING_ALL; for(auto it = startInfo->playerInfos.cbegin(); it != startInfo->playerInfos.cend(); ++it) { if(it->second.playerID) { playerColor = it->first; } } pos.w = 762; pos.h = 584; center(pos); assert(LOCPLINT); sInfo = *LOCPLINT->cb->getStartInfo(); assert(!SEL->current); current = mapInfoFromGame(); setPlayersFromGame(); screenType = CMenuScreen::scenarioInfo; card = new InfoCard(); opt = new OptionsTab(); opt->recreate(); card->changeSelection(current); card->difficulty->setSelected(startInfo->difficulty); back = new CButton(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE); } CScenarioInfo::~CScenarioInfo() { delete current; } bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb) { const CMapHeader * a = aaa->mapHeader.get(), * b = bbb->mapHeader.get(); if(a && b) //if we are sorting scenarios { switch (sortBy) { case _format: //by map format (RoE, WoG, etc) return (a->versionversion); 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 (playerAmntAwidthwidth); 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); } } } CMultiMode::CMultiMode() { OBJ_CONSTRUCTION_CAPTURING_ALL; bg = new CPicture("MUPOPUP.bmp"); bg->convertToScreenBPP(); //so we could draw without problems blitAt(CPicture("MUMAP.bmp"), 16, 77, *bg); //blit img pos = bg->center(); //center, window has size of bg graphic bar = new CGStatusBar(new CPicture(Rect(7, 465, 440, 18), 0));//226, 472 txt = new CTextInput(Rect(19, 436, 334, 16), *bg); txt->setText(settings["general"]["playerName"].String()); //Player btns[0] = new CButton(Point(373, 78), "MUBHOT.DEF", CGI->generaltexth->zelp[266], std::bind(&CMultiMode::openHotseat, this)); btns[1] = new CButton(Point(373, 78 + 57*1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), std::bind(&CMultiMode::hostTCP, this)); btns[2] = new CButton(Point(373, 78 + 57*2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), std::bind(&CMultiMode::joinTCP, this)); btns[6] = new CButton(Point(373, 424), "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&](){ GH.popIntTotally(this);}, SDLK_ESCAPE); } void CMultiMode::openHotseat() { GH.pushInt(new CHotSeatPlayers(txt->text)); } void CMultiMode::hostTCP() { Settings name = settings.write["general"]["playerName"]; name->String() = txt->text; GH.popIntTotally(this); if(settings["session"]["donotstartserver"].Bool()) GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_HOST)); else GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_HOST)); } void CMultiMode::joinTCP() { Settings name = settings.write["general"]["playerName"]; name->String() = txt->text; GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_GUEST)); } CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer) { OBJ_CONSTRUCTION_CAPTURING_ALL; bg = new CPicture("MUHOTSEA.bmp"); pos = bg->center(); //center, window has size of bg graphic std::string text = CGI->generaltexth->allTexts[446]; boost::replace_all(text, "\t","\n"); Rect boxRect(25, 20, 315, 50); title = new CTextBox(text, boxRect, 0, FONT_BIG, CENTER, Colors::WHITE);//HOTSEAT Please enter names for(int i = 0; i < ARRAY_COUNT(txt); i++) { txt[i] = new CTextInput(Rect(60, 85 + i*30, 280, 16), *bg); txt[i]->cb += std::bind(&CHotSeatPlayers::onChange, this, _1); } ok = new CButton(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CHotSeatPlayers::enterSelectionScreen, this), SDLK_RETURN); cancel = new CButton(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472 txt[0]->setText(firstPlayer, true); txt[0]->giveFocus(); } void CHotSeatPlayers::onChange(std::string newText) { size_t namesCount = 0; for(auto & elem : txt) if(!elem->text.empty()) namesCount++; ok->block(namesCount < 2); } void CHotSeatPlayers::enterSelectionScreen() { std::map names; for(int i = 0, j = 1; i < ARRAY_COUNT(txt); i++) if(txt[i]->text.length()) names[j++] = txt[i]->text; Settings name = settings.write["general"]["playerName"]; name->String() = names.begin()->second; GH.popInts(2); //pop MP mode window and this GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_HOT_SEAT, &names)); } void CBonusSelection::init() { highlightedRegion = nullptr; ourHeader.reset(); diffLb = nullptr; diffRb = nullptr; bonuses = nullptr; selectedMap = 0; // Initialize start info startInfo.mapname = ourCampaign->camp->header.filename; startInfo.mode = StartInfo::CAMPAIGN; startInfo.campState = ourCampaign; startInfo.turnTime = 0; OBJ_CONSTRUCTION_CAPTURING_ALL; 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(); background = BitmapHandler::loadBitmap(bgNames[ourCampaign->camp->header.mapVersion]); pos.h = background->h; pos.w = background->w; center(); SDL_Surface * panel = BitmapHandler::loadBitmap("CAMPBRF.BMP"); blitAt(panel, 456, 6, background); startB = new CButton(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), SDLK_RETURN); restartB = new CButton(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN); backB = new CButton(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE); //campaign name if (ourCampaign->camp->header.name.length()) graphics->fonts[FONT_BIG]->renderTextLeft(background, ourCampaign->camp->header.name, Colors::YELLOW, Point(481, 28)); else graphics->fonts[FONT_BIG]->renderTextLeft(background, CGI->generaltexth->allTexts[508], Colors::YELLOW, Point(481, 28)); //map size icon sizes = new CAnimImage("SCNRMPSZ",4,0,735, 26); sizes->recActions &= ~(SHOWALL | UPDATE);//explicit draw //campaign description graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[38], Colors::YELLOW, Point(481, 63)); campaignDescription = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1); //campaignDescription->showAll(background); //map description mapDescription = new CTextBox("", Rect(480, 280, 286, 117), 1); //bonus choosing graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432)); bonuses = new CToggleGroup(std::bind(&CBonusSelection::selectBonus, this, _1)); //set left part of window bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap); for(int g = 0; g < ourCampaign->camp->scenarios.size(); ++g) { if(ourCampaign->camp->conquerable(g)) { regions.push_back(new CRegion(this, true, true, g)); regions[regions.size()-1]->rclickText = ourCampaign->camp->scenarios[g].regionText; if(highlightedRegion == nullptr) { if(!isCurrentMapConquerable || (isCurrentMapConquerable && g == *ourCampaign->currentMap)) { highlightedRegion = regions.back(); selectMap(g, true); } } } else if (ourCampaign->camp->scenarios[g].conquered) //display as striped { regions.push_back(new CRegion(this, false, false, g)); regions[regions.size()-1]->rclickText = ourCampaign->camp->scenarios[g].regionText; } } //allies / enemies graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[390] + ":", Colors::WHITE, Point(486, 407)); graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[391] + ":", Colors::WHITE, Point(619, 407)); SDL_FreeSurface(panel); //difficulty std::vector difficulty; boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" ")); graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, difficulty.back(), Colors::WHITE, Point(689, 432)); //difficulty pics for (size_t b=0; b < diffPics.size(); ++b) { diffPics[b] = new CAnimImage("GSPBUT" + boost::lexical_cast(b+3) + ".DEF", 0, 0, 709, 455); diffPics[b]->recActions &= ~(SHOWALL | UPDATE);//explicit draw } //difficulty selection buttons if (ourCampaign->camp->header.difficultyChoosenByPlayer) { diffLb = new CButton(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this)); diffRb = new CButton(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this)); } //load miniflags sFlags = std::make_shared("ITGFLAGS.DEF"); sFlags->load(); } CBonusSelection::CBonusSelection(std::shared_ptr _ourCampaign) : ourCampaign(_ourCampaign) { init(); } CBonusSelection::CBonusSelection(const std::string & campaignFName) { ourCampaign = std::make_shared(CCampaignHandler::getCampaign(campaignFName)); init(); } CBonusSelection::~CBonusSelection() { SDL_FreeSurface(background); sFlags->unload(); } void CBonusSelection::goBack() { GH.popIntTotally(this); } void CBonusSelection::showAll(SDL_Surface * to) { blitAt(background, pos.x, pos.y, to); CIntObject::showAll(to); show(to); if (pos.h != to->h || pos.w != to->w) CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15); } 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::selectMap(int mapNr, bool initialSelect) { if(initialSelect || selectedMap != mapNr) { // initialize restart / start button if(!ourCampaign->currentMap || *ourCampaign->currentMap != mapNr) { // draw start button restartB->disable(); startB->enable(); if (!ourCampaign->mapsConquered.empty()) backB->block(true); else backB->block(false); } else { // draw restart button startB->disable(); restartB->enable(); backB->block(false); } startInfo.difficulty = ourCampaign->camp->scenarios[mapNr].difficulty; selectedMap = mapNr; selectedBonus = boost::none; std::string scenarioName = ourCampaign->camp->header.filename.substr(0, ourCampaign->camp->header.filename.find('.')); boost::to_lower(scenarioName); scenarioName += ':' + boost::lexical_cast(selectedMap); //get header std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second; auto buffer = reinterpret_cast(headerStr.data()); ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName); std::map names; names[1] = settings["general"]["playerName"].String(); updateStartInfo(ourCampaign->camp->header.filename, startInfo, ourHeader, names); mapDescription->setText(ourHeader->description); updateBonusSelection(); GH.totalRedraw(); } } void CBonusSelection::show(SDL_Surface * to) { //map name std::string mapName = ourHeader->name; if (mapName.length()) printAtLoc(mapName, 481, 219, FONT_BIG, Colors::YELLOW, to); else printAtLoc("Unnamed", 481, 219, FONT_BIG, Colors::YELLOW, to); //map description printAtLoc(CGI->generaltexth->allTexts[496], 481, 253, FONT_SMALL, Colors::YELLOW, to); mapDescription->showAll(to); //showAll because CTextBox has no show() //map size icon int temp; switch (ourHeader->width) { case 36: temp=0; break; case 72: temp=1; break; case 108: temp=2; break; case 144: temp=3; break; default: temp=4; break; } sizes->setFrame(temp); sizes->showAll(to); //flags int fx = 496 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]); int ex = 629 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]); TeamID myT; myT = ourHeader->players[playerColor.getNum()].team; for (auto i = startInfo.playerInfos.cbegin(); i != startInfo.playerInfos.cend(); i++) { int *myx = ((i->first == playerColor || ourHeader->players[i->first.getNum()].team == myT) ? &fx : &ex); IImage * flag = sFlags->getImage(i->first.getNum(),0); flag->draw(to, pos.x + *myx, pos.y + 405); *myx += flag->width(); flag->decreaseRef(); } //difficulty diffPics[startInfo.difficulty]->showAll(to); CIntObject::show(to); } void CBonusSelection::updateBonusSelection() { OBJ_CONSTRUCTION_CAPTURING_ALL; //graphics: //spell - SPELLBON.DEF //monster - TWCRPORT.DEF //building - BO*.BMP graphics //artifact - ARTIFBON.DEF //spell scroll - SPELLBON.DEF //prim skill - PSKILBON.DEF //sec skill - SSKILBON.DEF //resource - BORES.DEF //player - CREST58.DEF //hero - PORTRAITSLARGE (HPL###.BMPs) const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap]; const std::vector & bonDescs = scenario.travelOptions.bonusesToChoose; updateStartButtonState(-1); delete bonuses; bonuses = new CToggleGroup(std::bind(&CBonusSelection::selectBonus, this, _1)); static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF", "PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"}; 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(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 : startInfo.playerInfos) { if (elem.second.playerID) { faction = elem.second.castle; break; } } assert(faction != -1); BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set()); 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 > toPrint; //primary skills to be listed const ui8* ptr = reinterpret_cast(&bonDescs[i].info2); for (int g=0; 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[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(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 = ourCampaign->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", ourCampaign->camp->scenarios[bonDescs[i].info2].scenarioName); //scenario } 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); //hero's name } break; } CToggleButton *bonusButton = new CToggleButton(Point(475 + i*68, 455), "", CButton::tooltip(desc, desc)); if (picNumber != -1) picName += ":" + boost::lexical_cast(picNumber); auto anim = std::make_shared(); anim->setCustom(picName, 0); bonusButton->setImage(anim); const SDL_Color brightYellow = { 242, 226, 110, 0 }; bonusButton->borderColor = boost::make_optional(brightYellow); bonuses->addToggle(i, bonusButton); } // set bonus if already chosen if(vstd::contains(ourCampaign->chosenCampaignBonuses, selectedMap)) { bonuses->setSelected(ourCampaign->chosenCampaignBonuses[selectedMap]); } } void CBonusSelection::updateCampaignState() { ourCampaign->currentMap = boost::make_optional(selectedMap); if (selectedBonus) ourCampaign->chosenCampaignBonuses[selectedMap] = *selectedBonus; } void CBonusSelection::startMap() { auto si = new StartInfo(startInfo); auto showPrologVideo = [=]() { auto exitCb = [=]() { logGlobal->info("Starting scenario %d", selectedMap); CGP->showLoadingScreen(std::bind(&startGame, si, (CConnection *)nullptr)); }; const CCampaignScenario & scenario = ourCampaign->camp->scenarios[selectedMap]; 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], [=]() { updateCampaignState(); endGame(); GH.curInt = CGPreGame::create(); showPrologVideo(); }, 0); } else { updateCampaignState(); showPrologVideo(); } } void CBonusSelection::restartMap() { GH.popInt(this); LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]() { updateCampaignState(); auto si = new StartInfo(startInfo); SDL_Event event; event.type = SDL_USEREVENT; event.user.code = PREPARE_RESTART_CAMPAIGN; event.user.data1 = si; SDL_PushEvent(&event); }, 0); } void CBonusSelection::selectBonus(int id) { // Total redraw is needed because the border around the bonus images // have to be undrawn/drawn. if (!selectedBonus || *selectedBonus != id) { selectedBonus = boost::make_optional(id); GH.totalRedraw(); updateStartButtonState(id); } const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap]; const std::vector & bonDescs = scenario.travelOptions.bonusesToChoose; if (bonDescs[id].type == CScenarioTravel::STravelBonus::HERO) { std::map names; names[1] = settings["general"]["playerName"].String(); for(auto & elem : startInfo.playerInfos) { if(elem.first == PlayerColor(bonDescs[id].info1)) ::setPlayer(elem.second, 1, names); else ::setPlayer(elem.second, 0, names); } } } void CBonusSelection::increaseDifficulty() { startInfo.difficulty = std::min(startInfo.difficulty + 1, 4); } void CBonusSelection::decreaseDifficulty() { startInfo.difficulty = std::max(startInfo.difficulty - 1, 0); } void CBonusSelection::updateStartButtonState(int selected) { if(selected == -1) { startB->block(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size()); } else if(startB->isBlocked()) { startB->block(false); } } CBonusSelection::CRegion::CRegion( CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber ) : owner(_owner), accessible(_accessible), selectable(_selectable), myNumber(_myNumber) { OBJ_CONSTRUCTION; addUsedEvents(LCLICK | RCLICK); 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 & campDsc = owner->campDescriptions[owner->ourCampaign->camp->header.mapVersion]; const SCampPositions::SRegionDesc & desc = campDsc.regions[myNumber]; pos.x += desc.xpos; pos.y += desc.ypos; //loading of graphics std::string prefix = campDsc.campPrefix + desc.infix + "_"; std::string suffix = colors[campDsc.colorSuffixLength - 1][owner->ourCampaign->camp->scenarios[myNumber].regionColor]; static const std::string infix [] = {"En", "Se", "Co"}; for (int g = 0; g < ARRAY_COUNT(infix); g++) { graphics[g] = BitmapHandler::loadBitmap(prefix + infix[g] + suffix + ".BMP"); } pos.w = graphics[0]->w; pos.h = graphics[0]->h; } CBonusSelection::CRegion::~CRegion() { for (auto & elem : graphics) { SDL_FreeSurface(elem); } } 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(graphics[0], GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) ) { owner->selectMap(myNumber, false); owner->highlightedRegion = this; parent->showAll(screen); } } void CBonusSelection::CRegion::clickRight( tribool down, bool previousState ) { //show r-click text if( down && !CSDL_Ext::isTransparent(graphics[0], GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) && rclickText.size() ) { CRClickPopup::createAndPush(rclickText); } } void CBonusSelection::CRegion::show(SDL_Surface * to) { //const SCampPositions::SRegionDesc & desc = owner->campDescriptions[owner->ourCampaign->camp->header.mapVersion].regions[myNumber]; if (!accessible) { //show as striped blitAtLoc(graphics[2], 0, 0, to); } else if (this == owner->highlightedRegion) { //show as selected blitAtLoc(graphics[1], 0, 0, to); } else { //show as not selected selected blitAtLoc(graphics[0], 0, 0, to); } } CSavingScreen::CSavingScreen(bool hotseat) : CSelectionScreen( CMenuScreen::saveGame, hotseat ? CMenuScreen::MULTI_HOT_SEAT : (LOCPLINT->cb->getStartInfo()->mode == StartInfo::CAMPAIGN ? CMenuScreen::SINGLE_CAMPAIGN : CMenuScreen::SINGLE_PLAYER) ) { ourGame = mapInfoFromGame(); sInfo = *LOCPLINT->cb->getStartInfo(); setPlayersFromGame(); } CSavingScreen::~CSavingScreen() { } ISelectionScreenInfo::ISelectionScreenInfo(const std::map * Names) { gameMode = CMenuScreen::SINGLE_PLAYER; screenType = CMenuScreen::mainMenu; assert(!SEL); SEL = this; current = nullptr; if(Names && !Names->empty()) //if have custom set of player names - use it playerNames = *Names; else playerNames[1] = settings["general"]["playerName"].String(); //by default we have only one player and his name is "Player" (or whatever the last used name was) } ISelectionScreenInfo::~ISelectionScreenInfo() { assert(SEL == this); SEL = nullptr; } void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr & mapHeader) { ::updateStartInfo(filename, sInfo, mapHeader, playerNames); } void ISelectionScreenInfo::setPlayer(PlayerSettings &pset, ui8 player) { ::setPlayer(pset, player, playerNames); } ui8 ISelectionScreenInfo::getIdOfFirstUnallocatedPlayer() { for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++) if(!sInfo.getPlayersSettings(i->first)) // return i->first; return 0; } bool ISelectionScreenInfo::isGuest() const { return gameMode == CMenuScreen::MULTI_NETWORK_GUEST; } bool ISelectionScreenInfo::isHost() const { return gameMode == CMenuScreen::MULTI_NETWORK_HOST; } void ChatMessage::apply(CSelectionScreen *selScreen) { selScreen->card->chat->addNewMessage(playerName + ": " + message); GH.totalRedraw(); } void QuitMenuWithoutStarting::apply(CSelectionScreen *selScreen) { if(!selScreen->ongoingClosing) { *selScreen->serv << this; //resend to confirm GH.popIntTotally(selScreen); //will wait with deleting us before this thread ends } vstd::clear_pointer(selScreen->serv); } void PlayerJoined::apply(CSelectionScreen *selScreen) { //assert(SEL->playerNames.size() == connectionID); //temporary, TODO when player exits SEL->playerNames[connectionID] = playerName; //put new player in first slot with AI for(auto & elem : SEL->sInfo.playerInfos) { if(!elem.second.playerID && !elem.second.compOnly) { selScreen->setPlayer(elem.second, connectionID); selScreen->opt->entries[elem.second.color]->selectButtons(); break; } } selScreen->propagateNames(); selScreen->propagateOptions(); selScreen->toggleTab(selScreen->curTab); GH.totalRedraw(); } void SelectMap::apply(CSelectionScreen *selScreen) { if(selScreen->isGuest()) { free = false; selScreen->changeSelection(mapInfo); } } void UpdateStartOptions::apply(CSelectionScreen *selScreen) { if(!selScreen->isGuest()) return; selScreen->setSInfo(*options); } void PregameGuiAction::apply(CSelectionScreen *selScreen) { if(!selScreen->isGuest()) return; switch(action) { case NO_TAB: selScreen->toggleTab(selScreen->curTab); break; case OPEN_OPTIONS: selScreen->toggleTab(selScreen->opt); break; case OPEN_SCENARIO_LIST: selScreen->toggleTab(selScreen->sel); break; case OPEN_RANDOM_MAP_OPTIONS: selScreen->toggleTab(selScreen->randMapTab); break; } } void RequestOptionsChange::apply(CSelectionScreen *selScreen) { if(!selScreen->isHost()) return; PlayerColor color = selScreen->sInfo.getPlayersSettings(playerID)->color; switch(what) { case TOWN: selScreen->opt->nextCastle(color, direction); break; case HERO: selScreen->opt->nextHero(color, direction); break; case BONUS: selScreen->opt->nextBonus(color, direction); break; } } void PlayerLeft::apply(CSelectionScreen *selScreen) { if(selScreen->isGuest()) return; SEL->playerNames.erase(playerID); if(PlayerSettings *s = selScreen->sInfo.getPlayersSettings(playerID)) //it's possible that player was unallocated { selScreen->setPlayer(*s, 0); selScreen->opt->entries[s->color]->selectButtons(); } selScreen->propagateNames(); selScreen->propagateOptions(); GH.totalRedraw(); } void PlayersNames::apply(CSelectionScreen *selScreen) { if(selScreen->isGuest()) selScreen->playerNames = playerNames; } void StartWithCurrentSettings::apply(CSelectionScreen *selScreen) { startingInfo.reset(); startingInfo.serv = selScreen->serv; startingInfo.sInfo = new StartInfo(selScreen->sInfo); if(!selScreen->ongoingClosing) { *selScreen->serv << this; //resend to confirm } selScreen->serv = nullptr; //hide it so it won't be deleted vstd::clear_pointer(selScreen->serverHandlingThread); //detach us saveGameName.clear(); CGP->showLoadingScreen(std::bind(&startGame, startingInfo.sInfo, startingInfo.serv)); throw 666; //EVIL, EVIL, EVIL workaround to kill thread (does "goto catch" outside listening loop) } CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode &config ) { OBJ_CONSTRUCTION_CAPTURING_ALL; 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; hoverLabel = nullptr; if (status != CCampaignScreen::DISABLED) { addUsedEvents(LCLICK | HOVER); new CPicture(config["image"].String()); hoverLabel = new CLabel(pos.w / 2, pos.h + 20, FONT_MEDIUM, CENTER, Colors::YELLOW, ""); parent->addChild(hoverLabel); } if (status == CCampaignScreen::COMPLETED) new CPicture("CAMPCHK"); } void CCampaignScreen::CCampaignButton::clickLeft(tribool down, bool previousState) { if (down) { // Close running video and open the selected campaign CCS->videoh->close(); GH.pushInt( new CBonusSelection(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(" "); } } 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(); } } CButton* CCampaignScreen::createExitButton(const JsonNode& button) { std::pair help; if (!button["help"].isNull() && button["help"].Float() > 0) help = CGI->generaltexth->zelp[button["help"].Float()]; std::function close = std::bind(&CGuiHandler::popIntTotally, &GH, this); return new CButton(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float()); } CCampaignScreen::CCampaignScreen(const JsonNode &config) { OBJ_CONSTRUCTION_CAPTURING_ALL; for(const JsonNode& node : config["images"].Vector()) images.push_back(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()) { CButton * back = createExitButton(config["exitbutton"]); back->hoverable = true; } for(const JsonNode& node : config["items"].Vector()) campButtons.push_back(new CCampaignButton(node)); } void CCampaignScreen::showAll(SDL_Surface *to) { CIntObject::showAll(to); if (pos.h != to->h || pos.w != to->w) CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15); } void CGPreGame::showLoadingScreen(std::function loader) { if (GH.listInt.size() && GH.listInt.front() == CGP) //pregame active CGP->removeFromGui(); GH.pushInt(new CLoadingScreen(loader)); } std::string CLoadingScreen::getBackground() { const auto & conf = CGPreGameConfig::get().getConfig()["loading"].Vector(); if(conf.empty()) { return "loadbar"; } else { return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String(); } } CLoadingScreen::CLoadingScreen(std::function 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); } CPrologEpilogVideo::CPrologEpilogVideo( CCampaignScenario::SScenarioPrologEpilog _spe, std::function callback ): CWindowObject(BORDERED), spe(_spe), positionCounter(0), voiceSoundHandle(-1), exitCb(callback) { OBJ_CONSTRUCTION_CAPTURING_ALL; 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); voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo)); text = new 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(); } CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EGameMode mode) { OBJ_CONSTRUCTION_CAPTURING_ALL; bg = new CPicture("MUDIALOG.bmp"); // address background pos = bg->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212) Rect boxRect(20, 20, 205, 50); title = new CTextBox("Enter address:", boxRect, 0, FONT_BIG, CENTER, Colors::WHITE); address = new CTextInput(Rect(25, 68, 175, 16), *bg); address->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1); port = new CTextInput(Rect(25, 115, 175, 16), *bg); port->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1); port->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535); ok = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this, mode), SDLK_RETURN); cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0)); port->setText(CServerHandler::getDefaultPortStr(), true); address->setText(settings["server"]["server"].String(), true); address->giveFocus(); } void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EGameMode mode) { std::string textAddress = address->text; std::string textPort = port->text; GH.popIntTotally(this); GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, boost::lexical_cast(textPort))); } void CSimpleJoinScreen::onChange(const std::string & newText) { ok->block(address->text.empty() || port->text.empty()); }