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

Merge branch 'develop' of https://github.com/vcmi/vcmi into develop

This commit is contained in:
DJWarmonger 2017-06-10 20:06:05 +02:00
commit e901fcd293
82 changed files with 1315 additions and 829 deletions

View File

@ -36,9 +36,6 @@ matrix:
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.6 REAL_CXX=clang++-3.6 PACKAGE=clang-3.6 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.5 REAL_CXX=clang++-3.5 PACKAGE=clang-3.5 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
- os: linux
compiler: clang
env: VCMI_PLATFORM='linux' REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0'
@ -57,14 +54,15 @@ addons:
name: vcmi/vcmi
description: Build submitted via Travis CI
notification_email: coverity@arseniyshestakov.com
build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=1 -DENABLE_TEST=0"
build_command: ninja -j 2
build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0 -DENABLE_TEST=0"
build_command: ninja -j 3
branch_pattern: coverity_scan
notifications:
email:
recipients:
- vcmi.fail@mixaill.tk
- saven.ivan@gmail.com
- noreply@vcmi.eu
on_success: change
on_failure: always
slack:
secure: "KHXFe14FFKtw5mErWbj730+utqy7i/3AUobWfAMAGvWI5sJYlhbBU+KvvCoD2SlRQg3mQqgwVw8NBJF1Mffs7WcRmrFFFmuMqZxFLAfKBd3T0CxWpAGfnfNgDmlfV4OfEgQWk1pakEPOymhxbbmLUuCjykZDuTcioxAk0UAHDwY="

View File

@ -186,16 +186,15 @@ void CBattleAI::attemptCastingSpell()
if(!hero)
return;
if(!cb->battleCanCastSpell())
if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK)
return;
LOGL("Casting spells sounds like fun. Let's see...");
//Get all spells we can cast
std::vector<const CSpell*> possibleSpells;
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool
vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool
{
auto problem = getCbc()->battleCanCastThisSpell(s);
return problem == ESpellCastProblem::OK;
return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK;
});
LOGFL("I can cast %d spells.", possibleSpells.size());

View File

@ -1,3 +1,8 @@
0.99 -> 1.00
GENERAL:
* Spectator mode was implemented through command-line options
0.98 -> 0.99
GENERAL:

View File

@ -143,6 +143,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#include <unordered_map>
#include <utility>
#include <vector>
#include <atomic>
//The only available version is 3, as of Boost 1.50
#include <boost/version.hpp>
@ -184,6 +185,9 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size.");
#include <boost/variant.hpp>
#include <boost/math/special_functions/round.hpp>
#include <boost/multi_array.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#ifndef M_PI
# define M_PI 3.14159265358979323846

View File

@ -91,7 +91,6 @@ SDL_Surface *screen = nullptr, //main screen surface
std::queue<SDL_Event> events;
boost::mutex eventsM;
bool gNoGUI = false;
CondSh<bool> serverAlive(false);
static po::variables_map vm;
@ -114,6 +113,29 @@ void endGame();
#include <getopt.h>
#endif
void startTestMap(const std::string &mapname)
{
StartInfo si;
si.mapname = mapname;
si.mode = StartInfo::NEW_GAME;
for (int i = 0; i < 8; i++)
{
PlayerSettings &pset = si.playerInfos[PlayerColor(i)];
pset.color = PlayerColor(i);
pset.name = CGI->generaltexth->allTexts[468];//Computer
pset.playerID = PlayerSettings::PLAYER_AI;
pset.compOnly = true;
pset.castle = 0;
pset.hero = -1;
pset.heroPortrait = -1;
pset.handicap = PlayerSettings::NO_HANDICAP;
}
while(GH.topInt())
GH.popIntTotally(GH.topInt());
startGame(&si);
}
void startGameFromFile(const bfs::path &fname)
{
StartInfo si;
@ -150,7 +172,7 @@ void init()
logGlobal->infoStream()<<"Initializing VCMI_Lib: "<<tmh.getDiff();
if(!gNoGUI)
if(!settings["session"]["headless"].Bool())
{
pomtime.getDiff();
CCS->curh = new CCursorHandler;
@ -238,10 +260,19 @@ int main(int argc, char** argv)
opts.add_options()
("help,h", "display help and exit")
("version,v", "display version information and exit")
("disable-shm", "force disable shared memory usage")
("enable-shm-uuid", "use UUID for shared memory identifier")
("battle,b", po::value<std::string>(), "runs game in duel mode (battle-only")
("start", po::value<bfs::path>(), "starts game from saved StartInfo file")
("testmap", po::value<std::string>(), "")
("spectate,s", "enable spectator interface for AI-only games")
("spectate-ignore-hero", "wont follow heroes on adventure map")
("spectate-hero-speed", po::value<int>(), "hero movement speed on adventure map")
("spectate-battle-speed", po::value<int>(), "battle animation speed for spectator")
("spectate-skip-battle", "skip battles in spectator view")
("spectate-skip-battle-result", "skip battle result window")
("onlyAI", "runs without human player, all players will be default AI")
("noGUI", "runs without GUI, implies --onlyAI")
("headless", "runs without GUI, implies --onlyAI")
("ai", po::value<std::vector<std::string>>(), "AI to be used for the player, can be specified several times for the consecutive players")
("oneGoodAI", "puts one default AI and the rest will be EmptyAI")
("autoSkip", "automatically skip turns in GUI")
@ -254,9 +285,9 @@ int main(int argc, char** argv)
("loadplayer", po::value<int>(),"specifies which player we are in multiplayer loaded games (0=Red, etc.)")
("loadserverip",po::value<std::string>(),"IP for loaded game server")
("loadserverport",po::value<std::string>(),"port for loaded game server")
("testingport",po::value<std::string>(),"port for testing, override specified in config file")
("testingfileprefix",po::value<std::string>(),"prefix for auto save files")
("testingsavefrequency",po::value<int>(),"how often auto save should be created");
("serverport", po::value<si64>(), "override port specified in config file")
("saveprefix", po::value<std::string>(), "prefix for auto save files")
("savefrequency", po::value<si64>(), "limit auto save creation to each N days");
if(argc > 1)
{
@ -281,11 +312,6 @@ int main(int argc, char** argv)
prog_version();
return 0;
}
if(vm.count("noGUI"))
{
gNoGUI = true;
vm.insert(std::pair<std::string, po::variable_value>("onlyAI", po::variable_value()));
}
if(vm.count("donotstartserver"))
{
CServerHandler::DO_NOT_START_SERVER = true;
@ -314,16 +340,21 @@ int main(int argc, char** argv)
// Init filesystem and settings
preinitDLL(::console);
settings.init();
Settings session = settings.write["session"];
session["onlyai"].Bool() = vm.count("onlyAI");
if(vm.count("headless"))
{
session["headless"].Bool() = true;
session["onlyai"].Bool() = true;
}
// Shared memory options
session["disable-shm"].Bool() = vm.count("disable-shm");
session["enable-shm-uuid"].Bool() = vm.count("enable-shm-uuid");
// Init special testing settings
Settings testingSettings = settings.write["testing"];
if(vm.count("testingport") && vm.count("testingfileprefix"))
{
testingSettings["enabled"].Bool() = true;
testingSettings["port"].String() = vm["testingport"].as<std::string>();
testingSettings["prefix"].String() = vm["testingfileprefix"].as<std::string>();
testingSettings["savefrequency"].Float() = vm.count("testingsavefrequency") ? vm["testingsavefrequency"].as<int>() : 1;
}
session["serverport"].Integer() = vm.count("serverport") ? vm["serverport"].as<si64>() : 0;
session["saveprefix"].String() = vm.count("saveprefix") ? vm["saveprefix"].as<std::string>() : "";
session["savefrequency"].Integer() = vm.count("savefrequency") ? vm["savefrequency"].as<si64>() : 1;
// Initialize logging based on settings
logConfig.configure();
@ -368,7 +399,7 @@ int main(int argc, char** argv)
exit(EXIT_FAILURE);
}
if(!gNoGUI)
if(!settings["session"]["headless"].Bool())
{
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
{
@ -431,7 +462,7 @@ int main(int argc, char** argv)
#ifdef DISABLE_VIDEO
CCS->videoh = new CEmptyVideoPlayer;
#else
if (!gNoGUI && !vm.count("disable-video"))
if (!settings["session"]["headless"].Bool() && !vm.count("disable-video"))
CCS->videoh = new CVideoPlayer;
else
CCS->videoh = new CEmptyVideoPlayer;
@ -460,7 +491,7 @@ int main(int argc, char** argv)
init();
#endif
if(!gNoGUI )
if(!settings["session"]["headless"].Bool())
{
if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
playIntro();
@ -484,7 +515,6 @@ int main(int argc, char** argv)
if(!vm.count("battle"))
{
Settings session = settings.write["session"];
session["autoSkip"].Bool() = vm.count("autoSkip");
session["oneGoodAI"].Bool() = vm.count("oneGoodAI");
session["aiSolo"].Bool() = false;
@ -492,17 +522,39 @@ int main(int argc, char** argv)
bfs::path fileToStartFrom; //none by default
if(vm.count("start"))
fileToStartFrom = vm["start"].as<bfs::path>();
if(vm.count("testmap"))
{
session["testmap"].String() = vm["testmap"].as<std::string>();
}
if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
session["spectate"].Bool() = vm.count("spectate");
if(session["spectate"].Bool())
{
session["spectate-ignore-hero"].Bool() = vm.count("spectate-ignore-hero");
session["spectate-skip-battle"].Bool() = vm.count("spectate-skip-battle");
session["spectate-skip-battle-result"].Bool() = vm.count("spectate-skip-battle-result");
if(vm.count("spectate-hero-speed"))
session["spectate-hero-speed"].Integer() = vm["spectate-hero-speed"].as<int>();
if(vm.count("spectate-battle-speed"))
session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as<int>();
}
if(!session["testmap"].isNull())
{
startTestMap(session["testmap"].String());
}
else
{
if(!fileToStartFrom.empty())
if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom))
startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file
else
{
logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom
<< "). Falling back to main menu.";
if(!fileToStartFrom.empty())
{
logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom
<< "). Falling back to main menu.";
}
GH.curInt = CGPreGame::create(); //will set CGP pointer to itself
}
GH.curInt = CGPreGame::create(); //will set CGP pointer to itself
}
}
else
@ -515,7 +567,7 @@ int main(int argc, char** argv)
startGame(si);
}
if(!gNoGUI)
if(!settings["session"]["headless"].Bool())
{
mainLoop();
}
@ -558,6 +610,20 @@ void printInfoAboutIntObject(const CIntObject *obj, int level)
printInfoAboutIntObject(child, level+1);
}
void removeGUI()
{
// CClient::endGame
GH.curInt = nullptr;
if(GH.topInt())
GH.topInt()->deactivate();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = nullptr;
logGlobal->infoStream() << "Removed GUI.";
LOCPLINT = nullptr;
};
void processCommand(const std::string &message)
{
std::istringstream readed;
@ -678,10 +744,6 @@ void processCommand(const std::string &message)
*ptr = 666;
//disaster!
}
else if(cn == "onlyai")
{
vm.insert(std::pair<std::string, po::variable_value>("onlyAI", po::variable_value()));
}
else if(cn == "mp" && adventureInt)
{
if(const CGHeroInstance *h = dynamic_cast<const CGHeroInstance *>(adventureInt->selection))
@ -817,20 +879,6 @@ void processCommand(const std::string &message)
}
}
auto removeGUI = [&]()
{
// CClient::endGame
GH.curInt = nullptr;
if(GH.topInt())
GH.topInt()->deactivate();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = nullptr;
logNetwork->infoStream() << "Removed GUI.";
LOCPLINT = nullptr;
};
auto giveTurn = [&](PlayerColor player)
{
YourTurn yt;
@ -1273,10 +1321,13 @@ static void mainLoop()
void startGame(StartInfo * options, CConnection *serv/* = nullptr*/)
{
serverAlive.waitWhileTrue();
serverAlive.setn(true);
if(!CServerHandler::DO_NOT_START_SERVER)
{
serverAlive.waitWhileTrue();
serverAlive.setn(true);
}
if(vm.count("onlyAI"))
if(settings["session"]["onlyai"].Bool())
{
auto ais = vm.count("ai") ? vm["ai"].as<std::vector<std::string>>() : std::vector<std::string>();
@ -1306,7 +1357,7 @@ void startGame(StartInfo * options, CConnection *serv/* = nullptr*/)
if(!vm.count("loadplayer"))
client->loadGame(fname);
else
client->loadGame(fname,vm.count("loadserver"),vm.count("loadhumanplayerindices") ? vm["loadhumanplayerindices"].as<std::vector<int>>() : std::vector<int>(),vm.count("loadnumplayers") ? vm["loadnumplayers"].as<int>() : 1,vm["loadplayer"].as<int>(),vm.count("loadserverip") ? vm["loadserverip"].as<std::string>() : "", vm.count("loadserverport") ? vm["loadserverport"].as<std::string>() : "3030");
client->loadGame(fname,vm.count("loadserver"),vm.count("loadhumanplayerindices") ? vm["loadhumanplayerindices"].as<std::vector<int>>() : std::vector<int>(),vm.count("loadnumplayers") ? vm["loadnumplayers"].as<int>() : 1,vm["loadplayer"].as<int>(),vm.count("loadserverip") ? vm["loadserverip"].as<std::string>() : "", vm.count("loadserverport") ? vm["loadserverport"].as<ui16>() : CServerHandler::getDefaultPort());
break;
}
@ -1328,7 +1379,7 @@ void handleQuit(bool ask/* = true*/)
dispose();
vstd::clear_pointer(console);
boost::this_thread::sleep(boost::posix_time::milliseconds(750));
if(!gNoGUI)
if(!settings["session"]["headless"].Bool())
{
cleanupRenderer();
SDL_Quit();

View File

@ -11,9 +11,7 @@ extern SDL_Surface *screen; // main screen surface
extern SDL_Surface *screen2; // and hlp surface (used to store not-active interfaces layer)
extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
extern bool gNoGUI; //if true there is no client window and game is silently played between AIs
extern CondSh<bool> serverAlive; //used to prevent game start from executing if server is already running
void removeGUI();
void handleQuit(bool ask = true);

View File

@ -296,6 +296,8 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play
void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x, int y)
{
if(playerColor.isSpectator())
playerColor = PlayerColor(1);
std::vector<const IImage*> &box = piecesOfBox.at(playerColor.getNum());
// Note: this code assumes that the corner dimensions are all the same.

View File

@ -171,12 +171,7 @@ void CPlayerInterface::yourTurn()
GH.curInt = this;
adventureInt->selection = nullptr;
std::string prefix = "";
if (settings["testing"]["enabled"].Bool())
{
prefix = settings["testing"]["prefix"].String();
}
std::string prefix = settings["session"]["saveprefix"].String();
if (firstCall)
{
if (howManyPeople == 1)
@ -192,7 +187,7 @@ void CPlayerInterface::yourTurn()
}
firstCall = 0;
}
else if (settings["testing"].isNull() || cb->getDate() % static_cast<int>(settings["testing"]["savefrequency"].Float()) == 0)
else if (cb->getDate() % static_cast<int>(settings["session"]["savefrequency"].Integer()) == 0)
{
LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + boost::lexical_cast<std::string>(autosaveCount++ + 1));
autosaveCount %= 5;
@ -250,6 +245,9 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
if (LOCPLINT != this)
return;
if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-ignore-hero"].Bool())
return;
const CGHeroInstance * hero = cb->getHero(details.id); //object representing this hero
int3 hp = details.start;
@ -326,7 +324,12 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
}
ui32 speed;
if (makingTurn) // our turn, our hero moves
if(settings["session"]["spectate"].Bool())
{
if(!settings["session"]["spectate-hero-speed"].isNull())
speed = settings["session"]["spectate-hero-speed"].Integer();
}
else if (makingTurn) // our turn, our hero moves
speed = settings["adventure"]["heroSpeed"].Float();
else
speed = settings["adventure"]["enemySpeed"].Float();
@ -339,7 +342,6 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details)
return; // no animation
}
adventureInt->centerOn(hero); //actualizing screen pos
adventureInt->minimap.redraw();
adventureInt->heroList.redraw();
@ -476,6 +478,14 @@ int3 CPlayerInterface::repairScreenPos(int3 pos)
pos.y = CGI->mh->sizes.y - adventureInt->terrain.tilesh + CGI->mh->frameH;
return pos;
}
void CPlayerInterface::activateForSpectator()
{
adventureInt->state = CAdvMapInt::INGAME;
adventureInt->activate();
adventureInt->minimap.activate();
}
void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val)
{
EVENT_HANDLER_CALLED_BY_CLIENT;
@ -1642,7 +1652,8 @@ void CPlayerInterface::update()
}
//in some conditions we may receive calls before selection is initialized - we must ignore them
if (adventureInt && !adventureInt->selection && GH.topInt() == adventureInt)
if(adventureInt && GH.topInt() == adventureInt
&& (!adventureInt->selection && !settings["session"]["spectate"].Bool()))
{
return;
}
@ -2136,7 +2147,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
--howManyPeople;
if (howManyPeople == 0) //all human players eliminated
if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
{
if (adventureInt)
{
@ -2157,7 +2168,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
}
else
{
if (howManyPeople == 0) //all human players eliminated
if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated
{
requestReturningToMainMenu();
}

View File

@ -236,6 +236,7 @@ public:
void updateInfo(const CGObjectInstance * specific);
void init(std::shared_ptr<CCallback> CB) override;
int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on
void activateForSpectator(); // TODO: spectator probably need own player interface class
// show dialogs
void showInfoDialog(const std::string &text, CComponent * component);

View File

@ -83,7 +83,7 @@ struct EvilHlpStruct
void reset()
{
vstd::clear_pointer(serv);
// vstd::clear_pointer(serv);
vstd::clear_pointer(sInfo);
}
@ -131,10 +131,10 @@ void setPlayer(PlayerSettings &pset, ui8 player, const std::map<ui8, std::string
playerColor = pset.color;
}
void updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader, const std::map<ui8, std::string> &playerNames)
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)
if(!mapHeader.get())
{
return;
}
@ -559,7 +559,7 @@ void CGPreGame::removeFromGui()
GH.popInt(GH.topInt()); //remove background
}
CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/, const std::map<ui8, std::string> * Names /*= nullptr*/, const std::string & Address /*=""*/, const std::string & Port /*= ""*/)
CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/, const std::map<ui8, std::string> * Names /*= nullptr*/, const std::string & Address /*=""*/, const ui16 Port)
: ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex),
serv(nullptr), ongoingClosing(false), myNameID(255)
{
@ -799,7 +799,12 @@ void CSelectionScreen::changeSelection(const CMapInfo * to)
SEL->sInfo.difficulty = to->scenarioOpts->difficulty;
if(screenType != CMenuScreen::campaignList)
{
updateStartInfo(to ? to->fileURI : "", sInfo, to ? to->mapHeader.get() : nullptr);
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)
@ -3219,7 +3224,7 @@ void CHotSeatPlayers::enterSelectionScreen()
void CBonusSelection::init()
{
highlightedRegion = nullptr;
ourHeader = nullptr;
ourHeader.reset();
diffLb = nullptr;
diffRb = nullptr;
bonuses = nullptr;
@ -3342,7 +3347,6 @@ CBonusSelection::CBonusSelection(const std::string & campaignFName)
CBonusSelection::~CBonusSelection()
{
SDL_FreeSurface(background);
delete ourHeader;
sFlags->unload();
}
@ -3423,10 +3427,9 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect)
scenarioName += ':' + boost::lexical_cast<std::string>(selectedMap);
//get header
delete ourHeader;
std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second;
auto buffer = reinterpret_cast<const ui8 *>(headerStr.data());
ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName).release();
ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName);
std::map<ui8, std::string> names;
names[1] = settings["general"]["playerName"].String();
@ -3922,7 +3925,7 @@ ISelectionScreenInfo::~ISelectionScreenInfo()
SEL = nullptr;
}
void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader)
void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader)
{
::updateStartInfo(filename, sInfo, mapHeader, playerNames);
}
@ -4320,7 +4323,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EMultiMode mode)
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(boost::lexical_cast<std::string>(settings["server"]["port"].Float()), true);
port->setText(CServerHandler::getDefaultPortStr(), true);
address->setText(settings["server"]["server"].String(), true);
address->giveFocus();
}
@ -4332,7 +4335,7 @@ void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EMultiMode mode)
GH.popIntTotally(this);
GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, textPort));
GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, boost::lexical_cast<ui16>(textPort)));
}
void CSimpleJoinScreen::onChange(const std::string & newText)
{

View File

@ -341,7 +341,7 @@ public:
virtual void postChatMessage(const std::string &txt){};
void setPlayer(PlayerSettings &pset, ui8 player);
void updateStartInfo( std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader );
void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr<CMapHeader> & mapHeader);
ui8 getIdOfFirstUnallocatedPlayer(); //returns 0 if none
bool isGuest() const;
@ -371,7 +371,7 @@ public:
bool ongoingClosing;
ui8 myNameID; //used when networking - otherwise all player are "mine"
CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer = CMenuScreen::SINGLE_PLAYER, const std::map<ui8, std::string> * Names = nullptr, const std::string & Address = "", const std::string & Port = "");
CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer = CMenuScreen::SINGLE_PLAYER, const std::map<ui8, std::string> * Names = nullptr, const std::string & Address = "", const ui16 Port = 0);
~CSelectionScreen();
void toggleTab(CIntObject *tab);
void changeSelection(const CMapInfo *to);
@ -539,7 +539,7 @@ private:
int selectedMap;
boost::optional<int> selectedBonus;
StartInfo startInfo;
CMapHeader * ourHeader;
std::unique_ptr<CMapHeader> ourHeader;
};
/// Campaign selection screen

