mirror of
https://github.com/vcmi/vcmi.git
synced 2024-12-24 22:14:36 +02:00
cd98dd0b89
Static variable might be not yet initialized by the time it's used inside CGuiHandler constructor. This fix let us avoid crash that occur when built with Xcode 8.2.1
4360 lines
124 KiB
C++
4360 lines
124 KiB
C++
/*
|
|
* CPreGame.cpp, part of VCMI engine
|
|
*
|
|
* Authors: listed in file AUTHORS in main folder
|
|
*
|
|
* License: GNU General Public License v2.0 or later
|
|
* Full text of license available in license.txt file, in main folder
|
|
*
|
|
*/
|
|
#include "StdInc.h"
|
|
#include "CPreGame.h"
|
|
|
|
#include "../lib/filesystem/Filesystem.h"
|
|
#include "../lib/filesystem/CCompressedStream.h"
|
|
|
|
#include "../lib/CStopWatch.h"
|
|
#include "gui/SDL_Extensions.h"
|
|
#include "CGameInfo.h"
|
|
#include "gui/CCursorHandler.h"
|
|
#include "../lib/CGeneralTextHandler.h"
|
|
#include "../lib/CTownHandler.h"
|
|
#include "../lib/CHeroHandler.h"
|
|
#include "../lib/mapping/CCampaignHandler.h"
|
|
#include "../lib/CCreatureHandler.h"
|
|
#include "../lib/JsonNode.h"
|
|
#include "CMusicHandler.h"
|
|
#include "CVideoHandler.h"
|
|
#include "Graphics.h"
|
|
#include "../lib/serializer/Connection.h"
|
|
#include "../lib/serializer/CTypeList.h"
|
|
#include "../lib/VCMIDirs.h"
|
|
#include "../lib/mapping/CMap.h"
|
|
#include "windows/GUIClasses.h"
|
|
#include "CPlayerInterface.h"
|
|
#include "../CCallback.h"
|
|
#include "CMessage.h"
|
|
#include "../lib/spells/CSpellHandler.h" /*for campaign bonuses*/
|
|
#include "../lib/CArtHandler.h" /*for campaign bonuses*/
|
|
#include "../lib/CBuildingHandler.h" /*for campaign bonuses*/
|
|
#include "CBitmapHandler.h"
|
|
#include "Client.h"
|
|
#include "../lib/NetPacks.h"
|
|
#include "../lib/registerTypes//RegisterTypes.h"
|
|
#include "../lib/CThreadHelper.h"
|
|
#include "../lib/CConfigHandler.h"
|
|
#include "../lib/GameConstants.h"
|
|
#include "gui/CGuiHandler.h"
|
|
#include "gui/CAnimation.h"
|
|
#include "widgets/CComponent.h"
|
|
#include "widgets/Buttons.h"
|
|
#include "widgets/MiscWidgets.h"
|
|
#include "widgets/ObjectLists.h"
|
|
#include "widgets/TextControls.h"
|
|
#include "windows/InfoWindows.h"
|
|
#include "../lib/mapping/CMapService.h"
|
|
#include "../lib/CRandomGenerator.h"
|
|
#include "../lib/CondSh.h"
|
|
|
|
namespace fs = boost::filesystem;
|
|
|
|
void startGame(StartInfo * options, CConnection *serv = nullptr);
|
|
void endGame();
|
|
|
|
CGPreGame * CGP = nullptr;
|
|
ISelectionScreenInfo *SEL;
|
|
|
|
static PlayerColor playerColor; //if more than one player - applies to the first
|
|
|
|
/**
|
|
* Stores the current name of the savegame.
|
|
*
|
|
* TODO better solution for auto-selection when saving already saved games.
|
|
* -> CSelectionScreen should be divided into CLoadGameScreen, CSaveGameScreen,...
|
|
* The name of the savegame can then be stored non-statically in CGameState and
|
|
* passed separately to CSaveGameScreen.
|
|
*/
|
|
static std::string saveGameName;
|
|
|
|
struct EvilHlpStruct
|
|
{
|
|
CConnection *serv;
|
|
StartInfo *sInfo;
|
|
|
|
void reset()
|
|
{
|
|
// vstd::clear_pointer(serv);
|
|
vstd::clear_pointer(sInfo);
|
|
}
|
|
|
|
} startingInfo;
|
|
|
|
static void do_quit()
|
|
{
|
|
SDL_Event event;
|
|
event.quit.type = SDL_QUIT;
|
|
SDL_PushEvent(&event);
|
|
}
|
|
|
|
static CMapInfo *mapInfoFromGame()
|
|
{
|
|
auto ret = new CMapInfo();
|
|
ret->mapHeader = std::unique_ptr<CMapHeader>(new CMapHeader(*LOCPLINT->cb->getMapHeader()));
|
|
return ret;
|
|
}
|
|
|
|
static void setPlayersFromGame()
|
|
{
|
|
playerColor = LOCPLINT->playerID;
|
|
}
|
|
|
|
static void swapPlayers(PlayerSettings &a, PlayerSettings &b)
|
|
{
|
|
std::swap(a.playerID, b.playerID);
|
|
std::swap(a.name, b.name);
|
|
|
|
if(a.playerID == 1)
|
|
playerColor = a.color;
|
|
else if(b.playerID == 1)
|
|
playerColor = b.color;
|
|
}
|
|
|
|
void setPlayer(PlayerSettings &pset, ui8 player, const std::map<ui8, std::string> &playerNames)
|
|
{
|
|
if(vstd::contains(playerNames, player))
|
|
pset.name = playerNames.find(player)->second;
|
|
else
|
|
pset.name = CGI->generaltexth->allTexts[468];//Computer
|
|
|
|
pset.playerID = player;
|
|
if(player == playerNames.begin()->first)
|
|
playerColor = pset.color;
|
|
}
|
|
|
|
void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader, const std::map<ui8, std::string> &playerNames)
|
|
{
|
|
sInfo.playerInfos.clear();
|
|
if(!mapHeader.get())
|
|
{
|
|
return;
|
|
}
|
|
|
|
sInfo.mapname = filename;
|
|
playerColor = PlayerColor::NEUTRAL;
|
|
|
|
auto namesIt = playerNames.cbegin();
|
|
|
|
for (int i = 0; i < mapHeader->players.size(); i++)
|
|
{
|
|
const PlayerInfo &pinfo = mapHeader->players[i];
|
|
|
|
//neither computer nor human can play - no player
|
|
if (!(pinfo.canHumanPlay || pinfo.canComputerPlay))
|
|
continue;
|
|
|
|
PlayerSettings &pset = sInfo.playerInfos[PlayerColor(i)];
|
|
pset.color = PlayerColor(i);
|
|
if(pinfo.canHumanPlay && namesIt != playerNames.cend())
|
|
{
|
|
setPlayer(pset, namesIt++->first, playerNames);
|
|
}
|
|
else
|
|
{
|
|
setPlayer(pset, 0, playerNames);
|
|
if(!pinfo.canHumanPlay)
|
|
{
|
|
pset.compOnly = true;
|
|
}
|
|
}
|
|
|
|
pset.castle = pinfo.defaultCastle();
|
|
pset.hero = pinfo.defaultHero();
|
|
|
|
if(pset.hero != PlayerSettings::RANDOM && pinfo.hasCustomMainHero())
|
|
{
|
|
pset.hero = pinfo.mainCustomHeroId;
|
|
pset.heroName = pinfo.mainCustomHeroName;
|
|
pset.heroPortrait = pinfo.mainCustomHeroPortrait;
|
|
}
|
|
|
|
pset.handicap = PlayerSettings::NO_HANDICAP;
|
|
}
|
|
}
|
|
|
|
template <typename T> class CApplyOnPG;
|
|
|
|
class CBaseForPGApply
|
|
{
|
|
public:
|
|
virtual void applyOnPG(CSelectionScreen *selScr, void *pack) const =0;
|
|
virtual ~CBaseForPGApply(){};
|
|
template<typename U> static CBaseForPGApply *getApplier(const U * t=nullptr)
|
|
{
|
|
return new CApplyOnPG<U>();
|
|
}
|
|
};
|
|
|
|
template <typename T> class CApplyOnPG : public CBaseForPGApply
|
|
{
|
|
public:
|
|
void applyOnPG(CSelectionScreen *selScr, void *pack) const override
|
|
{
|
|
T *ptr = static_cast<T*>(pack);
|
|
ptr->apply(selScr);
|
|
}
|
|
};
|
|
|
|
template <> class CApplyOnPG<CPack> : public CBaseForPGApply
|
|
{
|
|
public:
|
|
void applyOnPG(CSelectionScreen *selScr, void *pack) const override
|
|
{
|
|
logGlobal->error("Cannot apply on PG plain CPack!");
|
|
assert(0);
|
|
}
|
|
};
|
|
|
|
static CApplier<CBaseForPGApply> *applier = nullptr;
|
|
|
|
static CPicture* createPicture(const JsonNode& config)
|
|
{
|
|
return new CPicture(config["name"].String(), config["x"].Float(), config["y"].Float());
|
|
}
|
|
|
|
CMenuScreen::CMenuScreen(const JsonNode& configNode):
|
|
config(configNode)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
background = new CPicture(config["background"].String());
|
|
if (config["scalable"].Bool())
|
|
{
|
|
if (background->bg->format->palette)
|
|
background->convertToScreenBPP();
|
|
background->scaleTo(Point(screen->w, screen->h));
|
|
}
|
|
|
|
pos = background->center();
|
|
|
|
for(const JsonNode& node : config["items"].Vector())
|
|
menuNameToEntry.push_back(node["name"].String());
|
|
|
|
for(const JsonNode& node : config["images"].Vector())
|
|
images.push_back(createPicture(node));
|
|
|
|
//Hardcoded entry
|
|
menuNameToEntry.push_back("credits");
|
|
|
|
tabs = new CTabbedInt(std::bind(&CMenuScreen::createTab, this, _1), CTabbedInt::DestroyFunc());
|
|
tabs->type |= REDRAW_PARENT;
|
|
}
|
|
|
|
CIntObject * CMenuScreen::createTab(size_t index)
|
|
{
|
|
if (config["items"].Vector().size() == index)
|
|
return new CreditsScreen();
|
|
|
|
return new CMenuEntry(this, config["items"].Vector()[index]);
|
|
}
|
|
|
|
void CMenuScreen::showAll(SDL_Surface * to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
|
|
if (pos.h != to->h || pos.w != to->w)
|
|
CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
|
|
|
|
}
|
|
|
|
void CMenuScreen::show(SDL_Surface * to)
|
|
{
|
|
if (!config["video"].isNull())
|
|
CCS->videoh->update(config["video"]["x"].Float() + pos.x, config["video"]["y"].Float() + pos.y, to, true, false);
|
|
CIntObject::show(to);
|
|
}
|
|
|
|
void CMenuScreen::activate()
|
|
{
|
|
CCS->musich->playMusic("Music/MainMenu", true);
|
|
if (!config["video"].isNull())
|
|
CCS->videoh->open(config["video"]["name"].String());
|
|
CIntObject::activate();
|
|
}
|
|
|
|
void CMenuScreen::deactivate()
|
|
{
|
|
if (!config["video"].isNull())
|
|
CCS->videoh->close();
|
|
|
|
CIntObject::deactivate();
|
|
}
|
|
|
|
void CMenuScreen::switchToTab(size_t index)
|
|
{
|
|
tabs->setActive(index);
|
|
}
|
|
|
|
//funciton for std::string -> std::function conversion for main menu
|
|
static std::function<void()> genCommand(CMenuScreen* menu, std::vector<std::string> menuType, const std::string &string)
|
|
{
|
|
static const std::vector<std::string> commandType =
|
|
{"to", "campaigns", "start", "load", "exit", "highscores"};
|
|
|
|
static const std::vector<std::string> gameType =
|
|
{"single", "multi", "campaign", "tutorial"};
|
|
|
|
std::list<std::string> commands;
|
|
boost::split(commands, string, boost::is_any_of("\t "));
|
|
|
|
if (!commands.empty())
|
|
{
|
|
size_t index = std::find(commandType.begin(), commandType.end(), commands.front()) - commandType.begin();
|
|
commands.pop_front();
|
|
if (index > 3 || !commands.empty())
|
|
{
|
|
switch (index)
|
|
{
|
|
break; case 0://to - switch to another tab, if such tab exists
|
|
{
|
|
size_t index2 = std::find(menuType.begin(), menuType.end(), commands.front()) - menuType.begin();
|
|
if ( index2 != menuType.size())
|
|
return std::bind(&CMenuScreen::switchToTab, menu, index2);
|
|
}
|
|
break; case 1://open campaign selection window
|
|
{
|
|
return std::bind(&CGPreGame::openCampaignScreen, CGP, commands.front());
|
|
}
|
|
break; case 2://start
|
|
{
|
|
switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
|
|
{
|
|
case 0: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::newGame, CMenuScreen::SINGLE_PLAYER);
|
|
case 1: return &pushIntT<CMultiMode>;
|
|
case 2: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::campaignList, CMenuScreen::SINGLE_PLAYER);
|
|
//TODO: start tutorial
|
|
case 3: return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent*>*)nullptr, false, PlayerColor(1));
|
|
}
|
|
}
|
|
break; case 3://load
|
|
{
|
|
switch (std::find(gameType.begin(), gameType.end(), commands.front()) - gameType.begin())
|
|
{
|
|
case 0: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_PLAYER);
|
|
case 1: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::MULTI_HOT_SEAT);
|
|
case 2: return std::bind(&CGPreGame::openSel, CGP, CMenuScreen::loadGame, CMenuScreen::SINGLE_CAMPAIGN);
|
|
//TODO: load tutorial
|
|
case 3: return std::bind(CInfoWindow::showInfoDialog, "Sorry, tutorial is not implemented yet\n", (const std::vector<CComponent*>*)nullptr, false, PlayerColor(1));
|
|
}
|
|
}
|
|
break; case 4://exit
|
|
{
|
|
return std::bind(CInfoWindow::showYesNoDialog, std::ref(CGI->generaltexth->allTexts[69]), (const std::vector<CComponent*>*)nullptr, do_quit, 0, false, PlayerColor(1));
|
|
}
|
|
break; case 5://highscores
|
|
{
|
|
//TODO: high scores
|
|
return std::bind(CInfoWindow::showInfoDialog, "Sorry, high scores menu is not implemented yet\n", (const std::vector<CComponent*>*)nullptr, false, PlayerColor(1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
logGlobal->error("Failed to parse command: %s", string);
|
|
return std::function<void()>();
|
|
}
|
|
|
|
CButton* CMenuEntry::createButton(CMenuScreen* parent, const JsonNode& button)
|
|
{
|
|
std::function<void()> command = genCommand(parent, parent->menuNameToEntry, button["command"].String());
|
|
|
|
std::pair<std::string, std::string> help;
|
|
if (!button["help"].isNull() && button["help"].Float() > 0)
|
|
help = CGI->generaltexth->zelp[button["help"].Float()];
|
|
|
|
int posx = button["x"].Float();
|
|
if (posx < 0)
|
|
posx = pos.w + posx;
|
|
|
|
int posy = button["y"].Float();
|
|
if (posy < 0)
|
|
posy = pos.h + posy;
|
|
|
|
return new CButton(Point(posx, posy), button["name"].String(), help, command, button["hotkey"].Float());
|
|
}
|
|
|
|
CMenuEntry::CMenuEntry(CMenuScreen* parent, const JsonNode &config)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
type |= REDRAW_PARENT;
|
|
pos = parent->pos;
|
|
|
|
for(const JsonNode& node : config["images"].Vector())
|
|
images.push_back(createPicture(node));
|
|
|
|
for(const JsonNode& node : config["buttons"].Vector())
|
|
{
|
|
buttons.push_back(createButton(parent, node));
|
|
buttons.back()->hoverable = true;
|
|
buttons.back()->type |= REDRAW_PARENT;
|
|
}
|
|
}
|
|
|
|
CreditsScreen::CreditsScreen():
|
|
positionCounter(0)
|
|
{
|
|
addUsedEvents(LCLICK | RCLICK);
|
|
type |= REDRAW_PARENT;
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
pos.w = CGP->menu->pos.w;
|
|
pos.h = CGP->menu->pos.h;
|
|
auto textFile = CResourceHandler::get()->load(ResourceID("DATA/CREDITS.TXT"))->readAll();
|
|
std::string text((char*)textFile.first.get(), textFile.second);
|
|
size_t firstQuote = text.find('\"')+1;
|
|
text = text.substr(firstQuote, text.find('\"', firstQuote) - firstQuote );
|
|
credits = new CMultiLineLabel(Rect(pos.w - 350, 0, 350, 600), FONT_CREDITS, CENTER, Colors::WHITE, text);
|
|
credits->scrollTextTo(-600); // move all text below the screen
|
|
}
|
|
|
|
void CreditsScreen::show(SDL_Surface * to)
|
|
{
|
|
CIntObject::show(to);
|
|
positionCounter++;
|
|
if (positionCounter % 2 == 0)
|
|
credits->scrollTextBy(1);
|
|
|
|
//end of credits, close this screen
|
|
if (credits->textSize.y + 600 < positionCounter / 2)
|
|
clickRight(false, false);
|
|
}
|
|
|
|
void CreditsScreen::clickLeft(tribool down, bool previousState)
|
|
{
|
|
clickRight(down, previousState);
|
|
}
|
|
|
|
void CreditsScreen::clickRight(tribool down, bool previousState)
|
|
{
|
|
CTabbedInt* menu = dynamic_cast<CTabbedInt*>(parent);
|
|
assert(menu);
|
|
menu->setActive(0);
|
|
}
|
|
|
|
CGPreGameConfig & CGPreGameConfig::get()
|
|
{
|
|
static CGPreGameConfig config;
|
|
return config;
|
|
}
|
|
|
|
const JsonNode & CGPreGameConfig::getConfig() const
|
|
{
|
|
return config;
|
|
}
|
|
|
|
const JsonNode & CGPreGameConfig::getCampaigns() const
|
|
{
|
|
return campaignSets;
|
|
}
|
|
|
|
|
|
CGPreGameConfig::CGPreGameConfig() :
|
|
campaignSets(JsonNode(ResourceID("config/campaignSets.json"))),
|
|
config(JsonNode(ResourceID("config/mainmenu.json")))
|
|
{
|
|
|
|
}
|
|
|
|
CGPreGame::CGPreGame()
|
|
{
|
|
pos.w = screen->w;
|
|
pos.h = screen->h;
|
|
|
|
GH.defActionsDef = 63;
|
|
CGP = this;
|
|
menu = new CMenuScreen(CGPreGameConfig::get().getConfig()["window"]);
|
|
loadGraphics();
|
|
}
|
|
|
|
CGPreGame::~CGPreGame()
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
|
|
disposeGraphics();
|
|
if(CGP == this)
|
|
CGP = nullptr;
|
|
|
|
if(GH.curInt == this)
|
|
GH.curInt = nullptr;
|
|
}
|
|
|
|
void CGPreGame::openSel(CMenuScreen::EState screenType, CMenuScreen::EGameMode gameMode)
|
|
{
|
|
GH.pushInt(new CSelectionScreen(screenType, gameMode));
|
|
}
|
|
|
|
void CGPreGame::loadGraphics()
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
new CFilledTexture("DIBOXBCK", pos);
|
|
|
|
victoryIcons = std::make_shared<CAnimation>("SCNRVICT.DEF");
|
|
victoryIcons->load();
|
|
lossIcons = std::make_shared<CAnimation>("SCNRLOSS.DEF");
|
|
lossIcons->load();
|
|
}
|
|
|
|
void CGPreGame::disposeGraphics()
|
|
{
|
|
victoryIcons->unload();
|
|
lossIcons->unload();
|
|
}
|
|
|
|
void CGPreGame::update()
|
|
{
|
|
if(CGP != this) //don't update if you are not a main interface
|
|
return;
|
|
|
|
if (GH.listInt.empty())
|
|
{
|
|
GH.pushInt(this);
|
|
GH.pushInt(menu);
|
|
menu->switchToTab(0);
|
|
}
|
|
|
|
if(SEL)
|
|
SEL->update();
|
|
|
|
// Handles mouse and key input
|
|
GH.updateTime();
|
|
GH.handleEvents();
|
|
|
|
// check for null othervice crash on finishing a campaign
|
|
// /FIXME: find out why GH.listInt is empty to begin with
|
|
if (GH.topInt() != nullptr)
|
|
GH.topInt()->show(screen);
|
|
}
|
|
|
|
void CGPreGame::openCampaignScreen(std::string name)
|
|
{
|
|
if (vstd::contains(CGPreGameConfig::get().getCampaigns().Struct(), name))
|
|
{
|
|
GH.pushInt(new CCampaignScreen(CGPreGameConfig::get().getCampaigns()[name]));
|
|
return;
|
|
}
|
|
logGlobal->error("Unknown campaign set: %s", name);
|
|
}
|
|
|
|
CGPreGame *CGPreGame::create()
|
|
{
|
|
if(!CGP)
|
|
CGP = new CGPreGame();
|
|
|
|
GH.terminate_cond->set(false);
|
|
return CGP;
|
|
}
|
|
|
|
void CGPreGame::removeFromGui()
|
|
{
|
|
//remove everything but main menu and background
|
|
GH.popInts(GH.listInt.size() - 2);
|
|
GH.popInt(GH.topInt()); //remove main menu
|
|
GH.popInt(GH.topInt()); //remove background
|
|
}
|
|
|
|
CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EGameMode GameMode, const std::map<ui8, std::string> * Names, const std::string & Address, const ui16 Port)
|
|
: ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex),
|
|
serv(nullptr), ongoingClosing(false), myNameID(255)
|
|
{
|
|
CGPreGame::create(); //we depend on its graphics
|
|
screenType = Type;
|
|
gameMode = GameMode;
|
|
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
bool network = (isGuest() || isHost());
|
|
|
|
CServerHandler *sh = nullptr;
|
|
if(isHost())
|
|
{
|
|
sh = new CServerHandler();
|
|
sh->startServer();
|
|
}
|
|
|
|
IShowActivatable::type = BLOCK_ADV_HOTKEYS;
|
|
pos.w = 762;
|
|
pos.h = 584;
|
|
if(Type == CMenuScreen::saveGame)
|
|
{
|
|
bordered = false;
|
|
center(pos);
|
|
}
|
|
else if(Type == CMenuScreen::campaignList)
|
|
{
|
|
bordered = false;
|
|
bg = new CPicture("CamCust.bmp", 0, 0);
|
|
pos = bg->center();
|
|
}
|
|
else
|
|
{
|
|
bordered = true;
|
|
//load random background
|
|
const JsonVector & bgNames = CGPreGameConfig::get().getConfig()["game-select"].Vector();
|
|
bg = new CPicture(RandomGeneratorUtil::nextItem(bgNames, CRandomGenerator::getDefault())->String(), 0, 0);
|
|
pos = bg->center();
|
|
}
|
|
|
|
sInfo.difficulty = 1;
|
|
current = nullptr;
|
|
|
|
sInfo.mode = (Type == CMenuScreen::newGame ? StartInfo::NEW_GAME : StartInfo::LOAD_GAME);
|
|
sInfo.turnTime = 0;
|
|
curTab = nullptr;
|
|
|
|
card = new InfoCard(network); //right info card
|
|
if (screenType == CMenuScreen::campaignList)
|
|
{
|
|
opt = nullptr;
|
|
randMapTab = nullptr;
|
|
}
|
|
else
|
|
{
|
|
opt = new OptionsTab(); //scenario options tab
|
|
opt->recActions = DISPOSE;
|
|
|
|
randMapTab = new CRandomMapTab();
|
|
randMapTab->getMapInfoChanged() += std::bind(&CSelectionScreen::changeSelection, this, _1);
|
|
randMapTab->recActions = DISPOSE;
|
|
}
|
|
sel = new SelectionTab(screenType, std::bind(&CSelectionScreen::changeSelection, this, _1), gameMode); //scenario selection tab
|
|
sel->recActions = DISPOSE;
|
|
|
|
switch(screenType)
|
|
{
|
|
case CMenuScreen::newGame:
|
|
{
|
|
SDL_Color orange = {232, 184, 32, 0};
|
|
SDL_Color overlayColor = isGuest() ? orange : Colors::WHITE;
|
|
|
|
card->difficulty->addCallback(std::bind(&CSelectionScreen::difficultyChange, this, _1));
|
|
card->difficulty->setSelected(1);
|
|
CButton * select = new CButton(Point(411, 80), "GSPBUTT.DEF", CGI->generaltexth->zelp[45], 0, SDLK_s);
|
|
select->addCallback([&]()
|
|
{
|
|
toggleTab(sel);
|
|
changeSelection(sel->getSelectedMapInfo());
|
|
});
|
|
select->addTextOverlay(CGI->generaltexth->allTexts[500], FONT_SMALL, overlayColor);
|
|
|
|
CButton *opts = new CButton(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CSelectionScreen::toggleTab, this, opt), SDLK_a);
|
|
opts->addTextOverlay(CGI->generaltexth->allTexts[501], FONT_SMALL, overlayColor);
|
|
|
|
CButton * randomBtn = new CButton(Point(411, 105), "GSPBUTT.DEF", CGI->generaltexth->zelp[47], 0, SDLK_r);
|
|
randomBtn->addTextOverlay(CGI->generaltexth->allTexts[740], FONT_SMALL, overlayColor);
|
|
randomBtn->addCallback([&]()
|
|
{
|
|
toggleTab(randMapTab);
|
|
changeSelection(randMapTab->getMapInfo());
|
|
});
|
|
|
|
start = new CButton(Point(411, 535), "SCNRBEG.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_b);
|
|
|
|
if(network)
|
|
{
|
|
CButton *hideChat = new CButton(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&InfoCard::toggleChat, card), SDLK_h);
|
|
hideChat->addTextOverlay(CGI->generaltexth->allTexts[531], FONT_SMALL);
|
|
|
|
if(isGuest())
|
|
{
|
|
select->block(true);
|
|
opts->block(true);
|
|
randomBtn->block(true);
|
|
start->block(true);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CMenuScreen::loadGame:
|
|
sel->recActions = 255;
|
|
start = new CButton(Point(411, 535), "SCNRLOD.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_l);
|
|
break;
|
|
case CMenuScreen::saveGame:
|
|
sel->recActions = 255;
|
|
start = new CButton(Point(411, 535), "SCNRSAV.DEF", CGI->generaltexth->zelp[103], std::bind(&CSelectionScreen::startScenario, this), SDLK_s);
|
|
break;
|
|
case CMenuScreen::campaignList:
|
|
sel->recActions = 255;
|
|
start = new CButton(Point(411, 535), "SCNRLOD.DEF", CButton::tooltip(), std::bind(&CSelectionScreen::startCampaign, this), SDLK_b);
|
|
break;
|
|
}
|
|
|
|
start->assignedKeys.insert(SDLK_RETURN);
|
|
|
|
back = new CButton(Point(581, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
|
|
|
|
if(network)
|
|
{
|
|
if(isHost())
|
|
{
|
|
assert(playerNames.size() == 1 && vstd::contains(playerNames, 1)); //TODO hot-seat/network combo
|
|
if(settings["session"]["donotstartserver"].Bool())
|
|
serv = CServerHandler::justConnectToServer(Address, Port);
|
|
else
|
|
serv = sh->connectToServer();
|
|
*serv << (ui8) 4;
|
|
myNameID = 1;
|
|
}
|
|
else
|
|
serv = CServerHandler::justConnectToServer(Address, Port);
|
|
|
|
serv->enterPregameConnectionMode();
|
|
*serv << playerNames.begin()->second;
|
|
|
|
if(isGuest())
|
|
{
|
|
const CMapInfo *map;
|
|
*serv >> myNameID >> map;
|
|
serv->connectionID = myNameID;
|
|
changeSelection(map);
|
|
}
|
|
else if(current)
|
|
{
|
|
SelectMap sm(*current);
|
|
*serv << &sm;
|
|
|
|
UpdateStartOptions uso(sInfo);
|
|
*serv << &uso;
|
|
}
|
|
|
|
applier = new CApplier<CBaseForPGApply>();
|
|
registerTypesPregamePacks(*applier);
|
|
serverHandlingThread = new boost::thread(&CSelectionScreen::handleConnection, this);
|
|
}
|
|
delete sh;
|
|
}
|
|
|
|
CSelectionScreen::~CSelectionScreen()
|
|
{
|
|
ongoingClosing = true;
|
|
if(serv)
|
|
{
|
|
assert(serverHandlingThread);
|
|
QuitMenuWithoutStarting qmws;
|
|
*serv << &qmws;
|
|
// while(!serverHandlingThread->timed_join(boost::posix_time::milliseconds(50)))
|
|
// processPacks();
|
|
serverHandlingThread->join();
|
|
delete serverHandlingThread;
|
|
}
|
|
playerColor = PlayerColor::CANNOT_DETERMINE;
|
|
playerNames.clear();
|
|
|
|
assert(!serv);
|
|
vstd::clear_pointer(applier);
|
|
delete mx;
|
|
}
|
|
|
|
void CSelectionScreen::toggleTab(CIntObject *tab)
|
|
{
|
|
if(isHost() && serv)
|
|
{
|
|
PregameGuiAction pga;
|
|
if(tab == curTab)
|
|
pga.action = PregameGuiAction::NO_TAB;
|
|
else if(tab == opt)
|
|
pga.action = PregameGuiAction::OPEN_OPTIONS;
|
|
else if(tab == sel)
|
|
pga.action = PregameGuiAction::OPEN_SCENARIO_LIST;
|
|
else if(tab == randMapTab)
|
|
pga.action = PregameGuiAction::OPEN_RANDOM_MAP_OPTIONS;
|
|
|
|
*serv << &pga;
|
|
}
|
|
|
|
if(curTab && curTab->active)
|
|
{
|
|
curTab->deactivate();
|
|
curTab->recActions = DISPOSE;
|
|
}
|
|
if(curTab != tab)
|
|
{
|
|
tab->recActions = 255;
|
|
tab->activate();
|
|
curTab = tab;
|
|
}
|
|
else
|
|
{
|
|
curTab = nullptr;
|
|
}
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
void CSelectionScreen::changeSelection(const CMapInfo * to)
|
|
{
|
|
if(current == to) return;
|
|
if(isGuest())
|
|
vstd::clear_pointer(current);
|
|
|
|
current = to;
|
|
|
|
if(to && (screenType == CMenuScreen::loadGame ||
|
|
screenType == CMenuScreen::saveGame))
|
|
SEL->sInfo.difficulty = to->scenarioOpts->difficulty;
|
|
if(screenType != CMenuScreen::campaignList)
|
|
{
|
|
std::unique_ptr<CMapHeader> emptyHeader;
|
|
if(to)
|
|
updateStartInfo(to->fileURI, sInfo, to->mapHeader);
|
|
else
|
|
updateStartInfo("", sInfo, emptyHeader);
|
|
|
|
if(screenType == CMenuScreen::newGame)
|
|
{
|
|
if(to && to->isRandomMap)
|
|
{
|
|
sInfo.mapGenOptions = std::shared_ptr<CMapGenOptions>(new CMapGenOptions(randMapTab->getMapGenOptions()));
|
|
}
|
|
else
|
|
{
|
|
sInfo.mapGenOptions.reset();
|
|
}
|
|
}
|
|
}
|
|
card->changeSelection(to);
|
|
if(screenType != CMenuScreen::campaignList)
|
|
{
|
|
opt->recreate();
|
|
}
|
|
|
|
if(isHost() && serv)
|
|
{
|
|
SelectMap sm(*to);
|
|
*serv << &sm;
|
|
|
|
UpdateStartOptions uso(sInfo);
|
|
*serv << &uso;
|
|
}
|
|
}
|
|
|
|
void CSelectionScreen::startCampaign()
|
|
{
|
|
if (SEL->current)
|
|
GH.pushInt(new CBonusSelection(SEL->current->fileURI));
|
|
}
|
|
|
|
void CSelectionScreen::startScenario()
|
|
{
|
|
if(screenType == CMenuScreen::newGame)
|
|
{
|
|
//there must be at least one human player before game can be started
|
|
std::map<PlayerColor, PlayerSettings>::const_iterator i;
|
|
for(i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++)
|
|
if(i->second.playerID != PlayerSettings::PLAYER_AI)
|
|
break;
|
|
|
|
if(i == SEL->sInfo.playerInfos.cend())
|
|
{
|
|
GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[530])); //You must position yourself prior to starting the game.
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(isHost())
|
|
{
|
|
start->block(true);
|
|
StartWithCurrentSettings swcs;
|
|
*serv << &swcs;
|
|
ongoingClosing = true;
|
|
return;
|
|
}
|
|
|
|
if(screenType != CMenuScreen::saveGame)
|
|
{
|
|
if(!current)
|
|
return;
|
|
|
|
if(sInfo.mapGenOptions)
|
|
{
|
|
//copy settings from interface to actual options. TODO: refactor, it used to have no effect at all -.-
|
|
sInfo.mapGenOptions = std::shared_ptr<CMapGenOptions>(new CMapGenOptions(randMapTab->getMapGenOptions()));
|
|
|
|
// Update player settings for RMG
|
|
for(const auto & psetPair : sInfo.playerInfos)
|
|
{
|
|
const auto & pset = psetPair.second;
|
|
sInfo.mapGenOptions->setStartingTownForPlayer(pset.color, pset.castle);
|
|
if(pset.playerID != PlayerSettings::PLAYER_AI)
|
|
{
|
|
sInfo.mapGenOptions->setPlayerTypeForStandardPlayer(pset.color, EPlayerType::HUMAN);
|
|
}
|
|
}
|
|
|
|
if(!sInfo.mapGenOptions->checkOptions())
|
|
{
|
|
GH.pushInt(CInfoWindow::create(CGI->generaltexth->allTexts[751]));
|
|
return;
|
|
}
|
|
}
|
|
|
|
saveGameName.clear();
|
|
if(screenType == CMenuScreen::loadGame)
|
|
{
|
|
saveGameName = sInfo.mapname;
|
|
}
|
|
|
|
auto si = new StartInfo(sInfo);
|
|
CGP->removeFromGui();
|
|
CGP->showLoadingScreen(std::bind(&startGame, si, (CConnection *)nullptr));
|
|
}
|
|
else
|
|
{
|
|
if(!(sel && sel->txt && sel->txt->text.size()))
|
|
return;
|
|
|
|
saveGameName = "Saves/" + sel->txt->text;
|
|
|
|
CFunctionList<void()> overWrite;
|
|
overWrite += std::bind(&CCallback::save, LOCPLINT->cb.get(), saveGameName);
|
|
overWrite += std::bind(&CGuiHandler::popIntTotally, &GH, this);
|
|
|
|
if(CResourceHandler::get("local")->existsResource(ResourceID(saveGameName, EResType::CLIENT_SAVEGAME)))
|
|
{
|
|
std::string hlp = CGI->generaltexth->allTexts[493]; //%s exists. Overwrite?
|
|
boost::algorithm::replace_first(hlp, "%s", sel->txt->text);
|
|
LOCPLINT->showYesNoDialog(hlp, overWrite, 0, false);
|
|
}
|
|
else
|
|
overWrite();
|
|
}
|
|
}
|
|
|
|
void CSelectionScreen::difficultyChange( int to )
|
|
{
|
|
assert(screenType == CMenuScreen::newGame);
|
|
sInfo.difficulty = to;
|
|
propagateOptions();
|
|
redraw();
|
|
}
|
|
|
|
void CSelectionScreen::handleConnection()
|
|
{
|
|
setThreadName("CSelectionScreen::handleConnection");
|
|
try
|
|
{
|
|
assert(serv);
|
|
while(serv)
|
|
{
|
|
CPackForSelectionScreen *pack = nullptr;
|
|
*serv >> pack;
|
|
logNetwork->trace("Received a pack of type %s", typeid(*pack).name());
|
|
assert(pack);
|
|
if(QuitMenuWithoutStarting *endingPack = dynamic_cast<QuitMenuWithoutStarting *>(pack))
|
|
{
|
|
endingPack->apply(this);
|
|
}
|
|
else if(StartWithCurrentSettings *endingPack = dynamic_cast<StartWithCurrentSettings *>(pack))
|
|
{
|
|
endingPack->apply(this);
|
|
}
|
|
else
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> lll(*mx);
|
|
upcomingPacks.push_back(pack);
|
|
}
|
|
}
|
|
}
|
|
catch(int i)
|
|
{
|
|
if(i != 666)
|
|
throw;
|
|
}
|
|
catch(...)
|
|
{
|
|
handleException();
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void CSelectionScreen::setSInfo(const StartInfo &si)
|
|
{
|
|
std::map<PlayerColor, PlayerSettings>::const_iterator i;
|
|
for(i = si.playerInfos.cbegin(); i != si.playerInfos.cend(); i++)
|
|
{
|
|
if(i->second.playerID == myNameID)
|
|
{
|
|
playerColor = i->first;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(i == si.playerInfos.cend()) //not found
|
|
playerColor = PlayerColor::CANNOT_DETERMINE;
|
|
|
|
sInfo = si;
|
|
if(current)
|
|
opt->recreate(); //will force to recreate using current sInfo
|
|
|
|
card->difficulty->setSelected(si.difficulty);
|
|
|
|
if(curTab == randMapTab)
|
|
randMapTab->setMapGenOptions(si.mapGenOptions);
|
|
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
void CSelectionScreen::processPacks()
|
|
{
|
|
boost::unique_lock<boost::recursive_mutex> lll(*mx);
|
|
while(!upcomingPacks.empty())
|
|
{
|
|
CPackForSelectionScreen *pack = upcomingPacks.front();
|
|
upcomingPacks.pop_front();
|
|
CBaseForPGApply *apply = applier->getApplier(typeList.getTypeID(pack)); //find the applier
|
|
apply->applyOnPG(this, pack);
|
|
delete pack;
|
|
}
|
|
}
|
|
|
|
void CSelectionScreen::update()
|
|
{
|
|
if(serverHandlingThread)
|
|
processPacks();
|
|
}
|
|
|
|
void CSelectionScreen::propagateOptions()
|
|
{
|
|
if(isHost() && serv)
|
|
{
|
|
UpdateStartOptions ups(sInfo);
|
|
*serv << &ups;
|
|
}
|
|
}
|
|
|
|
void CSelectionScreen::postRequest(ui8 what, ui8 dir)
|
|
{
|
|
if(!isGuest() || !serv)
|
|
return;
|
|
|
|
RequestOptionsChange roc(what, dir, myNameID);
|
|
*serv << &roc;
|
|
}
|
|
|
|
void CSelectionScreen::postChatMessage(const std::string &txt)
|
|
{
|
|
assert(serv);
|
|
ChatMessage cm;
|
|
cm.message = txt;
|
|
cm.playerName = sInfo.getPlayersSettings(myNameID)->name;
|
|
*serv << &cm;
|
|
}
|
|
|
|
void CSelectionScreen::propagateNames()
|
|
{
|
|
PlayersNames pn;
|
|
pn.playerNames = playerNames;
|
|
*serv << &pn;
|
|
}
|
|
|
|
void CSelectionScreen::showAll(SDL_Surface *to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
if (bordered && (pos.h != to->h || pos.w != to->w))
|
|
CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
|
|
}
|
|
|
|
// A new size filter (Small, Medium, ...) has been selected. Populate
|
|
// selMaps with the relevant data.
|
|
void SelectionTab::filter( int size, bool selectFirst )
|
|
{
|
|
curItems.clear();
|
|
|
|
if(tabType == CMenuScreen::campaignList)
|
|
{
|
|
for (auto & elem : allItems)
|
|
curItems.push_back(&elem);
|
|
}
|
|
else
|
|
{
|
|
for (auto & elem : allItems)
|
|
if( elem.mapHeader && elem.mapHeader->version && (!size || elem.mapHeader->width == size))
|
|
curItems.push_back(&elem);
|
|
}
|
|
|
|
if(curItems.size())
|
|
{
|
|
slider->block(false);
|
|
slider->setAmount(curItems.size());
|
|
sort();
|
|
if(selectFirst)
|
|
{
|
|
slider->moveTo(0);
|
|
onSelect(curItems[0]);
|
|
selectAbs(0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
slider->block(true);
|
|
onSelect(nullptr);
|
|
}
|
|
}
|
|
|
|
std::unordered_set<ResourceID> SelectionTab::getFiles(std::string dirURI, int resType)
|
|
{
|
|
boost::to_upper(dirURI);
|
|
CResourceHandler::get()->updateFilteredFiles([&](const std::string & mount)
|
|
{
|
|
return boost::algorithm::starts_with(mount, dirURI);
|
|
});
|
|
|
|
std::unordered_set<ResourceID> ret = CResourceHandler::get()->getFilteredFiles([&](const ResourceID & ident)
|
|
{
|
|
return ident.getType() == resType
|
|
&& boost::algorithm::starts_with(ident.getName(), dirURI);
|
|
});
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SelectionTab::parseMaps(const std::unordered_set<ResourceID> &files)
|
|
{
|
|
logGlobal->debug("Parsing %d maps", files.size());
|
|
allItems.clear();
|
|
for(auto & file : files)
|
|
{
|
|
try
|
|
{
|
|
CMapInfo mapInfo;
|
|
mapInfo.mapInit(file.getName());
|
|
|
|
// ignore unsupported map versions (e.g. WoG maps without WoG)
|
|
// but accept VCMI maps
|
|
if((mapInfo.mapHeader->version >= EMapFormat::VCMI) || (mapInfo.mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float()))
|
|
allItems.push_back(std::move(mapInfo));
|
|
}
|
|
catch(std::exception & e)
|
|
{
|
|
logGlobal->error("Map %s is invalid. Message: %s", file.getName(), e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SelectionTab::parseGames(const std::unordered_set<ResourceID> &files, CMenuScreen::EGameMode gameMode)
|
|
{
|
|
for(auto & file : files)
|
|
{
|
|
try
|
|
{
|
|
CLoadFile lf(*CResourceHandler::get()->getResourceName(file), MINIMAL_SERIALIZATION_VERSION);
|
|
lf.checkMagicBytes(SAVEGAME_MAGIC);
|
|
// ui8 sign[8];
|
|
// lf >> sign;
|
|
// if(std::memcmp(sign,"VCMISVG",7))
|
|
// {
|
|
// throw std::runtime_error("not a correct savefile!");
|
|
// }
|
|
|
|
// Create the map info object
|
|
CMapInfo mapInfo;
|
|
mapInfo.mapHeader = make_unique<CMapHeader>();
|
|
mapInfo.scenarioOpts = nullptr;//to be created by serialiser
|
|
lf >> *(mapInfo.mapHeader.get()) >> mapInfo.scenarioOpts;
|
|
mapInfo.fileURI = file.getName();
|
|
mapInfo.countPlayers();
|
|
std::time_t time = boost::filesystem::last_write_time(*CResourceHandler::get()->getResourceName(file));
|
|
mapInfo.date = std::asctime(std::localtime(&time));
|
|
|
|
// Filter out other game modes
|
|
bool isCampaign = mapInfo.scenarioOpts->mode == StartInfo::CAMPAIGN;
|
|
bool isMultiplayer = mapInfo.actualHumanPlayers > 1;
|
|
switch(gameMode)
|
|
{
|
|
case CMenuScreen::SINGLE_PLAYER:
|
|
if(isMultiplayer || isCampaign)
|
|
mapInfo.mapHeader.reset();
|
|
break;
|
|
case CMenuScreen::SINGLE_CAMPAIGN:
|
|
if(!isCampaign)
|
|
mapInfo.mapHeader.reset();
|
|
break;
|
|
default:
|
|
if(!isMultiplayer)
|
|
mapInfo.mapHeader.reset();
|
|
break;
|
|
}
|
|
|
|
allItems.push_back(std::move(mapInfo));
|
|
}
|
|
catch(const std::exception & e)
|
|
{
|
|
logGlobal->error("Error: Failed to process %s: %s", file.getName(), e.what());
|
|
}
|
|
}
|
|
}
|
|
|
|
void SelectionTab::parseCampaigns(const std::unordered_set<ResourceID> &files )
|
|
{
|
|
allItems.reserve(files.size());
|
|
for (auto & file : files)
|
|
{
|
|
CMapInfo info;
|
|
//allItems[i].date = std::asctime(std::localtime(&files[i].date));
|
|
info.fileURI = file.getName();
|
|
info.campaignInit();
|
|
allItems.push_back(std::move(info));
|
|
}
|
|
}
|
|
|
|
SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::function<void(CMapInfo *)> &OnSelect, CMenuScreen::EGameMode GameMode)
|
|
:bg(nullptr), onSelect(OnSelect)
|
|
{
|
|
OBJ_CONSTRUCTION;
|
|
selectionPos = 0;
|
|
addUsedEvents(LCLICK | WHEEL | KEYBOARD | DOUBLECLICK);
|
|
slider = nullptr;
|
|
txt = nullptr;
|
|
tabType = Type;
|
|
|
|
if (Type != CMenuScreen::campaignList)
|
|
{
|
|
bg = new CPicture("SCSELBCK.bmp", 0, 6);
|
|
pos = bg->pos;
|
|
}
|
|
else
|
|
{
|
|
bg = nullptr; //use background from parent
|
|
type |= REDRAW_PARENT; // we use parent background so we need to make sure it's will be redrawn too
|
|
pos.w = parent->pos.w;
|
|
pos.h = parent->pos.h;
|
|
pos.x += 3; pos.y += 6;
|
|
}
|
|
|
|
if(GameMode == CMenuScreen::MULTI_NETWORK_GUEST)
|
|
{
|
|
positions = 18;
|
|
}
|
|
else
|
|
{
|
|
switch(tabType)
|
|
{
|
|
case CMenuScreen::newGame:
|
|
parseMaps(getFiles("Maps/", EResType::MAP));
|
|
positions = 18;
|
|
break;
|
|
|
|
case CMenuScreen::loadGame:
|
|
case CMenuScreen::saveGame:
|
|
parseGames(getFiles("Saves/", EResType::CLIENT_SAVEGAME), GameMode);
|
|
if(tabType == CMenuScreen::loadGame)
|
|
{
|
|
positions = 18;
|
|
}
|
|
else
|
|
{
|
|
positions = 16;
|
|
}
|
|
if(tabType == CMenuScreen::saveGame)
|
|
{
|
|
txt = new CTextInput(Rect(32, 539, 350, 20), Point(-32, -25), "GSSTRIP.bmp", 0);
|
|
txt->filters += CTextInput::filenameFilter;
|
|
}
|
|
break;
|
|
|
|
case CMenuScreen::campaignList:
|
|
parseCampaigns(getFiles("Maps/", EResType::CAMPAIGN));
|
|
positions = 18;
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
generalSortingBy = (tabType == CMenuScreen::loadGame || tabType == CMenuScreen::saveGame) ? _fileName : _name;
|
|
|
|
if (tabType != CMenuScreen::campaignList)
|
|
{
|
|
//size filter buttons
|
|
{
|
|
int sizes[] = {36, 72, 108, 144, 0};
|
|
const char * names[] = {"SCSMBUT.DEF", "SCMDBUT.DEF", "SCLGBUT.DEF", "SCXLBUT.DEF", "SCALBUT.DEF"};
|
|
for(int i = 0; i < 5; i++)
|
|
new CButton(Point(158 + 47*i, 46), names[i], CGI->generaltexth->zelp[54+i], std::bind(&SelectionTab::filter, this, sizes[i], true));
|
|
}
|
|
|
|
//sort buttons buttons
|
|
{
|
|
int xpos[] = {23, 55, 88, 121, 306, 339};
|
|
const char * names[] = {"SCBUTT1.DEF", "SCBUTT2.DEF", "SCBUTCP.DEF", "SCBUTT3.DEF", "SCBUTT4.DEF", "SCBUTT5.DEF"};
|
|
for(int i = 0; i < 6; i++)
|
|
{
|
|
ESortBy criteria = (ESortBy)i;
|
|
|
|
if(criteria == _name)
|
|
criteria = generalSortingBy;
|
|
|
|
new CButton(Point(xpos[i], 86), names[i], CGI->generaltexth->zelp[107+i], std::bind(&SelectionTab::sortBy, this, criteria));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//sort by buttons
|
|
new CButton(Point(23, 86), "CamCusM.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _numOfMaps)); //by num of maps
|
|
new CButton(Point(55, 86), "CamCusL.DEF", CButton::tooltip(), std::bind(&SelectionTab::sortBy, this, _name)); //by name
|
|
}
|
|
|
|
slider = new CSlider(Point(372, 86), tabType != CMenuScreen::saveGame ? 480 : 430, std::bind(&SelectionTab::sliderMove, this, _1), positions, curItems.size(), 0, false, CSlider::BLUE);
|
|
slider->addUsedEvents(WHEEL);
|
|
|
|
formatIcons = std::make_shared<CAnimation>("SCSELC.DEF");
|
|
formatIcons->load();
|
|
|
|
sortingBy = _format;
|
|
ascending = true;
|
|
filter(0);
|
|
//select(0);
|
|
switch(tabType)
|
|
{
|
|
case CMenuScreen::newGame:
|
|
logGlobal->error(settings["session"]["lastMap"].String());
|
|
if(settings["session"]["lastMap"].isNull())
|
|
selectFName("Maps/Arrogance");
|
|
else
|
|
selectFName(settings["session"]["lastMap"].String());
|
|
|
|
break;
|
|
case CMenuScreen::campaignList:
|
|
select(0);
|
|
break;
|
|
case CMenuScreen::loadGame:
|
|
case CMenuScreen::saveGame:
|
|
if(saveGameName.empty())
|
|
{
|
|
if(tabType == CMenuScreen::saveGame)
|
|
txt->setText("NEWGAME");
|
|
else
|
|
select(0);
|
|
}
|
|
else
|
|
{
|
|
selectFName(saveGameName);
|
|
}
|
|
}
|
|
}
|
|
|
|
SelectionTab::~SelectionTab()
|
|
{
|
|
formatIcons->unload();
|
|
}
|
|
|
|
void SelectionTab::sortBy( int criteria )
|
|
{
|
|
if(criteria == sortingBy)
|
|
{
|
|
ascending = !ascending;
|
|
}
|
|
else
|
|
{
|
|
sortingBy = (ESortBy)criteria;
|
|
ascending = true;
|
|
}
|
|
sort();
|
|
|
|
selectAbs(0);
|
|
}
|
|
|
|
void SelectionTab::sort()
|
|
{
|
|
if(sortingBy != generalSortingBy)
|
|
std::stable_sort(curItems.begin(), curItems.end(), mapSorter(generalSortingBy));
|
|
std::stable_sort(curItems.begin(), curItems.end(), mapSorter(sortingBy));
|
|
|
|
if(!ascending)
|
|
std::reverse(curItems.begin(), curItems.end());
|
|
|
|
redraw();
|
|
}
|
|
|
|
void SelectionTab::select( int position )
|
|
{
|
|
if(!curItems.size()) return;
|
|
|
|
// New selection. py is the index in curItems.
|
|
int py = position + slider->getValue();
|
|
vstd::amax(py, 0);
|
|
vstd::amin(py, curItems.size()-1);
|
|
|
|
selectionPos = py;
|
|
|
|
if(position < 0)
|
|
slider->moveBy(position);
|
|
else if(position >= positions)
|
|
slider->moveBy(position - positions + 1);
|
|
|
|
if(tabType == CMenuScreen::newGame)
|
|
{
|
|
Settings lastMap = settings.write["session"]["lastMap"];
|
|
lastMap->String() = getSelectedMapInfo()->fileURI;
|
|
}
|
|
|
|
if(txt)
|
|
{
|
|
auto filename = *CResourceHandler::get("local")->getResourceName(
|
|
ResourceID(curItems[py]->fileURI, EResType::CLIENT_SAVEGAME));
|
|
txt->setText(filename.stem().string());
|
|
}
|
|
|
|
onSelect(curItems[py]);
|
|
}
|
|
|
|
void SelectionTab::selectAbs( int position )
|
|
{
|
|
select(position - slider->getValue());
|
|
}
|
|
|
|
int SelectionTab::getPosition( int x, int y )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
void SelectionTab::sliderMove( int slidPos )
|
|
{
|
|
if(!slider) return; //ignore spurious call when slider is being created
|
|
redraw();
|
|
}
|
|
|
|
|
|
// Display the tab with the scenario names
|
|
//
|
|
// elemIdx is the index of the maps or saved game to display on line 0
|
|
// slider->capacity contains the number of available screen lines
|
|
// slider->positionsAmnt is the number of elements after filtering
|
|
void SelectionTab::printMaps(SDL_Surface *to)
|
|
{
|
|
|
|
int elemIdx = slider->getValue();
|
|
|
|
// Display all elements if there's enough space
|
|
//if(slider->amount < slider->capacity)
|
|
// elemIdx = 0;
|
|
|
|
|
|
SDL_Color itemColor;
|
|
for (int line = 0; line < positions && elemIdx < curItems.size(); elemIdx++, line++)
|
|
{
|
|
CMapInfo *currentItem = curItems[elemIdx];
|
|
|
|
if(elemIdx == selectionPos)
|
|
itemColor=Colors::YELLOW;
|
|
else
|
|
itemColor=Colors::WHITE;
|
|
|
|
if(tabType != CMenuScreen::campaignList)
|
|
{
|
|
//amount of players
|
|
std::ostringstream ostr(std::ostringstream::out);
|
|
ostr << currentItem->playerAmnt << "/" << currentItem->humanPlayers;
|
|
printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to);
|
|
|
|
//map size
|
|
std::string temp2 = "C";
|
|
switch (currentItem->mapHeader->width)
|
|
{
|
|
case 36:
|
|
temp2="S";
|
|
break;
|
|
case 72:
|
|
temp2="M";
|
|
break;
|
|
case 108:
|
|
temp2="L";
|
|
break;
|
|
case 144:
|
|
temp2="XL";
|
|
break;
|
|
}
|
|
printAtMiddleLoc(temp2, 70, 128 + line * 25, FONT_SMALL, itemColor, to);
|
|
|
|
int frame = -1, group = 0;
|
|
switch (currentItem->mapHeader->version)
|
|
{
|
|
case EMapFormat::ROE:
|
|
frame = 0;
|
|
break;
|
|
case EMapFormat::AB:
|
|
frame = 1;
|
|
break;
|
|
case EMapFormat::SOD:
|
|
frame = 2;
|
|
break;
|
|
case EMapFormat::WOG:
|
|
frame = 3;
|
|
break;
|
|
case EMapFormat::VCMI:
|
|
frame = 0;
|
|
group = 1;
|
|
break;
|
|
default:
|
|
// Unknown version. Be safe and ignore that map
|
|
logGlobal->warn("Warning: %s has wrong version!", currentItem->fileURI);
|
|
continue;
|
|
}
|
|
IImage * icon = formatIcons->getImage(frame,group);
|
|
if(icon)
|
|
{
|
|
icon->draw(to, pos.x + 88, pos.y + 117 + line * 25);
|
|
icon->decreaseRef();
|
|
}
|
|
|
|
//victory conditions
|
|
icon = CGP->victoryIcons->getImage(currentItem->mapHeader->victoryIconIndex,0);
|
|
if(icon)
|
|
{
|
|
icon->draw(to, pos.x + 306, pos.y + 117 + line * 25);
|
|
icon->decreaseRef();
|
|
}
|
|
//loss conditions
|
|
icon = CGP->lossIcons->getImage(currentItem->mapHeader->defeatIconIndex,0);
|
|
if(icon)
|
|
{
|
|
icon->draw(to, pos.x + 339, pos.y + 117 + line * 25);
|
|
icon->decreaseRef();
|
|
}
|
|
}
|
|
else //if campaign
|
|
{
|
|
//number of maps in campaign
|
|
std::ostringstream ostr(std::ostringstream::out);
|
|
ostr << CGI->generaltexth->campaignRegionNames[ currentItem->campaignHeader->mapVersion ].size();
|
|
printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to);
|
|
}
|
|
|
|
std::string name;
|
|
if(tabType == CMenuScreen::newGame)
|
|
{
|
|
if (!currentItem->mapHeader->name.length())
|
|
currentItem->mapHeader->name = "Unnamed";
|
|
name = currentItem->mapHeader->name;
|
|
}
|
|
else if(tabType == CMenuScreen::campaignList)
|
|
{
|
|
name = currentItem->campaignHeader->name;
|
|
}
|
|
else
|
|
{
|
|
name = CResourceHandler::get("local")->getResourceName(
|
|
ResourceID(currentItem->fileURI, EResType::CLIENT_SAVEGAME))->stem().string();
|
|
}
|
|
|
|
//print name
|
|
printAtMiddleLoc(name, 213, 128 + line * 25, FONT_SMALL, itemColor, to);
|
|
}
|
|
}
|
|
|
|
void SelectionTab::showAll(SDL_Surface * to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
printMaps(to);
|
|
|
|
std::string title;
|
|
switch(tabType) {
|
|
case CMenuScreen::newGame:
|
|
title = CGI->generaltexth->arraytxt[229];
|
|
break;
|
|
case CMenuScreen::loadGame:
|
|
title = CGI->generaltexth->arraytxt[230];
|
|
break;
|
|
case CMenuScreen::saveGame:
|
|
title = CGI->generaltexth->arraytxt[231];
|
|
break;
|
|
case CMenuScreen::campaignList:
|
|
title = CGI->generaltexth->allTexts[726];
|
|
break;
|
|
}
|
|
|
|
printAtMiddleLoc(title, 205, 28, FONT_MEDIUM, Colors::YELLOW, to); //Select a Scenario to Play
|
|
if(tabType != CMenuScreen::campaignList)
|
|
{
|
|
printAtMiddleLoc(CGI->generaltexth->allTexts[510], 87, 62, FONT_SMALL, Colors::YELLOW, to); //Map sizes
|
|
}
|
|
}
|
|
|
|
void SelectionTab::clickLeft( tribool down, bool previousState )
|
|
{
|
|
if(down)
|
|
{
|
|
int line = getLine();
|
|
if(line != -1)
|
|
select(line);
|
|
}
|
|
}
|
|
void SelectionTab::keyPressed( const SDL_KeyboardEvent & key )
|
|
{
|
|
if(key.state != SDL_PRESSED) return;
|
|
|
|
int moveBy = 0;
|
|
switch(key.keysym.sym)
|
|
{
|
|
case SDLK_UP:
|
|
moveBy = -1;
|
|
break;
|
|
case SDLK_DOWN:
|
|
moveBy = +1;
|
|
break;
|
|
case SDLK_PAGEUP:
|
|
moveBy = -positions+1;
|
|
break;
|
|
case SDLK_PAGEDOWN:
|
|
moveBy = +positions-1;
|
|
break;
|
|
case SDLK_HOME:
|
|
select(-slider->getValue());
|
|
return;
|
|
case SDLK_END:
|
|
select(curItems.size() - slider->getValue());
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
select(selectionPos - slider->getValue() + moveBy);
|
|
}
|
|
|
|
void SelectionTab::onDoubleClick()
|
|
{
|
|
if(getLine() != -1) //double clicked scenarios list
|
|
{
|
|
//act as if start button was pressed
|
|
(static_cast<CSelectionScreen*>(parent))->start->clickLeft(false, true);
|
|
}
|
|
}
|
|
|
|
int SelectionTab::getLine()
|
|
{
|
|
int line = -1;
|
|
Point clickPos(GH.current->button.x, GH.current->button.y);
|
|
clickPos = clickPos - pos.topLeft();
|
|
|
|
// Ignore clicks on save name area
|
|
int maxPosY;
|
|
if(tabType == CMenuScreen::saveGame)
|
|
maxPosY = 516;
|
|
else
|
|
maxPosY = 564;
|
|
|
|
if(clickPos.y > 115 && clickPos.y < maxPosY && clickPos.x > 22 && clickPos.x < 371)
|
|
{
|
|
line = (clickPos.y-115) / 25; //which line
|
|
}
|
|
|
|
return line;
|
|
}
|
|
|
|
void SelectionTab::selectFName( std::string fname )
|
|
{
|
|
boost::to_upper(fname);
|
|
for(int i = curItems.size() - 1; i >= 0; i--)
|
|
{
|
|
if(curItems[i]->fileURI == fname)
|
|
{
|
|
slider->moveTo(i);
|
|
selectAbs(i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
selectAbs(0);
|
|
}
|
|
|
|
const CMapInfo * SelectionTab::getSelectedMapInfo() const
|
|
{
|
|
return curItems.empty() ? nullptr : curItems[selectionPos];
|
|
}
|
|
|
|
CRandomMapTab::CRandomMapTab()
|
|
{
|
|
OBJ_CONSTRUCTION;
|
|
bg = new CPicture("RANMAPBK", 0, 6);
|
|
|
|
// Map Size
|
|
mapSizeBtnGroup = new CToggleGroup(0);
|
|
mapSizeBtnGroup->pos.y += 81;
|
|
mapSizeBtnGroup->pos.x += 158;
|
|
const std::vector<std::string> mapSizeBtns = {"RANSIZS", "RANSIZM","RANSIZL","RANSIZX"};
|
|
addButtonsToGroup(mapSizeBtnGroup, mapSizeBtns, 0, 3, 47, 198);
|
|
mapSizeBtnGroup->setSelected(1);
|
|
mapSizeBtnGroup->addCallback([&](int btnId)
|
|
{
|
|
auto mapSizeVal = getPossibleMapSizes();
|
|
mapGenOptions.setWidth(mapSizeVal[btnId]);
|
|
mapGenOptions.setHeight(mapSizeVal[btnId]);
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
});
|
|
|
|
// Two levels
|
|
twoLevelsBtn = new CToggleButton(Point(346, 81), "RANUNDR", CGI->generaltexth->zelp[202]);
|
|
//twoLevelsBtn->select(true); for now, deactivated
|
|
twoLevelsBtn->addCallback([&](bool on)
|
|
{
|
|
mapGenOptions.setHasTwoLevels(on);
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
});
|
|
|
|
// Create number defs list
|
|
std::vector<std::string> numberDefs;
|
|
for(int i = 0; i <= 8; ++i)
|
|
{
|
|
numberDefs.push_back("RANNUM" + boost::lexical_cast<std::string>(i));
|
|
}
|
|
|
|
const int NUMBERS_WIDTH = 32;
|
|
const int BTNS_GROUP_LEFT_MARGIN = 67;
|
|
// Amount of players
|
|
playersCntGroup = new CToggleGroup(0);
|
|
playersCntGroup->pos.y += 153;
|
|
playersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
|
|
addButtonsWithRandToGroup(playersCntGroup, numberDefs, 1, 8, NUMBERS_WIDTH, 204, 212);
|
|
playersCntGroup->addCallback([&](int btnId)
|
|
{
|
|
mapGenOptions.setPlayerCount(btnId);
|
|
deactivateButtonsFrom(teamsCntGroup, btnId);
|
|
deactivateButtonsFrom(compOnlyPlayersCntGroup, btnId);
|
|
validatePlayersCnt(btnId);
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
});
|
|
|
|
// Amount of teams
|
|
teamsCntGroup = new CToggleGroup(0);
|
|
teamsCntGroup->pos.y += 219;
|
|
teamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
|
|
addButtonsWithRandToGroup(teamsCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 214, 222);
|
|
teamsCntGroup->addCallback([&](int btnId)
|
|
{
|
|
mapGenOptions.setTeamCount(btnId);
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
});
|
|
|
|
// Computer only players
|
|
compOnlyPlayersCntGroup = new CToggleGroup(0);
|
|
compOnlyPlayersCntGroup->pos.y += 285;
|
|
compOnlyPlayersCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
|
|
addButtonsWithRandToGroup(compOnlyPlayersCntGroup, numberDefs, 0, 7, NUMBERS_WIDTH, 224, 232);
|
|
compOnlyPlayersCntGroup->addCallback([&](int btnId)
|
|
{
|
|
mapGenOptions.setCompOnlyPlayerCount(btnId);
|
|
deactivateButtonsFrom(compOnlyTeamsCntGroup, (btnId == 0 ? 1 : btnId));
|
|
validateCompOnlyPlayersCnt(btnId);
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
});
|
|
|
|
// Computer only teams
|
|
compOnlyTeamsCntGroup = new CToggleGroup(0);
|
|
compOnlyTeamsCntGroup->pos.y += 351;
|
|
compOnlyTeamsCntGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
|
|
addButtonsWithRandToGroup(compOnlyTeamsCntGroup, numberDefs, 0, 6, NUMBERS_WIDTH, 234, 241);
|
|
deactivateButtonsFrom(compOnlyTeamsCntGroup, 1);
|
|
compOnlyTeamsCntGroup->addCallback([&](int btnId)
|
|
{
|
|
mapGenOptions.setCompOnlyTeamCount(btnId);
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
});
|
|
|
|
const int WIDE_BTN_WIDTH = 85;
|
|
// Water content
|
|
waterContentGroup = new CToggleGroup(0);
|
|
waterContentGroup->pos.y += 419;
|
|
waterContentGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
|
|
const std::vector<std::string> waterContentBtns = {"RANNONE","RANNORM","RANISLD"};
|
|
addButtonsWithRandToGroup(waterContentGroup, waterContentBtns, 0, 2, WIDE_BTN_WIDTH, 243, 246);
|
|
waterContentGroup->addCallback([&](int btnId)
|
|
{
|
|
mapGenOptions.setWaterContent(static_cast<EWaterContent::EWaterContent>(btnId));
|
|
});
|
|
|
|
// Monster strength
|
|
monsterStrengthGroup = new CToggleGroup(0);
|
|
monsterStrengthGroup->pos.y += 485;
|
|
monsterStrengthGroup->pos.x += BTNS_GROUP_LEFT_MARGIN;
|
|
const std::vector<std::string> monsterStrengthBtns = {"RANWEAK","RANNORM","RANSTRG"};
|
|
addButtonsWithRandToGroup(monsterStrengthGroup, monsterStrengthBtns, 0, 2, WIDE_BTN_WIDTH, 248, 251);
|
|
monsterStrengthGroup->addCallback([&](int btnId)
|
|
{
|
|
if (btnId < 0)
|
|
mapGenOptions.setMonsterStrength(EMonsterStrength::RANDOM);
|
|
else
|
|
mapGenOptions.setMonsterStrength(static_cast<EMonsterStrength::EMonsterStrength>(btnId + EMonsterStrength::GLOBAL_WEAK)); //value 2 to 4
|
|
});
|
|
|
|
// Show random maps btn
|
|
showRandMaps = new CButton(Point(54, 535), "RANSHOW", CGI->generaltexth->zelp[252]);
|
|
|
|
// Initialize map info object
|
|
if(!SEL->isGuest())
|
|
updateMapInfo();
|
|
}
|
|
|
|
void CRandomMapTab::addButtonsWithRandToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex, int helpRandIndex) const
|
|
{
|
|
addButtonsToGroup(group, defs, nStart, nEnd, btnWidth, helpStartIndex);
|
|
|
|
// Buttons are relative to button group, TODO better solution?
|
|
SObjectConstruction obj__i(group);
|
|
const std::string RANDOM_DEF = "RANRAND";
|
|
group->addToggle(CMapGenOptions::RANDOM_SIZE, new CToggleButton(Point(256, 0), RANDOM_DEF, CGI->generaltexth->zelp[helpRandIndex]));
|
|
group->setSelected(CMapGenOptions::RANDOM_SIZE);
|
|
}
|
|
|
|
void CRandomMapTab::addButtonsToGroup(CToggleGroup * group, const std::vector<std::string> & defs, int nStart, int nEnd, int btnWidth, int helpStartIndex) const
|
|
{
|
|
// Buttons are relative to button group, TODO better solution?
|
|
SObjectConstruction obj__i(group);
|
|
int cnt = nEnd - nStart + 1;
|
|
for(int i = 0; i < cnt; ++i)
|
|
{
|
|
auto button = new CToggleButton(Point(i * btnWidth, 0), defs[i + nStart], CGI->generaltexth->zelp[helpStartIndex + i]);
|
|
// For blocked state we should use pressed image actually
|
|
button->setImageOrder(0, 1, 1, 3);
|
|
|
|
group->addToggle(i + nStart, button);
|
|
}
|
|
}
|
|
|
|
void CRandomMapTab::deactivateButtonsFrom(CToggleGroup * group, int startId)
|
|
{
|
|
logGlobal->debug("Blocking buttons from %d", startId);
|
|
for(auto toggle : group->buttons)
|
|
{
|
|
if (auto button = dynamic_cast<CToggleButton*>(toggle.second))
|
|
{
|
|
if(startId == CMapGenOptions::RANDOM_SIZE || toggle.first < startId)
|
|
{
|
|
button->block(false);
|
|
}
|
|
else
|
|
{
|
|
button->block(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CRandomMapTab::validatePlayersCnt(int playersCnt)
|
|
{
|
|
if(playersCnt == CMapGenOptions::RANDOM_SIZE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(mapGenOptions.getTeamCount() >= playersCnt)
|
|
{
|
|
mapGenOptions.setTeamCount(playersCnt - 1);
|
|
teamsCntGroup->setSelected(mapGenOptions.getTeamCount());
|
|
}
|
|
if(mapGenOptions.getCompOnlyPlayerCount() >= playersCnt)
|
|
{
|
|
mapGenOptions.setCompOnlyPlayerCount(playersCnt - 1);
|
|
compOnlyPlayersCntGroup->setSelected(mapGenOptions.getCompOnlyPlayerCount());
|
|
}
|
|
|
|
validateCompOnlyPlayersCnt(mapGenOptions.getCompOnlyPlayerCount());
|
|
}
|
|
|
|
void CRandomMapTab::validateCompOnlyPlayersCnt(int compOnlyPlayersCnt)
|
|
{
|
|
if(compOnlyPlayersCnt == CMapGenOptions::RANDOM_SIZE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(mapGenOptions.getCompOnlyTeamCount() >= compOnlyPlayersCnt)
|
|
{
|
|
int compOnlyTeamCount = compOnlyPlayersCnt == 0 ? 0 : compOnlyPlayersCnt - 1;
|
|
mapGenOptions.setCompOnlyTeamCount(compOnlyTeamCount);
|
|
compOnlyTeamsCntGroup->setSelected(compOnlyTeamCount);
|
|
}
|
|
}
|
|
|
|
std::vector<int> CRandomMapTab::getPossibleMapSizes()
|
|
{
|
|
return {CMapHeader::MAP_SIZE_SMALL,CMapHeader::MAP_SIZE_MIDDLE,CMapHeader::MAP_SIZE_LARGE,CMapHeader::MAP_SIZE_XLARGE};
|
|
}
|
|
|
|
void CRandomMapTab::showAll(SDL_Surface * to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
|
|
// Headline
|
|
printAtMiddleLoc(CGI->generaltexth->allTexts[738], 222, 36, FONT_BIG, Colors::YELLOW, to);
|
|
printAtMiddleLoc(CGI->generaltexth->allTexts[739], 222, 56, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Map size
|
|
printAtMiddleLoc(CGI->generaltexth->allTexts[752], 104, 97, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Players cnt
|
|
printAtLoc(CGI->generaltexth->allTexts[753], 68, 133, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Teams cnt
|
|
printAtLoc(CGI->generaltexth->allTexts[754], 68, 199, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Computer only players cnt
|
|
printAtLoc(CGI->generaltexth->allTexts[755], 68, 265, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Computer only teams cnt
|
|
printAtLoc(CGI->generaltexth->allTexts[756], 68, 331, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Water content
|
|
printAtLoc(CGI->generaltexth->allTexts[757], 68, 398, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
// Monster strength
|
|
printAtLoc(CGI->generaltexth->allTexts[758], 68, 465, FONT_SMALL, Colors::WHITE, to);
|
|
}
|
|
|
|
void CRandomMapTab::updateMapInfo()
|
|
{
|
|
// Generate header info
|
|
mapInfo = make_unique<CMapInfo>();
|
|
mapInfo->isRandomMap = true;
|
|
mapInfo->mapHeader = make_unique<CMapHeader>();
|
|
mapInfo->mapHeader->version = EMapFormat::SOD;
|
|
mapInfo->mapHeader->name = CGI->generaltexth->allTexts[740];
|
|
mapInfo->mapHeader->description = CGI->generaltexth->allTexts[741];
|
|
mapInfo->mapHeader->difficulty = 1; // Normal
|
|
mapInfo->mapHeader->height = mapGenOptions.getHeight();
|
|
mapInfo->mapHeader->width = mapGenOptions.getWidth();
|
|
mapInfo->mapHeader->twoLevel = mapGenOptions.getHasTwoLevels();
|
|
|
|
// Generate player information
|
|
mapInfo->mapHeader->players.clear();
|
|
int playersToGen = PlayerColor::PLAYER_LIMIT_I;
|
|
if(mapGenOptions.getPlayerCount() != CMapGenOptions::RANDOM_SIZE)
|
|
playersToGen = mapGenOptions.getPlayerCount();
|
|
mapInfo->mapHeader->howManyTeams = playersToGen;
|
|
|
|
for(int i = 0; i < playersToGen; ++i)
|
|
{
|
|
PlayerInfo player;
|
|
player.isFactionRandom = true;
|
|
player.canComputerPlay = true;
|
|
if(mapGenOptions.getCompOnlyPlayerCount() != CMapGenOptions::RANDOM_SIZE &&
|
|
i >= mapGenOptions.getHumanOnlyPlayerCount())
|
|
{
|
|
player.canHumanPlay = false;
|
|
}
|
|
else
|
|
{
|
|
player.canHumanPlay = true;
|
|
}
|
|
player.team = TeamID(i);
|
|
player.hasMainTown = true;
|
|
player.generateHeroAtMainTown = true;
|
|
mapInfo->mapHeader->players.push_back(player);
|
|
}
|
|
|
|
mapInfoChanged(mapInfo.get());
|
|
}
|
|
|
|
CFunctionList<void(const CMapInfo *)> & CRandomMapTab::getMapInfoChanged()
|
|
{
|
|
return mapInfoChanged;
|
|
}
|
|
|
|
const CMapInfo * CRandomMapTab::getMapInfo() const
|
|
{
|
|
return mapInfo.get();
|
|
}
|
|
|
|
const CMapGenOptions & CRandomMapTab::getMapGenOptions() const
|
|
{
|
|
return mapGenOptions;
|
|
}
|
|
|
|
void CRandomMapTab::setMapGenOptions(std::shared_ptr<CMapGenOptions> opts)
|
|
{
|
|
mapSizeBtnGroup->setSelected(vstd::find_pos(getPossibleMapSizes(), opts->getWidth()));
|
|
twoLevelsBtn->setSelected(opts->getHasTwoLevels());
|
|
playersCntGroup->setSelected(opts->getPlayerCount());
|
|
teamsCntGroup->setSelected(opts->getTeamCount());
|
|
compOnlyPlayersCntGroup->setSelected(opts->getCompOnlyPlayerCount());
|
|
compOnlyTeamsCntGroup->setSelected(opts->getCompOnlyTeamCount());
|
|
waterContentGroup->setSelected(opts->getWaterContent());
|
|
monsterStrengthGroup->setSelected(opts->getMonsterStrength());
|
|
}
|
|
|
|
CChatBox::CChatBox(const Rect &rect)
|
|
{
|
|
OBJ_CONSTRUCTION;
|
|
pos += rect;
|
|
addUsedEvents(KEYBOARD | TEXTINPUT);
|
|
captureAllKeys = true;
|
|
type |= REDRAW_PARENT;
|
|
|
|
const int height = graphics->fonts[FONT_SMALL]->getLineHeight();
|
|
inputBox = new CTextInput(Rect(0, rect.h - height, rect.w, height));
|
|
inputBox->removeUsedEvents(KEYBOARD);
|
|
chatHistory = new CTextBox("", Rect(0, 0, rect.w, rect.h - height), 1);
|
|
|
|
chatHistory->label->color = Colors::GREEN;
|
|
}
|
|
|
|
void CChatBox::keyPressed(const SDL_KeyboardEvent & key)
|
|
{
|
|
if(key.keysym.sym == SDLK_RETURN && key.state == SDL_PRESSED && inputBox->text.size())
|
|
{
|
|
SEL->postChatMessage(inputBox->text);
|
|
inputBox->setText("");
|
|
}
|
|
else
|
|
inputBox->keyPressed(key);
|
|
}
|
|
|
|
void CChatBox::addNewMessage(const std::string &text)
|
|
{
|
|
CCS->soundh->playSound("CHAT");
|
|
chatHistory->setText(chatHistory->label->text + text + "\n");
|
|
if(chatHistory->slider)
|
|
chatHistory->slider->moveToMax();
|
|
}
|
|
|
|
InfoCard::InfoCard( bool Network )
|
|
: sizes(nullptr), bg(nullptr), network(Network), chatOn(false), chat(nullptr), playerListBg(nullptr),
|
|
difficulty(nullptr)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
CIntObject::type |= REDRAW_PARENT;
|
|
pos.x += 393;
|
|
pos.y += 6;
|
|
addUsedEvents(RCLICK);
|
|
mapDescription = nullptr;
|
|
|
|
Rect descriptionRect(26, 149, 320, 115);
|
|
mapDescription = new CTextBox("", descriptionRect, 1);
|
|
|
|
if(SEL->screenType == CMenuScreen::campaignList)
|
|
{
|
|
CSelectionScreen *ss = static_cast<CSelectionScreen*>(parent);
|
|
mapDescription->addChild(new CPicture(*ss->bg, descriptionRect + Point(-393, 0)), true); //move subpicture bg to our description control (by default it's our (Infocard) child)
|
|
}
|
|
else
|
|
{
|
|
bg = new CPicture("GSELPOP1.bmp", 0, 0);
|
|
parent->addChild(bg);
|
|
auto it = vstd::find(parent->children, this); //our position among parent children
|
|
parent->children.insert(it, bg); //put BG before us
|
|
parent->children.pop_back();
|
|
pos.w = bg->pos.w;
|
|
pos.h = bg->pos.h;
|
|
sizes = new CAnimImage("SCNRMPSZ", 4, 0, 318, 22);//let it be custom size (frame 4) by default
|
|
sizes->recActions &= ~(SHOWALL | UPDATE);//explicit draw
|
|
|
|
sFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
|
|
sFlags->load();
|
|
difficulty = new CToggleGroup(0);
|
|
{
|
|
static const char *difButns[] = {"GSPBUT3.DEF", "GSPBUT4.DEF", "GSPBUT5.DEF", "GSPBUT6.DEF", "GSPBUT7.DEF"};
|
|
|
|
for(int i = 0; i < 5; i++)
|
|
{
|
|
auto button = new CToggleButton(Point(110 + i*32, 450), difButns[i], CGI->generaltexth->zelp[24+i]);
|
|
|
|
difficulty->addToggle(i, button);
|
|
if(SEL->screenType != CMenuScreen::newGame)
|
|
button->block(true);
|
|
}
|
|
}
|
|
|
|
if(network)
|
|
{
|
|
playerListBg = new CPicture("CHATPLUG.bmp", 16, 276);
|
|
chat = new CChatBox(Rect(26, 132, 340, 132));
|
|
|
|
chatOn = true;
|
|
mapDescription->disable();
|
|
}
|
|
}
|
|
|
|
victory = new CAnimImage("SCNRVICT",0, 0, 24, 302);
|
|
victory->recActions &= ~(SHOWALL | UPDATE);//explicit draw
|
|
loss = new CAnimImage("SCNRLOSS", 0, 0, 24, 359);
|
|
loss->recActions &= ~(SHOWALL | UPDATE);//explicit draw
|
|
}
|
|
|
|
InfoCard::~InfoCard()
|
|
{
|
|
if(sFlags)
|
|
sFlags->unload();
|
|
}
|
|
|
|
void InfoCard::showAll(SDL_Surface * to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
|
|
//blit texts
|
|
if(SEL->screenType != CMenuScreen::campaignList)
|
|
{
|
|
printAtLoc(CGI->generaltexth->allTexts[390] + ":", 24, 400, FONT_SMALL, Colors::WHITE, to); //Allies
|
|
printAtLoc(CGI->generaltexth->allTexts[391] + ":", 190, 400, FONT_SMALL, Colors::WHITE, to); //Enemies
|
|
printAtLoc(CGI->generaltexth->allTexts[494], 33, 430, FONT_SMALL, Colors::YELLOW, to);//"Map Diff:"
|
|
printAtLoc(CGI->generaltexth->allTexts[492] + ":", 133,430, FONT_SMALL, Colors::YELLOW, to); //player difficulty
|
|
printAtLoc(CGI->generaltexth->allTexts[218] + ":", 290,430, FONT_SMALL, Colors::YELLOW, to); //"Rating:"
|
|
printAtLoc(CGI->generaltexth->allTexts[495], 26, 22, FONT_SMALL, Colors::YELLOW, to); //Scenario Name:
|
|
if(!chatOn)
|
|
{
|
|
printAtLoc(CGI->generaltexth->allTexts[496], 26, 132, FONT_SMALL, Colors::YELLOW, to); //Scenario Description:
|
|
printAtLoc(CGI->generaltexth->allTexts[497], 26, 283, FONT_SMALL, Colors::YELLOW, to); //Victory Condition:
|
|
printAtLoc(CGI->generaltexth->allTexts[498], 26, 339, FONT_SMALL, Colors::YELLOW, to); //Loss Condition:
|
|
}
|
|
else //players list
|
|
{
|
|
std::map<ui8, std::string> playerNames = SEL->playerNames;
|
|
int playerSoFar = 0;
|
|
for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++)
|
|
{
|
|
if(i->second.playerID != PlayerSettings::PLAYER_AI)
|
|
{
|
|
printAtLoc(i->second.name, 24, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to);
|
|
playerNames.erase(i->second.playerID);
|
|
}
|
|
}
|
|
|
|
playerSoFar = 0;
|
|
for (auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
|
|
{
|
|
printAtLoc(i->second, 193, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if(SEL->current)
|
|
{
|
|
if(SEL->screenType != CMenuScreen::campaignList)
|
|
{
|
|
if(!chatOn)
|
|
{
|
|
CMapHeader * header = SEL->current->mapHeader.get();
|
|
//victory conditions
|
|
printAtLoc(header->victoryMessage, 60, 307, FONT_SMALL, Colors::WHITE, to);
|
|
victory->setFrame(header->victoryIconIndex);
|
|
victory->showAll(to);
|
|
//loss conditoins
|
|
printAtLoc(header->defeatMessage, 60, 366, FONT_SMALL, Colors::WHITE, to);
|
|
loss->setFrame(header->defeatIconIndex);
|
|
loss->showAll(to);
|
|
}
|
|
|
|
//difficulty
|
|
assert(SEL->current->mapHeader->difficulty <= 4);
|
|
std::string &diff = CGI->generaltexth->arraytxt[142 + SEL->current->mapHeader->difficulty];
|
|
printAtMiddleLoc(diff, 62, 472, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
//selecting size icon
|
|
switch (SEL->current->mapHeader->width)
|
|
{
|
|
case 36:
|
|
sizes->setFrame(0);
|
|
break;
|
|
case 72:
|
|
sizes->setFrame(1);
|
|
break;
|
|
case 108:
|
|
sizes->setFrame(2);
|
|
break;
|
|
case 144:
|
|
sizes->setFrame(3);
|
|
break;
|
|
default:
|
|
sizes->setFrame(4);
|
|
break;
|
|
}
|
|
sizes->showAll(to);
|
|
|
|
if(SEL->screenType == CMenuScreen::loadGame)
|
|
printToLoc((static_cast<const CMapInfo*>(SEL->current))->date,308,34, FONT_SMALL, Colors::WHITE, to);
|
|
|
|
//print flags
|
|
int fx = 34 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]);
|
|
int ex = 200 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]);
|
|
|
|
TeamID myT;
|
|
|
|
if(playerColor < PlayerColor::PLAYER_LIMIT)
|
|
myT = SEL->current->mapHeader->players[playerColor.getNum()].team;
|
|
else
|
|
myT = TeamID::NO_TEAM;
|
|
|
|
for (auto i = SEL->sInfo.playerInfos.cbegin(); i != SEL->sInfo.playerInfos.cend(); i++)
|
|
{
|
|
int *myx = ((i->first == playerColor || SEL->current->mapHeader->players[i->first.getNum()].team == myT) ? &fx : &ex);
|
|
IImage * flag = sFlags->getImage(i->first.getNum(),0);
|
|
flag->draw(to, pos.x + *myx, pos.y + 399);
|
|
*myx += flag->width();
|
|
flag->decreaseRef();
|
|
}
|
|
|
|
std::string tob;
|
|
switch (SEL->sInfo.difficulty)
|
|
{
|
|
case 0:
|
|
tob="80%";
|
|
break;
|
|
case 1:
|
|
tob="100%";
|
|
break;
|
|
case 2:
|
|
tob="130%";
|
|
break;
|
|
case 3:
|
|
tob="160%";
|
|
break;
|
|
case 4:
|
|
tob="200%";
|
|
break;
|
|
}
|
|
printAtMiddleLoc(tob, 311, 472, FONT_SMALL, Colors::WHITE, to);
|
|
}
|
|
|
|
//blit description
|
|
std::string name;
|
|
|
|
if (SEL->screenType == CMenuScreen::campaignList)
|
|
{
|
|
name = SEL->current->campaignHeader->name;
|
|
}
|
|
else
|
|
{
|
|
name = SEL->current->mapHeader->name;
|
|
}
|
|
|
|
//name
|
|
if (name.length())
|
|
printAtLoc(name, 26, 39, FONT_BIG, Colors::YELLOW, to);
|
|
else
|
|
printAtLoc("Unnamed", 26, 39, FONT_BIG, Colors::YELLOW, to);
|
|
}
|
|
}
|
|
|
|
void InfoCard::changeSelection( const CMapInfo *to )
|
|
{
|
|
if(to && mapDescription)
|
|
{
|
|
if (SEL->screenType == CMenuScreen::campaignList)
|
|
mapDescription->setText(to->campaignHeader->description);
|
|
else
|
|
mapDescription->setText(to->mapHeader->description);
|
|
|
|
mapDescription->label->scrollTextTo(0);
|
|
if (mapDescription->slider)
|
|
mapDescription->slider->moveToMin();
|
|
|
|
if(SEL->screenType != CMenuScreen::newGame && SEL->screenType != CMenuScreen::campaignList) {
|
|
//difficulty->block(true);
|
|
difficulty->setSelected(SEL->sInfo.difficulty);
|
|
}
|
|
}
|
|
redraw();
|
|
}
|
|
|
|
void InfoCard::clickRight( tribool down, bool previousState )
|
|
{
|
|
static const Rect flagArea(19, 397, 335, 23);
|
|
if(SEL->current && down && SEL->current && isItInLoc(flagArea, GH.current->motion.x, GH.current->motion.y))
|
|
showTeamsPopup();
|
|
}
|
|
|
|
void InfoCard::showTeamsPopup()
|
|
{
|
|
SDL_Surface *bmp = CMessage::drawDialogBox(256, 90 + 50 * SEL->current->mapHeader->howManyTeams);
|
|
|
|
graphics->fonts[FONT_MEDIUM]->renderTextCenter(bmp, CGI->generaltexth->allTexts[657], Colors::YELLOW, Point(128, 30));
|
|
|
|
for(int i = 0; i < SEL->current->mapHeader->howManyTeams; i++)
|
|
{
|
|
std::vector<ui8> flags;
|
|
std::string hlp = CGI->generaltexth->allTexts[656]; //Team %d
|
|
hlp.replace(hlp.find("%d"), 2, boost::lexical_cast<std::string>(i+1));
|
|
|
|
graphics->fonts[FONT_SMALL]->renderTextCenter(bmp, hlp, Colors::WHITE, Point(128, 65 + 50 * i));
|
|
|
|
for(int j = 0; j < PlayerColor::PLAYER_LIMIT_I; j++)
|
|
if((SEL->current->mapHeader->players[j].canHumanPlay || SEL->current->mapHeader->players[j].canComputerPlay)
|
|
&& SEL->current->mapHeader->players[j].team == TeamID(i))
|
|
flags.push_back(j);
|
|
|
|
int curx = 128 - 9*flags.size();
|
|
for(auto & flag : flags)
|
|
{
|
|
IImage * icon = sFlags->getImage(flag,0);
|
|
icon->draw(bmp, curx, 75 + 50*i);
|
|
icon->decreaseRef();
|
|
curx += 18;
|
|
}
|
|
}
|
|
|
|
GH.pushInt(new CInfoPopup(bmp, true));
|
|
}
|
|
|
|
void InfoCard::toggleChat()
|
|
{
|
|
setChat(!chatOn);
|
|
}
|
|
|
|
void InfoCard::setChat(bool activateChat)
|
|
{
|
|
if(chatOn == activateChat)
|
|
return;
|
|
|
|
assert(active);
|
|
|
|
if(activateChat)
|
|
{
|
|
mapDescription->disable();
|
|
chat->enable();
|
|
playerListBg->enable();
|
|
}
|
|
else
|
|
{
|
|
mapDescription->enable();
|
|
chat->disable();
|
|
playerListBg->disable();
|
|
}
|
|
|
|
chatOn = activateChat;
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
OptionsTab::OptionsTab():
|
|
turnDuration(nullptr)
|
|
{
|
|
OBJ_CONSTRUCTION;
|
|
bg = new CPicture("ADVOPTBK", 0, 6);
|
|
pos = bg->pos;
|
|
|
|
if(SEL->screenType == CMenuScreen::newGame)
|
|
turnDuration = new CSlider(Point(55, 551), 194, std::bind(&OptionsTab::setTurnLength, this, _1), 1, 11, 11, true, CSlider::BLUE);
|
|
}
|
|
|
|
OptionsTab::~OptionsTab()
|
|
{
|
|
|
|
}
|
|
|
|
void OptionsTab::showAll(SDL_Surface * to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
printAtMiddleLoc(CGI->generaltexth->allTexts[515], 222, 30, FONT_BIG, Colors::YELLOW, to);
|
|
printAtMiddleWBLoc(CGI->generaltexth->allTexts[516], 222, 68, FONT_SMALL, 300, Colors::WHITE, to); //Select starting options, handicap, and name for each player in the game.
|
|
printAtMiddleWBLoc(CGI->generaltexth->allTexts[517], 107, 110, FONT_SMALL, 100, Colors::YELLOW, to); //Player Name Handicap Type
|
|
printAtMiddleWBLoc(CGI->generaltexth->allTexts[518], 197, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Town
|
|
printAtMiddleWBLoc(CGI->generaltexth->allTexts[519], 273, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Hero
|
|
printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Bonus
|
|
printAtMiddleLoc(CGI->generaltexth->allTexts[521], 222, 538, FONT_SMALL, Colors::YELLOW, to); // Player Turn Duration
|
|
if (turnDuration)
|
|
printAtMiddleLoc(CGI->generaltexth->turnDurations[turnDuration->getValue()], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value
|
|
}
|
|
|
|
void OptionsTab::nextCastle( PlayerColor player, int dir )
|
|
{
|
|
if(SEL->isGuest())
|
|
{
|
|
SEL->postRequest(RequestOptionsChange::TOWN, dir);
|
|
return;
|
|
}
|
|
|
|
PlayerSettings &s = SEL->sInfo.playerInfos[player];
|
|
si16 &cur = s.castle;
|
|
auto & allowed = SEL->current->mapHeader->players[s.color.getNum()].allowedFactions;
|
|
|
|
if (cur == PlayerSettings::NONE) //no change
|
|
return;
|
|
|
|
if (cur == PlayerSettings::RANDOM) //first/last available
|
|
{
|
|
if (dir > 0)
|
|
cur = *allowed.begin(); //id of first town
|
|
else
|
|
cur = *allowed.rbegin(); //id of last town
|
|
|
|
}
|
|
else // next/previous available
|
|
{
|
|
if ( (cur == *allowed.begin() && dir < 0 )
|
|
|| (cur == *allowed.rbegin() && dir > 0) )
|
|
cur = -1;
|
|
else
|
|
{
|
|
assert(dir >= -1 && dir <= 1); //othervice std::advance may go out of range
|
|
auto iter = allowed.find(cur);
|
|
std::advance(iter, dir);
|
|
cur = *iter;
|
|
}
|
|
}
|
|
|
|
if(s.hero >= 0 && !SEL->current->mapHeader->players[s.color.getNum()].hasCustomMainHero()) // remove hero unless it set to fixed one in map editor
|
|
{
|
|
usedHeroes.erase(s.hero); // restore previously selected hero back to available pool
|
|
s.hero = PlayerSettings::RANDOM;
|
|
}
|
|
if(cur < 0 && s.bonus == PlayerSettings::RESOURCE)
|
|
s.bonus = PlayerSettings::RANDOM;
|
|
|
|
entries[player]->selectButtons();
|
|
|
|
SEL->propagateOptions();
|
|
entries[player]->update();
|
|
redraw();
|
|
}
|
|
|
|
void OptionsTab::nextHero( PlayerColor player, int dir )
|
|
{
|
|
if(SEL->isGuest())
|
|
{
|
|
SEL->postRequest(RequestOptionsChange::HERO, dir);
|
|
return;
|
|
}
|
|
|
|
PlayerSettings &s = SEL->sInfo.playerInfos[player];
|
|
int old = s.hero;
|
|
if (s.castle < 0 || s.playerID == PlayerSettings::PLAYER_AI || s.hero == PlayerSettings::NONE)
|
|
return;
|
|
|
|
if (s.hero == PlayerSettings::RANDOM) // first/last available
|
|
{
|
|
int max = CGI->heroh->heroes.size(),
|
|
min = 0;
|
|
s.hero = nextAllowedHero(player, min,max,0,dir);
|
|
}
|
|
else
|
|
{
|
|
if(dir > 0)
|
|
s.hero = nextAllowedHero(player, s.hero, CGI->heroh->heroes.size(), 1, dir);
|
|
else
|
|
s.hero = nextAllowedHero(player, -1, s.hero, 1, dir); // min needs to be -1 -- hero at index 0 would be skipped otherwise
|
|
}
|
|
|
|
if(old != s.hero)
|
|
{
|
|
usedHeroes.erase(old);
|
|
usedHeroes.insert(s.hero);
|
|
entries[player]->update();
|
|
redraw();
|
|
}
|
|
SEL->propagateOptions();
|
|
}
|
|
|
|
int OptionsTab::nextAllowedHero( PlayerColor player, int min, int max, int incl, int dir )
|
|
{
|
|
if(dir>0)
|
|
{
|
|
for(int i=min+incl; i<=max-incl; i++)
|
|
if(canUseThisHero(player, i))
|
|
return i;
|
|
}
|
|
else
|
|
{
|
|
for(int i=max-incl; i>=min+incl; i--)
|
|
if(canUseThisHero(player, i))
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
bool OptionsTab::canUseThisHero( PlayerColor player, int ID )
|
|
{
|
|
return CGI->heroh->heroes.size() > ID
|
|
&& SEL->sInfo.playerInfos[player].castle == CGI->heroh->heroes[ID]->heroClass->faction
|
|
&& !vstd::contains(usedHeroes, ID)
|
|
&& SEL->current->mapHeader->allowedHeroes[ID];
|
|
}
|
|
|
|
void OptionsTab::nextBonus( PlayerColor player, int dir )
|
|
{
|
|
if(SEL->isGuest())
|
|
{
|
|
SEL->postRequest(RequestOptionsChange::BONUS, dir);
|
|
return;
|
|
}
|
|
|
|
PlayerSettings &s = SEL->sInfo.playerInfos[player];
|
|
PlayerSettings::Ebonus &ret = s.bonus = static_cast<PlayerSettings::Ebonus>(static_cast<int>(s.bonus) + dir);
|
|
|
|
if (s.hero==PlayerSettings::NONE &&
|
|
!SEL->current->mapHeader->players[s.color.getNum()].heroesNames.size() &&
|
|
ret==PlayerSettings::ARTIFACT) //no hero - can't be artifact
|
|
{
|
|
if (dir<0)
|
|
ret=PlayerSettings::RANDOM;
|
|
else ret=PlayerSettings::GOLD;
|
|
}
|
|
|
|
if(ret > PlayerSettings::RESOURCE)
|
|
ret = PlayerSettings::RANDOM;
|
|
if(ret < PlayerSettings::RANDOM)
|
|
ret = PlayerSettings::RESOURCE;
|
|
|
|
if (s.castle==PlayerSettings::RANDOM && ret==PlayerSettings::RESOURCE) //random castle - can't be resource
|
|
{
|
|
if (dir<0)
|
|
ret=PlayerSettings::GOLD;
|
|
else ret=PlayerSettings::RANDOM;
|
|
}
|
|
|
|
SEL->propagateOptions();
|
|
entries[player]->update();
|
|
redraw();
|
|
}
|
|
|
|
void OptionsTab::recreate()
|
|
{
|
|
for(auto & elem : entries)
|
|
{
|
|
delete elem.second;
|
|
}
|
|
entries.clear();
|
|
usedHeroes.clear();
|
|
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
for(auto it = SEL->sInfo.playerInfos.begin(); it != SEL->sInfo.playerInfos.end(); ++it)
|
|
{
|
|
entries.insert(std::make_pair(it->first, new PlayerOptionsEntry(this, it->second)));
|
|
const std::vector<SHeroName> &heroes = SEL->current->mapHeader->players[it->first.getNum()].heroesNames;
|
|
for(auto & heroe : heroes)
|
|
if(heroe.heroId >= 0)//in VCMI map format heroId = -1 means random hero
|
|
usedHeroes.insert(heroe.heroId);
|
|
}
|
|
}
|
|
|
|
void OptionsTab::setTurnLength( int npos )
|
|
{
|
|
static const int times[] = {1, 2, 4, 6, 8, 10, 15, 20, 25, 30, 0};
|
|
vstd::amin(npos, ARRAY_COUNT(times) - 1);
|
|
|
|
SEL->sInfo.turnTime = times[npos];
|
|
redraw();
|
|
}
|
|
|
|
void OptionsTab::flagPressed( PlayerColor color )
|
|
{
|
|
PlayerSettings &clicked = SEL->sInfo.playerInfos[color];
|
|
PlayerSettings *old = nullptr;
|
|
|
|
if(SEL->playerNames.size() == 1) //single player -> just swap
|
|
{
|
|
if(color == playerColor) //that color is already selected, no action needed
|
|
return;
|
|
|
|
old = &SEL->sInfo.playerInfos[playerColor];
|
|
swapPlayers(*old, clicked);
|
|
}
|
|
else
|
|
{
|
|
//identify clicked player
|
|
int clickedNameID = clicked.playerID; //human is a number of player, zero means AI
|
|
|
|
if(clickedNameID > 0 && playerToRestore.id == clickedNameID) //player to restore is about to being replaced -> put him back to the old place
|
|
{
|
|
PlayerSettings &restPos = SEL->sInfo.playerInfos[playerToRestore.color];
|
|
SEL->setPlayer(restPos, playerToRestore.id);
|
|
playerToRestore.reset();
|
|
}
|
|
|
|
int newPlayer; //which player will take clicked position
|
|
|
|
//who will be put here?
|
|
if(!clickedNameID) //AI player clicked -> if possible replace computer with unallocated player
|
|
{
|
|
newPlayer = SEL->getIdOfFirstUnallocatedPlayer();
|
|
if(!newPlayer) //no "free" player -> get just first one
|
|
newPlayer = SEL->playerNames.begin()->first;
|
|
}
|
|
else //human clicked -> take next
|
|
{
|
|
auto i = SEL->playerNames.find(clickedNameID); //clicked one
|
|
i++; //player AFTER clicked one
|
|
|
|
if(i != SEL->playerNames.end())
|
|
newPlayer = i->first;
|
|
else
|
|
newPlayer = 0; //AI if we scrolled through all players
|
|
}
|
|
|
|
SEL->setPlayer(clicked, newPlayer); //put player
|
|
|
|
//if that player was somewhere else, we need to replace him with computer
|
|
if(newPlayer) //not AI
|
|
{
|
|
for(auto i = SEL->sInfo.playerInfos.begin(); i != SEL->sInfo.playerInfos.end(); i++)
|
|
{
|
|
int curNameID = i->second.playerID;
|
|
if(i->first != color && curNameID == newPlayer)
|
|
{
|
|
assert(i->second.playerID);
|
|
playerToRestore.color = i->first;
|
|
playerToRestore.id = newPlayer;
|
|
SEL->setPlayer(i->second, 0); //set computer
|
|
old = &i->second;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
entries[clicked.color]->selectButtons();
|
|
if(old)
|
|
{
|
|
entries[old->color]->selectButtons();
|
|
if(old->hero >= 0)
|
|
usedHeroes.erase(old->hero);
|
|
|
|
old->hero = entries[old->color]->pi.defaultHero();
|
|
entries[old->color]->update(); // update previous frame images in case entries were auto-updated
|
|
}
|
|
|
|
SEL->propagateOptions();
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry( OptionsTab *owner, PlayerSettings &S)
|
|
: pi(SEL->current->mapHeader->players[S.color.getNum()]), s(S)
|
|
{
|
|
OBJ_CONSTRUCTION;
|
|
defActions |= SHARE_POS;
|
|
|
|
int serial = 0;
|
|
for(int g=0; g < s.color.getNum(); ++g)
|
|
{
|
|
PlayerInfo &itred = SEL->current->mapHeader->players[g];
|
|
if( itred.canComputerPlay || itred.canHumanPlay)
|
|
serial++;
|
|
}
|
|
|
|
pos.x += 54;
|
|
pos.y += 122 + serial*50;
|
|
|
|
static const char *flags[] = {"AOFLGBR.DEF", "AOFLGBB.DEF", "AOFLGBY.DEF", "AOFLGBG.DEF",
|
|
"AOFLGBO.DEF", "AOFLGBP.DEF", "AOFLGBT.DEF", "AOFLGBS.DEF"};
|
|
static const char *bgs[] = {"ADOPRPNL.bmp", "ADOPBPNL.bmp", "ADOPYPNL.bmp", "ADOPGPNL.bmp",
|
|
"ADOPOPNL.bmp", "ADOPPPNL.bmp", "ADOPTPNL.bmp", "ADOPSPNL.bmp"};
|
|
|
|
bg = new CPicture(BitmapHandler::loadBitmap(bgs[s.color.getNum()]), 0, 0, true);
|
|
if(SEL->screenType == CMenuScreen::newGame)
|
|
{
|
|
btns[0] = new CButton(Point(107, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[132], std::bind(&OptionsTab::nextCastle, owner, s.color, -1));
|
|
btns[1] = new CButton(Point(168, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[133], std::bind(&OptionsTab::nextCastle, owner, s.color, +1));
|
|
btns[2] = new CButton(Point(183, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[148], std::bind(&OptionsTab::nextHero, owner, s.color, -1));
|
|
btns[3] = new CButton(Point(244, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[149], std::bind(&OptionsTab::nextHero, owner, s.color, +1));
|
|
btns[4] = new CButton(Point(259, 5), "ADOPLFA.DEF", CGI->generaltexth->zelp[164], std::bind(&OptionsTab::nextBonus, owner, s.color, -1));
|
|
btns[5] = new CButton(Point(320, 5), "ADOPRTA.DEF", CGI->generaltexth->zelp[165], std::bind(&OptionsTab::nextBonus, owner, s.color, +1));
|
|
}
|
|
else
|
|
for(auto & elem : btns)
|
|
elem = nullptr;
|
|
|
|
selectButtons();
|
|
|
|
assert(SEL->current && SEL->current->mapHeader);
|
|
const PlayerInfo &p = SEL->current->mapHeader->players[s.color.getNum()];
|
|
assert(p.canComputerPlay || p.canHumanPlay); //someone must be able to control this player
|
|
if(p.canHumanPlay && p.canComputerPlay)
|
|
whoCanPlay = HUMAN_OR_CPU;
|
|
else if(p.canComputerPlay)
|
|
whoCanPlay = CPU;
|
|
else
|
|
whoCanPlay = HUMAN;
|
|
|
|
if(SEL->screenType != CMenuScreen::scenarioInfo
|
|
&& SEL->current->mapHeader->players[s.color.getNum()].canHumanPlay)
|
|
{
|
|
flag = new CButton(Point(-43, 2), flags[s.color.getNum()], CGI->generaltexth->zelp[180], std::bind(&OptionsTab::flagPressed, owner, s.color));
|
|
flag->hoverable = true;
|
|
flag->block(SEL->isGuest());
|
|
}
|
|
else
|
|
flag = nullptr;
|
|
|
|
town = new SelectedBox(Point(119, 2), s, TOWN);
|
|
hero = new SelectedBox(Point(195, 2), s, HERO);
|
|
bonus = new SelectedBox(Point(271, 2), s, BONUS);
|
|
}
|
|
|
|
void OptionsTab::PlayerOptionsEntry::showAll(SDL_Surface * to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
printAtMiddleLoc(s.name, 55, 10, FONT_SMALL, Colors::WHITE, to);
|
|
printAtMiddleWBLoc(CGI->generaltexth->arraytxt[206+whoCanPlay], 28, 39, FONT_TINY, 50, Colors::WHITE, to);
|
|
}
|
|
|
|
void OptionsTab::PlayerOptionsEntry::update()
|
|
{
|
|
town->update();
|
|
hero->update();
|
|
bonus->update();
|
|
}
|
|
|
|
void OptionsTab::PlayerOptionsEntry::selectButtons()
|
|
{
|
|
if(!btns[0])
|
|
return;
|
|
|
|
if( (pi.defaultCastle() != -1) //fixed tow
|
|
|| (SEL->isGuest() && s.color != playerColor)) //or not our player
|
|
{
|
|
btns[0]->disable();
|
|
btns[1]->disable();
|
|
}
|
|
else
|
|
{
|
|
btns[0]->enable();
|
|
btns[1]->enable();
|
|
}
|
|
|
|
if( (pi.defaultHero() != -1 || !s.playerID || s.castle < 0) //fixed hero
|
|
|| (SEL->isGuest() && s.color != playerColor))//or not our player
|
|
{
|
|
btns[2]->disable();
|
|
btns[3]->disable();
|
|
}
|
|
else
|
|
{
|
|
btns[2]->enable();
|
|
btns[3]->enable();
|
|
}
|
|
|
|
if(SEL->isGuest() && s.color != playerColor)//or not our player
|
|
{
|
|
btns[4]->disable();
|
|
btns[5]->disable();
|
|
}
|
|
else
|
|
{
|
|
btns[4]->enable();
|
|
btns[5]->enable();
|
|
}
|
|
}
|
|
|
|
size_t OptionsTab::CPlayerSettingsHelper::getImageIndex()
|
|
{
|
|
enum EBonusSelection //frames of bonuses file
|
|
{
|
|
WOOD_ORE = 0, CRYSTAL = 1, GEM = 2,
|
|
MERCURY = 3, SULFUR = 5, GOLD = 8,
|
|
ARTIFACT = 9, RANDOM = 10,
|
|
WOOD = 0, ORE = 0, MITHRIL = 10, // resources unavailable in bonuses file
|
|
|
|
TOWN_RANDOM = 38, TOWN_NONE = 39, // Special frames in ITPA
|
|
HERO_RANDOM = 163, HERO_NONE = 164 // Special frames in PortraitsSmall
|
|
};
|
|
|
|
switch(type)
|
|
{
|
|
case TOWN:
|
|
switch (settings.castle)
|
|
{
|
|
case PlayerSettings::NONE: return TOWN_NONE;
|
|
case PlayerSettings::RANDOM: return TOWN_RANDOM;
|
|
default: return CGI->townh->factions[settings.castle]->town->clientInfo.icons[true][false] + 2;
|
|
}
|
|
|
|
case HERO:
|
|
switch (settings.hero)
|
|
{
|
|
case PlayerSettings::NONE: return HERO_NONE;
|
|
case PlayerSettings::RANDOM: return HERO_RANDOM;
|
|
default:
|
|
{
|
|
if(settings.heroPortrait >= 0)
|
|
return settings.heroPortrait;
|
|
return CGI->heroh->heroes[settings.hero]->imageIndex;
|
|
}
|
|
}
|
|
|
|
case BONUS:
|
|
{
|
|
switch(settings.bonus)
|
|
{
|
|
case PlayerSettings::RANDOM: return RANDOM;
|
|
case PlayerSettings::ARTIFACT: return ARTIFACT;
|
|
case PlayerSettings::GOLD: return GOLD;
|
|
case PlayerSettings::RESOURCE:
|
|
{
|
|
switch(CGI->townh->factions[settings.castle]->town->primaryRes)
|
|
{
|
|
case Res::WOOD_AND_ORE : return WOOD_ORE;
|
|
case Res::WOOD : return WOOD;
|
|
case Res::MERCURY : return MERCURY;
|
|
case Res::ORE : return ORE;
|
|
case Res::SULFUR : return SULFUR;
|
|
case Res::CRYSTAL : return CRYSTAL;
|
|
case Res::GEMS : return GEM;
|
|
case Res::GOLD : return GOLD;
|
|
case Res::MITHRIL : return MITHRIL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
std::string OptionsTab::CPlayerSettingsHelper::getImageName()
|
|
{
|
|
switch(type)
|
|
{
|
|
case OptionsTab::TOWN: return "ITPA";
|
|
case OptionsTab::HERO: return "PortraitsSmall";
|
|
case OptionsTab::BONUS: return "SCNRSTAR";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string OptionsTab::CPlayerSettingsHelper::getTitle()
|
|
{
|
|
switch(type)
|
|
{
|
|
case OptionsTab::TOWN: return (settings.castle < 0) ? CGI->generaltexth->allTexts[103] : CGI->generaltexth->allTexts[80];
|
|
case OptionsTab::HERO: return (settings.hero < 0) ? CGI->generaltexth->allTexts[101] : CGI->generaltexth->allTexts[77];
|
|
case OptionsTab::BONUS:
|
|
{
|
|
switch(settings.bonus)
|
|
{
|
|
case PlayerSettings::RANDOM: return CGI->generaltexth->allTexts[86]; //{Random Bonus}
|
|
case PlayerSettings::ARTIFACT: return CGI->generaltexth->allTexts[83]; //{Artifact Bonus}
|
|
case PlayerSettings::GOLD: return CGI->generaltexth->allTexts[84]; //{Gold Bonus}
|
|
case PlayerSettings::RESOURCE: return CGI->generaltexth->allTexts[85]; //{Resource Bonus}
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string OptionsTab::CPlayerSettingsHelper::getName()
|
|
{
|
|
switch(type)
|
|
{
|
|
case TOWN:
|
|
{
|
|
switch (settings.castle)
|
|
{
|
|
case PlayerSettings::NONE : return CGI->generaltexth->allTexts[523];
|
|
case PlayerSettings::RANDOM : return CGI->generaltexth->allTexts[522];
|
|
default : return CGI->townh->factions[settings.castle]->name;
|
|
}
|
|
}
|
|
case HERO:
|
|
{
|
|
switch (settings.hero)
|
|
{
|
|
case PlayerSettings::NONE : return CGI->generaltexth->allTexts[523];
|
|
case PlayerSettings::RANDOM : return CGI->generaltexth->allTexts[522];
|
|
default :
|
|
{
|
|
if (!settings.heroName.empty())
|
|
return settings.heroName;
|
|
return CGI->heroh->heroes[settings.hero]->name;
|
|
}
|
|
}
|
|
}
|
|
case BONUS:
|
|
{
|
|
switch (settings.bonus)
|
|
{
|
|
case PlayerSettings::RANDOM : return CGI->generaltexth->allTexts[522];
|
|
default: return CGI->generaltexth->arraytxt[214 + settings.bonus];
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string OptionsTab::CPlayerSettingsHelper::getSubtitle()
|
|
{
|
|
switch(type)
|
|
{
|
|
case TOWN: return getName();
|
|
case HERO:
|
|
{
|
|
if (settings.hero >= 0)
|
|
return getName() + " - " + CGI->heroh->heroes[settings.hero]->heroClass->name;
|
|
return getName();
|
|
}
|
|
|
|
case BONUS:
|
|
{
|
|
switch(settings.bonus)
|
|
{
|
|
case PlayerSettings::GOLD: return CGI->generaltexth->allTexts[87]; //500-1000
|
|
case PlayerSettings::RESOURCE:
|
|
{
|
|
switch(CGI->townh->factions[settings.castle]->town->primaryRes)
|
|
{
|
|
case Res::MERCURY: return CGI->generaltexth->allTexts[694];
|
|
case Res::SULFUR: return CGI->generaltexth->allTexts[695];
|
|
case Res::CRYSTAL: return CGI->generaltexth->allTexts[692];
|
|
case Res::GEMS: return CGI->generaltexth->allTexts[693];
|
|
case Res::WOOD_AND_ORE: return CGI->generaltexth->allTexts[89]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::string OptionsTab::CPlayerSettingsHelper::getDescription()
|
|
{
|
|
switch(type)
|
|
{
|
|
case TOWN: return CGI->generaltexth->allTexts[104];
|
|
case HERO: return CGI->generaltexth->allTexts[102];
|
|
case BONUS:
|
|
{
|
|
switch(settings.bonus)
|
|
{
|
|
case PlayerSettings::RANDOM: return CGI->generaltexth->allTexts[94]; //Gold, wood and ore, or an artifact is randomly chosen as your starting bonus
|
|
case PlayerSettings::ARTIFACT: return CGI->generaltexth->allTexts[90]; //An artifact is randomly chosen and equipped to your starting hero
|
|
case PlayerSettings::GOLD: return CGI->generaltexth->allTexts[92]; //At the start of the game, 500-1000 gold is added to your Kingdom's resource pool
|
|
case PlayerSettings::RESOURCE:
|
|
{
|
|
switch(CGI->townh->factions[settings.castle]->town->primaryRes)
|
|
{
|
|
case Res::MERCURY: return CGI->generaltexth->allTexts[690];
|
|
case Res::SULFUR: return CGI->generaltexth->allTexts[691];
|
|
case Res::CRYSTAL: return CGI->generaltexth->allTexts[688];
|
|
case Res::GEMS: return CGI->generaltexth->allTexts[689];
|
|
case Res::WOOD_AND_ORE: return CGI->generaltexth->allTexts[93]; //At the start of the game, 5-10 wood and 5-10 ore are added to your Kingdom's resource pool
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
OptionsTab::CPregameTooltipBox::CPregameTooltipBox(CPlayerSettingsHelper & helper):
|
|
CWindowObject(BORDERED | RCLICK_POPUP),
|
|
CPlayerSettingsHelper(helper)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
int value = PlayerSettings::NONE;
|
|
|
|
switch(CPlayerSettingsHelper::type)
|
|
{
|
|
break; case TOWN:
|
|
value = settings.castle;
|
|
break; case HERO:
|
|
value = settings.hero;
|
|
break; case BONUS:
|
|
value = settings.bonus;
|
|
}
|
|
|
|
if (value == PlayerSettings::RANDOM)
|
|
genBonusWindow();
|
|
else if (CPlayerSettingsHelper::type == BONUS)
|
|
genBonusWindow();
|
|
else if (CPlayerSettingsHelper::type == HERO)
|
|
genHeroWindow();
|
|
else if (CPlayerSettingsHelper::type == TOWN)
|
|
genTownWindow();
|
|
|
|
center();
|
|
}
|
|
|
|
void OptionsTab::CPregameTooltipBox::genHeader()
|
|
{
|
|
new CFilledTexture("DIBOXBCK", pos);
|
|
updateShadow();
|
|
|
|
new CLabel(pos.w / 2 + 8, 21, FONT_MEDIUM, CENTER, Colors::YELLOW, getTitle());
|
|
|
|
new CLabel(pos.w / 2, 88, FONT_SMALL, CENTER, Colors::WHITE, getSubtitle());
|
|
|
|
new CAnimImage(getImageName(), getImageIndex(), 0, pos.w / 2 - 24, 45);
|
|
}
|
|
|
|
void OptionsTab::CPregameTooltipBox::genTownWindow()
|
|
{
|
|
pos = Rect(0, 0, 228, 290);
|
|
genHeader();
|
|
|
|
new CLabel(pos.w / 2 + 8, 122, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[79]);
|
|
|
|
std::vector<CComponent *> components;
|
|
const CTown * town = CGI->townh->factions[settings.castle]->town;
|
|
|
|
for (auto & elem : town->creatures)
|
|
{
|
|
if (!elem.empty())
|
|
components.push_back(new CComponent(CComponent::creature, elem.front(), 0, CComponent::tiny));
|
|
}
|
|
|
|
new CComponentBox(components, Rect(10, 140, pos.w - 20, 140));
|
|
}
|
|
|
|
void OptionsTab::CPregameTooltipBox::genHeroWindow()
|
|
{
|
|
pos = Rect(0, 0, 292, 226);
|
|
genHeader();
|
|
|
|
// specialty
|
|
new CAnimImage("UN44", CGI->heroh->heroes[settings.hero]->imageIndex, 0, pos.w / 2 - 22, 134);
|
|
|
|
new CLabel(pos.w / 2 + 4, 117, FONT_MEDIUM, CENTER, Colors::YELLOW, CGI->generaltexth->allTexts[78]);
|
|
new CLabel(pos.w / 2, 188, FONT_SMALL, CENTER, Colors::WHITE, CGI->heroh->heroes[settings.hero]->specName);
|
|
}
|
|
|
|
void OptionsTab::CPregameTooltipBox::genBonusWindow()
|
|
{
|
|
pos = Rect(0, 0, 228, 162);
|
|
genHeader();
|
|
|
|
new CTextBox(getDescription(), Rect(10, 100, pos.w - 20, 70), 0, FONT_SMALL, CENTER, Colors::WHITE );
|
|
}
|
|
|
|
OptionsTab::SelectedBox::SelectedBox(Point position, PlayerSettings & settings, SelType type)
|
|
:CIntObject(RCLICK, position),
|
|
CPlayerSettingsHelper(settings, type)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
image = new CAnimImage(getImageName(), getImageIndex());
|
|
subtitle = new CLabel(23, 39, FONT_TINY, CENTER, Colors::WHITE, getName());
|
|
|
|
pos = image->pos;
|
|
}
|
|
|
|
void OptionsTab::SelectedBox::update()
|
|
{
|
|
image->setFrame(getImageIndex());
|
|
subtitle->setText(getName());
|
|
}
|
|
|
|
void OptionsTab::SelectedBox::clickRight( tribool down, bool previousState )
|
|
{
|
|
if (down)
|
|
{
|
|
// cases when we do not need to display a message
|
|
if (settings.castle == -2 && CPlayerSettingsHelper::type == TOWN )
|
|
return;
|
|
if (settings.hero == -2 && !SEL->current->mapHeader->players[settings.color.getNum()].hasCustomMainHero() && CPlayerSettingsHelper::type == HERO)
|
|
return;
|
|
|
|
GH.pushInt(new CPregameTooltipBox(*this));
|
|
}
|
|
}
|
|
|
|
CScenarioInfo::CScenarioInfo(const CMapHeader *mapHeader, const StartInfo *startInfo)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
for(auto it = startInfo->playerInfos.cbegin(); it != startInfo->playerInfos.cend(); ++it)
|
|
{
|
|
if(it->second.playerID)
|
|
{
|
|
playerColor = it->first;
|
|
}
|
|
}
|
|
|
|
pos.w = 762;
|
|
pos.h = 584;
|
|
center(pos);
|
|
|
|
assert(LOCPLINT);
|
|
sInfo = *LOCPLINT->cb->getStartInfo();
|
|
assert(!SEL->current);
|
|
current = mapInfoFromGame();
|
|
setPlayersFromGame();
|
|
|
|
screenType = CMenuScreen::scenarioInfo;
|
|
|
|
card = new InfoCard();
|
|
opt = new OptionsTab();
|
|
opt->recreate();
|
|
card->changeSelection(current);
|
|
|
|
card->difficulty->setSelected(startInfo->difficulty);
|
|
back = new CButton(Point(584, 535), "SCNRBACK.DEF", CGI->generaltexth->zelp[105], std::bind(&CGuiHandler::popIntTotally, &GH, this), SDLK_ESCAPE);
|
|
}
|
|
|
|
CScenarioInfo::~CScenarioInfo()
|
|
{
|
|
delete current;
|
|
}
|
|
|
|
bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb)
|
|
{
|
|
const CMapHeader * a = aaa->mapHeader.get(),
|
|
* b = bbb->mapHeader.get();
|
|
if(a && b) //if we are sorting scenarios
|
|
{
|
|
switch (sortBy)
|
|
{
|
|
case _format: //by map format (RoE, WoG, etc)
|
|
return (a->version<b->version);
|
|
break;
|
|
case _loscon: //by loss conditions
|
|
return (a->defeatMessage < b->defeatMessage);
|
|
break;
|
|
case _playerAm: //by player amount
|
|
int playerAmntB,humenPlayersB,playerAmntA,humenPlayersA;
|
|
playerAmntB=humenPlayersB=playerAmntA=humenPlayersA=0;
|
|
for (int i=0;i<8;i++)
|
|
{
|
|
if (a->players[i].canHumanPlay) {playerAmntA++;humenPlayersA++;}
|
|
else if (a->players[i].canComputerPlay) {playerAmntA++;}
|
|
if (b->players[i].canHumanPlay) {playerAmntB++;humenPlayersB++;}
|
|
else if (b->players[i].canComputerPlay) {playerAmntB++;}
|
|
}
|
|
if (playerAmntB!=playerAmntA)
|
|
return (playerAmntA<playerAmntB);
|
|
else
|
|
return (humenPlayersA<humenPlayersB);
|
|
break;
|
|
case _size: //by size of map
|
|
return (a->width<b->width);
|
|
break;
|
|
case _viccon: //by victory conditions
|
|
return (a->victoryMessage < b->victoryMessage);
|
|
break;
|
|
case _name: //by name
|
|
return boost::ilexicographical_compare(a->name, b->name);
|
|
case _fileName: //by filename
|
|
return boost::ilexicographical_compare(aaa->fileURI, bbb->fileURI);
|
|
default:
|
|
return boost::ilexicographical_compare(a->name, b->name);
|
|
}
|
|
}
|
|
else //if we are sorting campaigns
|
|
{
|
|
switch(sortBy)
|
|
{
|
|
case _numOfMaps: //by number of maps in campaign
|
|
return CGI->generaltexth->campaignRegionNames[ aaa->campaignHeader->mapVersion ].size() <
|
|
CGI->generaltexth->campaignRegionNames[ bbb->campaignHeader->mapVersion ].size();
|
|
break;
|
|
case _name: //by name
|
|
return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
|
|
default:
|
|
return boost::ilexicographical_compare(aaa->campaignHeader->name, bbb->campaignHeader->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
CMultiMode::CMultiMode()
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
bg = new CPicture("MUPOPUP.bmp");
|
|
bg->convertToScreenBPP(); //so we could draw without problems
|
|
blitAt(CPicture("MUMAP.bmp"), 16, 77, *bg); //blit img
|
|
pos = bg->center(); //center, window has size of bg graphic
|
|
|
|
bar = new CGStatusBar(new CPicture(Rect(7, 465, 440, 18), 0));//226, 472
|
|
txt = new CTextInput(Rect(19, 436, 334, 16), *bg);
|
|
txt->setText(settings["general"]["playerName"].String()); //Player
|
|
|
|
btns[0] = new CButton(Point(373, 78), "MUBHOT.DEF", CGI->generaltexth->zelp[266], std::bind(&CMultiMode::openHotseat, this));
|
|
btns[1] = new CButton(Point(373, 78 + 57*1), "MUBHOST.DEF", CButton::tooltip("Host TCP/IP game", ""), std::bind(&CMultiMode::hostTCP, this));
|
|
btns[2] = new CButton(Point(373, 78 + 57*2), "MUBJOIN.DEF", CButton::tooltip("Join TCP/IP game", ""), std::bind(&CMultiMode::joinTCP, this));
|
|
btns[6] = new CButton(Point(373, 424), "MUBCANC.DEF", CGI->generaltexth->zelp[288], [&](){ GH.popIntTotally(this);}, SDLK_ESCAPE);
|
|
}
|
|
|
|
void CMultiMode::openHotseat()
|
|
{
|
|
GH.pushInt(new CHotSeatPlayers(txt->text));
|
|
}
|
|
|
|
void CMultiMode::hostTCP()
|
|
{
|
|
Settings name = settings.write["general"]["playerName"];
|
|
name->String() = txt->text;
|
|
GH.popIntTotally(this);
|
|
if(settings["session"]["donotstartserver"].Bool())
|
|
GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_HOST));
|
|
else
|
|
GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_NETWORK_HOST));
|
|
}
|
|
|
|
void CMultiMode::joinTCP()
|
|
{
|
|
Settings name = settings.write["general"]["playerName"];
|
|
name->String() = txt->text;
|
|
GH.pushInt(new CSimpleJoinScreen(CMenuScreen::MULTI_NETWORK_GUEST));
|
|
}
|
|
|
|
CHotSeatPlayers::CHotSeatPlayers(const std::string &firstPlayer)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
bg = new CPicture("MUHOTSEA.bmp");
|
|
pos = bg->center(); //center, window has size of bg graphic
|
|
|
|
std::string text = CGI->generaltexth->allTexts[446];
|
|
boost::replace_all(text, "\t","\n");
|
|
Rect boxRect(25, 20, 315, 50);
|
|
title = new CTextBox(text, boxRect, 0, FONT_BIG, CENTER, Colors::WHITE);//HOTSEAT Please enter names
|
|
|
|
for(int i = 0; i < ARRAY_COUNT(txt); i++)
|
|
{
|
|
txt[i] = new CTextInput(Rect(60, 85 + i*30, 280, 16), *bg);
|
|
txt[i]->cb += std::bind(&CHotSeatPlayers::onChange, this, _1);
|
|
}
|
|
|
|
ok = new CButton(Point(95, 338), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CHotSeatPlayers::enterSelectionScreen, this), SDLK_RETURN);
|
|
cancel = new CButton(Point(205, 338), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
|
|
bar = new CGStatusBar(new CPicture(Rect(7, 381, 348, 18), 0));//226, 472
|
|
|
|
txt[0]->setText(firstPlayer, true);
|
|
txt[0]->giveFocus();
|
|
}
|
|
|
|
void CHotSeatPlayers::onChange(std::string newText)
|
|
{
|
|
size_t namesCount = 0;
|
|
|
|
for(auto & elem : txt)
|
|
if(!elem->text.empty())
|
|
namesCount++;
|
|
|
|
ok->block(namesCount < 2);
|
|
}
|
|
|
|
void CHotSeatPlayers::enterSelectionScreen()
|
|
{
|
|
std::map<ui8, std::string> names;
|
|
for(int i = 0, j = 1; i < ARRAY_COUNT(txt); i++)
|
|
if(txt[i]->text.length())
|
|
names[j++] = txt[i]->text;
|
|
|
|
Settings name = settings.write["general"]["playerName"];
|
|
name->String() = names.begin()->second;
|
|
|
|
GH.popInts(2); //pop MP mode window and this
|
|
GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, CMenuScreen::MULTI_HOT_SEAT, &names));
|
|
}
|
|
|
|
void CBonusSelection::init()
|
|
{
|
|
highlightedRegion = nullptr;
|
|
ourHeader.reset();
|
|
diffLb = nullptr;
|
|
diffRb = nullptr;
|
|
bonuses = nullptr;
|
|
selectedMap = 0;
|
|
|
|
// Initialize start info
|
|
startInfo.mapname = ourCampaign->camp->header.filename;
|
|
startInfo.mode = StartInfo::CAMPAIGN;
|
|
startInfo.campState = ourCampaign;
|
|
startInfo.turnTime = 0;
|
|
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
static const std::string bgNames [] = {"E1_BG.BMP", "G2_BG.BMP", "E2_BG.BMP", "G1_BG.BMP", "G3_BG.BMP", "N1_BG.BMP",
|
|
"S1_BG.BMP", "BR_BG.BMP", "IS_BG.BMP", "KR_BG.BMP", "NI_BG.BMP", "TA_BG.BMP", "AR_BG.BMP", "HS_BG.BMP",
|
|
"BB_BG.BMP", "NB_BG.BMP", "EL_BG.BMP", "RN_BG.BMP", "UA_BG.BMP", "SP_BG.BMP"};
|
|
|
|
loadPositionsOfGraphics();
|
|
|
|
background = BitmapHandler::loadBitmap(bgNames[ourCampaign->camp->header.mapVersion]);
|
|
pos.h = background->h;
|
|
pos.w = background->w;
|
|
center();
|
|
|
|
SDL_Surface * panel = BitmapHandler::loadBitmap("CAMPBRF.BMP");
|
|
|
|
blitAt(panel, 456, 6, background);
|
|
|
|
startB = new CButton(Point(475, 536), "CBBEGIB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::startMap, this), SDLK_RETURN);
|
|
restartB = new CButton(Point(475, 536), "CBRESTB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::restartMap, this), SDLK_RETURN);
|
|
backB = new CButton(Point(624, 536), "CBCANCB.DEF", CButton::tooltip(), std::bind(&CBonusSelection::goBack, this), SDLK_ESCAPE);
|
|
|
|
//campaign name
|
|
if (ourCampaign->camp->header.name.length())
|
|
graphics->fonts[FONT_BIG]->renderTextLeft(background, ourCampaign->camp->header.name, Colors::YELLOW, Point(481, 28));
|
|
else
|
|
graphics->fonts[FONT_BIG]->renderTextLeft(background, CGI->generaltexth->allTexts[508], Colors::YELLOW, Point(481, 28));
|
|
|
|
//map size icon
|
|
sizes = new CAnimImage("SCNRMPSZ",4,0,735, 26);
|
|
sizes->recActions &= ~(SHOWALL | UPDATE);//explicit draw
|
|
|
|
//campaign description
|
|
graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[38], Colors::YELLOW, Point(481, 63));
|
|
|
|
campaignDescription = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1);
|
|
//campaignDescription->showAll(background);
|
|
|
|
//map description
|
|
mapDescription = new CTextBox("", Rect(480, 280, 286, 117), 1);
|
|
|
|
//bonus choosing
|
|
graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432));
|
|
bonuses = new CToggleGroup(std::bind(&CBonusSelection::selectBonus, this, _1));
|
|
|
|
//set left part of window
|
|
bool isCurrentMapConquerable = ourCampaign->currentMap && ourCampaign->camp->conquerable(*ourCampaign->currentMap);
|
|
for(int g = 0; g < ourCampaign->camp->scenarios.size(); ++g)
|
|
{
|
|
if(ourCampaign->camp->conquerable(g))
|
|
{
|
|
regions.push_back(new CRegion(this, true, true, g));
|
|
regions[regions.size()-1]->rclickText = ourCampaign->camp->scenarios[g].regionText;
|
|
if(highlightedRegion == nullptr)
|
|
{
|
|
if(!isCurrentMapConquerable || (isCurrentMapConquerable && g == *ourCampaign->currentMap))
|
|
{
|
|
highlightedRegion = regions.back();
|
|
selectMap(g, true);
|
|
}
|
|
}
|
|
}
|
|
else if (ourCampaign->camp->scenarios[g].conquered) //display as striped
|
|
{
|
|
regions.push_back(new CRegion(this, false, false, g));
|
|
regions[regions.size()-1]->rclickText = ourCampaign->camp->scenarios[g].regionText;
|
|
}
|
|
}
|
|
|
|
//allies / enemies
|
|
graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[390] + ":", Colors::WHITE, Point(486, 407));
|
|
graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[391] + ":", Colors::WHITE, Point(619, 407));
|
|
|
|
SDL_FreeSurface(panel);
|
|
|
|
//difficulty
|
|
std::vector<std::string> difficulty;
|
|
boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" "));
|
|
graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, difficulty.back(), Colors::WHITE, Point(689, 432));
|
|
|
|
//difficulty pics
|
|
for (size_t b=0; b < diffPics.size(); ++b)
|
|
{
|
|
diffPics[b] = new CAnimImage("GSPBUT" + boost::lexical_cast<std::string>(b+3) + ".DEF", 0, 0, 709, 455);
|
|
diffPics[b]->recActions &= ~(SHOWALL | UPDATE);//explicit draw
|
|
}
|
|
|
|
//difficulty selection buttons
|
|
if (ourCampaign->camp->header.difficultyChoosenByPlayer)
|
|
{
|
|
diffLb = new CButton(Point(694, 508), "SCNRBLF.DEF", CButton::tooltip(), std::bind(&CBonusSelection::decreaseDifficulty, this));
|
|
diffRb = new CButton(Point(738, 508), "SCNRBRT.DEF", CButton::tooltip(), std::bind(&CBonusSelection::increaseDifficulty, this));
|
|
}
|
|
|
|
//load miniflags
|
|
sFlags = std::make_shared<CAnimation>("ITGFLAGS.DEF");
|
|
sFlags->load();
|
|
}
|
|
|
|
CBonusSelection::CBonusSelection(std::shared_ptr<CCampaignState> _ourCampaign) : ourCampaign(_ourCampaign)
|
|
{
|
|
init();
|
|
}
|
|
|
|
CBonusSelection::CBonusSelection(const std::string & campaignFName)
|
|
{
|
|
ourCampaign = std::make_shared<CCampaignState>(CCampaignHandler::getCampaign(campaignFName));
|
|
init();
|
|
}
|
|
|
|
CBonusSelection::~CBonusSelection()
|
|
{
|
|
SDL_FreeSurface(background);
|
|
sFlags->unload();
|
|
}
|
|
|
|
void CBonusSelection::goBack()
|
|
{
|
|
GH.popIntTotally(this);
|
|
}
|
|
|
|
void CBonusSelection::showAll(SDL_Surface * to)
|
|
{
|
|
blitAt(background, pos.x, pos.y, to);
|
|
CIntObject::showAll(to);
|
|
|
|
show(to);
|
|
if (pos.h != to->h || pos.w != to->w)
|
|
CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
|
|
}
|
|
|
|
void CBonusSelection::loadPositionsOfGraphics()
|
|
{
|
|
const JsonNode config(ResourceID("config/campaign_regions.json"));
|
|
int idx = 0;
|
|
|
|
for(const JsonNode &campaign : config["campaign_regions"].Vector())
|
|
{
|
|
SCampPositions sc;
|
|
|
|
sc.campPrefix = campaign["prefix"].String();
|
|
sc.colorSuffixLength = campaign["color_suffix_length"].Float();
|
|
|
|
for(const JsonNode &desc : campaign["desc"].Vector())
|
|
{
|
|
SCampPositions::SRegionDesc rd;
|
|
|
|
rd.infix = desc["infix"].String();
|
|
rd.xpos = desc["x"].Float();
|
|
rd.ypos = desc["y"].Float();
|
|
sc.regions.push_back(rd);
|
|
}
|
|
|
|
campDescriptions.push_back(sc);
|
|
|
|
idx++;
|
|
}
|
|
|
|
assert(idx == CGI->generaltexth->campaignMapNames.size());
|
|
}
|
|
|
|
void CBonusSelection::selectMap(int mapNr, bool initialSelect)
|
|
{
|
|
if(initialSelect || selectedMap != mapNr)
|
|
{
|
|
// initialize restart / start button
|
|
if(!ourCampaign->currentMap || *ourCampaign->currentMap != mapNr)
|
|
{
|
|
// draw start button
|
|
restartB->disable();
|
|
startB->enable();
|
|
if (!ourCampaign->mapsConquered.empty())
|
|
backB->block(true);
|
|
else
|
|
backB->block(false);
|
|
}
|
|
else
|
|
{
|
|
// draw restart button
|
|
startB->disable();
|
|
restartB->enable();
|
|
backB->block(false);
|
|
}
|
|
|
|
startInfo.difficulty = ourCampaign->camp->scenarios[mapNr].difficulty;
|
|
selectedMap = mapNr;
|
|
selectedBonus = boost::none;
|
|
|
|
std::string scenarioName = ourCampaign->camp->header.filename.substr(0, ourCampaign->camp->header.filename.find('.'));
|
|
boost::to_lower(scenarioName);
|
|
scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
|
|
|
|
//get header
|
|
std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
|
|
auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
|
|
ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName);
|
|
|
|
std::map<ui8, std::string> names;
|
|
names[1] = settings["general"]["playerName"].String();
|
|
updateStartInfo(ourCampaign->camp->header.filename, startInfo, ourHeader, names);
|
|
|
|
mapDescription->setText(ourHeader->description);
|
|
|
|
updateBonusSelection();
|
|
|
|
GH.totalRedraw();
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::show(SDL_Surface * to)
|
|
{
|
|
//map name
|
|
std::string mapName = ourHeader->name;
|
|
|
|
if (mapName.length())
|
|
printAtLoc(mapName, 481, 219, FONT_BIG, Colors::YELLOW, to);
|
|
else
|
|
printAtLoc("Unnamed", 481, 219, FONT_BIG, Colors::YELLOW, to);
|
|
|
|
//map description
|
|
printAtLoc(CGI->generaltexth->allTexts[496], 481, 253, FONT_SMALL, Colors::YELLOW, to);
|
|
|
|
mapDescription->showAll(to); //showAll because CTextBox has no show()
|
|
|
|
//map size icon
|
|
int temp;
|
|
switch (ourHeader->width)
|
|
{
|
|
case 36:
|
|
temp=0;
|
|
break;
|
|
case 72:
|
|
temp=1;
|
|
break;
|
|
case 108:
|
|
temp=2;
|
|
break;
|
|
case 144:
|
|
temp=3;
|
|
break;
|
|
default:
|
|
temp=4;
|
|
break;
|
|
}
|
|
sizes->setFrame(temp);
|
|
sizes->showAll(to);
|
|
|
|
//flags
|
|
int fx = 496 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]);
|
|
int ex = 629 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]);
|
|
TeamID myT;
|
|
myT = ourHeader->players[playerColor.getNum()].team;
|
|
for (auto i = startInfo.playerInfos.cbegin(); i != startInfo.playerInfos.cend(); i++)
|
|
{
|
|
int *myx = ((i->first == playerColor || ourHeader->players[i->first.getNum()].team == myT) ? &fx : &ex);
|
|
|
|
IImage * flag = sFlags->getImage(i->first.getNum(),0);
|
|
flag->draw(to, pos.x + *myx, pos.y + 405);
|
|
*myx += flag->width();
|
|
flag->decreaseRef();
|
|
}
|
|
|
|
//difficulty
|
|
diffPics[startInfo.difficulty]->showAll(to);
|
|
|
|
CIntObject::show(to);
|
|
}
|
|
|
|
void CBonusSelection::updateBonusSelection()
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
//graphics:
|
|
//spell - SPELLBON.DEF
|
|
//monster - TWCRPORT.DEF
|
|
//building - BO*.BMP graphics
|
|
//artifact - ARTIFBON.DEF
|
|
//spell scroll - SPELLBON.DEF
|
|
//prim skill - PSKILBON.DEF
|
|
//sec skill - SSKILBON.DEF
|
|
//resource - BORES.DEF
|
|
//player - CREST58.DEF
|
|
//hero - PORTRAITSLARGE (HPL###.BMPs)
|
|
const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap];
|
|
const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
|
|
|
|
updateStartButtonState(-1);
|
|
|
|
delete bonuses;
|
|
bonuses = new CToggleGroup(std::bind(&CBonusSelection::selectBonus, this, _1));
|
|
|
|
static const char *bonusPics[] = {"SPELLBON.DEF", "TWCRPORT.DEF", "", "ARTIFBON.DEF", "SPELLBON.DEF",
|
|
"PSKILBON.DEF", "SSKILBON.DEF", "BORES.DEF", "PORTRAITSLARGE", "PORTRAITSLARGE"};
|
|
|
|
for(int i = 0; i < bonDescs.size(); i++)
|
|
{
|
|
std::string picName=bonusPics[bonDescs[i].type];
|
|
size_t picNumber=bonDescs[i].info2;
|
|
|
|
std::string desc;
|
|
switch(bonDescs[i].type)
|
|
{
|
|
case CScenarioTravel::STravelBonus::SPELL:
|
|
desc = CGI->generaltexth->allTexts[715];
|
|
boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
|
|
break;
|
|
case CScenarioTravel::STravelBonus::MONSTER:
|
|
picNumber = bonDescs[i].info2 + 2;
|
|
desc = CGI->generaltexth->allTexts[717];
|
|
boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info3));
|
|
boost::algorithm::replace_first(desc, "%s", CGI->creh->creatures[bonDescs[i].info2]->namePl);
|
|
break;
|
|
case CScenarioTravel::STravelBonus::BUILDING:
|
|
{
|
|
int faction = -1;
|
|
for(auto & elem : startInfo.playerInfos)
|
|
{
|
|
if (elem.second.playerID)
|
|
{
|
|
faction = elem.second.castle;
|
|
break;
|
|
}
|
|
|
|
}
|
|
assert(faction != -1);
|
|
|
|
BuildingID buildID = CBuildingHandler::campToERMU(bonDescs[i].info1, faction, std::set<BuildingID>());
|
|
picName = graphics->ERMUtoPicture[faction][buildID];
|
|
picNumber = -1;
|
|
|
|
if (vstd::contains(CGI->townh->factions[faction]->town->buildings, buildID))
|
|
desc = CGI->townh->factions[faction]->town->buildings.find(buildID)->second->Name();
|
|
}
|
|
break;
|
|
case CScenarioTravel::STravelBonus::ARTIFACT:
|
|
desc = CGI->generaltexth->allTexts[715];
|
|
boost::algorithm::replace_first(desc, "%s", CGI->arth->artifacts[bonDescs[i].info2]->Name());
|
|
break;
|
|
case CScenarioTravel::STravelBonus::SPELL_SCROLL:
|
|
desc = CGI->generaltexth->allTexts[716];
|
|
boost::algorithm::replace_first(desc, "%s", CGI->spellh->objects[bonDescs[i].info2]->name);
|
|
break;
|
|
case CScenarioTravel::STravelBonus::PRIMARY_SKILL:
|
|
{
|
|
int leadingSkill = -1;
|
|
std::vector<std::pair<int, int> > toPrint; //primary skills to be listed <num, val>
|
|
const ui8* ptr = reinterpret_cast<const ui8*>(&bonDescs[i].info2);
|
|
for (int g=0; g<GameConstants::PRIMARY_SKILLS; ++g)
|
|
{
|
|
if (leadingSkill == -1 || ptr[g] > ptr[leadingSkill])
|
|
{
|
|
leadingSkill = g;
|
|
}
|
|
if (ptr[g] != 0)
|
|
{
|
|
toPrint.push_back(std::make_pair(g, ptr[g]));
|
|
}
|
|
}
|
|
picNumber = leadingSkill;
|
|
desc = CGI->generaltexth->allTexts[715];
|
|
|
|
std::string substitute; //text to be printed instead of %s
|
|
for (int v=0; v<toPrint.size(); ++v)
|
|
{
|
|
substitute += boost::lexical_cast<std::string>(toPrint[v].second);
|
|
substitute += " " + CGI->generaltexth->primarySkillNames[toPrint[v].first];
|
|
if(v != toPrint.size() - 1)
|
|
{
|
|
substitute += ", ";
|
|
}
|
|
}
|
|
|
|
boost::algorithm::replace_first(desc, "%s", substitute);
|
|
break;
|
|
}
|
|
case CScenarioTravel::STravelBonus::SECONDARY_SKILL:
|
|
desc = CGI->generaltexth->allTexts[718];
|
|
|
|
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->levels[bonDescs[i].info3 - 1]); //skill level
|
|
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->skillName[bonDescs[i].info2]); //skill name
|
|
picNumber = bonDescs[i].info2 * 3 + bonDescs[i].info3 - 1;
|
|
|
|
break;
|
|
case CScenarioTravel::STravelBonus::RESOURCE:
|
|
{
|
|
int serialResID = 0;
|
|
switch(bonDescs[i].info1)
|
|
{
|
|
case 0: case 1: case 2: case 3: case 4: case 5: case 6:
|
|
serialResID = bonDescs[i].info1;
|
|
break;
|
|
case 0xFD: //wood + ore
|
|
serialResID = 7;
|
|
break;
|
|
case 0xFE: //rare resources
|
|
serialResID = 8;
|
|
break;
|
|
}
|
|
picNumber = serialResID;
|
|
|
|
desc = CGI->generaltexth->allTexts[717];
|
|
boost::algorithm::replace_first(desc, "%d", boost::lexical_cast<std::string>(bonDescs[i].info2));
|
|
std::string replacement;
|
|
if (serialResID <= 6)
|
|
{
|
|
replacement = CGI->generaltexth->restypes[serialResID];
|
|
}
|
|
else
|
|
{
|
|
replacement = CGI->generaltexth->allTexts[714 + serialResID];
|
|
}
|
|
boost::algorithm::replace_first(desc, "%s", replacement);
|
|
}
|
|
break;
|
|
case CScenarioTravel::STravelBonus::HEROES_FROM_PREVIOUS_SCENARIO:
|
|
{
|
|
auto superhero = ourCampaign->camp->scenarios[bonDescs[i].info2].strongestHero(PlayerColor(bonDescs[i].info1));
|
|
if (!superhero) logGlobal->warn("No superhero! How could it be transferred?");
|
|
picNumber = superhero ? superhero->portrait : 0;
|
|
desc = CGI->generaltexth->allTexts[719];
|
|
|
|
boost::algorithm::replace_first(desc, "%s", ourCampaign->camp->scenarios[bonDescs[i].info2].scenarioName); //scenario
|
|
}
|
|
|
|
break;
|
|
case CScenarioTravel::STravelBonus::HERO:
|
|
|
|
desc = CGI->generaltexth->allTexts[718];
|
|
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->capColors[bonDescs[i].info1]); //hero's color
|
|
|
|
if (bonDescs[i].info2 == 0xFFFF)
|
|
{
|
|
boost::algorithm::replace_first(desc, "%s", CGI->generaltexth->allTexts[101]); //hero's name
|
|
picNumber = -1;
|
|
picName = "CBONN1A3.BMP";
|
|
}
|
|
else
|
|
{
|
|
boost::algorithm::replace_first(desc, "%s", CGI->heroh->heroes[bonDescs[i].info2]->name); //hero's name
|
|
}
|
|
break;
|
|
}
|
|
|
|
CToggleButton *bonusButton = new CToggleButton(Point(475 + i*68, 455), "", CButton::tooltip(desc, desc));
|
|
|
|
if (picNumber != -1)
|
|
picName += ":" + boost::lexical_cast<std::string>(picNumber);
|
|
|
|
auto anim = std::make_shared<CAnimation>();
|
|
anim->setCustom(picName, 0);
|
|
bonusButton->setImage(anim);
|
|
const SDL_Color brightYellow = { 242, 226, 110, 0 };
|
|
bonusButton->borderColor = boost::make_optional(brightYellow);
|
|
bonuses->addToggle(i, bonusButton);
|
|
}
|
|
|
|
// set bonus if already chosen
|
|
if(vstd::contains(ourCampaign->chosenCampaignBonuses, selectedMap))
|
|
{
|
|
bonuses->setSelected(ourCampaign->chosenCampaignBonuses[selectedMap]);
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::updateCampaignState()
|
|
{
|
|
ourCampaign->currentMap = boost::make_optional(selectedMap);
|
|
if (selectedBonus)
|
|
ourCampaign->chosenCampaignBonuses[selectedMap] = *selectedBonus;
|
|
}
|
|
|
|
void CBonusSelection::startMap()
|
|
{
|
|
auto si = new StartInfo(startInfo);
|
|
auto showPrologVideo = [=]()
|
|
{
|
|
auto exitCb = [=]()
|
|
{
|
|
logGlobal->info("Starting scenario %d", selectedMap);
|
|
CGP->showLoadingScreen(std::bind(&startGame, si, (CConnection *)nullptr));
|
|
};
|
|
|
|
const CCampaignScenario & scenario = ourCampaign->camp->scenarios[selectedMap];
|
|
if (scenario.prolog.hasPrologEpilog)
|
|
{
|
|
GH.pushInt(new CPrologEpilogVideo(scenario.prolog, exitCb));
|
|
}
|
|
else
|
|
{
|
|
exitCb();
|
|
}
|
|
};
|
|
|
|
if(LOCPLINT) // we're currently ingame, so ask for starting new map and end game
|
|
{
|
|
GH.popInt(this);
|
|
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
|
|
{
|
|
updateCampaignState();
|
|
endGame();
|
|
GH.curInt = CGPreGame::create();
|
|
showPrologVideo();
|
|
}, 0);
|
|
}
|
|
else
|
|
{
|
|
updateCampaignState();
|
|
showPrologVideo();
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::restartMap()
|
|
{
|
|
GH.popInt(this);
|
|
LOCPLINT->showYesNoDialog(CGI->generaltexth->allTexts[67], [=]()
|
|
{
|
|
updateCampaignState();
|
|
auto si = new StartInfo(startInfo);
|
|
|
|
SDL_Event event;
|
|
event.type = SDL_USEREVENT;
|
|
event.user.code = PREPARE_RESTART_CAMPAIGN;
|
|
event.user.data1 = si;
|
|
SDL_PushEvent(&event);
|
|
}, 0);
|
|
}
|
|
|
|
void CBonusSelection::selectBonus(int id)
|
|
{
|
|
// Total redraw is needed because the border around the bonus images
|
|
// have to be undrawn/drawn.
|
|
if (!selectedBonus || *selectedBonus != id)
|
|
{
|
|
selectedBonus = boost::make_optional(id);
|
|
GH.totalRedraw();
|
|
|
|
updateStartButtonState(id);
|
|
}
|
|
|
|
const CCampaignScenario &scenario = ourCampaign->camp->scenarios[selectedMap];
|
|
const std::vector<CScenarioTravel::STravelBonus> & bonDescs = scenario.travelOptions.bonusesToChoose;
|
|
if (bonDescs[id].type == CScenarioTravel::STravelBonus::HERO)
|
|
{
|
|
std::map<ui8, std::string> names;
|
|
names[1] = settings["general"]["playerName"].String();
|
|
for(auto & elem : startInfo.playerInfos)
|
|
{
|
|
if(elem.first == PlayerColor(bonDescs[id].info1))
|
|
::setPlayer(elem.second, 1, names);
|
|
else
|
|
::setPlayer(elem.second, 0, names);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::increaseDifficulty()
|
|
{
|
|
startInfo.difficulty = std::min(startInfo.difficulty + 1, 4);
|
|
}
|
|
|
|
void CBonusSelection::decreaseDifficulty()
|
|
{
|
|
startInfo.difficulty = std::max(startInfo.difficulty - 1, 0);
|
|
}
|
|
|
|
void CBonusSelection::updateStartButtonState(int selected)
|
|
{
|
|
if(selected == -1)
|
|
{
|
|
startB->block(ourCampaign->camp->scenarios[selectedMap].travelOptions.bonusesToChoose.size());
|
|
}
|
|
else if(startB->isBlocked())
|
|
{
|
|
startB->block(false);
|
|
}
|
|
}
|
|
|
|
CBonusSelection::CRegion::CRegion( CBonusSelection * _owner, bool _accessible, bool _selectable, int _myNumber )
|
|
: owner(_owner), accessible(_accessible), selectable(_selectable), myNumber(_myNumber)
|
|
{
|
|
OBJ_CONSTRUCTION;
|
|
addUsedEvents(LCLICK | RCLICK);
|
|
|
|
static const std::string colors[2][8] = {
|
|
{"R", "B", "N", "G", "O", "V", "T", "P"},
|
|
{"Re", "Bl", "Br", "Gr", "Or", "Vi", "Te", "Pi"}};
|
|
|
|
const SCampPositions & campDsc = owner->campDescriptions[owner->ourCampaign->camp->header.mapVersion];
|
|
const SCampPositions::SRegionDesc & desc = campDsc.regions[myNumber];
|
|
pos.x += desc.xpos;
|
|
pos.y += desc.ypos;
|
|
|
|
//loading of graphics
|
|
|
|
std::string prefix = campDsc.campPrefix + desc.infix + "_";
|
|
std::string suffix = colors[campDsc.colorSuffixLength - 1][owner->ourCampaign->camp->scenarios[myNumber].regionColor];
|
|
|
|
static const std::string infix [] = {"En", "Se", "Co"};
|
|
for (int g = 0; g < ARRAY_COUNT(infix); g++)
|
|
{
|
|
graphics[g] = BitmapHandler::loadBitmap(prefix + infix[g] + suffix + ".BMP");
|
|
}
|
|
pos.w = graphics[0]->w;
|
|
pos.h = graphics[0]->h;
|
|
|
|
}
|
|
|
|
CBonusSelection::CRegion::~CRegion()
|
|
{
|
|
for (auto & elem : graphics)
|
|
{
|
|
SDL_FreeSurface(elem);
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::CRegion::clickLeft( tribool down, bool previousState )
|
|
{
|
|
//select if selectable & clicked inside our graphic
|
|
if ( indeterminate(down) )
|
|
{
|
|
return;
|
|
}
|
|
if( !down && selectable && !CSDL_Ext::isTransparent(graphics[0], GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) )
|
|
{
|
|
owner->selectMap(myNumber, false);
|
|
owner->highlightedRegion = this;
|
|
parent->showAll(screen);
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::CRegion::clickRight( tribool down, bool previousState )
|
|
{
|
|
//show r-click text
|
|
if( down && !CSDL_Ext::isTransparent(graphics[0], GH.current->motion.x-pos.x, GH.current->motion.y-pos.y) &&
|
|
rclickText.size() )
|
|
{
|
|
CRClickPopup::createAndPush(rclickText);
|
|
}
|
|
}
|
|
|
|
void CBonusSelection::CRegion::show(SDL_Surface * to)
|
|
{
|
|
//const SCampPositions::SRegionDesc & desc = owner->campDescriptions[owner->ourCampaign->camp->header.mapVersion].regions[myNumber];
|
|
if (!accessible)
|
|
{
|
|
//show as striped
|
|
blitAtLoc(graphics[2], 0, 0, to);
|
|
}
|
|
else if (this == owner->highlightedRegion)
|
|
{
|
|
//show as selected
|
|
blitAtLoc(graphics[1], 0, 0, to);
|
|
}
|
|
else
|
|
{
|
|
//show as not selected selected
|
|
blitAtLoc(graphics[0], 0, 0, to);
|
|
}
|
|
}
|
|
|
|
CSavingScreen::CSavingScreen(bool hotseat)
|
|
: CSelectionScreen(
|
|
CMenuScreen::saveGame,
|
|
hotseat ? CMenuScreen::MULTI_HOT_SEAT : (LOCPLINT->cb->getStartInfo()->mode == StartInfo::CAMPAIGN ? CMenuScreen::SINGLE_CAMPAIGN : CMenuScreen::SINGLE_PLAYER)
|
|
)
|
|
{
|
|
ourGame = mapInfoFromGame();
|
|
sInfo = *LOCPLINT->cb->getStartInfo();
|
|
setPlayersFromGame();
|
|
}
|
|
|
|
CSavingScreen::~CSavingScreen()
|
|
{
|
|
|
|
}
|
|
|
|
ISelectionScreenInfo::ISelectionScreenInfo(const std::map<ui8, std::string> * Names)
|
|
{
|
|
gameMode = CMenuScreen::SINGLE_PLAYER;
|
|
screenType = CMenuScreen::mainMenu;
|
|
assert(!SEL);
|
|
SEL = this;
|
|
current = nullptr;
|
|
|
|
if(Names && !Names->empty()) //if have custom set of player names - use it
|
|
playerNames = *Names;
|
|
else
|
|
playerNames[1] = settings["general"]["playerName"].String(); //by default we have only one player and his name is "Player" (or whatever the last used name was)
|
|
}
|
|
|
|
ISelectionScreenInfo::~ISelectionScreenInfo()
|
|
{
|
|
assert(SEL == this);
|
|
SEL = nullptr;
|
|
}
|
|
|
|
void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader)
|
|
{
|
|
::updateStartInfo(filename, sInfo, mapHeader, playerNames);
|
|
}
|
|
|
|
void ISelectionScreenInfo::setPlayer(PlayerSettings &pset, ui8 player)
|
|
{
|
|
::setPlayer(pset, player, playerNames);
|
|
}
|
|
|
|
ui8 ISelectionScreenInfo::getIdOfFirstUnallocatedPlayer()
|
|
{
|
|
for(auto i = playerNames.cbegin(); i != playerNames.cend(); i++)
|
|
if(!sInfo.getPlayersSettings(i->first)) //
|
|
return i->first;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool ISelectionScreenInfo::isGuest() const
|
|
{
|
|
return gameMode == CMenuScreen::MULTI_NETWORK_GUEST;
|
|
}
|
|
|
|
bool ISelectionScreenInfo::isHost() const
|
|
{
|
|
return gameMode == CMenuScreen::MULTI_NETWORK_HOST;
|
|
}
|
|
|
|
void ChatMessage::apply(CSelectionScreen *selScreen)
|
|
{
|
|
selScreen->card->chat->addNewMessage(playerName + ": " + message);
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
void QuitMenuWithoutStarting::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(!selScreen->ongoingClosing)
|
|
{
|
|
*selScreen->serv << this; //resend to confirm
|
|
GH.popIntTotally(selScreen); //will wait with deleting us before this thread ends
|
|
}
|
|
|
|
vstd::clear_pointer(selScreen->serv);
|
|
}
|
|
|
|
void PlayerJoined::apply(CSelectionScreen *selScreen)
|
|
{
|
|
//assert(SEL->playerNames.size() == connectionID); //temporary, TODO when player exits
|
|
SEL->playerNames[connectionID] = playerName;
|
|
|
|
//put new player in first slot with AI
|
|
for(auto & elem : SEL->sInfo.playerInfos)
|
|
{
|
|
if(!elem.second.playerID && !elem.second.compOnly)
|
|
{
|
|
selScreen->setPlayer(elem.second, connectionID);
|
|
selScreen->opt->entries[elem.second.color]->selectButtons();
|
|
break;
|
|
}
|
|
}
|
|
|
|
selScreen->propagateNames();
|
|
selScreen->propagateOptions();
|
|
selScreen->toggleTab(selScreen->curTab);
|
|
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
void SelectMap::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(selScreen->isGuest())
|
|
{
|
|
free = false;
|
|
selScreen->changeSelection(mapInfo);
|
|
}
|
|
}
|
|
|
|
void UpdateStartOptions::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(!selScreen->isGuest())
|
|
return;
|
|
|
|
selScreen->setSInfo(*options);
|
|
}
|
|
|
|
void PregameGuiAction::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(!selScreen->isGuest())
|
|
return;
|
|
|
|
switch(action)
|
|
{
|
|
case NO_TAB:
|
|
selScreen->toggleTab(selScreen->curTab);
|
|
break;
|
|
case OPEN_OPTIONS:
|
|
selScreen->toggleTab(selScreen->opt);
|
|
break;
|
|
case OPEN_SCENARIO_LIST:
|
|
selScreen->toggleTab(selScreen->sel);
|
|
break;
|
|
case OPEN_RANDOM_MAP_OPTIONS:
|
|
selScreen->toggleTab(selScreen->randMapTab);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void RequestOptionsChange::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(!selScreen->isHost())
|
|
return;
|
|
|
|
PlayerColor color = selScreen->sInfo.getPlayersSettings(playerID)->color;
|
|
|
|
switch(what)
|
|
{
|
|
case TOWN:
|
|
selScreen->opt->nextCastle(color, direction);
|
|
break;
|
|
case HERO:
|
|
selScreen->opt->nextHero(color, direction);
|
|
break;
|
|
case BONUS:
|
|
selScreen->opt->nextBonus(color, direction);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void PlayerLeft::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(selScreen->isGuest())
|
|
return;
|
|
|
|
SEL->playerNames.erase(playerID);
|
|
|
|
if(PlayerSettings *s = selScreen->sInfo.getPlayersSettings(playerID)) //it's possible that player was unallocated
|
|
{
|
|
selScreen->setPlayer(*s, 0);
|
|
selScreen->opt->entries[s->color]->selectButtons();
|
|
}
|
|
|
|
selScreen->propagateNames();
|
|
selScreen->propagateOptions();
|
|
GH.totalRedraw();
|
|
}
|
|
|
|
void PlayersNames::apply(CSelectionScreen *selScreen)
|
|
{
|
|
if(selScreen->isGuest())
|
|
selScreen->playerNames = playerNames;
|
|
}
|
|
|
|
void StartWithCurrentSettings::apply(CSelectionScreen *selScreen)
|
|
{
|
|
startingInfo.reset();
|
|
startingInfo.serv = selScreen->serv;
|
|
startingInfo.sInfo = new StartInfo(selScreen->sInfo);
|
|
|
|
if(!selScreen->ongoingClosing)
|
|
{
|
|
*selScreen->serv << this; //resend to confirm
|
|
}
|
|
|
|
selScreen->serv = nullptr; //hide it so it won't be deleted
|
|
vstd::clear_pointer(selScreen->serverHandlingThread); //detach us
|
|
saveGameName.clear();
|
|
|
|
CGP->showLoadingScreen(std::bind(&startGame, startingInfo.sInfo, startingInfo.serv));
|
|
throw 666; //EVIL, EVIL, EVIL workaround to kill thread (does "goto catch" outside listening loop)
|
|
}
|
|
|
|
CCampaignScreen::CCampaignButton::CCampaignButton(const JsonNode &config )
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
pos.x += config["x"].Float();
|
|
pos.y += config["y"].Float();
|
|
pos.w = 200;
|
|
pos.h = 116;
|
|
|
|
campFile = config["file"].String();
|
|
video = config["video"].String();
|
|
|
|
status = config["open"].Bool() ? CCampaignScreen::ENABLED : CCampaignScreen::DISABLED;
|
|
|
|
CCampaignHeader header = CCampaignHandler::getHeader(campFile);
|
|
hoverText = header.name;
|
|
|
|
hoverLabel = nullptr;
|
|
if (status != CCampaignScreen::DISABLED)
|
|
{
|
|
addUsedEvents(LCLICK | HOVER);
|
|
new CPicture(config["image"].String());
|
|
|
|
hoverLabel = new CLabel(pos.w / 2, pos.h + 20, FONT_MEDIUM, CENTER, Colors::YELLOW, "");
|
|
parent->addChild(hoverLabel);
|
|
}
|
|
|
|
if (status == CCampaignScreen::COMPLETED)
|
|
new CPicture("CAMPCHK");
|
|
}
|
|
|
|
void CCampaignScreen::CCampaignButton::clickLeft(tribool down, bool previousState)
|
|
{
|
|
if (down)
|
|
{
|
|
// Close running video and open the selected campaign
|
|
CCS->videoh->close();
|
|
GH.pushInt( new CBonusSelection(campFile) );
|
|
}
|
|
}
|
|
|
|
void CCampaignScreen::CCampaignButton::hover(bool on)
|
|
{
|
|
if (hoverLabel)
|
|
{
|
|
if (on)
|
|
hoverLabel->setText(hoverText); // Shows the name of the campaign when you get into the bounds of the button
|
|
else
|
|
hoverLabel->setText(" ");
|
|
}
|
|
}
|
|
|
|
void CCampaignScreen::CCampaignButton::show(SDL_Surface * to)
|
|
{
|
|
if (status == CCampaignScreen::DISABLED)
|
|
return;
|
|
|
|
CIntObject::show(to);
|
|
|
|
// Play the campaign button video when the mouse cursor is placed over the button
|
|
if (hovered)
|
|
{
|
|
if (CCS->videoh->fname != video)
|
|
CCS->videoh->open(video);
|
|
|
|
CCS->videoh->update(pos.x, pos.y, to, true, false); // plays sequentially frame by frame, starts at the beginning when the video is over
|
|
}
|
|
else if (CCS->videoh->fname == video) // When you got out of the bounds of the button then close the video
|
|
{
|
|
CCS->videoh->close();
|
|
redraw();
|
|
}
|
|
}
|
|
|
|
CButton* CCampaignScreen::createExitButton(const JsonNode& button)
|
|
{
|
|
std::pair<std::string, std::string> help;
|
|
if (!button["help"].isNull() && button["help"].Float() > 0)
|
|
help = CGI->generaltexth->zelp[button["help"].Float()];
|
|
|
|
std::function<void()> close = std::bind(&CGuiHandler::popIntTotally, &GH, this);
|
|
return new CButton(Point(button["x"].Float(), button["y"].Float()), button["name"].String(), help, close, button["hotkey"].Float());
|
|
}
|
|
|
|
|
|
CCampaignScreen::CCampaignScreen(const JsonNode &config)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
|
|
for(const JsonNode& node : config["images"].Vector())
|
|
images.push_back(createPicture(node));
|
|
|
|
if (!images.empty())
|
|
{
|
|
images[0]->center(); // move background to center
|
|
moveTo(images[0]->pos.topLeft()); // move everything else to center
|
|
images[0]->moveTo(pos.topLeft()); // restore moved twice background
|
|
pos = images[0]->pos; // fix height\width of this window
|
|
}
|
|
|
|
if (!config["exitbutton"].isNull())
|
|
{
|
|
CButton * back = createExitButton(config["exitbutton"]);
|
|
back->hoverable = true;
|
|
}
|
|
|
|
for(const JsonNode& node : config["items"].Vector())
|
|
campButtons.push_back(new CCampaignButton(node));
|
|
}
|
|
|
|
void CCampaignScreen::showAll(SDL_Surface *to)
|
|
{
|
|
CIntObject::showAll(to);
|
|
if (pos.h != to->h || pos.w != to->w)
|
|
CMessage::drawBorder(PlayerColor(1), to, pos.w+28, pos.h+30, pos.x-14, pos.y-15);
|
|
}
|
|
|
|
void CGPreGame::showLoadingScreen(std::function<void()> loader)
|
|
{
|
|
if (GH.listInt.size() && GH.listInt.front() == CGP) //pregame active
|
|
CGP->removeFromGui();
|
|
GH.pushInt(new CLoadingScreen(loader));
|
|
}
|
|
|
|
std::string CLoadingScreen::getBackground()
|
|
{
|
|
const auto & conf = CGPreGameConfig::get().getConfig()["loading"].Vector();
|
|
|
|
if(conf.empty())
|
|
{
|
|
return "loadbar";
|
|
}
|
|
else
|
|
{
|
|
return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String();
|
|
}
|
|
}
|
|
|
|
CLoadingScreen::CLoadingScreen(std::function<void ()> loader):
|
|
CWindowObject(BORDERED, getBackground()),
|
|
loadingThread(loader)
|
|
{
|
|
CCS->musich->stopMusic(5000);
|
|
}
|
|
|
|
CLoadingScreen::~CLoadingScreen()
|
|
{
|
|
loadingThread.join();
|
|
}
|
|
|
|
void CLoadingScreen::showAll(SDL_Surface *to)
|
|
{
|
|
Rect rect(0,0,to->w, to->h);
|
|
SDL_FillRect(to, &rect, 0);
|
|
|
|
CWindowObject::showAll(to);
|
|
}
|
|
|
|
CPrologEpilogVideo::CPrologEpilogVideo( CCampaignScenario::SScenarioPrologEpilog _spe, std::function<void()> callback ):
|
|
CWindowObject(BORDERED),
|
|
spe(_spe),
|
|
positionCounter(0),
|
|
voiceSoundHandle(-1),
|
|
exitCb(callback)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
addUsedEvents(LCLICK);
|
|
pos = center(Rect(0,0, 800, 600));
|
|
updateShadow();
|
|
|
|
CCS->videoh->open(CCampaignHandler::prologVideoName(spe.prologVideo));
|
|
CCS->musich->playMusic("Music/" + CCampaignHandler::prologMusicName(spe.prologMusic), true);
|
|
voiceSoundHandle = CCS->soundh->playSound(CCampaignHandler::prologVoiceName(spe.prologVideo));
|
|
|
|
text = new CMultiLineLabel(Rect(100, 500, 600, 100), EFonts::FONT_BIG, CENTER, Colors::METALLIC_GOLD, spe.prologText );
|
|
text->scrollTextTo(-100);
|
|
}
|
|
|
|
void CPrologEpilogVideo::show( SDL_Surface * to )
|
|
{
|
|
CSDL_Ext::fillRectBlack(to, &pos);
|
|
//BUG: some videos are 800x600 in size while some are 800x400
|
|
//VCMI should center them in the middle of the screen. Possible but needs modification
|
|
//of video player API which I'd like to avoid until we'll get rid of Windows-specific player
|
|
CCS->videoh->update(pos.x, pos.y, to, true, false);
|
|
|
|
//move text every 5 calls/frames; seems to be good enough
|
|
++positionCounter;
|
|
if(positionCounter % 5 == 0)
|
|
{
|
|
text->scrollTextBy(1);
|
|
}
|
|
else
|
|
text->showAll(to);// blit text over video, if needed
|
|
|
|
if (text->textSize.y + 100 < positionCounter / 5)
|
|
clickLeft(false, false);
|
|
}
|
|
|
|
void CPrologEpilogVideo::clickLeft( tribool down, bool previousState )
|
|
{
|
|
GH.popInt(this);
|
|
CCS->soundh->stopSound(voiceSoundHandle);
|
|
exitCb();
|
|
}
|
|
|
|
CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EGameMode mode)
|
|
{
|
|
OBJ_CONSTRUCTION_CAPTURING_ALL;
|
|
bg = new CPicture("MUDIALOG.bmp"); // address background
|
|
pos = bg->center(); //center, window has size of bg graphic (x,y = 396,278 w=232 h=212)
|
|
|
|
Rect boxRect(20, 20, 205, 50);
|
|
title = new CTextBox("Enter address:", boxRect, 0, FONT_BIG, CENTER, Colors::WHITE);
|
|
|
|
address = new CTextInput(Rect(25, 68, 175, 16), *bg);
|
|
address->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
|
|
|
|
port = new CTextInput(Rect(25, 115, 175, 16), *bg);
|
|
port->cb += std::bind(&CSimpleJoinScreen::onChange, this, _1);
|
|
port->filters += std::bind(&CTextInput::numberFilter, _1, _2, 0, 65535);
|
|
|
|
ok = new CButton(Point( 26, 142), "MUBCHCK.DEF", CGI->generaltexth->zelp[560], std::bind(&CSimpleJoinScreen::enterSelectionScreen, this, mode), SDLK_RETURN);
|
|
cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE);
|
|
bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0));
|
|
|
|
port->setText(CServerHandler::getDefaultPortStr(), true);
|
|
address->setText(settings["server"]["server"].String(), true);
|
|
address->giveFocus();
|
|
}
|
|
|
|
void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EGameMode mode)
|
|
{
|
|
std::string textAddress = address->text;
|
|
std::string textPort = port->text;
|
|
|
|
GH.popIntTotally(this);
|
|
|
|
GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, boost::lexical_cast<ui16>(textPort)));
|
|
}
|
|
void CSimpleJoinScreen::onChange(const std::string & newText)
|
|
{
|
|
ok->block(address->text.empty() || port->text.empty());
|
|
}
|
|
|