#include "StdInc.h" #include "CPreGame.h" #include "../lib/Filesystem/CResourceLoader.h" #include "../lib/Filesystem/CFileInfo.h" #include "../lib/Filesystem/CCompressedStream.h" #include "../lib/CStopWatch.h" #include "UIFramework/SDL_Extensions.h" #include "CGameInfo.h" #include "UIFramework/CCursorHandler.h" #include "CAnimation.h" #include "CDefHandler.h" #include "../lib/CDefObjInfoHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CObjectHandler.h" #include "../lib/Map/CCampaignHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/JsonNode.h" #include "CMusicHandler.h" #include "CVideoHandler.h" #include "Graphics.h" #include "../lib/Connection.h" #include "../lib/VCMIDirs.h" #include "../lib/Map/CMap.h" #include "GUIClasses.h" #include "CPlayerInterface.h" #include "../CCallback.h" #include "CMessage.h" #include "../lib/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.h" #include "../lib/CThreadHelper.h" #include "../lib/CConfigHandler.h" #include "../lib/GameConstants.h" #include "UIFramework/CGuiHandler.h" #include "UIFramework/CIntObjectClasses.h" #include "../lib/Map/CMapService.h" /* * 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 * */ namespace fs = boost::filesystem; using boost::bind; using boost::ref; #if _MSC_VER >= 1600 //#define bind boost::bind //#define ref boost::ref #endif void startGame(StartInfo * options, CConnection *serv = NULL); CGPreGame * CGP = nullptr; ISelectionScreenInfo *SEL; static int 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(bool strong = true) { if(strong) { vstd::clear_pointer(serv); vstd::clear_pointer(sInfo); } else { serv = NULL; sInfo = NULL; } } } startingInfo; static void do_quit() { SDL_Event event; event.quit.type = SDL_QUIT; SDL_PushEvent(&event); } static CMapInfo *mapInfoFromGame() { CMapInfo * 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, TPlayerColor 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 CMapHeader * mapHeader, const std::map &playerNames) { sInfo.playerInfos.clear(); if(!mapHeader) { return; } sInfo.mapname = filename; playerColor = -1; 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[i]; pset.color = i; if(pinfo.canHumanPlay && namesIt != playerNames.cend()) setPlayer(pset, namesIt++->first, playerNames); else setPlayer(pset, 0, playerNames); pset.castle = pinfo.defaultCastle(); pset.hero = pinfo.defaultHero(); if(pinfo.customHeroID >= 0) { pset.hero = pinfo.customHeroID; if (!pinfo.mainHeroName.empty()) pset.heroName = pinfo.mainHeroName; else pset.heroName = CGI->heroh->heroes[pinfo.customHeroID]->name; if (pinfo.mainHeroPortrait >= 0) pset.heroPortrait = pinfo.mainHeroPortrait; else pset.heroPortrait = pinfo.customHeroID; } pset.handicap = 0; } } template class CApplyOnPG; class CBaseForPGApply { public: virtual void applyOnPG(CSelectionScreen *selScr, void *pack) const =0; virtual ~CBaseForPGApply(){}; template static CBaseForPGApply *getApplier(const U * t=NULL) { return new CApplyOnPG; } }; template class CApplyOnPG : public CBaseForPGApply { public: void applyOnPG(CSelectionScreen *selScr, void *pack) const { T *ptr = static_cast(pack); ptr->apply(selScr); } }; static CApplier *applier = NULL; 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(); BOOST_FOREACH(const JsonNode& node, config["items"].Vector()) menuNameToEntry.push_back(node["name"].String()); BOOST_FOREACH(const JsonNode& node, config["images"].Vector()) images.push_back(createPicture(node)); //Hardcoded entry menuNameToEntry.push_back("credits"); tabs = new CTabbedInt(boost::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(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 -> boost::function conversion for main menu static boost::function genCommand(CMenuScreen* menu, std::vector menuType, const std::string &string) { static const std::vector commandType = boost::assign::list_of ("to")("campaigns")("start")("load")("exit")("highscores"); static const std::vector gameType = boost::assign::list_of ("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 boost::bind(&CMenuScreen::switchToTab, menu, index2); } break; case 1://open campaign selection window { return boost::bind(&CGPreGame::openCampaignScreen, CGP, commands.front()); } break; case 2://start { switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin()) { case 0: return bind(&CGPreGame::openSel, CGP, CMenuScreen::newGame, CMenuScreen::SINGLE_PLAYER); case 1: return &pushIntT; case 2: return boost::bind(&CGPreGame::openSel, CGP, CMenuScreen::campaignList, CMenuScreen::SINGLE_PLAYER); case 3: return boost::function();//TODO: start tutorial } } break; case 3://load { switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin()) { case 0: return boost::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_PLAYER); case 1: return boost::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::MULTI_HOT_SEAT); case 2: return boost::function();//TODO: load campaign case 3: return boost::function();//TODO: load tutorial } } break; case 4://exit { return boost::bind(CInfoWindow::showYesNoDialog, boost::ref(CGI->generaltexth->allTexts[69]), (const std::vector*)0, do_quit, 0, false, 1); } break; case 5://highscores { return boost::function(); //TODO: high scores &pushIntT; } } } } tlog0<<"Failed to parse command: "<(); } CAdventureMapButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button) { boost::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 CAdventureMapButton(help, command, posx, posy, button["name"].String(), button["hotkey"].Float()); } CMenuEntry::CMenuEntry(CMenuScreen* parent, const JsonNode &config) { OBJ_CONSTRUCTION_CAPTURING_ALL; type |= REDRAW_PARENT; pos = parent->pos; BOOST_FOREACH(const JsonNode& node, config["images"].Vector()) images.push_back(createPicture(node)); BOOST_FOREACH(const JsonNode& node, config["buttons"].Vector()) { buttons.push_back(createButton(parent, node)); buttons.back()->hoverable = true; buttons.back()->type |= REDRAW_PARENT; } } CreditsScreen::CreditsScreen() { 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()->loadData(ResourceID("DATA/CREDITS.TXT")); 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 CTextBox(text, Rect(pos.w - 350, 600, 350, 32000), 0, FONT_CREDITS, CENTER, Colors::WHITE); credits->pos.h = credits->maxH; } void CreditsScreen::showAll(SDL_Surface * to) { //Do not draw anything } void CreditsScreen::show(SDL_Surface * to) { static int count = 0; count++; if (count == 2) { credits->pos.y--; count = 0; } Rect creditsArea = credits->pos & pos; SDL_SetClipRect(screenBuf, &creditsArea); SDL_SetClipRect(screen, &creditsArea); redraw(); CIntObject::showAll(to); SDL_SetClipRect(screen, NULL); SDL_SetClipRect(screenBuf, NULL); //end of credits, close this screen if (credits->pos.y + credits->pos.h < 0) 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); } CGPreGame::CGPreGame(): pregameConfig(new JsonNode(ResourceID("config/mainmenu.json"))) { pos.w = screen->w; pos.h = screen->h; GH.defActionsDef = 63; CGP = this; menu = new CMenuScreen((*pregameConfig)["window"]); loadGraphics(); } CGPreGame::~CGPreGame() { disposeGraphics(); } void CGPreGame::openSel(CMenuScreen::EState screenType, CMenuScreen::EMultiMode multi /*= CMenuScreen::SINGLE_PLAYER*/) { GH.pushInt(new CSelectionScreen(screenType, multi)); } void CGPreGame::loadGraphics() { OBJ_CONSTRUCTION_CAPTURING_ALL; new CFilledTexture("DIBOXBCK", pos); victory = CDefHandler::giveDef("SCNRVICT.DEF"); loss = CDefHandler::giveDef("SCNRLOSS.DEF"); } void CGPreGame::disposeGraphics() { delete victory; delete loss; } void CGPreGame::update() { 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(); if (GH.curInt == NULL) // no redraw, when a new game was created return; GH.topInt()->show(screen); if (settings["general"]["showfps"].Bool()) GH.drawFPSCounter(); // draw the mouse cursor and update the screen CCS->curh->drawWithScreenRestore(); CSDL_Ext::update(screen); CCS->curh->drawRestored(); } void CGPreGame::openCampaignScreen(std::string name) { BOOST_FOREACH(const JsonNode& node, (*pregameConfig)["campaignsset"].Vector()) { if (node["name"].String() == name) { GH.pushInt(new CCampaignScreen(node)); return; } } tlog1<<"Unknown campaign set: "< *Names /*= NULL*/) : ISelectionScreenInfo(Names), serverHandlingThread(NULL), mx(new boost::recursive_mutex), serv(NULL), ongoingClosing(false), myNameID(255) { CGPreGame::create(); //we depend on its graphics screenType = Type; multiPlayer = MultiPlayer; OBJ_CONSTRUCTION_CAPTURING_ALL; bool network = (MultiPlayer == CMenuScreen::MULTI_NETWORK_GUEST || MultiPlayer == CMenuScreen::MULTI_NETWORK_HOST); CServerHandler *sh = NULL; if(multiPlayer == CMenuScreen::MULTI_NETWORK_HOST) { 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 = (*CGP->pregameConfig)["game-select"].Vector(); bg = new CPicture(bgNames[rand() % bgNames.size()].String(), 0, 0); pos = bg->center(); } sInfo.difficulty = 1; current = NULL; sInfo.mode = (Type == CMenuScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME); sInfo.turnTime = 0; curTab = NULL; card = new InfoCard(network); //right info card if (screenType == CMenuScreen::campaignList) { opt = NULL; } else { opt = new OptionsTab(); //scenario options tab opt->recActions = DISPOSE; randMapTab = new RandomMapTab(); randMapTab->getMapInfoChanged() += bind(&CSelectionScreen::changeSelection, this, _1); randMapTab->recActions = DISPOSE; } sel = new SelectionTab(screenType, bind(&CSelectionScreen::changeSelection, this, _1), multiPlayer); //scenario selection tab sel->recActions = DISPOSE; switch(screenType) { case CMenuScreen::newGame: { card->difficulty->onChange = bind(&CSelectionScreen::difficultyChange, this, _1); card->difficulty->select(1, 0); CAdventureMapButton * select = new CAdventureMapButton(CGI->generaltexth->zelp[45], 0, 411, 80, "GSPBUTT.DEF", SDLK_s); select->callback = [&]() { toggleTab(sel); changeSelection(sel->getSelectedMapInfo()); }; select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL); CAdventureMapButton *opts = new CAdventureMapButton(CGI->generaltexth->zelp[46], bind(&CSelectionScreen::toggleTab, this, opt), 411, 510, "GSPBUTT.DEF", SDLK_a); opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL); CAdventureMapButton * randomBtn = new CAdventureMapButton(CGI->generaltexth->zelp[47], 0, 411, 105, "GSPBUTT.DEF", SDLK_r); randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL); if(settings["general"]["enableRMG"].Bool()) { randomBtn->callback = [&]() { toggleTab(randMapTab); changeSelection(&randMapTab->getMapInfo()); }; } start = new CAdventureMapButton(CGI->generaltexth->zelp[103], bind(&CSelectionScreen::startGame, this), 411, 535, "SCNRBEG.DEF", SDLK_b); if(network) { CAdventureMapButton *hideChat = new CAdventureMapButton(CGI->generaltexth->zelp[48], bind(&InfoCard::toggleChat, card), 619, 83, "GSPBUT2.DEF", SDLK_h); hideChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL); if(multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST) { SDL_Color orange = {232, 184, 32, 0}; select->text->color = opts->text->color = randomBtn->text->color = orange; select->block(true); opts->block(true); randomBtn->block(true); start->block(true); } } } break; case CMenuScreen::loadGame: sel->recActions = 255; start = new CAdventureMapButton(CGI->generaltexth->zelp[103], bind(&CSelectionScreen::startGame, this), 411, 535, "SCNRLOD.DEF", SDLK_l); break; case CMenuScreen::saveGame: sel->recActions = 255; start = new CAdventureMapButton("", CGI->generaltexth->zelp[103].second, bind(&CSelectionScreen::startGame, this), 411, 535, "SCNRSAV.DEF"); break; case CMenuScreen::campaignList: sel->recActions = 255; start = new CAdventureMapButton(std::pair(), bind(&CSelectionScreen::startCampaign, this), 411, 535, "SCNRLOD.DEF", SDLK_b); break; } start->assignedKeys.insert(SDLK_RETURN); std::string backName; if(Type == CMenuScreen::campaignList) { backName = "SCNRBACK.DEF"; } else { backName = "SCNRBACK.DEF"; } back = new CAdventureMapButton("", CGI->generaltexth->zelp[105].second, bind(&CGuiHandler::popIntTotally, &GH, this), 581, 535, backName, SDLK_ESCAPE); if(network) { if(multiPlayer == CMenuScreen::MULTI_NETWORK_HOST) { assert(playerNames.size() == 1 && vstd::contains(playerNames, 1)); //TODO hot-seat/network combo serv = sh->connectToServer(); *serv << (ui8) 4; myNameID = 1; } else { serv = CServerHandler::justConnectToServer(); } *serv << playerNames.begin()->second; if(multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST) { const CMapInfo *map; *serv >> myNameID >> map; serv->connectionID = myNameID; changeSelection(map); } else //host { if(current) { SelectMap sm(*current); *serv << &sm; UpdateStartOptions uso(sInfo); *serv << &uso; } } applier = new CApplier; registerTypes4(*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 = -1; playerNames.clear(); assert(!serv); vstd::clear_pointer(applier); delete mx; } void CSelectionScreen::toggleTab(CIntObject *tab) { if(multiPlayer == CMenuScreen::MULTI_NETWORK_HOST && 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 = NULL; }; GH.totalRedraw(); } void CSelectionScreen::changeSelection(const CMapInfo * to) { if(multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST) { vstd::clear_pointer(current); } current = to; if(to && (screenType == CMenuScreen::loadGame || screenType == CMenuScreen::saveGame)) SEL->sInfo.difficulty = to->scenarioOpts->difficulty; if(screenType != CMenuScreen::campaignList) { updateStartInfo(to ? to->fileURI : "", sInfo, to ? to->mapHeader.get() : NULL); if(screenType == CMenuScreen::newGame) { sInfo.createRandomMap = to->isRandomMap; if(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(multiPlayer == CMenuScreen::MULTI_NETWORK_HOST && 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::startGame() { 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(multiPlayer == CMenuScreen::MULTI_NETWORK_HOST) { start->block(true); StartWithCurrentSettings swcs; *serv << &swcs; ongoingClosing = true; return; } if(screenType != CMenuScreen::saveGame) { if(!current) return; saveGameName.clear(); if(screenType == CMenuScreen::loadGame) { saveGameName = sInfo.mapname; } if(sInfo.createRandomMap) { // Random map generation fails for now, so don't start game... return; } StartInfo * si = new StartInfo(sInfo); CGP->removeFromGui(); ::startGame(si); } else { if(!(sel && sel->txt && sel->txt->text.size())) return; saveGameName = "Saves/" + sel->txt->text; CFunctionList overWrite; overWrite += boost::bind(&CCallback::save, LOCPLINT->cb, saveGameName); overWrite += bind(&CGuiHandler::popIntTotally, &GH, this); if(CResourceHandler::get()->existsResource(ResourceID(saveGameName, EResType::LIB_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 = NULL; *serv >> pack; 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); } } } HANDLE_EXCEPTION catch(int i) { if(i != 666) 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 = -1; sInfo = si; if(current) opt->recreate(); //will force to recreate using current sInfo card->difficulty->select(si.difficulty, 0); GH.totalRedraw(); } void CSelectionScreen::processPacks() { boost::unique_lock lll(*mx); while(upcomingPacks.size()) { CPackForSelectionScreen *pack = upcomingPacks.front(); upcomingPacks.pop_front(); CBaseForPGApply *apply = applier->apps[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(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 (size_t i=0; iversion && (!size || allItems[i].mapHeader->width == size)) curItems.push_back(&allItems[i]); } 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(NULL); } } std::vector SelectionTab::getFiles(std::string dirURI, int resType) { std::vector ret; boost::to_upper(dirURI); auto iterator = CResourceHandler::get()->getIterator([&](const ResourceID & ident) { return ident.getType() == resType && boost::algorithm::starts_with(ident.getName(), dirURI); }); while (iterator.hasNext()) { ret.push_back(*iterator); ++iterator; } return ret; } void SelectionTab::parseMaps(const std::vector & files) { allItems.clear(); for(int i = 0; i < files.size(); ++i) { try { CMapInfo mapInfo; mapInfo.mapInit(files[i].getName()); allItems.push_back(mapInfo); } catch(std::exception & e) { tlog2 << "Map " << files[i].getName() << " is invalid. Message: " << e.what() << std::endl; } } } void SelectionTab::parseGames(const std::vector &files, bool multi) { for(int i=0; igetResourceName(files[i])); 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 = std::shared_ptr(new CMapHeader); mapInfo.scenarioOpts = new StartInfo; lf >> *(mapInfo.mapHeader.get()) >> mapInfo.scenarioOpts; mapInfo.fileURI = files[i].getName(); mapInfo.countPlayers(); std::time_t time = CFileInfo(CResourceHandler::get()->getResourceName(files[i])).getDate(); mapInfo.date = std::asctime(std::localtime(&time)); // If multi mode then only multi games, otherwise single if((mapInfo.actualHumanPlayers > 1) != multi) { mapInfo.mapHeader.reset(); } allItems.push_back(mapInfo); } catch(std::exception & e) { tlog3 << "Failed to process " << files[i].getName() <<": " << e.what() << std::endl; } } } void SelectionTab::parseCampaigns(const std::vector & files ) { for(int i=0; i &OnSelect, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/) :bg(NULL), onSelect(OnSelect) { OBJ_CONSTRUCTION; selectionPos = 0; addUsedEvents(LCLICK | WHEEL | KEYBOARD | DOUBLECLICK); slider = NULL; txt = NULL; tabType = Type; if (Type != CMenuScreen::campaignList) { bg = new CPicture("SCSELBCK.bmp", 0, 6); pos = bg->pos; } else { bg = nullptr; //use background from parent pos.w = parent->pos.w; pos.h = parent->pos.h; pos.x += 3; pos.y += 6; } if(MultiPlayer == CMenuScreen::MULTI_NETWORK_GUEST) { positions = 18; } else { std::vector cpm; switch(tabType) { case CMenuScreen::newGame: parseMaps(getFiles("Maps/", EResType::MAP)); positions = 18; break; case CMenuScreen::loadGame: case CMenuScreen::saveGame: parseGames(getFiles("Saves/", EResType::LIB_SAVEGAME), MultiPlayer); 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.add(CTextInput::filenameFilter); } break; case CMenuScreen::campaignList: parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN)); positions = 18; break; default: assert(0); break; } } 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 CAdventureMapButton("", CGI->generaltexth->zelp[54+i].second, bind(&SelectionTab::filter, this, sizes[i], true), 158 + 47*i, 46, names[i]); } //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++) new CAdventureMapButton("", CGI->generaltexth->zelp[107+i].second, bind(&SelectionTab::sortBy, this, i), xpos[i], 86, names[i]); } } else { //sort by buttons new CAdventureMapButton("", "", bind(&SelectionTab::sortBy, this, _numOfMaps), 23, 86, "CamCusM.DEF"); //by num of maps new CAdventureMapButton("", "", bind(&SelectionTab::sortBy, this, _name), 55, 86, "CamCusL.DEF"); //by name } slider = new CSlider(372, 86, tabType != CMenuScreen::saveGame ? 480 : 430, bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, 1); slider->addUsedEvents(WHEEL); slider->slider->keepFrame = true; format = CDefHandler::giveDef("SCSELC.DEF"); sortingBy = _format; ascending = true; filter(0); //select(0); switch(tabType) { case CMenuScreen::newGame: selectFName("Maps/Arrogance"); break; case CMenuScreen::loadGame: case CMenuScreen::campaignList: select(0); break; case CMenuScreen::saveGame:; if(saveGameName.empty()) { txt->setTxt("NEWGAME"); } else { selectFName(saveGameName); } } } SelectionTab::~SelectionTab() { delete format; } void SelectionTab::sortBy( int criteria ) { if(criteria == sortingBy) { ascending = !ascending; } else { sortingBy = (ESortBy)criteria; ascending = true; } sort(); selectAbs(0); } void SelectionTab::sort() { if(sortingBy != _name) std::stable_sort(curItems.begin(), curItems.end(), mapSorter(_name)); 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->value; vstd::amax(py, 0); vstd::amin(py, curItems.size()-1); selectionPos = py; if(position < 0) slider->moveTo(slider->value + position); else if(position >= positions) slider->moveTo(slider->value + position - positions + 1); if(txt) { std::string filename = CResourceHandler::get()->getResourceName( ResourceID(curItems[py]->fileURI, EResType::LIB_SAVEGAME)); txt->setTxt(CFileInfo(filename).getBaseName()); } onSelect(curItems[py]); } void SelectionTab::selectAbs( int position ) { select(position - slider->value); } 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->value; // Display all elements if there's enough space //if(slider->amount < slider->capacity) // elemIdx = 0; SDL_Color itemColor; #define POS(xx, yy) pos.x + xx, pos.y + yy + line*25 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; CSDL_Ext::printAt(ostr.str(), POS(29, 120), 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; } CSDL_Ext::printAtMiddle(temp2, POS(70, 128), FONT_SMALL, itemColor, to); int temp=-1; switch (currentItem->mapHeader->version) { case EMapFormat::ROE: temp=0; break; case EMapFormat::AB: temp=1; break; case EMapFormat::SOD: temp=2; break; case EMapFormat::WOG: temp=3; break; default: // Unknown version. Be safe and ignore that map tlog2 << "Warning: " << currentItem->fileURI << " has wrong version!\n"; continue; } blitAt(format->ourImages[temp].bitmap, POS(88, 117), to); //victory conditions if (currentItem->mapHeader->victoryCondition.condition == EVictoryConditionType::WINSTANDARD) temp = 11; else temp = currentItem->mapHeader->victoryCondition.condition; blitAt(CGP->victory->ourImages[temp].bitmap, POS(306, 117), to); //loss conditions if (currentItem->mapHeader->lossCondition.typeOfLossCon == ELossConditionType::LOSSSTANDARD) temp=3; else temp=currentItem->mapHeader->lossCondition.typeOfLossCon; blitAt(CGP->loss->ourImages[temp].bitmap, POS(339, 117), to); } else //if campaign { //number of maps in campaign std::ostringstream ostr(std::ostringstream::out); ostr << CGI->generaltexth->campaignRegionNames[ currentItem->campaignHeader->mapVersion ].size(); CSDL_Ext::printAt(ostr.str(), POS(29, 120), 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 = CFileInfo(CResourceHandler::get()->getResourceName( ResourceID(currentItem->fileURI, EResType::LIB_SAVEGAME))).getBaseName(); } //print name CSDL_Ext::printAtMiddle(CSDL_Ext::trimToFit(name, 185, FONT_SMALL), POS(213, 128), FONT_SMALL, itemColor, to); } #undef POS } 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 = "Select a Campaign"; //TODO: find where is the title break; } CSDL_Ext::printAtMiddle(title, pos.x+205, pos.y+28, FONT_MEDIUM, Colors::YELLOW, to); //Select a Scenario to Play if(tabType != CMenuScreen::campaignList) { CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[510], pos.x+87, pos.y+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->value); return; case SDLK_END: select(curItems.size() - slider->value); return; default: return; } select(selectionPos - slider->value + moveBy); } void SelectionTab::onDoubleClick() { if(getLine() != -1) //double clicked scenarios list { //act as if start button was pressed (static_cast(parent))->start->callback(); } } int SelectionTab::getLine() { int line = -1; Point clickPos(GH.current->button.x, GH.current->button.y); clickPos = clickPos - pos.topLeft(); if (clickPos.y > 115 && clickPos.y < 564 && 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]; } RandomMapTab::RandomMapTab() { OBJ_CONSTRUCTION; bg = new CPicture("RANMAPBK", 0, 6); // Map Size mapSizeBtnGroup = new CHighlightableButtonsGroup(0); mapSizeBtnGroup->pos.y = 81; mapSizeBtnGroup->pos.x = 158; const std::vector mapSizeBtns = boost::assign::list_of("RANSIZS")("RANSIZM")("RANSIZL")("RANSIZX"); addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198); mapSizeBtnGroup->select(1, false); mapSizeBtnGroup->onChange = [&](int btnId) { const std::vector mapSizeVal = boost::assign::list_of(36)(72)(108)(144); // Map sizes in this order: S, M, L, XL mapGenOptions.setWidth(mapSizeVal[btnId]); mapGenOptions.setHeight(mapSizeVal[btnId]); updateMapInfo(); }; // Two levels twoLevelsBtn = new CHighlightableButton(0, 0, std::map(), CGI->generaltexth->zelp[202].second, false, "RANUNDR", nullptr, 346, 81); twoLevelsBtn->select(true); twoLevelsBtn->callback = [&]() { mapGenOptions.setHasTwoLevels(true); updateMapInfo(); }; twoLevelsBtn->callback2 = [&]() { mapGenOptions.setHasTwoLevels(false); 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 CHighlightableButtonsGroup(0); playersCntGroup->pos.y = 153; playersCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212); playersCntGroup->onChange = [&](int btnId) { mapGenOptions.setPlayersCnt(btnId); deactivateButtonsFrom(teamsCntGroup, btnId); deactivateButtonsFrom(compOnlyPlayersCntGroup, 8 - btnId + 1); validatePlayersCnt(btnId); updateMapInfo(); }; // Amount of teams teamsCntGroup = new CHighlightableButtonsGroup(0); teamsCntGroup->pos.y = 219; teamsCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222); teamsCntGroup->onChange = [&](int btnId) { mapGenOptions.setTeamsCnt(btnId); updateMapInfo(); }; // Computer only players compOnlyPlayersCntGroup = new CHighlightableButtonsGroup(0); compOnlyPlayersCntGroup->pos.y = 285; compOnlyPlayersCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232); compOnlyPlayersCntGroup->select(0, true); compOnlyPlayersCntGroup->onChange = [&](int btnId) { mapGenOptions.setCompOnlyPlayersCnt(btnId); deactivateButtonsFrom(compOnlyTeamsCntGroup, btnId); validateCompOnlyPlayersCnt(btnId); updateMapInfo(); }; // Computer only teams compOnlyTeamsCntGroup = new CHighlightableButtonsGroup(0); compOnlyTeamsCntGroup->pos.y = 351; compOnlyTeamsCntGroup->pos.x = BTNS_GROUP_LEFT_MARGIN; addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241); deactivateButtonsFrom(compOnlyTeamsCntGroup, 0); compOnlyTeamsCntGroup->onChange = [&](int btnId) { mapGenOptions.setCompOnlyTeamsCnt(btnId); updateMapInfo(); }; const int WIDE_BTN_WIDTH = 85; // Water content waterContentGroup = new CHighlightableButtonsGroup(0); waterContentGroup->pos.y = 419; waterContentGroup->pos.x = BTNS_GROUP_LEFT_MARGIN; const std::vector waterContentBtns = boost::assign::list_of("RANNONE")("RANNORM")("RANISLD"); addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246); waterContentGroup->onChange = [&](int btnId) { mapGenOptions.setWaterContent(static_cast(btnId)); }; // Monster strength monsterStrengthGroup = new CHighlightableButtonsGroup(0); monsterStrengthGroup->pos.y = 485; monsterStrengthGroup->pos.x = BTNS_GROUP_LEFT_MARGIN; const std::vector monsterStrengthBtns = boost::assign::list_of("RANWEAK")("RANNORM")("RANSTRG"); addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251); monsterStrengthGroup->onChange = [&](int btnId) { mapGenOptions.setMonsterStrength(static_cast(btnId)); }; // Show random maps btn showRandMaps = new CAdventureMapButton("", CGI->generaltexth->zelp[252].second, 0, 54, 535, "RANSHOW"); // Initialize map info object mapInfo.isRandomMap = true; shared_ptr mapHeader(new CMapHeader()); mapHeader->version = EMapFormat::SOD; mapHeader->name = CGI->generaltexth->allTexts[740]; mapHeader->description = CGI->generaltexth->allTexts[741]; mapHeader->difficulty = 1; // Normal mapInfo.mapHeader = mapHeader; updateMapInfo(); } void RandomMapTab::addButtonsWithRandToGroup(CHighlightableButtonsGroup * 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->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[helpRandIndex].second, 0, 256, 0, RANDOM_DEF, CMapGenOptions::RANDOM_SIZE)); group->select(CMapGenOptions::RANDOM_SIZE, true); } void RandomMapTab::addButtonsToGroup(CHighlightableButtonsGroup * 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) { group->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[helpStartIndex + i].second, 0, i * btnWidth, 0, defs[i + nStart], i + nStart)); } } void RandomMapTab::deactivateButtonsFrom(CHighlightableButtonsGroup * group, int startId) { BOOST_FOREACH(CHighlightableButton * btn, group->buttons) { if(startId == CMapGenOptions::RANDOM_SIZE || btn->ID < startId) { if(btn->isBlocked()) { btn->setOffset(0); btn->setState(CButtonBase::NORMAL); } } else { // Blocked state looks like frame 'selected'=1 btn->setOffset(-1); btn->setState(CButtonBase::BLOCKED); } } } void RandomMapTab::validatePlayersCnt(int playersCnt) { if(playersCnt == CMapGenOptions::RANDOM_SIZE) { return; } if(mapGenOptions.getTeamsCnt() >= playersCnt) { mapGenOptions.setTeamsCnt(playersCnt - 1); teamsCntGroup->select(mapGenOptions.getTeamsCnt(), true); } if(mapGenOptions.getCompOnlyPlayersCnt() > 8 - playersCnt) { mapGenOptions.setCompOnlyPlayersCnt(8 - playersCnt); compOnlyPlayersCntGroup->select(mapGenOptions.getCompOnlyPlayersCnt(), true); } validateCompOnlyPlayersCnt(mapGenOptions.getCompOnlyPlayersCnt()); } void RandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt) { if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE) { return; } if(mapGenOptions.getCompOnlyTeamsCnt() >= compOnlyPlayersCnt) { mapGenOptions.setCompOnlyTeamsCnt(compOnlyPlayersCnt - 1); compOnlyTeamsCntGroup->select(mapGenOptions.getCompOnlyTeamsCnt(), true); } } void RandomMapTab::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 RandomMapTab::updateMapInfo() { mapInfo.mapHeader->height = mapGenOptions.getHeight(); mapInfo.mapHeader->width = mapGenOptions.getWidth(); mapInfo.mapHeader->twoLevel = mapGenOptions.getHasTwoLevels(); // Generate player information mapInfo.mapHeader->players.clear(); int playersToGen = (mapGenOptions.getPlayersCnt() == CMapGenOptions::RANDOM_SIZE || mapGenOptions.getCompOnlyPlayersCnt() == CMapGenOptions::RANDOM_SIZE) ? 8 : mapGenOptions.getPlayersCnt() + mapGenOptions.getCompOnlyPlayersCnt(); mapInfo.mapHeader->howManyTeams = playersToGen; for(int i = 0; i < playersToGen; ++i) { PlayerInfo player; player.canComputerPlay = true; if(i >= mapGenOptions.getPlayersCnt() && mapGenOptions.getPlayersCnt() != CMapGenOptions::RANDOM_SIZE) { player.canHumanPlay = false; } else { player.canHumanPlay = true; } player.team = i; mapInfo.mapHeader->players.push_back(player); } mapInfoChanged(&mapInfo); } CFunctionList & RandomMapTab::getMapInfoChanged() { return mapInfoChanged; } const CMapInfo & RandomMapTab::getMapInfo() const { return mapInfo; } const CMapGenOptions & RandomMapTab::getMapGenOptions() const { return mapGenOptions; } CChatBox::CChatBox(const Rect &rect) { OBJ_CONSTRUCTION; pos += rect; addUsedEvents(KEYBOARD); captureAllKeys = true; const int height = graphics->fonts[FONT_SMALL]->height; 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); SDL_Color green = {0,252,0, SDL_ALPHA_OPAQUE}; chatHistory->color = 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->setTxt(""); } else inputBox->keyPressed(key); } void CChatBox::addNewMessage(const std::string &text) { chatHistory->setTxt(chatHistory->text + text + "\n"); if(chatHistory->slider) chatHistory->slider->moveToMax(); } InfoCard::InfoCard( bool Network ) : bg(NULL), network(Network), chatOn(false), chat(NULL), playerListBg(NULL), difficulty(NULL), sizes(NULL), sFlags(NULL) { OBJ_CONSTRUCTION_CAPTURING_ALL; pos.x += 393; pos.y += 6; addUsedEvents(RCLICK); mapDescription = NULL; 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 = CDefHandler::giveDef("SCNRMPSZ.DEF"); sFlags = CDefHandler::giveDef("ITGFLAGS.DEF"); difficulty = new CHighlightableButtonsGroup(0); { static const char *difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"}; for(int i = 0; i < 5; i++) { difficulty->addButton(new CHighlightableButton("", CGI->generaltexth->zelp[24+i].second, 0, 110 + i*32, 450, difButns[i], i)); } } if(SEL->screenType != CMenuScreen::newGame) difficulty->block(true); //description needs bg mapDescription->addChild(new CPicture(*bg, descriptionRect), true); //move subpicture bg to our description control (by default it's our (Infocard) child) if(network) { playerListBg = new CPicture("CHATPLUG.bmp", 16, 276); chat = new CChatBox(descriptionRect); chat->chatHistory->addChild(new CPicture(*bg, chat->chatHistory->pos - pos), true); //move subpicture bg to our description control (by default it's our (Infocard) child) chatOn = true; mapDescription->disable(); } } } InfoCard::~InfoCard() { delete sizes; delete sFlags; } 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]->height, 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]->height, FONT_SMALL, Colors::WHITE, to); } } } if(SEL->current) { if(SEL->screenType != CMenuScreen::campaignList) { int temp = -1; if(!chatOn) { //victory conditions temp = SEL->current->mapHeader->victoryCondition.condition+1; if (temp>20) temp=0; std::string sss = CGI->generaltexth->victoryConditions[temp]; if (temp && SEL->current->mapHeader->victoryCondition.allowNormalVictory) sss+= "/" + CGI->generaltexth->victoryConditions[0]; printAtLoc(sss, 60, 307, FONT_SMALL, Colors::WHITE, to); temp = SEL->current->mapHeader->victoryCondition.condition; if (temp>12) temp=11; blitAtLoc(CGP->victory->ourImages[temp].bitmap, 24, 302, to); //victory cond descr //loss conditoins temp = SEL->current->mapHeader->lossCondition.typeOfLossCon+1; if (temp>20) temp=0; sss = CGI->generaltexth->lossCondtions[temp]; printAtLoc(sss, 60, 366, FONT_SMALL, Colors::WHITE, to); temp=SEL->current->mapHeader->lossCondition.typeOfLossCon; if (temp>12) temp=3; blitAtLoc(CGP->loss->ourImages[temp].bitmap, 24, 359, to); //loss cond } //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: temp=0; break; case 72: temp=1; break; case 108: temp=2; break; case 144: temp=3; break; default: temp=4; break; } blitAtLoc(sizes->ourImages[temp].bitmap, 318, 22, 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]->getWidth(CGI->generaltexth->allTexts[390].c_str()); int ex = 200 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[391].c_str()); int myT; if(playerColor >= 0) myT = SEL->current->mapHeader->players[playerColor].team; else myT = -1; for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++) { int *myx = ((i->first == playerColor || SEL->current->mapHeader->players[i->first].team == myT) ? &fx : &ex); blitAtLoc(sFlags->ourImages[i->first].bitmap, *myx, 399, to); *myx += sFlags->ourImages[i->first].bitmap->w; } 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(CSDL_Ext::trimToFit(name, 300, FONT_BIG), 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->setTxt(to->campaignHeader->description); else mapDescription->setTxt(to->mapHeader->description); if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) { difficulty->block(true); difficulty->select(SEL->sInfo.difficulty, 0); } } GH.totalRedraw(); } void InfoCard::clickRight( tribool down, bool previousState ) { static const Rect flagArea(19, 397, 335, 23); if(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); CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[657], 128, 30, FONT_MEDIUM, Colors::YELLOW, bmp); //{Team Alignments} 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)); CSDL_Ext::printAtMiddle(hlp, 128, 65 + 50*i, FONT_SMALL, Colors::WHITE, bmp); for(int j = 0; j < GameConstants::PLAYER_LIMIT; j++) if((SEL->current->mapHeader->players[j].canHumanPlay || SEL->current->mapHeader->players[j].canComputerPlay) && SEL->current->mapHeader->players[j].team == i) flags.push_back(j); int curx = 128 - 9*flags.size(); for(int j = 0; j < flags.size(); j++) { blitAt(sFlags->ourImages[flags[j]].bitmap, curx, 75 + 50*i, bmp); 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(NULL) { OBJ_CONSTRUCTION; bg = new CPicture("ADVOPTBK", 0, 6); pos = bg->pos; if(SEL->screenType == CMenuScreen::newGame) turnDuration = new CSlider(55, 551, 194, bind(&OptionsTab::setTurnLength, this, _1), 1, 11, 11, true, 1); } 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, 58, FONT_SMALL, 55, Colors::WHITE, to); //Select starting options, handicap, and name for each player in the game. printAtMiddleWBLoc(CGI->generaltexth->allTexts[517], 107, 102, FONT_SMALL, 14, Colors::YELLOW, to); //Player Name Handicap Type printAtMiddleWBLoc(CGI->generaltexth->allTexts[518], 197, 102, FONT_SMALL, 10, Colors::YELLOW, to); //Starting Town printAtMiddleWBLoc(CGI->generaltexth->allTexts[519], 273, 102, FONT_SMALL, 10, Colors::YELLOW, to); //Starting Hero printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 102, FONT_SMALL, 10, 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->value], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value } void OptionsTab::nextCastle( int 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].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].customHeroID < 0) // remove hero unless it set to fixed one in map editor 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( int 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 = (s.castle*GameConstants::HEROES_PER_TYPE*2+15), min = (s.castle*GameConstants::HEROES_PER_TYPE*2); s.hero = nextAllowedHero(min,max,0,dir); } else { if(dir > 0) s.hero = nextAllowedHero(s.hero,(s.castle*GameConstants::HEROES_PER_TYPE*2+16),1,dir); else s.hero = nextAllowedHero(s.castle*GameConstants::HEROES_PER_TYPE*2-1,s.hero,1,dir); } if(old != s.hero) { usedHeroes.erase(old); usedHeroes.insert(s.hero); entries[player]->update(); redraw(); } SEL->propagateOptions(); } int OptionsTab::nextAllowedHero( int min, int max, int incl, int dir ) { if(dir>0) { for(int i=min+incl; i<=max-incl; i++) if(canUseThisHero(i)) return i; } else { for(int i=max-incl; i>=min+incl; i--) if(canUseThisHero(i)) return i; } return -1; } bool OptionsTab::canUseThisHero( int ID ) { return CGI->heroh->heroes.size() > ID && !vstd::contains(usedHeroes, ID) && SEL->current->mapHeader->allowedHeroes[ID]; } void OptionsTab::nextBonus( int player, int dir ) { if(SEL->isGuest()) { SEL->postRequest(RequestOptionsChange::BONUS, dir); return; } PlayerSettings &s = SEL->sInfo.playerInfos[player]; si8 &ret = s.bonus += dir; if (s.hero==PlayerSettings::NONE && !SEL->current->mapHeader->players[s.color].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(std::map::iterator it = entries.begin(); it != entries.end(); ++it) { delete it->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].heroesNames; for(size_t hi=0; hisInfo.turnTime = times[npos]; redraw(); } void OptionsTab::flagPressed( int color ) { PlayerSettings &clicked = SEL->sInfo.playerInfos[color]; PlayerSettings *old = NULL; 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(); } SEL->propagateOptions(); GH.totalRedraw(); } OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSettings &S) : pi(SEL->current->mapHeader->players[S.color]), s(S) { OBJ_CONSTRUCTION; defActions |= SHARE_POS; int serial = 0; for(int g=0; g < s.color; ++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]), 0, 0, true); if(SEL->screenType == CMenuScreen::newGame) { btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[132], bind(&OptionsTab::nextCastle, owner, s.color, -1), 107, 5, "ADOPLFA.DEF"); btns[1] = new CAdventureMapButton(CGI->generaltexth->zelp[133], bind(&OptionsTab::nextCastle, owner, s.color, +1), 168, 5, "ADOPRTA.DEF"); btns[2] = new CAdventureMapButton(CGI->generaltexth->zelp[148], bind(&OptionsTab::nextHero, owner, s.color, -1), 183, 5, "ADOPLFA.DEF"); btns[3] = new CAdventureMapButton(CGI->generaltexth->zelp[149], bind(&OptionsTab::nextHero, owner, s.color, +1), 244, 5, "ADOPRTA.DEF"); btns[4] = new CAdventureMapButton(CGI->generaltexth->zelp[164], bind(&OptionsTab::nextBonus, owner, s.color, -1), 259, 5, "ADOPLFA.DEF"); btns[5] = new CAdventureMapButton(CGI->generaltexth->zelp[165], bind(&OptionsTab::nextBonus, owner, s.color, +1), 320, 5, "ADOPRTA.DEF"); } else for(int i = 0; i < 6; i++) btns[i] = NULL; selectButtons(); assert(SEL->current && SEL->current->mapHeader); const PlayerInfo &p = SEL->current->mapHeader->players[s.color]; 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].canHumanPlay && SEL->multiPlayer != CMenuScreen::MULTI_NETWORK_GUEST) { flag = new CAdventureMapButton(CGI->generaltexth->zelp[180], bind(&OptionsTab::flagPressed, owner, s.color), -43, 2, flags[s.color]); flag->hoverable = true; } else flag = NULL; 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, 34, FONT_TINY, 8, 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 = 200, HERO_NONE = 201 // 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->towns[settings.castle].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 settings.hero; } } 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->towns[settings.castle].primaryRes) { case 127 : 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->towns[settings.castle].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 127: 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->towns[settings.castle].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 127: 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->towns[settings.castle]; for (size_t i=0; i< town.creatures.size(); i++) components.push_back(new CComponent(CComponent::creature, town.creatures[i].front(), 0, CComponent::tiny)); new CComponentBox(components, Rect(0, 140, pos.w, 140)); } void OptionsTab::CPregameTooltipBox::genHeroWindow() { pos = Rect(0, 0, 292, 226); genHeader(); // speciality new CAnimImage("UN44", settings.hero, 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->generaltexth->hTxts[settings.hero].bonusName); } void OptionsTab::CPregameTooltipBox::genBonusWindow() { pos = Rect(0, 0, 228, 162); genHeader(); new CTextBox(getDescription(), Rect(10, 88, 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->setTxt(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].customHeroID == -1 && 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->select(startInfo->difficulty, 0); back = new CAdventureMapButton("", CGI->generaltexth->zelp[105].second, bind(&CGuiHandler::popIntTotally, &GH, this), 584, 535, "SCNRBACK.DEF", 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->lossCondition.typeOfLossConlossCondition.typeOfLossCon); 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->victoryCondition.condition < b->victoryCondition.condition); break; case _name: //by name return boost::ilexicographical_compare(a->name, b->name); 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->setTxt(settings["general"]["playerName"].String()); //Player btns[0] = new CAdventureMapButton(CGI->generaltexth->zelp[266], bind(&CMultiMode::openHotseat, this), 373, 78, "MUBHOT.DEF"); btns[1] = new CAdventureMapButton("Host TCP/IP game", "", bind(&CMultiMode::hostTCP, this), 373, 78 + 57*1, "MUBHOST.DEF"); btns[2] = new CAdventureMapButton("Join TCP/IP game", "", bind(&CMultiMode::joinTCP, this), 373, 78 + 57*2, "MUBJOIN.DEF"); btns[6] = new CAdventureMapButton(CGI->generaltexth->zelp[288], bind(&CGuiHandler::popIntTotally, ref(GH), this), 373, 424, "MUBCANC.DEF", 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); GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_HOST)); } void CMultiMode::joinTCP() { Settings name = settings.write["general"]["playerName"]; name->String() = txt->text; GH.popIntTotally(this); GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, 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 += boost::bind(&CHotSeatPlayers::onChange, this, _1); } ok = new CAdventureMapButton(CGI->generaltexth->zelp[560], bind(&CHotSeatPlayers::enterSelectionScreen, this), 95, 338, "MUBCHCK.DEF", SDLK_RETURN); cancel = new CAdventureMapButton(CGI->generaltexth->zelp[561], bind(&CGuiHandler::popIntTotally, ref(GH), this), 205, 338, "MUBCANC.DEF", SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472 txt[0]->setTxt(firstPlayer, true); txt[0]->giveFocus(); } void CHotSeatPlayers::onChange(std::string newText) { size_t namesCount = 0; for(int i = 0; i < ARRAY_COUNT(txt); i++) if(!txt[i]->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 = nullptr; diffLb = nullptr; diffRb = nullptr; bonuses = nullptr; 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 CAdventureMapButton("", "", bind(&CBonusSelection::startMap, this), 475, 536, "CBBEGIB.DEF", SDLK_RETURN); backB = new CAdventureMapButton("", "", bind(&CBonusSelection::goBack, this), 624, 536, "CBCANCB.DEF", SDLK_ESCAPE); //campaign name if (ourCampaign->camp->header.name.length()) CSDL_Ext::printAt(ourCampaign->camp->header.name, 481, 28, FONT_BIG, Colors::YELLOW, background); else CSDL_Ext::printAt("Unnamed", 481, 28, FONT_BIG, Colors::YELLOW, background); //map size icon sizes = CDefHandler::giveDef("SCNRMPSZ.DEF"); //campaign description CSDL_Ext::printAt(CGI->generaltexth->allTexts[38], 481, 63, FONT_SMALL, Colors::YELLOW, background); cmpgDesc = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1); //cmpgDesc->showAll(background); //map description mapDesc = new CTextBox("", Rect(480, 280, 286, 117), 1); //bonus choosing CSDL_Ext::printAt(CGI->generaltexth->allTexts[71], 511, 432, FONT_MEDIUM, Colors::WHITE, background); //Choose a bonus: bonuses = new CHighlightableButtonsGroup(bind(&CBonusSelection::selectBonus, this, _1)); //set left part of window for (int g=0; gcamp->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 == NULL) { highlightedRegion = regions.back(); selectMap(g); } } 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; } } //unlock if no bonuses -- it's acceptable // //init campaign state if necessary // if (ourCampaign->campaignName.size() == 0) // { // ourCampaign->initNewCampaign(sInfo); // } //allies / enemies CSDL_Ext::printAt(CGI->generaltexth->allTexts[390] + ":", 486, 407, FONT_SMALL, Colors::WHITE, background); //Allies CSDL_Ext::printAt(CGI->generaltexth->allTexts[391] + ":", 619, 407, FONT_SMALL, Colors::WHITE, background); //Enemies SDL_FreeSurface(panel); //difficulty std::vector difficulty; boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" ")); CSDL_Ext::printAt(difficulty.back(), 689, 432, FONT_MEDIUM, Colors::WHITE, background); //Difficulty //difficulty pics for (int b=0; b(b+3) + ".DEF"); SDL_Surface * surfToDuplicate = cde->ourImages[0].bitmap; diffPics[b] = SDL_ConvertSurface(surfToDuplicate, surfToDuplicate->format, surfToDuplicate->flags); delete cde; } //difficulty selection buttons if (ourCampaign->camp->header.difficultyChoosenByPlayer) { diffLb = new CAdventureMapButton("", "", bind(&CBonusSelection::changeDiff, this, false), 694, 508, "SCNRBLF.DEF"); diffRb = new CAdventureMapButton("", "", bind(&CBonusSelection::changeDiff, this, true), 738, 508, "SCNRBRT.DEF"); } //load miniflags sFlags = CDefHandler::giveDef("ITGFLAGS.DEF"); } CBonusSelection::CBonusSelection(shared_ptr _ourCampaign) : ourCampaign(std::move(_ourCampaign)) { init(); } CBonusSelection::CBonusSelection( std::string campaignFName ) { ourCampaign = make_shared(CCampaignHandler::getCampaign(campaignFName)); sInfo.campState = ourCampaign; init(); } CBonusSelection::~CBonusSelection() { SDL_FreeSurface(background); delete sizes; delete ourHeader; delete sFlags; for (int b=0; bh || pos.w != to->w) CMessage::drawBorder(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; BOOST_FOREACH(const JsonNode &campaign, config["campaign_regions"].Vector()) { SCampPositions sc; sc.campPrefix = campaign["prefix"].String(); sc.colorSuffixLength = campaign["color_suffix_length"].Float(); BOOST_FOREACH(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 whichOne ) { sInfo.difficulty = ourCampaign->camp->scenarios[whichOne].difficulty; sInfo.mapname = ourCampaign->camp->header.filename; sInfo.mode = StartInfo::CAMPAIGN; sInfo.campState = ourCampaign; ourCampaign->currentMap = whichOne; //get header delete ourHeader; std::string & headerStr = ourCampaign->camp->mapPieces.find(whichOne)->second; auto buffer = reinterpret_cast(headerStr.data()); ourHeader = CMapService::loadMapHeader(buffer, headerStr.size()).release(); std::map names; names[1] = settings["general"]["playerName"].String(); updateStartInfo(ourCampaign->camp->header.filename, sInfo, ourHeader, names); sInfo.turnTime = 0; sInfo.difficulty = ourCampaign->camp->scenarios[whichOne].difficulty; mapDesc->setTxt(ourHeader->description); updateBonusSelection(); } void CBonusSelection::show(SDL_Surface * to) { //blitAt(background, pos.x, pos.y, 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); mapDesc->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; } blitAtLoc(sizes->ourImages[temp].bitmap, 735, 26, to); //flags int fx = 496 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[390].c_str()); int ex = 629 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[391].c_str()); int myT; myT = ourHeader->players[playerColor].team; for (auto i = sInfo.playerInfos.cbegin(); i != sInfo.playerInfos.cend(); i++) { int *myx = ((i->first == playerColor || ourHeader->players[i->first].team == myT) ? &fx : &ex); blitAtLoc(sFlags->ourImages[i->first].bitmap, pos.x + *myx, pos.y + 405, to); *myx += sFlags->ourImages[i->first].bitmap->w; } //difficulty blitAtLoc(diffPics[sInfo.difficulty], 709, 455, 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[sInfo.campState->currentMap]; const std::vector & bonDescs = scenario.travelOptions.bonusesToChoose; updateStartButtonState(-1); for (size_t i=0; ibuttons.size(); i++) { if (bonuses->buttons[i]->active) bonuses->buttons[i]->deactivate(); delete bonuses->buttons[i]; } bonuses->buttons.clear(); static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF", "PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "CREST58.DEF", "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->spells[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 it = sInfo.playerInfos.begin(); it != sInfo.playerInfos.end(); ++it) { if (it->second.playerID) { faction = it->second.castle; break; } } assert(faction != -1); int buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set()); picName = graphics->ERMUtoPicture[faction][buildID]; picNumber = -1; if (vstd::contains(CGI->townh->towns[faction].buildings, buildID)) desc = CGI->townh->towns[faction].buildings.find(buildID)->second->Description(); } 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->spells[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]); //skill level boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->skillName[bonDescs[i].info2]); //skill name 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::PLAYER_PREV_SCENARIO: picNumber = bonDescs[i].info1; desc = CGI->generaltexth->allTexts[718]; boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //player color boost::algorithm::replace_first(desc, "%s", ourCampaign->camp->scenarios[bonDescs[i].info2].mapName); //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; } CHighlightableButton *bonusButton = new CHighlightableButton(desc, desc, 0, 475 + i*68, 455, "", i); if (picNumber != -1) picName += ":" + boost::lexical_cast(picNumber); CAnimation * anim = new CAnimation(); anim->setCustom(picName, 0); bonusButton->setImage(anim); const SDL_Color brightYellow = { 242, 226, 110, 0 }; bonusButton->borderColor = brightYellow; bonuses->addButton(bonusButton); } } void CBonusSelection::startMap() { StartInfo *si = new StartInfo(sInfo); if (ourCampaign->mapsConquered.size()) { GH.popInts(1); } else { CGP->removeFromGui(); } ::startGame(si); } void CBonusSelection::selectBonus( int id ) { // Total redraw is needed because the border around the bonus images // have to be undrawn/drawn. if (id != sInfo.campState->currentBonusID()) { sInfo.campState->chosenCampaignBonuses[sInfo.campState->currentMap] = id; GH.totalRedraw(); updateStartButtonState(id); } const CCampaignScenario &scenario = ourCampaign->camp->scenarios[sInfo.campState->currentMap]; 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 it = sInfo.playerInfos.begin(); it != sInfo.playerInfos.end(); ++it) { if(it->first == bonDescs[id].info1) ::setPlayer(it->second, 1, names); else ::setPlayer(it->second, 0, names); } } } void CBonusSelection::changeDiff( bool increase ) { if (increase) { sInfo.difficulty = std::min(sInfo.difficulty + 1, 4); } else { sInfo.difficulty = std::max(sInfo.difficulty - 1, 0); } } void CBonusSelection::updateStartButtonState( int selected /*= -1*/ ) { if(selected == -1) startB->setState( ourCampaign->getCurrentScenario().travelOptions.bonusesToChoose.size() ? CButtonBase::BLOCKED : CButtonBase::NORMAL); else if(startB->getState() == CButtonBase::BLOCKED) startB->setState(CButtonBase::NORMAL); } 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 (int g=0; gmotion.x-pos.x, GH.current->motion.y-pos.y) ) { owner->selectMap(myNumber); 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 : CMenuScreen::SINGLE_PLAYER) { ourGame = mapInfoFromGame(); sInfo = *LOCPLINT->cb->getStartInfo(); setPlayersFromGame(); } CSavingScreen::~CSavingScreen() { } ISelectionScreenInfo::ISelectionScreenInfo(const std::map *Names /*= NULL*/) { multiPlayer = CMenuScreen::SINGLE_PLAYER; assert(!SEL); SEL = this; current = NULL; if(Names && Names->size()) //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 = NULL; } void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader) { ::updateStartInfo(filename, sInfo, mapHeader, playerNames); } void ISelectionScreenInfo::setPlayer(PlayerSettings &pset, TPlayerColor player) { ::setPlayer(pset, player, playerNames); } int 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 multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST; } bool ISelectionScreenInfo::isHost() const { return multiPlayer == 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 i = SEL->sInfo.playerInfos.begin(); i != SEL->sInfo.playerInfos.end(); i++) { if(!i->second.playerID) { selScreen->setPlayer(i->second, connectionID); selScreen->opt->entries[i->second.color]->selectButtons(); break; } } selScreen->propagateNames(); selScreen->propagateOptions(); GH.totalRedraw(); } void SelectMap::apply(CSelectionScreen *selScreen) { if(selScreen->multiPlayer == CMenuScreen::MULTI_NETWORK_GUEST) { 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; ui8 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 = NULL; //hide it so it won't be deleted vstd::clear_pointer(selScreen->serverHandlingThread); //detach us saveGameName.clear(); CGP->removeFromGui(); ::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 ) { pos.x += config["x"].Float(); pos.y += config["y"].Float(); pos.w = 200; pos.h = 116; campFile = config["file"].String(); video = config["video"].String(); //On linux we can only play *.mjpg videos from LOKI release #ifdef _WIN32 std::transform(video.begin(), video.end(), video.begin(), toupper); video += ".BIK"; #else std::transform(video.begin(), video.end(), video.begin(), tolower); video += ".mjpg"; #endif OBJ_CONSTRUCTION_CAPTURING_ALL; status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED; CCampaignHeader header = CCampaignHandler::getHeader(campFile); hoverText = header.name; if (status != CCampaignScreen::DISABLED) { addUsedEvents(LCLICK | HOVER); image = 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) checkMark = 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 (on) hoverLabel->setTxt(hoverText); // Shows the name of the campaign when you get into the bounds of the button else hoverLabel->setTxt(" "); } 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(); } } CAdventureMapButton* CCampaignScreen::createExitButton(const JsonNode& button) { std::pair help; if (!button["help"].isNull() && button["help"].Float() > 0) help = CGI->generaltexth->zelp[button["help"].Float()]; boost::function close = boost::bind(&CGuiHandler::popIntTotally, &GH, this); return new CAdventureMapButton(help, close, button["x"].Float(), button["y"].Float(), button["name"].String(), button["hotkey"].Float()); } CCampaignScreen::CCampaignScreen(const JsonNode &config) { OBJ_CONSTRUCTION_CAPTURING_ALL; BOOST_FOREACH(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()) { back = createExitButton(config["exitbutton"]); back->hoverable = true; } BOOST_FOREACH(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(1, to, pos.w+28, pos.h+30, pos.x-14, pos.y-15); }