View File

@ -41,9 +41,7 @@
#include "CMT.h"
extern std::string NAME;
#ifndef VCMI_ANDROID
namespace intpr = boost::interprocess;
#else
#ifdef VCMI_ANDROID
#include "lib/CAndroidVMHelper.h"
#endif
@ -251,24 +249,16 @@ void CClient::endGame(bool closeConnection /*= true*/)
}
#if 1
void CClient::loadGame(const std::string & fname, const bool server, const std::vector<int>& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const std::string & port)
void CClient::loadGame(const std::string & fname, const bool server, const std::vector<int>& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const ui16 port)
{
PlayerColor player(player_); //intentional shadowing
logNetwork->infoStream() << "Loading procedure started!";
std::string realPort;
if(settings["testing"]["enabled"].Bool())
realPort = settings["testing"]["port"].String();
else if(port.size())
realPort = port;
else
realPort = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
CServerHandler sh;
if(server)
sh.startServer();
else
serv = sh.justConnectToServer(ipaddr, realPort);
serv = sh.justConnectToServer(ipaddr, port);
CStopWatch tmh;
std::unique_ptr<CLoadFile> loader;
@ -391,7 +381,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
else
{
serv = con;
networkMode = (con->connectionID == 1) ? HOST : GUEST;
networkMode = con->isHost() ? HOST : GUEST;
}
CConnection &c = *serv;
@ -416,10 +406,9 @@ void CClient::newGame( CConnection *con, StartInfo *si )
// Initialize game state
gs = new CGameState();
logNetwork->infoStream() <<"\tCreating gamestate: "<<tmh.getDiff();
logNetwork->info("\tCreating gamestate: %i",tmh.getDiff());
gs->scenarioOps = si;
gs->init(si);
gs->init(si, settings["general"]["saveRandomMaps"].Bool());
logNetwork->infoStream() <<"Initializing GameState (together): "<<tmh.getDiff();
// Now after possible random map gen, we know exact player count.
@ -441,10 +430,13 @@ void CClient::newGame( CConnection *con, StartInfo *si )
// Init map handler
if(gs->map)
{
const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
CGI->mh->map = gs->map;
logNetwork->infoStream() << "Creating mapHandler: " << tmh.getDiff();
CGI->mh->init();
if(!settings["session"]["headless"].Bool())
{
const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
CGI->mh->map = gs->map;
logNetwork->infoStream() << "Creating mapHandler: " << tmh.getDiff();
CGI->mh->init();
}
pathInfo = make_unique<CPathsInfo>(getMapSize());
logNetwork->infoStream() << "Initializing mapHandler (together): " << tmh.getDiff();
}
@ -481,7 +473,7 @@ void CClient::newGame( CConnection *con, StartInfo *si )
if(si->mode == StartInfo::DUEL)
{
if(!gNoGUI)
if(!settings["session"]["headless"].Bool())
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
auto p = std::make_shared<CPlayerInterface>(PlayerColor::NEUTRAL);
@ -493,6 +485,10 @@ void CClient::newGame( CConnection *con, StartInfo *si )
}
else
{
if(settings["session"]["spectate"].Bool())
{
installNewPlayerInterface(std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true);
}
loadNeutralBattleAI();
}
@ -644,10 +640,33 @@ void CClient::serialize(BinaryDeserializer & h, const int version, const std::se
nInt->human = isHuman;
nInt->playerID = pid;
if(playerIDs.count(pid))
installNewPlayerInterface(nInt, pid);
nInt->loadGame(h, version);
if(settings["session"]["onlyai"].Bool() && isHuman)
{
removeGUI();
nInt.reset();
dllname = aiNameForPlayer(false);
nInt = CDynLibHandler::getNewAI(dllname);
nInt->dllName = dllname;
nInt->human = false;
nInt->playerID = pid;
installNewPlayerInterface(nInt, pid);
GH.totalRedraw();
}
else
{
if(playerIDs.count(pid))
installNewPlayerInterface(nInt, pid);
}
}
if(settings["session"]["spectate"].Bool())
{
removeGUI();
auto p = std::make_shared<CPlayerInterface>(PlayerColor::SPECTATOR);
installNewPlayerInterface(p, PlayerColor::SPECTATOR, true);
GH.curInt = p.get();
LOCPLINT->activateForSpectator();
GH.totalRedraw();
}
if(playerIDs.count(PlayerColor::NEUTRAL))
@ -693,13 +712,22 @@ void CClient::stopConnection()
{
terminate = true;
if (serv) //request closing connection
if(serv)
{
logNetwork->infoStream() << "Connection has been requested to be closed.";
boost::unique_lock<boost::mutex>(*serv->wmx);
CloseServer close_server;
sendRequest(&close_server, PlayerColor::NEUTRAL);
logNetwork->infoStream() << "Sent closing signal to the server";
if(serv->isHost()) //request closing connection
{
logNetwork->infoStream() << "Connection has been requested to be closed.";
CloseServer close_server;
sendRequest(&close_server, PlayerColor::NEUTRAL);
logNetwork->infoStream() << "Sent closing signal to the server";
}
else
{
LeaveGame leave_Game;
sendRequest(&leave_Game, PlayerColor::NEUTRAL);
logNetwork->infoStream() << "Sent leaving signal to the server";
}
}
if(connectionHandler)//end connection handler
@ -708,16 +736,13 @@ void CClient::stopConnection()
connectionHandler->join();
logNetwork->infoStream() << "Connection handler thread joined";
delete connectionHandler;
connectionHandler = nullptr;
vstd::clear_pointer(connectionHandler);
}
if (serv) //and delete connection
{
serv->close();
delete serv;
serv = nullptr;
vstd::clear_pointer(serv);
logNetwork->warnStream() << "Our socket has been closed.";
}
}
@ -750,14 +775,29 @@ void CClient::battleStarted(const BattleInfo * info)
def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );
}
if(!gNoGUI && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL))
if(!settings["session"]["headless"].Bool())
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
Rect((screen->w - 800)/2,
(screen->h - 600)/2, 800, 600), att, def);
if(!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL)
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
Rect((screen->w - 800)/2,
(screen->h - 600)/2, 800, 600), att, def);
GH.pushInt(bi);
GH.pushInt(bi);
}
else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
{
//TODO: This certainly need improvement
auto spectratorInt = std::dynamic_pointer_cast<CPlayerInterface>(playerint[PlayerColor::SPECTATOR]);
spectratorInt->cb->setBattle(info);
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
Rect((screen->w - 800)/2,
(screen->h - 600)/2, 800, 600), att, def, spectratorInt);
GH.pushInt(bi);
}
}
auto callBattleStart = [&](PlayerColor color, ui8 side){
@ -768,6 +808,8 @@ void CClient::battleStarted(const BattleInfo * info)
callBattleStart(leftSide.color, 0);
callBattleStart(rightSide.color, 1);
callBattleStart(PlayerColor::UNFLAGGABLE, 1);
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
callBattleStart(PlayerColor::SPECTATOR, 1);
if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))
{
@ -780,6 +822,9 @@ void CClient::battleFinished()
for(auto & side : gs->curB->sides)
if(battleCallbacks.count(side.color))
battleCallbacks[side.color]->setBattle(nullptr);
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr);
}
void CClient::loadNeutralBattleAI()
@ -877,7 +922,7 @@ void CClient::campaignMapFinished( std::shared_ptr<CCampaignState> camp )
}
}
void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color)
void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb)
{
boost::unique_lock<boost::recursive_mutex> un(*CPlayerInterface::pim);
PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
@ -893,7 +938,7 @@ void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInte
battleCallbacks[colorUsed] = cb;
gameInterface->init(cb);
installNewBattleInterface(gameInterface, color, false);
installNewBattleInterface(gameInterface, color, battlecb);
}
void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback /*= true*/)
@ -924,6 +969,11 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
return ps.name;
}
return aiNameForPlayer(battleAI);
}
std::string CClient::aiNameForPlayer(bool battleAI)
{
const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;
std::string goodAI = battleAI ? settings["server"]["neutralAI"].String() : settings["server"]["playerAI"].String();
std::string badAI = battleAI ? "StupidAI" : "EmptyAI";
@ -966,11 +1016,8 @@ void CServerHandler::waitForServer()
th.update();
#ifndef VCMI_ANDROID
intpr::scoped_lock<intpr::interprocess_mutex> slock(shared->sr->mutex);
while(!shared->sr->ready)
{
shared->sr->cond.wait(slock);
}
if(shared)
shared->sr->waitTillReady();
#else
logNetwork->infoStream() << "waiting for server";
while (!androidTestServerReadyFlag.load())
@ -987,16 +1034,15 @@ void CServerHandler::waitForServer()
CConnection * CServerHandler::connectToServer()
{
#ifndef VCMI_ANDROID
if(!shared->sr->ready)
waitForServer();
#else
waitForServer();
#endif
th.update(); //put breakpoint here to attach to server before it does something stupid
CConnection *ret = justConnectToServer(settings["server"]["server"].String(), port);
#ifndef VCMI_ANDROID
CConnection *ret = justConnectToServer(settings["server"]["server"].String(), shared ? shared->sr->port : 0);
#else
CConnection *ret = justConnectToServer(settings["server"]["server"].String());
#endif
if(verbose)
logNetwork->infoStream()<<"\tConnecting to the server: "<<th.getDiff();
@ -1004,24 +1050,43 @@ CConnection * CServerHandler::connectToServer()
return ret;
}
ui16 CServerHandler::getDefaultPort()
{
if(settings["session"]["serverport"].Integer())
return settings["session"]["serverport"].Integer();
else
return settings["server"]["port"].Integer();
}
std::string CServerHandler::getDefaultPortStr()
{
return boost::lexical_cast<std::string>(getDefaultPort());
}
CServerHandler::CServerHandler(bool runServer /*= false*/)
{
serverThread = nullptr;
shared = nullptr;
if(settings["testing"]["enabled"].Bool())
port = settings["testing"]["port"].String();
else
port = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
verbose = true;
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
#ifndef VCMI_ANDROID
boost::interprocess::shared_memory_object::remove("vcmi_memory"); //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
if(DO_NOT_START_SERVER || settings["session"]["disable-shm"].Bool())
return;
std::string sharedMemoryName = "vcmi_memory";
if(settings["session"]["enable-shm-uuid"].Bool())
{
//used or automated testing when multiple clients start simultaneously
sharedMemoryName += "_" + uuid;
}
try
{
shared = new SharedMem();
shared = new SharedMemory(sharedMemoryName, true);
}
catch(...)
{
vstd::clear_pointer(shared);
logNetwork->error("Cannot open interprocess memory.");
handleException();
throw;
@ -1040,7 +1105,18 @@ void CServerHandler::callServer()
#ifndef VCMI_ANDROID
setThreadName("CServerHandler::callServer");
const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
const std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + port + " > \"" + logName + '\"';
std::string comm = VCMIDirs::get().serverPath().string()
+ " --port=" + getDefaultPortStr()
+ " --run-by-client"
+ " --uuid=" + uuid;
if(shared)
{
comm += " --enable-shm";
if(settings["session"]["enable-shm-uuid"].Bool())
comm += " --enable-shm-uuid";
}
comm += " > \"" + logName + '\"';
int result = std::system(comm.c_str());
if (result == 0)
{
@ -1056,16 +1132,8 @@ void CServerHandler::callServer()
#endif
}
CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port)
CConnection * CServerHandler::justConnectToServer(const std::string &host, const ui16 port)
{
std::string realPort;
if(settings["testing"]["enabled"].Bool())
realPort = settings["testing"]["port"].String();
else if(port.size())
realPort = port;
else
realPort = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
CConnection *ret = nullptr;
while(!ret)
{
@ -1073,8 +1141,9 @@ CConnection * CServerHandler::justConnectToServer(const std::string &host, const
{
logNetwork->infoStream() << "Establishing connection...";
ret = new CConnection( host.size() ? host : settings["server"]["server"].String(),
realPort,
port ? port : getDefaultPort(),
NAME);
ret->connectionID = 1; // TODO: Refactoring for the server so IDs set outside of CConnection
}
catch(...)
{

View File

@ -28,7 +28,7 @@ class CGameInterface;
class CConnection;
class CCallback;
struct BattleAction;
struct SharedMem;
struct SharedMemory;
class CClient;
class CScriptingModule;
struct CPathsInfo;
@ -46,9 +46,9 @@ public:
CStopWatch th;
boost::thread *serverThread; //thread that called system to run server
SharedMem *shared; //interprocess memory (for waiting for server)
SharedMemory * shared;
std::string uuid;
bool verbose; //whether to print log msgs
std::string port; //port number in text form
//functions setting up local server
void startServer(); //creates a thread with callServer
@ -56,7 +56,9 @@ public:
CConnection * connectToServer(); //connects to server
//////////////////////////////////////////////////////////////////////////
static CConnection * justConnectToServer(const std::string &host = "", const std::string &port = ""); //connects to given host without taking any other actions (like setting up server)
static CConnection * justConnectToServer(const std::string &host = "", const ui16 port = 0); //connects to given host without taking any other actions (like setting up server)
static ui16 getDefaultPort();
static std::string getDefaultPortStr();
CServerHandler(bool runServer = false);
virtual ~CServerHandler();
@ -147,14 +149,15 @@ public:
void newGame(CConnection *con, StartInfo *si); //con - connection to server
void loadNeutralBattleAI();
void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color);
void installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color, bool battlecb = false);
void installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback = true);
std::string aiNameForPlayer(const PlayerSettings &ps, bool battleAI); //empty means no AI -> human
std::string aiNameForPlayer(bool battleAI);
void endGame(bool closeConnection = true);
void stopConnection();
void save(const std::string & fname);
void loadGame(const std::string & fname, const bool server = true, const std::vector<int>& humanplayerindices = std::vector<int>(), const int loadnumplayers = 1, int player_ = -1, const std::string & ipaddr = "", const std::string & port = "");
void loadGame(const std::string & fname, const bool server = true, const std::vector<int>& humanplayerindices = std::vector<int>(), const int loadnumplayers = 1, int player_ = -1, const std::string & ipaddr = "", const ui16 port = 0);
void run();
void campaignMapFinished( std::shared_ptr<CCampaignState> camp );
void finishCampaign( std::shared_ptr<CCampaignState> camp );

View File

@ -96,6 +96,10 @@
#define BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(function,...) \
CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0].color, function, __VA_ARGS__) \
CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1].color, function, __VA_ARGS__) \
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool() && LOCPLINT->battleInt) \
{ \
CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, function, __VA_ARGS__) \
} \
BATTLE_INTERFACE_CALL_RECEIVERS(function, __VA_ARGS__)
/*
* NetPacksClient.cpp, part of VCMI engine
@ -283,13 +287,13 @@ void GiveBonus::applyCl(CClient *cl)
void ChangeObjPos::applyFirstCl(CClient *cl)
{
CGObjectInstance *obj = GS(cl)->getObjInstance(objid);
if(flags & 1)
if(flags & 1 && CGI->mh)
CGI->mh->hideObject(obj);
}
void ChangeObjPos::applyCl(CClient *cl)
{
CGObjectInstance *obj = GS(cl)->getObjInstance(objid);
if(flags & 1)
if(flags & 1 && CGI->mh)
CGI->mh->printObject(obj);
cl->invalidatePaths();
@ -298,6 +302,10 @@ void ChangeObjPos::applyCl(CClient *cl)
void PlayerEndsGame::applyCl(CClient *cl)
{
CALL_IN_ALL_INTERFACES(gameOver, player, victoryLossCheckResult);
// In auto testing mode we always close client if red player won or lose
if(!settings["session"]["testmap"].isNull() && player == PlayerColor(0))
handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not
}
void RemoveBonus::applyCl(CClient *cl)
@ -335,7 +343,8 @@ void RemoveObject::applyFirstCl(CClient *cl)
{
const CGObjectInstance *o = cl->getObj(id);
CGI->mh->hideObject(o, true);
if(CGI->mh)
CGI->mh->hideObject(o, true);
//notify interfaces about removal
for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
@ -357,18 +366,20 @@ void TryMoveHero::applyFirstCl(CClient *cl)
//check if playerint will have the knowledge about movement - if not, directly update maphandler
for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
{
if(i->first >= PlayerColor::PLAYER_LIMIT)
continue;
TeamState *t = GS(cl)->getPlayerTeam(i->first);
if((t->fogOfWarMap[start.x-1][start.y][start.z] || t->fogOfWarMap[end.x-1][end.y][end.z])
&& GS(cl)->getPlayer(i->first)->human)
humanKnows = true;
auto ps = GS(cl)->getPlayer(i->first);
if(ps && (GS(cl)->isVisible(start - int3(1, 0, 0), i->first) || GS(cl)->isVisible(end - int3(1, 0, 0), i->first)))
{
if(ps->human)
humanKnows = true;
}
}
if(!CGI->mh)
return;
if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK || !humanKnows)
CGI->mh->hideObject(h, result == EMBARK && humanKnows);
if(result == DISEMBARK)
CGI->mh->printObject(h->boat);
}
@ -378,13 +389,14 @@ void TryMoveHero::applyCl(CClient *cl)
const CGHeroInstance *h = cl->getHero(id);
cl->invalidatePaths();
if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK)
if(CGI->mh)
{
CGI->mh->printObject(h, result == DISEMBARK);
}
if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK)
CGI->mh->printObject(h, result == DISEMBARK);
if(result == EMBARK)
CGI->mh->hideObject(h->boat);
if(result == EMBARK)
CGI->mh->hideObject(h->boat);
}
PlayerColor player = h->tempOwner;
@ -395,18 +407,17 @@ void TryMoveHero::applyCl(CClient *cl)
//notify interfaces about move
for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
{
if(i->first >= PlayerColor::PLAYER_LIMIT) continue;
TeamState *t = GS(cl)->getPlayerTeam(i->first);
if(t->fogOfWarMap[start.x-1][start.y][start.z] || t->fogOfWarMap[end.x-1][end.y][end.z])
if(GS(cl)->isVisible(start - int3(1, 0, 0), i->first)
|| GS(cl)->isVisible(end - int3(1, 0, 0), i->first))
{
i->second->heroMoved(*this);
}
}
if(!humanKnows) //maphandler didn't get update from playerint, do it now
{ //TODO: restructure nicely
//maphandler didn't get update from playerint, do it now
//TODO: restructure nicely
if(!humanKnows && CGI->mh)
CGI->mh->printObject(h);
}
}
void NewStructures::applyCl(CClient *cl)
@ -482,22 +493,22 @@ void HeroRecruited::applyCl(CClient *cl)
needsPrinting = false;
}
}
if (needsPrinting)
{
if(needsPrinting && CGI->mh)
CGI->mh->printObject(h);
}
}
void GiveHero::applyCl(CClient *cl)
{
CGHeroInstance *h = GS(cl)->getHero(id);
CGI->mh->printObject(h);
if(CGI->mh)
CGI->mh->printObject(h);
cl->playerint[h->tempOwner]->heroCreated(h);
}
void GiveHero::applyFirstCl(CClient *cl)
{
CGI->mh->hideObject(GS(cl)->getHero(id));
if(CGI->mh)
CGI->mh->hideObject(GS(cl)->getHero(id));
}
void InfoWindow::applyCl(CClient *cl)
@ -588,6 +599,8 @@ void BattleStart::applyFirstCl(CClient *cl)
info->tile, info->sides[0].hero, info->sides[1].hero);
CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[1].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
info->tile, info->sides[0].hero, info->sides[1].hero);
CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
info->tile, info->sides[0].hero, info->sides[1].hero);
BATTLE_INTERFACE_CALL_RECEIVERS(battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject,
info->tile, info->sides[0].hero, info->sides[1].hero);
}
@ -707,7 +720,7 @@ void BattleResultsApplied::applyCl(CClient *cl)
{
INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied);
INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied);
INTERFACE_CALL_IF_PRESENT(PlayerColor::UNFLAGGABLE, battleResultsApplied);
INTERFACE_CALL_IF_PRESENT(PlayerColor::SPECTATOR, battleResultsApplied);
if(GS(cl)->initialOpts->mode == StartInfo::DUEL)
{
handleQuit();
@ -808,7 +821,10 @@ void PlayerMessage::applyCl(CClient *cl)
logNetwork->debugStream() << "Player "<< player <<" sends a message: " << text;
std::ostringstream str;
str << cl->getPlayer(player)->nodeName() <<": " << text;
if(player.isSpectator())
str << "Spectator: " << text;
else
str << cl->getPlayer(player)->nodeName() <<": " << text;
if(LOCPLINT)
LOCPLINT->cingconsole->print(str.str());
}
@ -904,7 +920,8 @@ void NewObject::applyCl(CClient *cl)
cl->invalidatePaths();
const CGObjectInstance *obj = cl->getObj(id);
CGI->mh->printObject(obj, true);
if(CGI->mh)
CGI->mh->printObject(obj, true);
for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++)
{

View File

@ -292,6 +292,4 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -109,17 +109,10 @@
<Filter>gui</Filter>
</ClCompile>
<ClCompile Include="..\CCallback.cpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="CQuestLog.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="SDLRWwrapper.cpp" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="VCMI_client.rc" />
<ClInclude Include="CQuestLog.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="vcmi.ico" />
@ -254,5 +247,6 @@
<Filter>gui</Filter>
</ClInclude>
<ClInclude Include="..\CCallback.h" />
<ClInclude Include="SDLRWwrapper.h" />
</ItemGroup>
</Project>

View File

@ -95,7 +95,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation *anim)
CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2,
const CGHeroInstance *hero1, const CGHeroInstance *hero2,
const SDL_Rect & myRect,
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen)
std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt)
: background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0),
activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1),
currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr),
@ -105,12 +105,15 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
{
OBJ_CONSTRUCTION;
if (!curInt)
if(spectatorInt)
curInt = spectatorInt;
else if(!curInt)
{
//May happen when we are defending during network MP game -> attacker interface is just not present
curInt = defenderInt;
}
animsAreDisplayed.setn(false);
pos = myRect;
strongInterest = true;
@ -377,6 +380,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet
currentAction = INVALID;
selectedAction = INVALID;
addUsedEvents(RCLICK | MOVE | KEYBOARD);
blockUI(settings["session"]["spectate"].Bool());
}
CBattleInterface::~CBattleInterface()
@ -448,6 +453,7 @@ CBattleInterface::~CBattleInterface()
int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType;
CCS->musich->playMusicFromSet("terrain", terrain, true);
}
animsAreDisplayed.setn(false);
}
void CBattleInterface::setPrintCellBorders(bool set)
@ -871,19 +877,24 @@ void CBattleInterface::bSpellf()
if (spellDestSelectMode) //we are casting a spell
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
if (!myTurn)
return;
auto myHero = currentHero();
ESpellCastProblem::ESpellCastProblem spellCastProblem;
if (curInt->cb->battleCanCastSpell(&spellCastProblem))
if(!myHero)
return;
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING);
if(spellCastProblem == ESpellCastProblem::OK)
{
GH.pushInt(new CSpellWindow(myHero, curInt.get()));
}
else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED)
{
//TODO: move to spell mechanics, add more information to spell cast problem
//Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible
auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC));
if (!blockingBonus)
@ -1241,6 +1252,11 @@ void CBattleInterface::battleFinished(const BattleResult& br)
void CBattleInterface::displayBattleFinished()
{
CCS->curh->changeGraphic(ECursor::ADVENTURE,0);
if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool())
{
GH.popIntTotally(this);
return;
}
SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19);
resWindow = new CBattleResultWindow(*bresult, temp_rect, *this->curInt);
@ -1526,6 +1542,9 @@ void CBattleInterface::setAnimSpeed(int set)
int CBattleInterface::getAnimSpeed() const
{
if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull())
return vstd::round(settings["session"]["spectate-battle-speed"].Float() *100);
return vstd::round(settings["battle"]["animationSpeed"].Float() *100);
}
@ -1658,8 +1677,7 @@ void CBattleInterface::enterCreatureCastingMode()
{
const ISpellCaster *caster = activeStack;
const CSpell *spell = SpellID(creatureSpellToCast).toSpell();
const bool isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, spell, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK);
const bool isCastingPossible = (spell->canBeCastAt(curInt->cb.get(), caster, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK);
if (isCastingPossible)
{
@ -1849,11 +1867,17 @@ void CBattleInterface::showQueue()
void CBattleInterface::blockUI(bool on)
{
ESpellCastProblem::ESpellCastProblem spellcastingProblem;
bool canCastSpells = curInt->cb->battleCanCastSpell(&spellcastingProblem);
//if magic is blocked, we leave button active, so the message can be displayed (cf bug #97)
if (!canCastSpells)
canCastSpells = spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
bool canCastSpells = false;
auto hero = curInt->cb->battleGetMyHero();
if(hero)
{
ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
//if magic is blocked, we leave button active, so the message can be displayed after button click
canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED;
}
bool canWait = activeStack ? !activeStack->waited() : false;
bOptions->block(on);
@ -2511,7 +2535,7 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack
else
{
const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING;
isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, sp, mode, myNumber) == ESpellCastProblem::OK);
isCastingPossible = (sp->canBeCastAt(curInt->cb.get(), caster, mode, myNumber) == ESpellCastProblem::OK);
}
}
else

View File

@ -269,7 +269,7 @@ public:
ui32 animIDhelper; //for giving IDs for animations
static CondSh<bool> animsAreDisplayed; //for waiting with the end of battle for end of anims
CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen); //c-tor
CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr<CPlayerInterface> att, std::shared_ptr<CPlayerInterface> defen, std::shared_ptr<CPlayerInterface> spectatorInt = nullptr); //c-tor
virtual ~CBattleInterface(); //d-tor
//std::vector<TimeInterested*> timeinterested; //animation handling

View File

@ -191,7 +191,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState)
if(myOwner->spellDestSelectMode) //we are casting a spell
return;
if(myHero != nullptr && !down && myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell()) //check conditions
if(myHero != nullptr && !down && myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions
{
for(int it=0; it<GameConstants::BFIELD_SIZE; ++it) //do nothing when any hex is hovered - hero's animation overlaps battlefield
{
@ -211,14 +211,10 @@ void CBattleHero::clickRight(tribool down, bool previousState)
windowPosition.y = myOwner->pos.y + 135;
InfoAboutHero targetHero;
if (down && myOwner->myTurn)
if(down && (myOwner->myTurn || settings["session"]["spectate"].Bool()))
{
if (myHero != nullptr)
targetHero.initFromHero(myHero, InfoAboutHero::EInfoLevel::INBATTLE);
else
targetHero = myOwner->enemyHero();
auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance;
targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE);
GH.pushInt(new CHeroInfoWindow(targetHero, &windowPosition));
}
}

View File

@ -12,6 +12,7 @@
#include "../../lib/CConfigHandler.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../battle/CBattleInterface.h"
extern std::queue<SDL_Event> events;
extern boost::mutex eventsM;
@ -59,6 +60,7 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
{
processList(CIntObject::LCLICK,activityFlag,&lclickable,cb);
processList(CIntObject::RCLICK,activityFlag,&rclickable,cb);
processList(CIntObject::MCLICK,activityFlag,&mclickable,cb);
processList(CIntObject::HOVER,activityFlag,&hoverable,cb);
processList(CIntObject::MOVE,activityFlag,&motioninterested,cb);
processList(CIntObject::KEYBOARD,activityFlag,&keyinterested,cb);
@ -191,6 +193,46 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
if (sEvent->type==SDL_KEYDOWN || sEvent->type==SDL_KEYUP)
{
SDL_KeyboardEvent key = sEvent->key;
if(sEvent->type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool())
{
//TODO: we need some central place for all interface-independent hotkeys
Settings s = settings.write["session"];
switch(key.keysym.sym)
{
case SDLK_F5:
if(settings["session"]["spectate-locked-pim"].Bool())
LOCPLINT->pim->unlock();
else
LOCPLINT->pim->lock();
s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool();
break;
case SDLK_F6:
s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool();
break;
case SDLK_F7:
s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool();
break;
case SDLK_F8:
s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool();
break;
case SDLK_F9:
//not working yet since CClient::run remain locked after CBattleInterface removal
if(LOCPLINT->battleInt)
{
GH.popIntTotally(GH.topInt());
vstd::clear_pointer(LOCPLINT->battleInt);
}
break;
default:
break;
}
return;
}
//translate numpad keys
if(key.keysym.sym == SDLK_KP_ENTER)
@ -219,18 +261,18 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
CCS->curh->cursorMove(sEvent->motion.x, sEvent->motion.y);
handleMouseMotion(sEvent);
}
else if (sEvent->type==SDL_MOUSEBUTTONDOWN)
else if(sEvent->type == SDL_MOUSEBUTTONDOWN)
{
if(sEvent->button.button == SDL_BUTTON_LEFT)
switch(sEvent->button.button)
{
if(lastClick == sEvent->motion && (SDL_GetTicks() - lastClickTime) < 300)
case SDL_BUTTON_LEFT:
if(lastClick == sEvent->motion && (SDL_GetTicks() - lastClickTime) < 300)
{
std::list<CIntObject*> hlp = doubleClickInterested;
for(auto i=hlp.begin(); i != hlp.end() && current; i++)
for(auto i = hlp.begin(); i != hlp.end() && current; i++)
{
if(!vstd::contains(doubleClickInterested,*i)) continue;
if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
if(!vstd::contains(doubleClickInterested, *i)) continue;
if(isItIn(&(*i)->pos, sEvent->motion.x, sEvent->motion.y))
{
(*i)->onDoubleClick();
}
@ -241,31 +283,16 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
lastClick = sEvent->motion;
lastClickTime = SDL_GetTicks();
std::list<CIntObject*> hlp = lclickable;
for(auto i=hlp.begin(); i != hlp.end() && current; i++)
{
if(!vstd::contains(lclickable,*i)) continue;
if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
{
prev = (*i)->pressedL;
(*i)->pressedL = true;
(*i)->clickLeft(true, prev);
}
}
}
else if (sEvent->button.button == SDL_BUTTON_RIGHT)
{
std::list<CIntObject*> hlp = rclickable;
for(auto i=hlp.begin(); i != hlp.end() && current; i++)
{
if(!vstd::contains(rclickable,*i)) continue;
if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
{
prev = (*i)->pressedR;
(*i)->pressedR = true;
(*i)->clickRight(true, prev);
}
}
handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, true);
break;
case SDL_BUTTON_RIGHT:
handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true);
break;
case SDL_BUTTON_MIDDLE:
handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, true);
break;
default:
break;
}
}
else if (sEvent->type == SDL_MOUSEWHEEL)
@ -295,41 +322,45 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent)
}
}
//todo: muiltitouch
else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_LEFT))
else if(sEvent->type == SDL_MOUSEBUTTONUP)
{
std::list<CIntObject*> hlp = lclickable;
for(auto i=hlp.begin(); i != hlp.end() && current; i++)
switch(sEvent->button.button)
{
if(!vstd::contains(lclickable,*i)) continue;
prev = (*i)->pressedL;
(*i)->pressedL = false;
if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
{
(*i)->clickLeft(false, prev);
}
else
(*i)->clickLeft(boost::logic::indeterminate, prev);
}
}
else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_RIGHT))
{
std::list<CIntObject*> hlp = rclickable;
for(auto i=hlp.begin(); i != hlp.end() && current; i++)
{
if(!vstd::contains(rclickable,*i)) continue;
prev = (*i)->pressedR;
(*i)->pressedR = false;
if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y))
{
(*i)->clickRight(false, prev);
}
else
(*i)->clickRight(boost::logic::indeterminate, prev);
case SDL_BUTTON_LEFT:
handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false);
break;
case SDL_BUTTON_RIGHT:
handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false);
break;
case SDL_BUTTON_MIDDLE:
handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false);
break;
}
}
current = nullptr;
} //event end
void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed)
{
auto hlp = interestedObjs;
for(auto i = hlp.begin(); i != hlp.end() && current; i++)
{
if(!vstd::contains(interestedObjs, *i)) continue;
auto prev = (*i)->mouseState(btn);
if(!isPressed)
(*i)->updateMouseState(btn, isPressed);
if(isItIn(&(*i)->pos, current->motion.x, current->motion.y))
{
if(isPressed)
(*i)->updateMouseState(btn, isPressed);
(*i)->click(btn, isPressed, prev);
}
else if(!isPressed)
(*i)->click(btn, boost::logic::indeterminate, prev);
}
}
void CGuiHandler::handleMouseMotion(SDL_Event *sEvent)
{
//sending active, hovered hoverable objects hover() call
@ -361,7 +392,8 @@ void CGuiHandler::simpleRedraw()
//update only top interface and draw background
if(objsToBlit.size() > 1)
blitAt(screen2,0,0,screen); //blit background
objsToBlit.back()->show(screen); //blit active interface/window
if(!objsToBlit.empty())
objsToBlit.back()->show(screen); //blit active interface/window
}
void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion)

View File

@ -10,6 +10,7 @@ class CIntObject;
class IUpdateable;
class IShowActivatable;
class IShowable;
enum class EIntObjMouseBtnType;
template <typename T> struct CondSh;
/*
@ -53,6 +54,7 @@ private:
//active GUI elements (listening for events
CIntObjectList lclickable,
rclickable,
mclickable,
hoverable,
keyinterested,
motioninterested,
@ -62,6 +64,7 @@ private:
textInterested;
void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed);
void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
public:
void handleElementActivate(CIntObject * elem, ui16 activityFlag);

View File

@ -16,7 +16,7 @@ CIntObject::CIntObject(int used_, Point pos_):
parent(parent_m),
active(active_m)
{
pressedL = pressedR = hovered = captureAllKeys = strongInterest = false;
hovered = captureAllKeys = strongInterest = false;
toNextTick = timerDelay = 0;
used = used_;
@ -134,6 +134,23 @@ CIntObject::~CIntObject()
parent_m->removeChild(this);
}
void CIntObject::click(EIntObjMouseBtnType btn, tribool down, bool previousState)
{
switch(btn)
{
default:
case EIntObjMouseBtnType::LEFT:
clickLeft(down, previousState);
break;
case EIntObjMouseBtnType::MIDDLE:
clickMiddle(down, previousState);
break;
case EIntObjMouseBtnType::RIGHT:
clickRight(down, previousState);
break;
}
}
void CIntObject::printAtLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ )
{
graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y));
@ -340,16 +357,9 @@ void CKeyShortcut::keyPressed(const SDL_KeyboardEvent & key)
if(vstd::contains(assignedKeys,key.keysym.sym)
|| vstd::contains(assignedKeys, CGuiHandler::numToDigit(key.keysym.sym)))
{
bool prev = pressedL;
if(key.state == SDL_PRESSED)
{
pressedL = true;
clickLeft(true, prev);
}
else
{
pressedL = false;
clickLeft(false, prev);
}
bool prev = mouseState(EIntObjMouseBtnType::LEFT);
updateMouseState(EIntObjMouseBtnType::LEFT, key.state == SDL_PRESSED);
clickLeft(key.state == SDL_PRESSED, prev);
}
}

View File

@ -61,6 +61,7 @@ public:
virtual ~IShowActivatable(){}; //d-tor
};
enum class EIntObjMouseBtnType { LEFT, MIDDLE, RIGHT };
//typedef ui16 ActivityFlag;
// Base UI element
@ -73,6 +74,8 @@ class CIntObject : public IShowActivatable //interface object
int toNextTick;
int timerDelay;
std::map<EIntObjMouseBtnType, bool> currentMouseState;
void onTimer(int timePassed);
//non-const versions of fields to allow changing them in CIntObject
@ -104,13 +107,13 @@ public:
CIntObject(int used=0, Point offset=Point());
virtual ~CIntObject(); //d-tor
//l-clicks handling
/*const*/ bool pressedL; //for determining if object is L-pressed
virtual void clickLeft(tribool down, bool previousState){}
void updateMouseState(EIntObjMouseBtnType btn, bool state) { currentMouseState[btn] = state; }
bool mouseState(EIntObjMouseBtnType btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; }
//r-clicks handling
/*const*/ bool pressedR; //for determining if object is R-pressed
virtual void clickRight(tribool down, bool previousState){}
virtual void click(EIntObjMouseBtnType btn, tribool down, bool previousState);
virtual void clickLeft(tribool down, bool previousState) {}
virtual void clickRight(tribool down, bool previousState) {}
virtual void clickMiddle(tribool down, bool previousState) {}
//hover handling
/*const*/ bool hovered; //for determining if object is hovered
@ -138,7 +141,7 @@ public:
//double click
virtual void onDoubleClick(){}
enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, ALL=0xffff};
enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff};
const ui16 & active;
void addUsedEvents(ui16 newActions);
void removeUsedEvents(ui16 newActions);

View File

@ -54,7 +54,7 @@ struct NeighborTilesInfo
if ( dx + pos.x < 0 || dx + pos.x >= sizes.x
|| dy + pos.y < 0 || dy + pos.y >= sizes.y)
return false;
return visibilityMap[dx+pos.x][dy+pos.y][pos.z];
return settings["session"]["spectate"].Bool() ? true : visibilityMap[dx+pos.x][dy+pos.y][pos.z];
};
d7 = getTile(-1, -1); //789
d8 = getTile( 0, -1); //456
@ -563,7 +563,7 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf
const CGObjectInstance * obj = object.obj;
const bool sameLevel = obj->pos.z == pos.z;
const bool isVisible = (*info->visibilityMap)[pos.x][pos.y][pos.z];
const bool isVisible = settings["session"]["spectate"].Bool() ? true : (*info->visibilityMap)[pos.x][pos.y][pos.z];
const bool isVisitable = obj->visitableAt(pos.x, pos.y);
if(sameLevel && isVisible && isVisitable)
@ -895,7 +895,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn
{
const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z];
if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
if(!settings["session"]["spectate"].Bool() && !(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain)
drawFow(targetSurf);
// overlay needs to be drawn over fow, because of artifacts-aura-like spells
@ -1099,6 +1099,9 @@ bool CMapHandler::CMapBlitter::canDrawObject(const CGObjectInstance * obj) const
bool CMapHandler::CMapBlitter::canDrawCurrentTile() const
{
if(settings["session"]["spectate"].Bool())
return true;
const NeighborTilesInfo neighbors(pos, parent->sizes, *info->visibilityMap);
return !neighbors.areAllHidden();
}

View File

@ -587,7 +587,7 @@ void CMinimap::hover(bool on)
void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent)
{
if (pressedL)
if(mouseState(EIntObjMouseBtnType::LEFT))
moveAdvMapSelection();
}

View File

@ -623,7 +623,7 @@ void CSlider::clickLeft(tribool down, bool previousState)
return;
// if (rw>1) return;
// if (rw<0) return;
slider->clickLeft(true, slider->pressedL);
slider->clickLeft(true, slider->mouseState(EIntObjMouseBtnType::LEFT));
moveTo(rw * positions + 0.5);
return;
}

View File

@ -161,16 +161,18 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
{
const CArtifact * const cur = ourOwner->commonInfo->src.art->artType;
switch(cur->id)
if(cur->id == ArtifactID::CATAPULT)
{
case ArtifactID::CATAPULT:
//should not happen, catapult cannot be selected
assert(cur->id != ArtifactID::CATAPULT);
break;
case ArtifactID::BALLISTA: case ArtifactID::AMMO_CART: case ArtifactID::FIRST_AID_TENT: //war machines cannot go to backpack
logGlobal->error("Attempt to move Catapult");
}
else if(cur->isBig())
{
//war machines cannot go to backpack
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name()));
break;
default:
}
else
{
setMeAsDest();
vstd::amin(ourOwner->commonInfo->dst.slotID, ArtifactPosition(
ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START));
@ -184,7 +186,6 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState)
deselect();
else
ourOwner->realizeCurrentTransaction();
break;
}
}
}
@ -366,7 +367,7 @@ bool CHeroArtPlace::fitsHere(const CArtifactInstance * art) const
// Anything but War Machines can be placed in backpack.
if (slotID >= GameConstants::BACKPACK_START)
return !CGI->arth->isBigArtifact(art->artType->id);
return !art->artType->isBig();
return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true);
}

View File

@ -98,7 +98,7 @@ CTerrainRect::CTerrainRect()
pos.w=ADVOPT.advmapW;
pos.h=ADVOPT.advmapH;
moveX = moveY = 0;
addUsedEvents(LCLICK | RCLICK | HOVER | MOVE);
addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE);
}
CTerrainRect::~CTerrainRect()
@ -124,17 +124,10 @@ void CTerrainRect::clickLeft(tribool down, bool previousState)
#ifdef VCMI_ANDROID
if(adventureInt->swipeEnabled)
{
if(down == true)
if(handleSwipeStateChange(down == true))
{
swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0);
swipeInitialMapPos = int3(adventureInt->position);
return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture)
}
else if(isSwiping) // only accept this touch if it wasn't a swipe
{
isSwiping = false;
return;
}
}
else
{
@ -165,22 +158,32 @@ void CTerrainRect::clickRight(tribool down, bool previousState)
adventureInt->tileRClicked(mp);
}
void CTerrainRect::clickMiddle(tribool down, bool previousState)
{
handleSwipeStateChange(down == true);
}
void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent)
{
handleHover(sEvent);
#ifdef VCMI_ANDROID
if(!adventureInt->swipeEnabled || sEvent.state == 0)
if(!adventureInt->swipeEnabled)
return;
handleSwipeMove(sEvent);
#endif // !VCMI_ANDROID
}
#ifdef VCMI_ANDROID
void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
{
#ifdef VCMI_ANDROID
if(sEvent.state == 0) // any "button" is enough on android
#else //!VCMI_ANDROID
if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms
#endif //!VCMI_ANDROID
{
return;
}
if(!isSwiping)
{
// try to distinguish if this touch was meant to be a swipe or just fat-fingering press
@ -201,7 +204,21 @@ void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent)
}
}
#endif // VCMI_ANDROID
bool CTerrainRect::handleSwipeStateChange(bool btnPressed)
{
if(btnPressed)
{
swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0);
swipeInitialMapPos = int3(adventureInt->position);
return true;
}
else if(isSwiping) // only accept this touch if it wasn't a swipe
{
isSwiping = false;
return true;
}
return false;
}
void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent)
{
@ -534,11 +551,9 @@ CAdvMapInt::CAdvMapInt():
infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192)), state(NA),
spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr),
updateScreen(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0),
activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false)
#ifdef VCMI_ANDROID
, swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false),
swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false),
swipeTargetPosition(int3(-1, -1, -1))
#endif
{
adventureInt = this;
pos.x = pos.y = 0;
@ -1007,21 +1022,24 @@ void CAdvMapInt::show(SDL_Surface * to)
}
++heroAnim;
#ifdef VCMI_ANDROID
if(swipeEnabled)
{
handleSwipeUpdate();
}
#ifdef VCMI_ANDROID // on android, map-moving mode is exclusive (TODO technically it might work with both enabled; to be checked)
else
#endif // VCMI_ANDROID
{
#endif // !VCMI_ANDROID
handleMapScrollingUpdate();
#ifdef VCMI_ANDROID
}
#endif
for(int i = 0; i < 4; i++)
gems[i]->setFrame(LOCPLINT->playerID.getNum());
{
if(settings["session"]["spectate"].Bool())
gems[i]->setFrame(PlayerColor(1).getNum());
else
gems[i]->setFrame(LOCPLINT->playerID.getNum());
}
if(updateScreen)
{
int3 betterPos = LOCPLINT->repairScreenPos(position);
@ -1084,14 +1102,13 @@ void CAdvMapInt::handleMapScrollingUpdate()
}
}
#ifdef VCMI_ANDROID
void CAdvMapInt::handleSwipeUpdate()
{
if(swipeMovementRequested)
{
position.x = swipeTargetPosition.x;
position.y = swipeTargetPosition.y;
auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition);
position.x = fixedPos.x;
position.y = fixedPos.y;
CCS->curh->changeGraphic(ECursor::DEFAULT, 0);
updateScreen = true;
minimap.redraw();
@ -1099,8 +1116,6 @@ void CAdvMapInt::handleSwipeUpdate()
}
}
#endif
void CAdvMapInt::selectionChanged()
{
const CGTownInstance *to = LOCPLINT->towns[townList.getSelectedIndex()];
@ -1481,7 +1496,8 @@ void CAdvMapInt::setPlayer(PlayerColor Player)
void CAdvMapInt::startTurn()
{
state = INGAME;
if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID)
if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
|| settings["session"]["spectate"].Bool())
{
adjustActiveness(false);
minimap.setAIRadar(false);
@ -1490,6 +1506,9 @@ void CAdvMapInt::startTurn()
void CAdvMapInt::endingTurn()
{
if(settings["session"]["spectate"].Bool())
return;
if(LOCPLINT->cingconsole->active)
LOCPLINT->cingconsole->deactivate();
LOCPLINT->makingTurn = false;
@ -1817,6 +1836,9 @@ const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *ob
void CAdvMapInt::aiTurnStarted()
{
if(settings["session"]["spectate"].Bool())
return;
adjustActiveness(true);
CCS->musich->playMusicFromSet("enemy-turn", true);
adventureInt->minimap.setAIRadar(true);

View File

@ -62,10 +62,10 @@ class CTerrainRect
bool isSwiping;
static constexpr float SwipeTouchSlop = 16.0f;
void handleHover(const SDL_MouseMotionEvent &sEvent);
#ifdef VCMI_ANDROID
void handleSwipeMove(const SDL_MouseMotionEvent &sEvent);
#endif // VCMI_ANDROID
void handleHover(const SDL_MouseMotionEvent & sEvent);
void handleSwipeMove(const SDL_MouseMotionEvent & sEvent);
/// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled
bool handleSwipeStateChange(bool btnPressed);
public:
int tilesw, tilesh; //width and height of terrain to blit in tiles
int3 curHoveredTile;
@ -77,6 +77,7 @@ public:
void deactivate() override;
void clickLeft(tribool down, bool previousState) override;
void clickRight(tribool down, bool previousState) override;
void clickMiddle(tribool down, bool previousState) override;
void hover(bool on) override;
void mouseMoved (const SDL_MouseMotionEvent & sEvent) override;
void show(SDL_Surface * to) override;
@ -132,11 +133,9 @@ public:
enum{LEFT=1, RIGHT=2, UP=4, DOWN=8};
ui8 scrollingDir; //uses enum: LEFT RIGHT, UP, DOWN
bool scrollingState;
#ifdef VCMI_ANDROID
bool swipeEnabled;
bool swipeMovementRequested;
int3 swipeTargetPosition;
#endif // !VCMI_ANDROID
enum{NA, INGAME, WAITING} state;
@ -260,9 +259,7 @@ public:
void changeMode(EAdvMapMode newMode, float newScale = 0.36f);
void handleMapScrollingUpdate();
#ifdef VCMI_ANDROID
void handleSwipeUpdate();
#endif
};

View File

@ -754,7 +754,8 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID)
}
int price = CGI->arth->artifacts[artifactID]->price;
bool possible = LOCPLINT->cb->getResourceAmount(Res::GOLD) >= price && !hero->hasArt(artifactID);
GH.pushInt(new CBlacksmithDialog(possible, CArtHandler::machineIDToCreature(artifactID), artifactID, hero->id));
CreatureID cre = artifactID.toArtifact()->warMachine;
GH.pushInt(new CBlacksmithDialog(possible, cre, artifactID, hero->id));
}
void CCastleBuildings::enterBuilding(BuildingID building)

View File

@ -337,7 +337,7 @@ void CSpellWindow::computeSpellsPerArea()
spellsCurSite.reserve(mySpells.size());
for(const CSpell * spell : mySpells)
{
if(spell->combatSpell ^ !battleSpellsOnly
if(spell->isCombatSpell() ^ !battleSpellsOnly
&& ((selectedTab == 4) || spell->school.at((ESpellSchool)selectedTab))
)
{
@ -547,9 +547,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState)
}
//we will cast a spell
if(mySpell->combatSpell && owner->myInt->battleInt && owner->myInt->cb->battleCanCastSpell()) //if battle window is open
if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open
{
ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(mySpell);
ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero);
switch (problem)
{
case ESpellCastProblem::OK:

View File

@ -225,9 +225,13 @@ void CWindowObject::setShadow(bool on)
void CWindowObject::showAll(SDL_Surface *to)
{
auto color = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1);
if(settings["session"]["spectate"].Bool())
color = PlayerColor(1); // TODO: Spectator shouldn't need special code for UI colors
CIntObject::showAll(to);
if ((options & BORDERED) && (pos.h != to->h || pos.w != to->w))
CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15);
}
void CWindowObject::close()

View File

@ -205,7 +205,7 @@ void CRecruitmentWindow::buy()
CreatureID crid = selected->creature->idNumber;
SlotID dstslot = dst-> getSlotFor(crid);
if(!dstslot.validSlot() && !vstd::contains(CGI->arth->bigArtifacts,CGI->arth->creatureToMachineID(crid))) //no available slot
if(!dstslot.validSlot() && (selected->creature->warMachine == ArtifactID::NONE)) //no available slot
{
std::string txt;
if(dst->ID == Obj::HERO)

View File

@ -335,6 +335,8 @@ void CRClickPopup::close()
void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps)
{
PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue
if(settings["session"]["spectate"].Bool())//TODO: there must be better way to implement this
player = PlayerColor(1);
CSimpleWindow * temp = new CInfoWindow(txt, player, comps);
temp->center(Point(GH.current->motion)); //center on mouse

View File

@ -17,22 +17,26 @@
"catapult":
{
"index" : 3,
"type" : ["HERO"]
"type" : ["HERO"],
"warMachine" : "catapult"
},
"ballista":
{
"index" : 4,
"type" : ["HERO"]
"type" : ["HERO"],
"warMachine" : "ballista"
},
"ammoCart":
{
"index" : 5,
"type" : ["HERO"]
"type" : ["HERO"],
"warMachine" : "ammoCart"
},
"firstAidTent":
{
"index" : 6,
"type" : ["HERO"]
"type" : ["HERO"],
"warMachine" : "firstAidTent"
},
"centaurAxe":
{
@ -1333,7 +1337,7 @@
"subtype" : 1,
"val" : 0,
"valueType" : "BASE_NUMBER"
}
}
],
"index" : 93,
"type" : ["HERO"]

View File

@ -4,7 +4,7 @@
"title" : "VCMI artifact format",
"description" : "Format used to define new artifacts in VCMI",
"required" : [ "class", "text", "type", "value" ],
"definitions" : {
"growingBonusList" : {
"type" : "array",
@ -117,6 +117,12 @@
"value": {
"type":"number",
"description": "Cost of this artifact, in gold"
},
"warMachine":
{
"type":"string",
"description": "Creature id to use on battle field. If set, this artifact is war machine"
}
}
}

View File

@ -5,8 +5,8 @@
"$schema": "http://json-schema.org/draft-04/schema",
"required" : [ "general", "video", "adventure", "pathfinder", "battle", "server", "logging", "launcher" ],
"definitions" : {
"logLevelEnum" : {
"type" : "string",
"logLevelEnum" : {
"type" : "string",
"enum" : [ "trace", "debug", "info", "warn", "error" ]
}
},
@ -17,7 +17,7 @@
"type" : "object",
"default": {},
"additionalProperties" : false,
"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe" ],
"required" : [ "playerName", "showfps", "music", "sound", "encoding", "swipe", "saveRandomMaps" ],
"properties" : {
"playerName" : {
"type":"string",
@ -41,8 +41,12 @@
},
"swipe" : {
"type" : "boolean",
"default" : false
"default" : true
},
"saveRandomMaps" : {
"type" : "boolean",
"default" : false
}
}
},
"video" : {

View File

@ -55,7 +55,7 @@ void CSettingsView::loadSettings()
ui->comboBoxEnemyAI->setCurrentIndex(enemyAIIndex);
ui->comboBoxPlayerAI->setCurrentIndex(playerAIIndex);
ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Float());
ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Integer());
ui->comboBoxAutoCheck->setCurrentIndex(settings["launcher"]["autoCheckRepositories"].Bool());
// all calls to plainText will trigger textChanged() signal overwriting config. Create backup before editing widget

View File

@ -432,22 +432,36 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp
if(!creatureBank)
{
//Checks if hero has artifact and create appropriate stack
auto handleWarMachine= [&](int side, ArtifactPosition artslot, CreatureID cretype, BattleHex hex)
auto handleWarMachine= [&](int side, ArtifactPosition artslot, BattleHex hex)
{
if(heroes[side] && heroes[side]->getArt(artslot))
stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID::WAR_MACHINES_SLOT, hex));
const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot);
if(nullptr != warMachineArt)
{
CreatureID cre = warMachineArt->artType->warMachine;
if(cre != CreatureID::NONE)
stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cre, 1), !side, SlotID::WAR_MACHINES_SLOT, hex));
}
};
handleWarMachine(0, ArtifactPosition::MACH1, CreatureID::BALLISTA, 52);
handleWarMachine(0, ArtifactPosition::MACH2, CreatureID::AMMO_CART, 18);
handleWarMachine(0, ArtifactPosition::MACH3, CreatureID::FIRST_AID_TENT, 154);
if(town && town->hasFort())
handleWarMachine(0, ArtifactPosition::MACH4, CreatureID::CATAPULT, 120);
if(heroes[0])
{
if(!town) //defending hero shouldn't receive ballista (bug #551)
handleWarMachine(1, ArtifactPosition::MACH1, CreatureID::BALLISTA, 66);
handleWarMachine(1, ArtifactPosition::MACH2, CreatureID::AMMO_CART, 32);
handleWarMachine(1, ArtifactPosition::MACH3, CreatureID::FIRST_AID_TENT, 168);
handleWarMachine(0, ArtifactPosition::MACH1, 52);
handleWarMachine(0, ArtifactPosition::MACH2, 18);
handleWarMachine(0, ArtifactPosition::MACH3, 154);
if(town && town->hasFort())
handleWarMachine(0, ArtifactPosition::MACH4, 120);
}
if(heroes[1])
{
if(!town) //defending hero shouldn't receive ballista (bug #551)
handleWarMachine(1, ArtifactPosition::MACH1, 66);
handleWarMachine(1, ArtifactPosition::MACH2, 32);
handleWarMachine(1, ArtifactPosition::MACH3, 168);
}
}
//war machines added

View File

@ -15,6 +15,7 @@
#include "CGeneralTextHandler.h"
#include "VCMI_Lib.h"
#include "CModHandler.h"
#include "CCreatureHandler.h"
#include "spells/CSpellHandler.h"
#include "mapObjects/MapObjects.h"
#include "NetPacksBase.h"
@ -61,14 +62,21 @@ const std::string & CArtifact::EventText() const
return eventText;
}
bool CArtifact::isBig () const
bool CArtifact::isBig() const
{
return VLC->arth->isBigArtifact(id);
return warMachine != CreatureID::NONE;
}
bool CArtifact::isTradable () const
bool CArtifact::isTradable() const
{
return VLC->arth->isTradableArtifact(id);
switch(id)
{
case ArtifactID::SPELLBOOK:
case ArtifactID::GRAIL:
return false;
default:
return !isBig();
}
}
CArtifact::CArtifact()
@ -120,6 +128,26 @@ void CArtifact::addNewBonus(const std::shared_ptr<Bonus>& b)
CBonusSystemNode::addNewBonus(b);
}
void CArtifact::fillWarMachine()
{
switch (id)
{
case ArtifactID::CATAPULT:
warMachine = CreatureID::CATAPULT;
break;
case ArtifactID::BALLISTA:
warMachine = CreatureID::BALLISTA;
break;
case ArtifactID::FIRST_AID_TENT:
warMachine = CreatureID::FIRST_AID_TENT;
break;
case ArtifactID::AMMO_CART:
warMachine = CreatureID::AMMO_CART;
break;
}
warMachine = CreatureID::NONE; //this artifact is not a creature
}
void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
{
auto b = std::make_shared<Bonus>();
@ -146,11 +174,6 @@ void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art)
CArtHandler::CArtHandler()
{
//VLC->arth = this;
// War machines are the default big artifacts.
for (ArtifactID i = ArtifactID::CATAPULT; i <= ArtifactID::FIRST_AID_TENT; i.advance(1))
bigArtifacts.insert(i);
}
CArtHandler::~CArtHandler()
@ -310,6 +333,19 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node, const std::string &
auto bonus = JsonUtils::parseBonus(b);
art->addNewBonus(bonus);
}
const JsonNode & warMachine = node["warMachine"];
if(warMachine.getType() == JsonNode::DATA_STRING && warMachine.String() != "")
{
VLC->modh->identifiers.requestIdentifier("creature", warMachine, [=](si32 id)
{
art->warMachine = CreatureID(id);
//this assumes that creature object is stored before registration
VLC->creh->creatures.at(id)->warMachine = art->id;
});
}
return art;
}
@ -453,47 +489,6 @@ void CArtHandler::loadGrowingArt(CGrowingArtifact * art, const JsonNode & node)
}
}
//TODO: use bimap
ArtifactID CArtHandler::creatureToMachineID(CreatureID id)
{
switch (id)
{
case CreatureID::CATAPULT: //Catapult
return ArtifactID::CATAPULT;
break;
case CreatureID::BALLISTA: //Ballista
return ArtifactID::BALLISTA;
break;
case CreatureID::FIRST_AID_TENT: //First Aid tent
return ArtifactID::FIRST_AID_TENT;
break;
case CreatureID::AMMO_CART: //Ammo cart
return ArtifactID::AMMO_CART;
break;
}
return ArtifactID::NONE; //this creature is not artifact
}
CreatureID CArtHandler::machineIDToCreature(ArtifactID id)
{
switch (id)
{
case ArtifactID::CATAPULT:
return CreatureID::CATAPULT;
break;
case ArtifactID::BALLISTA:
return CreatureID::BALLISTA;
break;
case ArtifactID::FIRST_AID_TENT:
return CreatureID::FIRST_AID_TENT;
break;
case ArtifactID::AMMO_CART:
return CreatureID::AMMO_CART;
break;
}
return CreatureID::NONE; //this artifact is not a creature
}
ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts)
{
auto getAllowedArts = [&](std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, CArtifact::EartClass flag)
@ -635,22 +630,6 @@ bool CArtHandler::legalArtifact(ArtifactID id)
art->aClass <= CArtifact::ART_RELIC);
}
bool CArtHandler::isTradableArtifact(ArtifactID id) const
{
switch (id)
{
case ArtifactID::SPELLBOOK:
case ArtifactID::GRAIL:
case ArtifactID::CATAPULT:
case ArtifactID::BALLISTA:
case ArtifactID::AMMO_CART:
case ArtifactID::FIRST_AID_TENT:
return false;
default:
return true;
}
}
void CArtHandler::initAllowedArtifactsList(const std::vector<bool> &allowed)
{
allowedArtifacts.clear();

View File

@ -61,6 +61,7 @@ public:
std::vector<CArtifact *> constituentOf; // Reverse map of constituents - combined arts that include this art
EartClass aClass;
ArtifactID id;
CreatureID warMachine;
const std::string &Name() const; //getter
const std::string &Description() const; //getter
@ -84,12 +85,23 @@ public:
{
h & identifier;
}
if(version >= 771)
{
h & warMachine;
}
else if(!h.saving)
{
fillWarMachine();
}
}
CArtifact();
~CArtifact();
friend class CArtHandler;
private:
void fillWarMachine();
};
class DLL_LINKAGE CGrowingArtifact : public CArtifact //for example commander artifacts getting bonuses after battle
@ -213,7 +225,6 @@ public:
std::vector< ConstTransitivePtr<CArtifact> > artifacts;
std::vector<CArtifact *> allowedArtifacts;
std::set<ArtifactID> bigArtifacts; // Artifacts that cannot be moved to backpack, e.g. war machines.
std::set<ArtifactID> growingArtifacts;
void addBonuses(CArtifact *art, const JsonNode &bonusList);
@ -231,13 +242,7 @@ public:
ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function<bool(ArtifactID)> accepts);
bool legalArtifact(ArtifactID id);
//void getAllowedArts(std::vector<ConstTransitivePtr<CArtifact> > &out, std::vector<CArtifact*> *arts, int flag);
//void getAllowed(std::vector<ConstTransitivePtr<CArtifact> > &out, int flags);
bool isBigArtifact (ArtifactID artID) const {return bigArtifacts.find(artID) != bigArtifacts.end();}
bool isTradableArtifact (ArtifactID id) const;
void initAllowedArtifactsList(const std::vector<bool> &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed
static ArtifactID creatureToMachineID(CreatureID id);
static CreatureID machineIDToCreature(ArtifactID id);
void makeItCreatureArt (CArtifact * a, bool onlyCreature = true);
void makeItCreatureArt (ArtifactID aid, bool onlyCreature = true);
void makeItCommanderArt (CArtifact * a, bool onlyCommander = true);
@ -264,7 +269,6 @@ public:
{
h & artifacts & allowedArtifacts & treasures & minors & majors & relics
& growingArtifacts;
//if(!h.saving) sortArts();
}
private:

View File

@ -237,7 +237,7 @@ const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const
BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const
{
RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID);
if(!player)
if(!player || player.get().isSpectator())
return BattlePerspective::ALL_KNOWING;
if(*player == getBattle()->sides[0].color)
return BattlePerspective::LEFT_SIDE;
@ -1708,59 +1708,6 @@ std::vector<BattleHex> CBattleInfoCallback::getAttackableBattleHexes() const
return attackableBattleHexes;
}
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const
{
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
if(caster == nullptr)
{
logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster.";
return ESpellCastProblem::INVALID;
}
const PlayerColor player = caster->getOwner();
const si8 side = playerToSide(player);
if(side < 0)
return ESpellCastProblem::INVALID;
if(!battleDoWeKnowAbout(side))
return ESpellCastProblem::INVALID;
ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode);
if(genProblem != ESpellCastProblem::OK)
return genProblem;
switch(mode)
{
case ECastingMode::HERO_CASTING:
{
const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
if(!castingHero)
{
logGlobal->error("battleCanCastThisSpell: invalid caster");
return ESpellCastProblem::INVALID;
}
if(!castingHero->getArt(ArtifactPosition::SPELLBOOK))
return ESpellCastProblem::NO_SPELLBOOK;
if(!castingHero->canCastThisSpell(spell))
return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
if(castingHero->mana < battleGetSpellCost(spell, castingHero)) //not enough mana
return ESpellCastProblem::NOT_ENOUGH_MANA;
}
break;
}
if(!spell->combatSpell)
return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
//effect like Recanter's Cloak. Blocks also passive casting.
//TODO: check creature abilities to block
if(battleMaxSpellLevel(side) < spell->level)
return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
return spell->canBeCast(this, mode, caster);
}
ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const
{
RETURN_IF_NOT_BATTLE(-1);
@ -1789,22 +1736,6 @@ ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInst
return ret - manaReduction + manaIncrease;
}
ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const
{
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
if(caster == nullptr)
{
logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster.";
return ESpellCastProblem::INVALID;
}
ESpellCastProblem::ESpellCastProblem problem = battleCanCastThisSpell(caster, spell, mode);
if(problem != ESpellCastProblem::OK)
return problem;
return spell->canBeCastAt(this, caster, mode, dest);
}
const CStack * CBattleInfoCallback::getStackIf(std::function<bool(const CStack*)> pred) const
{
RETURN_IF_NOT_BATTLE(nullptr);
@ -1887,7 +1818,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c
if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str())
//TODO: this ability has special limitations
|| battleCanCastThisSpellHere(subject, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
|| spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK)
continue;
switch (spellID)
@ -2127,18 +2058,6 @@ ReachabilityInfo::Parameters::Parameters(const CStack *Stack)
knownAccessible = stack->getHexes();
}
ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell) const
{
RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID);
ASSERT_IF_CALLED_WITH_PLAYER
const ISpellCaster * hero = battleGetMyHero();
if(hero == nullptr)
return ESpellCastProblem::INVALID;
else
return CBattleInfoCallback::battleCanCastThisSpell(hero, spell, ECastingMode::HERO_CASTING);
}
bool CPlayerBattleCallback::battleCanFlee() const
{
RETURN_IF_NOT_BATTLE(false);
@ -2169,26 +2088,6 @@ int CPlayerBattleCallback::battleGetSurrenderCost() const
return CBattleInfoCallback::battleGetSurrenderCost(*player);
}
bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem /*= nullptr*/) const
{
RETURN_IF_NOT_BATTLE(false);
ASSERT_IF_CALLED_WITH_PLAYER
const CGHeroInstance * hero = battleGetMyHero();
if(!hero)
{
if(outProblem)
*outProblem = ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
return false;
}
auto problem = CBattleInfoCallback::battleCanCastSpell(hero, ECastingMode::HERO_CASTING);
if(outProblem)
*outProblem = problem;
return problem == ESpellCastProblem::OK;
}
const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const
{
return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide());

View File

@ -293,8 +293,6 @@ public:
si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned
ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell
ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode
SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const;
SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const;
@ -336,11 +334,9 @@ class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback
public:
bool battleCanFlee() const; //returns true if caller can flee from the battle
TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield
ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be cast (and returns problem description)
int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible
bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written.
const CGHeroInstance * battleGetMyHero() const;
InfoAboutHero battleGetEnemyHero() const;
};

View File

@ -152,6 +152,26 @@ void CCreature::setId(CreatureID ID)
CBonusSystemNode::treeHasChanged();
}
void CCreature::fillWarMachine()
{
switch (idNumber)
{
case CreatureID::CATAPULT: //Catapult
warMachine = ArtifactID::CATAPULT;
break;
case CreatureID::BALLISTA: //Ballista
warMachine = ArtifactID::BALLISTA;
break;
case CreatureID::FIRST_AID_TENT: //First Aid tent
warMachine = ArtifactID::FIRST_AID_TENT;
break;
case CreatureID::AMMO_CART: //Ammo cart
warMachine = ArtifactID::AMMO_CART;
break;
}
warMachine = ArtifactID::NONE; //this creature is not artifact
}
static void AddAbility(CCreature *cre, const JsonVector &ability_vec)
{
auto nsf = std::make_shared<Bonus>();

View File

@ -98,6 +98,8 @@ public:
}
} sounds;
ArtifactID warMachine;
bool isItNativeTerrain(int terrain) const;
bool isDoubleWide() const; //returns true if unit is double wide on battlefield
bool isFlying() const; //returns true if it is a flying unit
@ -142,9 +144,20 @@ public:
{
h & identifier;
}
if(version >= 771)
{
h & warMachine;
}
else if(!h.saving)
{
fillWarMachine();
}
}
CCreature();
private:
void fillWarMachine();
};
class DLL_LINKAGE CCreatureHandler : public IHandlerBase

View File

@ -595,7 +595,7 @@ const CMapHeader * CGameInfoCallback::getMapHeader() const
bool CGameInfoCallback::hasAccess(boost::optional<PlayerColor> playerId) const
{
return !player || gs->getPlayerRelations( *playerId, *player ) != PlayerRelations::ENEMIES;
return !player || player.get().isSpectator() || gs->getPlayerRelations( *playerId, *player ) != PlayerRelations::ENEMIES;
}
EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bool verbose) const

View File

@ -16,7 +16,6 @@
#include "StartInfo.h"
#include "NetPacks.h"
#include "registerTypes/RegisterTypes.h"
#include "mapping/CMapInfo.h"
#include "BattleInfo.h"
#include "JsonNode.h"
#include "filesystem/Filesystem.h"
@ -24,8 +23,10 @@
#include "rmg/CMapGenerator.h"
#include "CStopWatch.h"
#include "mapping/CMapEditManager.h"
#include "mapping/CMapService.h"
#include "serializer/CTypeList.h"
#include "serializer/CMemorySerializer.h"
#include "VCMIDirs.h"
#ifdef min
#undef min
@ -698,7 +699,7 @@ CGameState::~CGameState()
ptr.second.dellNull();
}
void CGameState::init(StartInfo * si)
void CGameState::init(StartInfo * si, bool allowSavingRandomMap)
{
logGlobal->infoStream() << "\tUsing random seed: "<< si->seedToBeUsed;
getRandomGenerator().setSeed(si->seedToBeUsed);
@ -709,7 +710,7 @@ void CGameState::init(StartInfo * si)
switch(scenarioOps->mode)
{
case StartInfo::NEW_GAME:
initNewGame();
initNewGame(allowSavingRandomMap);
break;
case StartInfo::CAMPAIGN:
initCampaign();
@ -771,7 +772,7 @@ void CGameState::init(StartInfo * si)
}
}
void CGameState::initNewGame()
void CGameState::initNewGame(bool allowSavingRandomMap)
{
if(scenarioOps->createRandomMap())
{
@ -780,8 +781,37 @@ void CGameState::initNewGame()
// Gen map
CMapGenerator mapGenerator;
map = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed).release();
std::unique_ptr<CMap> randomMap = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed);
if(allowSavingRandomMap)
{
try
{
auto path = VCMIDirs::get().userCachePath() / "RandomMaps";
boost::filesystem::create_directories(path);
std::shared_ptr<CMapGenOptions> options = scenarioOps->mapGenOptions;
const std::string templateName = options->getMapTemplate()->getName();
const ui32 seed = scenarioOps->seedToBeUsed;
const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed );
const auto fullPath = path / fileName;
CMapService::saveMap(randomMap, fullPath);
logGlobal->info("Random map has been saved to:");
logGlobal->info(fullPath.string());
}
catch(...)
{
logGlobal->error("Saving random map failed with exception");
handleException();
}
}
map = randomMap.release();
// Update starting options
for(int i = 0; i < map->players.size(); ++i)
{
@ -809,7 +839,8 @@ void CGameState::initNewGame()
else
{
logGlobal->infoStream() << "Open map file: " << scenarioOps->mapname;
map = CMapService::loadMap(scenarioOps->mapname).release();
const ResourceID mapURI(scenarioOps->mapname, EResType::MAP);
map = CMapService::loadMap(mapURI).release();
}
}
@ -2177,6 +2208,9 @@ bool CGameState::isVisible(int3 pos, PlayerColor player)
{
if(player == PlayerColor::NEUTRAL)
return false;
if(player.isSpectator())
return true;
return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z];
}

View File

@ -201,7 +201,7 @@ public:
CGameState();
virtual ~CGameState();
void init(StartInfo * si);
void init(StartInfo * si, bool allowSavingRandomMap = false);
ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
PlayerColor currentPlayer; //ID of player currently having turn
@ -283,7 +283,7 @@ private:
// ----- initialization -----
void initNewGame();
void initNewGame(bool allowSavingRandomMap);
void initCampaign();
void initDuel();
void checkMapChecksum();

View File

@ -584,7 +584,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source)
VLC->modh->identifiers.requestIdentifier("creature", source["warMachine"],
[&town](si32 creature)
{
town.warMachine = CArtHandler::creatureToMachineID(CreatureID(creature));
town.warMachine = CreatureID(creature).toCreature()->warMachine;
});
town.moatDamage = source["moatDamage"].Float();

View File

@ -29,6 +29,7 @@ const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3);
const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4);
const SlotID SlotID::ARROW_TOWERS_SLOT = SlotID(-5);
const PlayerColor PlayerColor::SPECTATOR = PlayerColor(252);
const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253);
const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254);
const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255);
@ -72,6 +73,11 @@ bool PlayerColor::isValidPlayer() const
return num < PLAYER_LIMIT_I;
}
bool PlayerColor::isSpectator() const
{
return num == 252;
}
std::string PlayerColor::getStr(bool L10n) const
{
std::string ret = "unnamed";

View File

@ -257,12 +257,14 @@ class PlayerColor : public BaseForID<PlayerColor, ui8>
PLAYER_LIMIT_I = 8
};
DLL_LINKAGE static const PlayerColor SPECTATOR; //252
DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253
DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks)
DLL_LINKAGE static const PlayerColor NEUTRAL; //255
DLL_LINKAGE static const PlayerColor PLAYER_LIMIT; //player limit per map
DLL_LINKAGE bool isValidPlayer() const; //valid means < PLAYER_LIMIT (especially non-neutral)
DLL_LINKAGE bool isSpectator() const;
DLL_LINKAGE std::string getStr(bool L10n = false) const;
DLL_LINKAGE std::string getStrCap(bool L10n = false) const;

View File

@ -19,40 +19,63 @@
struct ServerReady
{
bool ready;
boost::interprocess::interprocess_mutex mutex;
boost::interprocess::interprocess_condition cond;
uint16_t port; //ui16?
boost::interprocess::interprocess_mutex mutex;
boost::interprocess::interprocess_condition cond;
ServerReady()
{
ready = false;
port = 0;
}
void setToTrueAndNotify()
void waitTillReady()
{
boost::interprocess::scoped_lock<boost::interprocess::interprocess_mutex> slock(mutex);
while(!ready)
{
cond.wait(slock);
}
}
void setToReadyAndNotify(const uint16_t Port)
{
{
boost::unique_lock<boost::interprocess::interprocess_mutex> lock(mutex);
boost::unique_lock<boost::interprocess::interprocess_mutex> lock(mutex);
ready = true;
port = Port;
}
cond.notify_all();
}
};
struct SharedMem
struct SharedMemory
{
const char * name;
boost::interprocess::shared_memory_object smo;
boost::interprocess::mapped_region *mr;
ServerReady *sr;
boost::interprocess::mapped_region * mr;
ServerReady * sr;
SharedMem() //c-tor
:smo(boost::interprocess::open_or_create,"vcmi_memory",boost::interprocess::read_write)
SharedMemory(std::string Name, bool initialize = false)
: name(Name.c_str())
{
if(initialize)
{
//if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
boost::interprocess::shared_memory_object::remove(name);
}
smo = boost::interprocess::shared_memory_object(boost::interprocess::open_or_create, name, boost::interprocess::read_write);
smo.truncate(sizeof(ServerReady));
mr = new boost::interprocess::mapped_region(smo,boost::interprocess::read_write);
sr = new(mr->get_address())ServerReady();
if(initialize)
sr = new(mr->get_address())ServerReady();
else
sr = reinterpret_cast<ServerReady*>(mr->get_address());
};
~SharedMem() //d-tor
~SharedMemory()
{
delete mr;
boost::interprocess::shared_memory_object::remove("vcmi_memory");
boost::interprocess::shared_memory_object::remove(name);
}
};

View File

@ -152,6 +152,21 @@ struct PlayerBlocked : public CPackForClient
}
};
struct PlayerCheated : public CPackForClient
{
PlayerCheated() : losingCheatCode(false), winningCheatCode(false) {}
DLL_LINKAGE void applyGs(CGameState *gs);
PlayerColor player;
bool losingCheatCode;
bool winningCheatCode;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & player & losingCheatCode & winningCheatCode;
}
};
struct YourTurn : public CPackForClient
{
YourTurn(){}
@ -1774,6 +1789,13 @@ struct CloseServer : public CPackForServer
{}
};
struct LeaveGame : public CPackForServer
{
bool applyGh(CGameHandler *gh);
template <typename Handler> void serialize(Handler &h, const int version)
{}
};
struct EndTurn : public CPackForServer
{
bool applyGh(CGameHandler *gh);

View File

@ -1840,6 +1840,15 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs)
}
}
DLL_LINKAGE void PlayerCheated::applyGs(CGameState *gs)
{
if(!player.isValidPlayer())
return;
gs->getPlayer(player)->enteredLosingCheatCode = losingCheatCode;
gs->getPlayer(player)->enteredWinningCheatCode = winningCheatCode;
}
DLL_LINKAGE void YourTurn::applyGs(CGameState *gs)
{
gs->currentPlayer = player;

View File

@ -424,4 +424,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
</Project>

View File

@ -669,4 +669,4 @@
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
</Project>

View File

@ -352,34 +352,43 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor *dst /*=
int count = rand.nextInt(stack.minAmount, stack.maxAmount);
if(stack.creature >= CreatureID::CATAPULT &&
stack.creature <= CreatureID::ARROW_TOWERS) //war machine
const CCreature * creature = stack.creature.toCreature();
if(creature == nullptr)
{
logGlobal->error("Hero %s has invalid creature with id %d in initial army", name, stack.creature.toEnum());
continue;
}
if(creature->warMachine != ArtifactID::NONE) //war machine
{
warMachinesGiven++;
if(dst != this)
continue;
int slot = -1;
ArtifactID aid = ArtifactID::NONE;
switch (stack.creature)
ArtifactID aid = creature->warMachine;
const CArtifact * art = aid.toArtifact();
if(art != nullptr && !art->possibleSlots.at(ArtBearer::HERO).empty())
{
case CreatureID::CATAPULT:
slot = ArtifactPosition::MACH4;
aid = ArtifactID::CATAPULT;
break;
default:
aid = CArtHandler::creatureToMachineID(stack.creature);
slot = 9 + aid;
break;
//TODO: should we try another possible slots?
ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front();
if(!getArt(slot))
putArtifact(slot, CArtifactInstance::createNewArtifactInstance(aid));
else
logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid;
}
auto convSlot = ArtifactPosition(slot);
if(!getArt(convSlot))
putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid));
else
logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid;
{
logGlobal->error("Hero %s has invalid war machine in initial army", name);
}
}
else
{
dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count);
}
}
}
@ -1610,42 +1619,34 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler)
}
}
//primary skills
if(handler.saving)
{
if(handler.saving)
{
bool haveSkills = false;
const bool haveSkills = hasBonus(Selector::type(Bonus::PRIMARY_SKILL).And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
{
if(valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL))) != 0)
{
haveSkills = true;
break;
}
}
if(haveSkills)
{
auto primarySkills = handler.enterStruct("primarySkills");
for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
{
int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
handler.serializeInt(PrimarySkill::names[i], value, 0);
}
}
}
else
if(haveSkills)
{
auto primarySkills = handler.enterStruct("primarySkills");
for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
{
int value = 0;
int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL)));
handler.serializeInt(PrimarySkill::names[i], value, 0);
if(value != 0)
pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(i), value);
}
}
}
else
{
auto primarySkills = handler.enterStruct("primarySkills");
if(primarySkills.get().getType() == JsonNode::DATA_STRUCT)
{
for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
{
int value = 0;
primarySkills->serializeInt(PrimarySkill::names[i], value, 0);
pushPrimSkill(static_cast<PrimarySkill::PrimarySkill>(i), value);
}
}
}

View File

@ -1477,7 +1477,7 @@ std::string CGWitchHut::getHoverText(PlayerColor player) const
std::string CGWitchHut::getHoverText(const CGHeroInstance * hero) const
{
std::string hoverName = getHoverText(hero->tempOwner);
if(hero->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability
if(wasVisited(hero->tempOwner) && hero->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability
hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned)
return hoverName;
}

View File

@ -1,6 +1,7 @@
#include "StdInc.h"
#include "CMapInfo.h"
#include "../filesystem/ResourceID.h"
#include "../StartInfo.h"
#include "../GameConstants.h"
#include "CMapService.h"
@ -58,7 +59,7 @@ CMapInfo::~CMapInfo()
void CMapInfo::mapInit(const std::string & fname)
{
fileURI = fname;
mapHeader = CMapService::loadMapHeader(fname);
mapHeader = CMapService::loadMapHeader(ResourceID(fname, EResType::MAP));
countPlayers();
}
@ -81,3 +82,4 @@ CMapInfo & CMapInfo::operator=(CMapInfo &&tmp)
return *this;
}
#undef STEAL

View File

@ -5,6 +5,7 @@
#include "../filesystem/CBinaryReader.h"
#include "../filesystem/CCompressedStream.h"
#include "../filesystem/CMemoryStream.h"
#include "../filesystem/CMemoryBuffer.h"
#include "CMap.h"
@ -12,24 +13,16 @@
#include "MapFormatJson.h"
std::unique_ptr<CMap> CMapService::loadMap(const std::string & name)
std::unique_ptr<CMap> CMapService::loadMap(const ResourceID & name)
{
auto stream = getStreamFromFS(name);
std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
std::unique_ptr<CMapHeader> header(map.get());
getMapPatcher(name)->patchMapHeader(header);
header.release();
return map;
return getMapLoader(stream)->loadMap();
}
std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const std::string & name)
std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ResourceID & name)
{
auto stream = getStreamFromFS(name);
std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
getMapPatcher(name)->patchMapHeader(header);
return header;
return getMapLoader(stream)->loadMapHeader();
}
std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const std::string & name)
@ -38,6 +31,7 @@ std::unique_ptr<CMap> CMapService::loadMap(const ui8 * buffer, int size, const s
std::unique_ptr<CMap> map(getMapLoader(stream)->loadMap());
std::unique_ptr<CMapHeader> header(map.get());
//might be original campaign and require patch
getMapPatcher(name)->patchMapHeader(header);
header.release();
@ -48,13 +42,32 @@ std::unique_ptr<CMapHeader> CMapService::loadMapHeader(const ui8 * buffer, int s
{
auto stream = getStreamFromMem(buffer, size);
std::unique_ptr<CMapHeader> header = getMapLoader(stream)->loadMapHeader();
//might be original campaign and require patch
getMapPatcher(name)->patchMapHeader(header);
return header;
}
std::unique_ptr<CInputStream> CMapService::getStreamFromFS(const std::string & name)
void CMapService::saveMap(const std::unique_ptr<CMap> & map, boost::filesystem::path fullPath)
{
return CResourceHandler::get()->load(ResourceID(name, EResType::MAP));
CMemoryBuffer serializeBuffer;
{
CMapSaverJson saver(&serializeBuffer);
saver.saveMap(map);
}
{
boost::filesystem::remove(fullPath);
boost::filesystem::ofstream tmp(fullPath, boost::filesystem::ofstream::binary);
tmp.write((const char *)serializeBuffer.getBuffer().data(),serializeBuffer.getSize());
tmp.flush();
tmp.close();
}
}
std::unique_ptr<CInputStream> CMapService::getStreamFromFS(const ResourceID & name)
{
return CResourceHandler::get()->load(name);
}
std::unique_ptr<CInputStream> CMapService::getStreamFromMem(const ui8 * buffer, int size)

View File

@ -11,6 +11,8 @@
#pragma once
class ResourceID;
class CMap;
class CMapHeader;
class CInputStream;
@ -31,7 +33,7 @@ public:
* @param name the name of the map
* @return a unique ptr to the loaded map class
*/
static std::unique_ptr<CMap> loadMap(const std::string & name);
static std::unique_ptr<CMap> loadMap(const ResourceID & name);
/**
* Loads the VCMI/H3 map header specified by the name.
@ -39,7 +41,7 @@ public:
* @param name the name of the map
* @return a unique ptr to the loaded map header class
*/
static std::unique_ptr<CMapHeader> loadMapHeader(const std::string & name);
static std::unique_ptr<CMapHeader> loadMapHeader(const ResourceID & name);
/**
* Loads the VCMI/H3 map file from a buffer. This method is temporarily
@ -69,6 +71,7 @@ public:
*/
static std::unique_ptr<CMapHeader> loadMapHeader(const ui8 * buffer, int size, const std::string & name);
static void saveMap(const std::unique_ptr<CMap> & map, boost::filesystem::path fullPath);
private:
/**
* Gets a map input stream object specified by a map name.
@ -76,7 +79,7 @@ private:
* @param name the name of the map
* @return a unique ptr to the input stream class
*/
static std::unique_ptr<CInputStream> getStreamFromFS(const std::string & name);
static std::unique_ptr<CInputStream> getStreamFromFS(const ResourceID & name);
/**
* Gets a map input stream from a buffer.

View File

@ -872,9 +872,17 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
bool isArt = aid != artmask;
if(isArt)
{
if(vstd::contains(VLC->arth->bigArtifacts, aid) && slot >= GameConstants::BACKPACK_START)
const CArtifact * art = ArtifactID(aid).toArtifact();
if(nullptr == art)
{
logGlobal->warnStream() << "Warning: A big artifact (war machine) in hero's backpack, ignoring...";
logGlobal->warnStream() << "Invalid artifact in hero's backpack, ignoring...";
return false;
}
if(art->isBig() && slot >= GameConstants::BACKPACK_START)
{
logGlobal->warnStream() << "A big artifact (war machine) in hero's backpack, ignoring...";
return false;
}
if(aid == 0 && slot == ArtifactPosition::MISC5)

View File

@ -1212,6 +1212,8 @@ void CMapSaverJson::saveMap(const std::unique_ptr<CMap>& map)
void CMapSaverJson::writeHeader()
{
logGlobal->trace("Saving header");
JsonNode header;
JsonSerializer handler(mapObjectResolver.get(), header);
@ -1283,6 +1285,7 @@ JsonNode CMapSaverJson::writeTerrainLevel(const int index)
void CMapSaverJson::writeTerrain()
{
logGlobal->trace("Saving terrain");
//todo: multilevel map save support
JsonNode surface = writeTerrainLevel(0);
@ -1297,12 +1300,14 @@ void CMapSaverJson::writeTerrain()
void CMapSaverJson::writeObjects()
{
logGlobal->trace("Saving objects");
JsonNode data(JsonNode::DATA_STRUCT);
JsonSerializer handler(mapObjectResolver.get(), data);
for(CGObjectInstance * obj : map->objects)
{
logGlobal->trace("\t%s", obj->instanceName);
auto temp = handler.enterStruct(obj->instanceName);
obj->serializeJson(handler);

View File

@ -210,6 +210,7 @@ void registerTypesClientPacks1(Serializer &s)
s.template registerType<CPackForClient, PackageApplied>();
s.template registerType<CPackForClient, SystemMessage>();
s.template registerType<CPackForClient, PlayerBlocked>();
s.template registerType<CPackForClient, PlayerCheated>();
s.template registerType<CPackForClient, YourTurn>();
s.template registerType<CPackForClient, SetResources>();
s.template registerType<CPackForClient, SetPrimSkill>();
@ -314,6 +315,7 @@ void registerTypesServerPacks(Serializer &s)
{
s.template registerType<CPack, CPackForServer>();
s.template registerType<CPackForServer, CloseServer>();
s.template registerType<CPackForServer, LeaveGame>();
s.template registerType<CPackForServer, EndTurn>();
s.template registerType<CPackForServer, DismissHero>();
s.template registerType<CPackForServer, MoveHero>();

View File

@ -1,7 +1,6 @@
#include "StdInc.h"
#include "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CGameState.h"
#include "../mapping/CMap.h"

View File

@ -1,7 +1,6 @@
#include "StdInc.h"
#include "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CStack.h"
#include "../BattleInfo.h"

View File

@ -1,7 +1,6 @@
#include "StdInc.h"
#include "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CGameState.h"
#include "../mapping/CMap.h"

View File

@ -1,7 +1,6 @@
#include "StdInc.h"
#include "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CStack.h"
#include "../BattleInfo.h"

View File

@ -1,7 +1,6 @@
#include "StdInc.h"
#include "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CGameState.h"
#include "../mapping/CMap.h"

View File

@ -1,7 +1,6 @@
#include "StdInc.h"
#include "RegisterTypes.h"
#include "../mapping/CMapInfo.h"
#include "../StartInfo.h"
#include "../CGameState.h"
#include "../mapping/CMap.h"

View File

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

View File

@ -61,14 +61,14 @@ void CConnection::init()
iser.fileVersion = SERIALIZATION_VERSION;
}
CConnection::CConnection(std::string host, std::string port, std::string Name)
CConnection::CConnection(std::string host, ui16 port, std::string Name)
:iser(this), oser(this), io_service(new asio::io_service), name(Name)
{
int i;
boost::system::error_code error = asio::error::host_not_found;
socket = new tcp::socket(*io_service);
tcp::resolver resolver(*io_service);
tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host,port),error);
tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error);
if(error)
{
logNetwork->errorStream() << "Problem with resolving: \n" << error;
@ -191,8 +191,7 @@ void CConnection::close()
if(socket)
{
socket->close();
delete socket;
socket = nullptr;
vstd::clear_pointer(socket);
}
}
@ -201,6 +200,11 @@ bool CConnection::isOpen() const
return socket && connected;
}
bool CConnection::isHost() const
{
return connectionID == 1;
}
void CConnection::reportState(CLogger * out)
{
out->debugStream() << "CConnection";

View File

@ -68,12 +68,13 @@ public:
bool receivedStop, sendStop;
CConnection(std::string host, std::string port, std::string Name);
CConnection(std::string host, ui16 port, std::string Name);
CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name);
CConnection(TSocket * Socket, std::string Name); //use immediately after accepting connection into socket
void close();
bool isOpen() const;
bool isHost() const;
template<class T>
CConnection &operator&(const T&);
virtual ~CConnection(void);

View File

@ -157,10 +157,50 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte
ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const
{
const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster);
ESpellCastProblem::ESpellCastProblem genProblem = cb->battleCanCastSpell(caster, mode);
if(genProblem != ESpellCastProblem::OK)
return genProblem;
if(generalProblem != ESpellCastProblem::OK)
return generalProblem;
switch(mode)
{
case ECastingMode::HERO_CASTING:
{
const CGHeroInstance * castingHero = dynamic_cast<const CGHeroInstance *>(caster);//todo: unify hero|creature spell cost
if(!castingHero)
{
logGlobal->debug("CSpell::canBeCast: invalid caster");
return ESpellCastProblem::NO_HERO_TO_CAST_SPELL;
}
if(!castingHero->getArt(ArtifactPosition::SPELLBOOK))
return ESpellCastProblem::NO_SPELLBOOK;
if(!castingHero->canCastThisSpell(this))
return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL;
if(castingHero->mana < cb->battleGetSpellCost(this, castingHero)) //not enough mana
return ESpellCastProblem::NOT_ENOUGH_MANA;
}
break;
}
if(!isCombatSpell())
return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL;
const PlayerColor player = caster->getOwner();
const si8 side = cb->playerToSide(player);
if(side < 0)
return ESpellCastProblem::INVALID;
//effect like Recanter's Cloak. Blocks also passive casting.
//TODO: check creature abilities to block
//TODO: check any possible caster
if(cb->battleMaxSpellLevel(side) < level)
return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED;
const ESpellCastProblem::ESpellCastProblem specificProblem = mechanics->canBeCast(cb, mode, caster);
if(specificProblem != ESpellCastProblem::OK)
return specificProblem;
//check for creature target existence
//allow to cast spell if there is at least one smart target
@ -368,6 +408,10 @@ void CSpell::getEffects(std::vector<Bonus> & lst, const int level) const
ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const
{
ESpellCastProblem::ESpellCastProblem problem = canBeCast(cb, mode, caster);
if(problem != ESpellCastProblem::OK)
return problem;
SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination);
return mechanics->canBeCast(cb, ctx);

View File

@ -87,10 +87,14 @@ void BattleSpellCastParameters::cast(const SpellCastEnvironment * env)
spell->battleCast(env, *this);
}
void BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env)
bool BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env)
{
if(ESpellCastProblem::OK == cb->battleCanCastThisSpell(caster, spell, mode))
if(ESpellCastProblem::OK == spell->canBeCast(cb, mode, caster))
{
cast(env);
return true;
}
return false;
}
BattleHex BattleSpellCastParameters::getFirstDestinationHex() const

View File

@ -57,7 +57,8 @@ public:
void cast(const SpellCastEnvironment * env);
///cast with silent check for permitted cast
void castIfPossible(const SpellCastEnvironment * env);
///returns true if cast was permitted
bool castIfPossible(const SpellCastEnvironment * env);
BattleHex getFirstDestinationHex() const;

View File

@ -48,7 +48,7 @@
#ifndef _MSC_VER
#include <boost/thread/xtime.hpp>
#endif
extern bool end2;
extern std::atomic<bool> serverShuttingDown;
#ifdef min
#undef min
#endif
@ -1031,6 +1031,25 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
{
setThreadName("CGameHandler::handleConnection");
auto handleDisconnection = [&](const std::exception & e)
{
boost::unique_lock<boost::mutex> lock(*c.wmx);
assert(!c.connected); //make sure that connection has been marked as broken
logGlobal->error(e.what());
conns -= &c;
for(auto playerConn : connections)
{
if(!serverShuttingDown && playerConn.second == &c)
{
PlayerCheated pc;
pc.player = playerConn.first;
pc.losingCheatCode = true;
sendAndApply(&pc);
checkVictoryLossConditionsForPlayer(playerConn.first);
}
}
};
try
{
while(1)//server should never shut connection first //was: while(!end2)
@ -1042,6 +1061,8 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
{
boost::unique_lock<boost::mutex> lock(*c.rmx);
if(!c.connected)
throw clientDisconnectedException();
c >> player >> requestID >> pack; //get the package
if (!pack)
@ -1060,6 +1081,11 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
//prepare struct informing that action was applied
auto sendPackageResponse = [&](bool succesfullyApplied)
{
//dont reply to disconnected client
//TODO: this must be implemented as option of CPackForServer
if(dynamic_cast<LeaveGame *>(pack) || dynamic_cast<CloseServer *>(pack))
return;
PackageApplied applied;
applied.player = player;
applied.result = succesfullyApplied;
@ -1095,13 +1121,15 @@ void CGameHandler::handleConnection(std::set<PlayerColor> players, CConnection &
}
catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
{
assert(!c.connected); //make sure that connection has been marked as broken
logGlobal->error(e.what());
end2 = true;
handleDisconnection(e);
}
catch(clientDisconnectedException & e)
{
handleDisconnection(e);
}
catch(...)
{
end2 = true;
serverShuttingDown = true;
handleException();
throw;
}
@ -1851,7 +1879,8 @@ void CGameHandler::run(bool resume)
sbuffer << color << " ";
{
boost::unique_lock<boost::recursive_mutex> lock(gsm);
connections[color] = cc;
if(!color.isSpectator()) // there can be more than one spectator
connections[color] = cc;
}
}
logGlobal->info(sbuffer.str());
@ -1874,7 +1903,7 @@ void CGameHandler::run(bool resume)
if (gs->scenarioOps->mode == StartInfo::DUEL)
{
runBattle();
end2 = true;
serverShuttingDown = true;
while(conns.size() && (*conns.begin())->isOpen())
@ -1885,7 +1914,7 @@ void CGameHandler::run(bool resume)
auto playerTurnOrder = generatePlayerTurnOrder();
while(!end2)
while(!serverShuttingDown)
{
if (!resume) newTurn();
@ -1926,7 +1955,7 @@ void CGameHandler::run(bool resume)
//wait till turn is done
boost::unique_lock<boost::mutex> lock(states.mx);
while (states.players.at(playerColor).makingTurn && !end2)
while(states.players.at(playerColor).makingTurn && !serverShuttingDown)
{
static time_duration p = milliseconds(100);
states.cv.timed_wait(lock, p);
@ -1942,7 +1971,7 @@ void CGameHandler::run(bool resume)
activePlayer = true;
}
if (!activePlayer)
end2 = true;
serverShuttingDown = true;
}
while(conns.size() && (*conns.begin())->isOpen())
boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket
@ -2631,6 +2660,9 @@ void CGameHandler::sendToAllClients(CPackForClient * info)
logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name());
for (auto & elem : conns)
{
if(!elem->isOpen())
continue;
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
*elem << info;
}
@ -2703,11 +2735,31 @@ void CGameHandler::close()
{
exit(0);
}
serverShuttingDown = true;
//for (CConnection *cc : conns)
// if (cc && cc->socket && cc->socket->is_open())
// cc->socket->close();
//exit(0);
for (auto & elem : conns)
{
if(!elem->isOpen())
continue;
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
elem->close();
elem->connected = false;
}
}
void CGameHandler::playerLeftGame(int cid)
{
for (auto & elem : conns)
{
if(elem->isOpen() && elem->connectionID == cid)
{
boost::unique_lock<boost::mutex> lock(*(elem)->wmx);
elem->close();
elem->connected = false;
break;
}
}
}
bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player)
@ -3083,7 +3135,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
const CGDwelling * dw = static_cast<const CGDwelling *>(getObj(objid));
const CArmedInstance *dst = nullptr;
const CCreature *c = VLC->creh->creatures.at(crid);
bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON);
const bool warMachine = c->warMachine != ArtifactID::NONE;
//TODO: test for owning
//TODO: check if dst can recruit objects (e.g. hero is actually visiting object, town and source are same, etc)
@ -3134,24 +3186,18 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
if (warMachine)
{
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(dst);
if (!h)
COMPLAIN_RET("Only hero can buy war machines");
switch(crid)
{
case CreatureID::BALLISTA:
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1);
break;
case CreatureID::FIRST_AID_TENT:
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3);
break;
case CreatureID::AMMO_CART:
giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2);
break;
default:
complain("This war machine cannot be recruited!");
return false;
}
COMPLAIN_RET_FALSE_IF(!h, "Only hero can buy war machines");
ArtifactID artId = c->warMachine;
COMPLAIN_RET_FALSE_IF(artId == ArtifactID::CATAPULT, "Catapult cannot be recruited!");
const CArtifact * art = artId.toArtifact();
COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid war machine artifact");
return giveHeroNewArtifact(h, art);
}
else
{
@ -3392,7 +3438,10 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition
bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
{
const CGHeroInstance * hero = getHero(hid);
COMPLAIN_RET_FALSE_IF(nullptr == hero, "Invalid hero index");
const CGTownInstance * town = hero->visitedTown;
COMPLAIN_RET_FALSE_IF(nullptr == town, "Hero not in town");
if (aid==ArtifactID::SPELLBOOK)
{
if ((!town->hasBuilt(BuildingID::MAGES_GUILD_1) && complain("Cannot buy a spellbook, no mage guild in the town!"))
@ -3407,26 +3456,24 @@ bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid)
giveSpells(town,hero);
return true;
}
else if (aid < 7 && aid > 3) //war machine
else
{
int price = VLC->arth->artifacts[aid]->price;
const CArtifact * art = aid.toArtifact();
COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid artifact index to buy");
COMPLAIN_RET_FALSE_IF(art->warMachine == CreatureID::NONE, "War machine artifact required");
COMPLAIN_RET_FALSE_IF(hero->hasArt(aid),"Hero already has this machine!");
const int price = art->price;
COMPLAIN_RET_FALSE_IF(getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price, "Not enough gold!");
if ((hero->getArt(ArtifactPosition(9+aid)) && complain("Hero already has this machine!"))
|| (getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!")))
{
return false;
}
if ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid)
|| ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA))
{
giveResource(hero->getOwner(),Res::GOLD,-price);
giveHeroNewArtifact(hero, VLC->arth->artifacts[aid], ArtifactPosition(9+aid));
return true;
return giveHeroNewArtifact(hero, art);
}
else
COMPLAIN_RET("This machine is unavailable here!");
}
return false;
}
bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::ERes rid, ArtifactID aid)
@ -4379,7 +4426,9 @@ void CGameHandler::playerMessage(PlayerColor player, const std::string &message,
{
SystemMessage temp_message(VLC->generaltexth->allTexts.at(260));
sendAndApply(&temp_message);
checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
if(!player.isSpectator())
checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature
}
}
@ -4407,7 +4456,7 @@ bool CGameHandler::makeCustomAction(BattleAction &ba)
if (ba.selectedStack >= 0)
parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false));
ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)?
ESpellCastProblem::ESpellCastProblem escp = s->canBeCast(gs->curB, ECastingMode::HERO_CASTING, h);//todo: should we check aimed cast?
if (escp != ESpellCastProblem::OK)
{
logGlobal->warn("Spell cannot be cast! Problem: %d", escp);
@ -4580,14 +4629,14 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
const CSpell * spell = SpellID(spellID).toSpell();
bl.remove_if([&bonus](const Bonus* b){return b==bonus.get();});
if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK)
{
BattleSpellCastParameters parameters(gs->curB, st, spell);
parameters.spellLvl = bonus->val;
parameters.effectLevel = bonus->val;//todo: recheck
parameters.mode = ECastingMode::ENCHANTER_CASTING;
parameters.cast(spellEnv);
BattleSpellCastParameters parameters(gs->curB, st, spell);
parameters.spellLvl = bonus->val;
parameters.effectLevel = bonus->val;//todo: recheck
parameters.mode = ECastingMode::ENCHANTER_CASTING;
cast = parameters.castIfPossible(spellEnv);
if(cast)
{
//todo: move to mechanics
BattleSetStackProperty ssp;
ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER;
@ -4595,8 +4644,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st)
ssp.val = bonus->additionalInfo; //increase cooldown counter
ssp.stackID = st->ID;
sendAndApply(&ssp);
cast = true;
}
}
}
@ -5051,7 +5098,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
if (p->human)
{
end2 = true;
serverShuttingDown = true;
if (gs->scenarioOps->campState)
{
@ -5251,7 +5298,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta
vstd::amin(chance, 100);
const CSpell * spell = SpellID(spellID).toSpell();
if (gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
if(spell->canBeCastAt(gs->curB, attacker, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK)
continue;
//check if spell should be cast (probability handling)
@ -6001,6 +6048,18 @@ void CGameHandler::putArtifact(const ArtifactLocation &al, const CArtifactInstan
sendAndApply(&pa);
}
bool CGameHandler::giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * art)
{
COMPLAIN_RET_FALSE_IF(art->possibleSlots.at(ArtBearer::HERO).empty(), "Not a hero artifact!");
ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front();
COMPLAIN_RET_FALSE_IF(nullptr != h->getArt(slot, false), "Hero already has artifact in slot");
giveHeroNewArtifact(h, art, slot);
return true;
}
void CGameHandler::giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos)
{
CArtifactInstance *a = nullptr;
@ -6162,12 +6221,18 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons
else if (cheat == "vcmisilmaril")
{
///Player wins
gs->getPlayer(player)->enteredWinningCheatCode = 1;
PlayerCheated pc;
pc.player = player;
pc.winningCheatCode = true;
sendAndApply(&pc);
}
else if (cheat == "vcmimelkor")
{
///Player looses
gs->getPlayer(player)->enteredLosingCheatCode = 1;
PlayerCheated pc;
pc.player = player;
pc.losingCheatCode = true;
sendAndApply(&pc);
}
else if (cheat == "vcmieagles" || cheat == "vcmiungoliant")
{
@ -6357,10 +6422,12 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl
}
else if (st->slot == SlotID::WAR_MACHINES_SLOT)
{
auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber);
auto warMachine = st->type->warMachine;
if (warMachine == ArtifactID::NONE)
{
logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName());
}
//catapult artifact remain even if "creature" killed in siege
else if (warMachine != ArtifactID::CATAPULT && !st->count)
{

View File

@ -143,6 +143,7 @@ public:
void removeAfterVisit(const CGObjectInstance *object) override;
bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * art);
void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override;
void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override;
void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override;
@ -223,6 +224,7 @@ public:
bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player);
void save(const std::string &fname);
void close();
void playerLeftGame(int cid);
void handleTimeEvents();
void handleTownEvents(CGTownInstance *town, NewTurn &n);
bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true
@ -298,4 +300,9 @@ private:
void checkVictoryLossConditionsForAll();
};
class clientDisconnectedException : public std::exception
{
};
void makeStackDoNothing();

View File

@ -41,11 +41,7 @@
std::string NAME_AFFIX = "server";
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
#ifndef VCMI_ANDROID
namespace intpr = boost::interprocess;
#endif
bool end2 = false;
int port = 3030;
std::atomic<bool> serverShuttingDown(false);
boost::program_options::variables_map cmdLineOptions;
@ -106,6 +102,10 @@ void CPregameServer::handleConnection(CConnection *cpc)
auto unlock = vstd::makeUnlockGuard(mx);
while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
else if(quitting) // Server must be stopped if host is leaving from lobby to avoid crash
{
serverShuttingDown = true;
}
}
}
catch (const std::exception& e)
@ -206,20 +206,29 @@ void CPregameServer::connectionAccepted(const boost::system::error_code& ec)
return;
}
logNetwork->info("We got a new connection! :)");
CConnection *pc = new CConnection(upcomingConnection, NAME);
initConnection(pc);
upcomingConnection = nullptr;
try
{
logNetwork->info("We got a new connection! :)");
std::string name = NAME;
CConnection *pc = new CConnection(upcomingConnection, name.append(" STATE_PREGAME"));
initConnection(pc);
upcomingConnection = nullptr;
startListeningThread(pc);
startListeningThread(pc);
*pc << (ui8)pc->connectionID << curmap;
*pc << (ui8)pc->connectionID << curmap;
announceTxt(pc->name + " joins the game");
auto pj = new PlayerJoined();
pj->playerName = pc->name;
pj->connectionID = pc->connectionID;
toAnnounce.push_back(pj);
announceTxt(pc->name + " joins the game");
auto pj = new PlayerJoined();
pj->playerName = pc->name;
pj->connectionID = pc->connectionID;
toAnnounce.push_back(pj);
}
catch(std::exception& e)
{
upcomingConnection = nullptr;
logNetwork->info("I guess it was just my imagination!");
}
start_async_accept();
}
@ -314,9 +323,28 @@ void CPregameServer::startListeningThread(CConnection * pc)
}
CVCMIServer::CVCMIServer()
: io(new boost::asio::io_service()), acceptor(new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))), firstConnection(nullptr)
: port(3030), io(new boost::asio::io_service()), firstConnection(nullptr), shared(nullptr)
{
logNetwork->trace("CVCMIServer created!");
if(cmdLineOptions.count("port"))
port = cmdLineOptions["port"].as<ui16>();
logNetwork->info("Port %d will be used", port);
try
{
acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port));
}
catch(...)
{
logNetwork->info("Port %d is busy, trying to use random port instead", port);
if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("enable-shm"))
{
logNetwork->error("Cant pass port number to client without shared memory!", port);
exit(0);
}
acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0));
port = acceptor->local_endpoint().port();
}
logNetwork->info("Listening for connections at port %d", port);
}
CVCMIServer::~CVCMIServer()
{
@ -394,69 +422,76 @@ void CVCMIServer::newPregame()
void CVCMIServer::start()
{
#ifndef VCMI_ANDROID
ServerReady *sr = nullptr;
intpr::mapped_region *mr;
try
if(cmdLineOptions.count("enable-shm"))
{
intpr::shared_memory_object smo(intpr::open_only,"vcmi_memory",intpr::read_write);
smo.truncate(sizeof(ServerReady));
mr = new intpr::mapped_region(smo,intpr::read_write);
sr = reinterpret_cast<ServerReady*>(mr->get_address());
}
catch(...)
{
intpr::shared_memory_object smo(intpr::create_only,"vcmi_memory",intpr::read_write);
smo.truncate(sizeof(ServerReady));
mr = new intpr::mapped_region(smo,intpr::read_write);
sr = new(mr->get_address())ServerReady();
std::string sharedMemoryName = "vcmi_memory";
if(cmdLineOptions.count("enable-shm-uuid") && cmdLineOptions.count("uuid"))
{
sharedMemoryName += "_" + cmdLineOptions["uuid"].as<std::string>();
}
shared = new SharedMemory(sharedMemoryName);
}
#endif
boost::system::error_code error;
logNetwork->info("Listening for connections at port %d", acceptor->local_endpoint().port());
auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service());
boost::thread acc(std::bind(vaccept,acceptor,s,&error));
#ifndef VCMI_ANDROID
sr->setToTrueAndNotify();
delete mr;
for (;;)
{
try
{
auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service());
boost::thread acc(std::bind(vaccept,acceptor,s,&error));
#ifdef VCMI_ANDROID
{ // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm
CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
logNetwork->info("Sending server ready message to client");
}
#else
{ // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm
CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady");
logNetwork->info("Sending server ready message to client");
}
if(shared)
{
shared->sr->setToReadyAndNotify(port);
}
#endif
acc.join();
if (error)
{
logNetwork->warnStream() << "Got connection but there is an error " << error;
return;
}
logNetwork->info("We've accepted someone... ");
firstConnection = new CConnection(s, NAME);
logNetwork->info("Got connection!");
while (!end2)
{
ui8 mode;
*firstConnection >> mode;
switch (mode)
acc.join();
if (error)
{
logNetwork->warnStream()<<"Got connection but there is an error " << error;
return;
}
logNetwork->info("We've accepted someone... ");
std::string name = NAME;
firstConnection = new CConnection(s, name.append(" STATE_WAITING"));
logNetwork->info("Got connection!");
while(!serverShuttingDown)
{
ui8 mode;
*firstConnection >> mode;
switch (mode)
{
case 0:
firstConnection->close();
exit(0);
case 1:
firstConnection->close();
return;
case 2:
newGame();
break;
case 3:
loadGame();
break;
case 4:
newPregame();
break;
}
}
break;
}
catch(std::exception& e)
{
case 0:
firstConnection->close();
exit(0);
case 1:
firstConnection->close();
return;
case 2:
newGame();
break;
case 3:
loadGame();
break;
case 4:
newPregame();
break;
vstd::clear_pointer(firstConnection);
logNetwork->info("I guess it was just my imagination!");
}
}
}
@ -507,7 +542,11 @@ static void handleCommandOptions(int argc, char *argv[])
opts.add_options()
("help,h", "display help and exit")
("version,v", "display version information and exit")
("port", po::value<int>()->default_value(3030), "port at which server will listen to connections from client")
("run-by-client", "indicate that server launched by client on same machine")
("uuid", po::value<std::string>(), "")
("enable-shm-uuid", "use UUID for shared memory identifier")
("enable-shm", "enable usage of shared memory")
("port", po::value<ui16>(), "port at which server will listen to connections from client")
("resultsFile", po::value<std::string>()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode.");
if(argc > 1)
@ -584,12 +623,7 @@ int main(int argc, char** argv)
logConfig.configureDefault();
logGlobal->info(NAME);
handleCommandOptions(argc, argv);
if (cmdLineOptions.count("port"))
port = cmdLineOptions["port"].as<int>();
logNetwork->info("Port %d will be used.", port);
preinitDLL(console);
settings.init();
logConfig.configure();
@ -603,7 +637,7 @@ int main(int argc, char** argv)
try
{
while (!end2)
while(!serverShuttingDown)
{
server.start();
}
@ -612,7 +646,7 @@ int main(int argc, char** argv)
catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
{
logNetwork->error(e.what());
end2 = true;
serverShuttingDown = true;
}
catch (...)
{
@ -630,11 +664,9 @@ int main(int argc, char** argv)
CAndroidVMHelper envHelper;
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer");
#endif
delete VLC;
VLC = nullptr;
vstd::clear_pointer(VLC);
CResourceHandler::clear();
return 0;
return 0;
}
#ifdef VCMI_ANDROID

View File

@ -17,6 +17,7 @@ class CMapInfo;
class CConnection;
struct CPackForSelectionScreen;
class CGameHandler;
struct SharedMemory;
namespace boost
{
@ -43,8 +44,10 @@ typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::s
class CVCMIServer
{
ui16 port;
boost::asio::io_service *io;
TAcceptor * acceptor;
SharedMemory * shared;
CConnection *firstConnection;
public:
@ -73,7 +76,6 @@ public:
std::list<CPackForSelectionScreen*> toAnnounce;
boost::recursive_mutex mx;
//std::vector<CMapInfo> maps;
TAcceptor *acceptor;
TSocket *upcomingConnection;

View File

@ -65,6 +65,12 @@ bool CloseServer::applyGh( CGameHandler *gh )
return true;
}
bool LeaveGame::applyGh( CGameHandler *gh )
{
gh->playerLeftGame(c->connectionID);
return true;
}
bool EndTurn::applyGh( CGameHandler *gh )
{
PlayerColor player = GS(gh)->currentPlayer;
@ -277,8 +283,11 @@ bool CastAdvSpell::applyGh( CGameHandler *gh )
bool PlayerMessage::applyGh( CGameHandler *gh )
{
ERROR_IF_NOT(player);
if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN;
if(!player.isSpectator()) // TODO: clearly not a great way to verify permissions
{
ERROR_IF_NOT(player);
if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN;
}
gh->playerMessage(player,text, currObj);
return true;
}

View File

@ -115,9 +115,10 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View)
logGlobal->info("CMapEditManager_DrawTerrain_View start");
try
{
const ResourceID testMap("test/TerrainViewTest", EResType::MAP);
// Load maps and json config
const auto originalMap = CMapService::loadMap("test/TerrainViewTest");
auto map = CMapService::loadMap("test/TerrainViewTest");
const auto originalMap = CMapService::loadMap(testMap);
auto map = CMapService::loadMap(testMap);
logGlobal->info("Loaded test map successfully.");
// Validate edit manager