1 /*
2  *  Copyright (C) 2011-2016  OpenDungeons Team
3  *
4  *  This program is free software: you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation, either version 3 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "network/ODServer.h"
19 
20 #include "ai/KeeperAIType.h"
21 #include "entities/Creature.h"
22 #include "entities/CreatureDefinition.h"
23 #include "entities/GameEntityType.h"
24 #include "entities/MapLight.h"
25 #include "entities/Tile.h"
26 #include "entities/Weapon.h"
27 #include "game/Player.h"
28 #include "game/Skill.h"
29 #include "game/SkillManager.h"
30 #include "game/SkillType.h"
31 #include "game/Seat.h"
32 #include "gamemap/GameMap.h"
33 #include "gamemap/MapHandler.h"
34 #include "modes/ConsoleCommands.h"
35 #include "network/ODClient.h"
36 #include "network/ServerMode.h"
37 #include "network/ServerNotification.h"
38 #include "rooms/RoomManager.h"
39 #include "rooms/RoomType.h"
40 #include "spells/SpellManager.h"
41 #include "spells/SpellType.h"
42 #include "traps/Trap.h"
43 #include "traps/TrapType.h"
44 #include "traps/TrapManager.h"
45 #include "utils/ConfigManager.h"
46 #include "utils/Helper.h"
47 #include "utils/LogManager.h"
48 #include "utils/MasterServer.h"
49 #include "utils/ResourceManager.h"
50 #include "ODApplication.h"
51 
52 #include <SFML/Network.hpp>
53 #include <SFML/System.hpp>
54 
55 #include <boost/algorithm/string/join.hpp>
56 #include <boost/date_time/posix_time/posix_time.hpp>
57 #include <boost/filesystem.hpp>
58 #include <boost/lexical_cast.hpp>
59 
60 
61 const std::string SAVEGAME_SKIRMISH_PREFIX = "SK-";
62 const std::string SAVEGAME_MULTIPLAYER_PREFIX = "MP-";
63 static const double MASTER_SERVER_UPDATE_PERIOD_MS = 30000.0;
64 static const int32_t MASTER_SERVER_STATUS_PENDING = 0;
65 static const int32_t MASTER_SERVER_STATUS_STARTED = 1;
66 static const int32_t MASTER_SERVER_STATUS_FINISHED = 2;
67 
68 template<> ODServer* Ogre::Singleton<ODServer>::msSingleton = nullptr;
69 
ODServer()70 ODServer::ODServer() :
71     mUniqueNumberPlayer(0),
72     mServerMode(ServerMode::ModeNone),
73     mServerState(ServerState::StateNone),
74     mGameMap(new GameMap(true)),
75     mSeatsConfigured(false),
76     mPlayerConfig(nullptr),
77     mConsoleInterface(std::bind(&ODServer::printConsoleMsg, this, std::placeholders::_1)),
78     mMasterServerGameStatusUpdateTime(0)
79 {
80     ConsoleCommands::addConsoleCommands(mConsoleInterface);
81 }
82 
~ODServer()83 ODServer::~ODServer()
84 {
85     delete mGameMap;
86 }
87 
startServer(const std::string & creator,const std::string & levelFilename,ServerMode mode,bool useMasterServer)88 bool ODServer::startServer(const std::string& creator, const std::string& levelFilename, ServerMode mode, bool useMasterServer)
89 {
90     OD_LOG_INF("Asked to launch server with levelFilename=" + levelFilename);
91 
92     mSeatsConfigured = false;
93     mDisconnectedPlayers.clear();
94     mMasterServerGameId.clear();
95     mMasterServerGameStatusUpdateTime = 0.0;
96     mPlayerConfig = nullptr;
97 
98     // Start the server socket listener as well as the server socket thread
99     if (isConnected())
100     {
101         OD_LOG_INF("Couldn't start server: The server is already connected");
102         return false;
103     }
104     if ((ODClient::getSingletonPtr() != nullptr) &&
105         ODClient::getSingleton().isConnected())
106     {
107         OD_LOG_INF("Couldn't start server: The client is already connected");
108         return false;
109     }
110 
111     // Read in the map. The map loading should be happen here and not in the server thread to
112     // make sure it is valid before launching the server.
113     mServerMode = mode;
114     mServerState = ServerState::StateConfiguration;
115     mUniqueNumberPlayer = 0;
116     GameMap* gameMap = mGameMap;
117     if (!gameMap->loadLevel(levelFilename))
118     {
119         mServerMode = ServerMode::ModeNone;
120         mServerState = ServerState::StateNone;
121         OD_LOG_INF("Couldn't start server. The level file can't be loaded: " + levelFilename);
122         stopServer();
123         return false;
124     }
125 
126     // Set up the socket to listen on the specified port
127     int32_t port = getNetworkPort();
128     if (!createServer(port))
129     {
130         mServerMode = ServerMode::ModeNone;
131         mServerState = ServerState::StateNone;
132         OD_LOG_ERR("Server could not create server socket!");
133         stopServer();
134         return false;
135     }
136 
137     // We configure what is fixed (fixed AI, faction or team). While iterating seats, we keep in mind if there is
138     // at least a human only seat. If yes, we configure all player type choosable to AI. If not, we configure all player
139     // type choosable to AI except the first one.
140     uint32_t nbSeatsHuman = 0;
141     const std::vector<std::string>& factions = ConfigManager::getSingleton().getFactions();
142     for(Seat* seat : gameMap->getSeats())
143     {
144         if(seat->isRogueSeat())
145             continue;
146 
147         // Player faction
148         if(seat->getFaction().compare(Seat::PLAYER_FACTION_CHOICE) != 0)
149         {
150             uint32_t cptFaction = 0;
151             for(const std::string& faction : factions)
152             {
153                 if(seat->getFaction().compare(faction) == 0)
154                     break;
155 
156                 ++cptFaction;
157             }
158             // If the faction is not found, we set it to the first defined
159             if(cptFaction >= factions.size())
160                 cptFaction = 0;
161 
162             seat->setConfigFactionIndex(cptFaction);
163         }
164 
165         // Player type
166         if(seat->getPlayerType().compare(Seat::PLAYER_TYPE_INACTIVE) == 0)
167             seat->setConfigPlayerId(Seat::PLAYER_TYPE_INACTIVE_ID);
168         else if(seat->getPlayerType().compare(Seat::PLAYER_TYPE_AI) == 0)
169             seat->setConfigPlayerId(Seat::aITypeToPlayerId(KeeperAIType::normal));
170         else if(seat->getPlayerType().compare(Seat::PLAYER_TYPE_HUMAN) == 0)
171             ++nbSeatsHuman;
172 
173         // Player team
174         const std::vector<int>& availableTeamIds = seat->getAvailableTeamIds();
175         if(availableTeamIds.size() == 1)
176         {
177             seat->setConfigTeamId(availableTeamIds.front());
178         }
179     }
180 
181     if(useMasterServer)
182     {
183         LevelInfo info;
184         if(!MapHandler::getMapInfo(levelFilename, info))
185         {
186             info.mLevelName = "No name";
187             info.mLevelDescription = "No description";
188         }
189 
190         const std::string& label = info.mLevelName;
191         const std::string& descr = info.mLevelDescription;
192         std::string uuid;
193         if(!MasterServer::registerGame(ODApplication::VERSION, creator, port, label, descr, uuid))
194         {
195             OD_LOG_ERR("Could not register the game in the master server !!!");
196             stopServer();
197             return false;
198         }
199 
200         mMasterServerGameId = uuid;
201     }
202 
203     // In single player, we use a default value for seats that can be chosen
204     if(mServerMode != ServerMode::ModeGameSinglePlayer)
205         return true;
206 
207     for(Seat* seat : gameMap->getSeats())
208     {
209         if(seat->isRogueSeat())
210             continue;
211 
212         // Player faction to the first one defined
213         if(seat->getFaction().compare(Seat::PLAYER_FACTION_CHOICE) == 0)
214             seat->setConfigFactionIndex(0);
215 
216         // Player type to AI
217         if(seat->getPlayerType().compare(Seat::PLAYER_TYPE_CHOICE) == 0)
218         {
219             // We set default AI value
220             if(nbSeatsHuman == 0)
221                 ++nbSeatsHuman;
222             else
223                 seat->setConfigPlayerId(Seat::aITypeToPlayerId(KeeperAIType::normal));
224         }
225 
226         // Player team to first one available
227         const std::vector<int>& availableTeamIds = seat->getAvailableTeamIds();
228         if(availableTeamIds.size() > 1)
229             seat->setConfigTeamId(availableTeamIds.front());
230     }
231 
232     return true;
233 }
234 
queueServerNotification(ServerNotification * n)235 void ODServer::queueServerNotification(ServerNotification* n)
236 {
237     if ((n == nullptr) || (!isConnected()))
238     {
239         delete n;
240         return;
241     }
242     mServerNotificationQueue.push_back(n);
243 }
244 
sendAsyncMsg(ServerNotification & notif)245 void ODServer::sendAsyncMsg(ServerNotification& notif)
246 {
247     sendMsg(notif.mConcernedPlayer, notif.mPacket);
248 }
249 
sendMsg(Player * player,ODPacket & packet)250 void ODServer::sendMsg(Player* player, ODPacket& packet)
251 {
252     if(player == nullptr)
253     {
254         // If player is nullptr, we send the message to every connected player
255         for (ODSocketClient* client : mSockClients)
256             client->send(packet);
257 
258         return;
259     }
260 
261     ODSocketClient* client = getClientFromPlayer(player);
262     if((client == nullptr) &&
263        (std::find(mDisconnectedPlayers.begin(), mDisconnectedPlayers.end(), player) == mDisconnectedPlayers.end()))
264     {
265         ServerNotificationType type;
266         OD_ASSERT_TRUE(packet >> type);
267         OD_ASSERT_TRUE_MSG(client != nullptr, "player=" + player->getNick()
268             + ", ServerNotificationType=" + ServerNotification::typeString(type));
269         return;
270     }
271 
272     if(client != nullptr)
273         client->send(packet);
274 }
275 
handleConsoleCommand(Player * player,GameMap * gameMap,const std::vector<std::string> & args)276 void ODServer::handleConsoleCommand(Player* player, GameMap* gameMap, const std::vector<std::string>& args)
277 {
278     if(args.empty())
279     {
280         OD_LOG_WRN("Invalid empty console command");
281         return;
282     }
283 
284     if(mConsoleInterface.tryExecuteServerCommand(args, *gameMap) != Command::Result::SUCCESS)
285     {
286         std::string msg = "Cannot execute console command";
287         for(const std::string& str : args)
288         {
289             msg += " " + str;
290         }
291         OD_LOG_WRN(msg);
292         return;
293     }
294 
295     // We notify all players that a console command has been executed
296     ServerNotification *serverNotification = new ServerNotification(
297         ServerNotificationType::chatServer, nullptr);
298 
299     std::string msg = "Console cmd launched: " + args[0];
300     serverNotification->mPacket << msg << EventShortNoticeType::genericGameInfo;
301     ODServer::getSingleton().queueServerNotification(serverNotification);
302 }
303 
startNewTurn(double timeSinceLastTurn)304 void ODServer::startNewTurn(double timeSinceLastTurn)
305 {
306     GameMap* gameMap = mGameMap;
307     int64_t turn = gameMap->getTurnNumber();
308 
309     // We wait until every client acknowledge the turn to start the next one. This way, we ensure
310     // synchronisation is not too bad
311     for (ODSocketClient* client : mSockClients)
312     {
313         if(client->getLastTurnAck() != turn)
314             return;
315     }
316 
317     gameMap->setTurnNumber(++turn);
318 
319     ServerNotification* serverNotification = new ServerNotification(
320         ServerNotificationType::turnStarted, nullptr);
321     serverNotification->mPacket << turn;
322     queueServerNotification(serverNotification);
323 
324     if(mServerMode == ServerMode::ModeEditor)
325         gameMap->updateVisibleEntities();
326 
327     gameMap->updateAnimations(timeSinceLastTurn);
328 
329     // We notify the clients about what they got
330     for (ODSocketClient* sock : mSockClients)
331     {
332         Player* player = sock->getPlayer();
333         // For now, only the player whose seat changed is notified. If we need it, we could send the event to every player
334         // so that they can see how far from the goals the other players are
335         ServerNotification *serverNotification = new ServerNotification(
336             ServerNotificationType::refreshPlayerSeat, player);
337         std::string goals = gameMap->getGoalsStringForPlayer(player);
338         Seat* seat = player->getSeat();
339         seat->exportToPacketForUpdate(serverNotification->mPacket);
340         serverNotification->mPacket << goals;
341         ODServer::getSingleton().queueServerNotification(serverNotification);
342 
343         // Here, the creature list is pulled. It could be possible that the creature dies before the stat window is
344         // closed. So, if we cannot find the creature, we just erase it.
345         std::vector<std::string>& creatures = mCreaturesInfoWanted[sock];
346         std::vector<std::string>::iterator itCreatures = creatures.begin();
347         while(itCreatures != creatures.end())
348         {
349             std::string& name = *itCreatures;
350             Creature* creature = gameMap->getCreature(name);
351             if(creature == nullptr)
352                 itCreatures = creatures.erase(itCreatures);
353             else
354             {
355                 std::string creatureInfos = creature->getStatsText();
356 
357                 ServerNotification *serverNotification = new ServerNotification(
358                     ServerNotificationType::notifyCreatureInfo, player);
359                 serverNotification->mPacket << name << creatureInfos;
360                 ODServer::getSingleton().queueServerNotification(serverNotification);
361 
362                 ++itCreatures;
363             }
364         }
365     }
366 
367     gameMap->updateVisibleEntities();
368     switch(mServerMode)
369     {
370         case ServerMode::ModeGameSinglePlayer:
371         case ServerMode::ModeGameMultiPlayer:
372         case ServerMode::ModeGameLoaded:
373         {
374             gameMap->doTurn(timeSinceLastTurn);
375             gameMap->doPlayerAITurn(timeSinceLastTurn);
376             break;
377         }
378         case ServerMode::ModeEditor:
379             // We do not update turn in editor mode to not have creatures wander
380             break;
381         case ServerMode::ModeNone:
382             // It is not normal to have no mode selected and starting turns
383             OD_LOG_ERR("Wrong none server mode");
384             break;
385         default:
386             break;
387     }
388 
389     gameMap->fireRefreshEntities();
390     gameMap->processDeletionQueues();
391 }
392 
serverThread()393 void ODServer::serverThread()
394 {
395     GameMap* gameMap = mGameMap;
396     sf::Clock clock;
397     double turnLengthMs = 1000.0 / ODApplication::turnsPerSecond;
398     bool isClientConnected = true;
399     while(isConnected() && isClientConnected)
400     {
401         // doTask should return after the length of 1 turn even if their are communications. When
402         // it returns, we can launch next turn.
403         doTask(static_cast<int32_t>(turnLengthMs));
404         // If all the clients are disconnected during a game, we close the server
405         if((mServerState == ServerState::StateGame) &&
406            (mSockClients.empty()))
407         {
408             // Time to stop the game
409             isClientConnected = false;
410             continue;
411         }
412 
413         if(gameMap->getTurnNumber() == -1)
414         {
415             // The game is not started
416             if(mSeatsConfigured)
417             {
418                 // We notify the master server that we are not waiting for players anymore
419                 if(!mMasterServerGameId.empty())
420                 {
421                     mMasterServerGameStatusUpdateTime = 0.0;
422                     MasterServer::updateGame(mMasterServerGameId, MASTER_SERVER_STATUS_STARTED);
423                 }
424 
425                 // We configure the game for launching
426                 const std::vector<Seat*>& seats = gameMap->getSeats();
427                 for (int jj = 0; jj < gameMap->getMapSizeY(); ++jj)
428                 {
429                     for (int ii = 0; ii < gameMap->getMapSizeX(); ++ii)
430                     {
431                         Tile* tile = gameMap->getTile(ii,jj);
432                         tile->setSeats(seats);
433                     }
434                 }
435 
436                 // We set allied seats
437                 for(Seat* seat : seats)
438                 {
439                     for(Seat* alliedSeat : seats)
440                     {
441                         if(alliedSeat == seat)
442                             continue;
443                         if(!seat->isAlliedSeat(alliedSeat))
444                             continue;
445                         seat->addAlliedSeat(alliedSeat);
446                     }
447                 }
448 
449                 // Every client is connected and ready, we can launch the game
450                 // Send turn 0 to init the map
451                 ServerNotification* serverNotification = new ServerNotification(
452                     ServerNotificationType::turnStarted, nullptr);
453                 serverNotification->mPacket << static_cast<int64_t>(0);
454                 queueServerNotification(serverNotification);
455 
456                 OD_LOG_INF("Server ready, starting game");
457                 gameMap->setTurnNumber(0);
458                 gameMap->setGamePaused(false);
459 
460                 // In editor mode, we give vision on all the gamemap tiles
461                 if(mServerMode == ServerMode::ModeEditor)
462                 {
463                     for (Seat* seat : gameMap->getSeats())
464                     {
465                         for (int jj = 0; jj < gameMap->getMapSizeY(); ++jj)
466                         {
467                             for (int ii = 0; ii < gameMap->getMapSizeX(); ++ii)
468                             {
469                                 gameMap->getTile(ii,jj)->notifyVision(seat);
470                             }
471                         }
472 
473                         seat->sendVisibleTiles();
474                     }
475                 }
476 
477                 gameMap->createAllEntities();
478 
479                 // Fill starting gold
480                 for(Seat* seat : gameMap->getSeats())
481                 {
482                     if(seat->getPlayer() == nullptr)
483                         continue;
484 
485                     if(seat->getGold() > 0)
486                         gameMap->addGoldToSeat(seat->getGold(), seat->getId());
487                 }
488             }
489             else
490             {
491                 // We are still waiting for players
492                 if(!mMasterServerGameId.empty())
493                 {
494                     mMasterServerGameStatusUpdateTime += turnLengthMs;
495                     if(mMasterServerGameStatusUpdateTime >= MASTER_SERVER_UPDATE_PERIOD_MS)
496                     {
497                         mMasterServerGameStatusUpdateTime = 0.0;
498                         MasterServer::updateGame(mMasterServerGameId, MASTER_SERVER_STATUS_PENDING);
499                     }
500                 }
501                 continue;
502             }
503         }
504 
505         // After starting a new turn, we should process server notifications
506         // before processing client messages. Otherwise, we could have weird issues
507         // like allow picking up a dead creature for example.
508         // We make sure the server time is a little bit late regarding the clients to
509         // make sure server is not more advanced than clients. We do that because it is better for clients
510         // to wait for server. If server is in advance, he might send commands before the
511         // creatures arrive at their destination. That could result in weird issues like
512         // creatures going through walls.
513         startNewTurn(static_cast<double>(clock.restart().asSeconds()) * 0.95);
514 
515         processServerNotifications();
516     }
517 
518     if(!mMasterServerGameId.empty())
519     {
520         mMasterServerGameStatusUpdateTime = 0.0;
521         MasterServer::updateGame(mMasterServerGameId, MASTER_SERVER_STATUS_FINISHED);
522     }
523 }
524 
processServerNotifications()525 void ODServer::processServerNotifications()
526 {
527     GameMap* gameMap = mGameMap;
528 
529     bool running = true;
530 
531     while (running)
532     {
533         // If the queue is empty, let's get out of the loop.
534         if (mServerNotificationQueue.empty())
535             break;
536 
537         // Take a message out of the front of the notification queue
538         ServerNotification *event = mServerNotificationQueue.front();
539         mServerNotificationQueue.pop_front();
540 
541         if(event == nullptr)
542         {
543             OD_LOG_ERR("unexpected null event");
544             continue;
545         }
546 
547         OD_LOG_DBG("processServerNotifications type=" + ServerNotification::typeString(event->mType));
548         switch (event->mType)
549         {
550             case ServerNotificationType::turnStarted:
551                 OD_LOG_INF("Server sends newturn="
552                     + boost::lexical_cast<std::string>(gameMap->getTurnNumber()));
553                 sendMsg(event->mConcernedPlayer, event->mPacket);
554                 break;
555 
556             case ServerNotificationType::entityPickedUp:
557                 // This message should not be sent by human players (they are notified asynchronously)
558                 OD_ASSERT_TRUE_MSG(event->mConcernedPlayer->getIsHuman(), "nick=" + event->mConcernedPlayer->getNick());
559                 sendMsg(event->mConcernedPlayer, event->mPacket);
560                 break;
561 
562             case ServerNotificationType::entityDropped:
563                 // This message should not be sent by human players (they are notified asynchronously)
564                 OD_ASSERT_TRUE_MSG(event->mConcernedPlayer->getIsHuman(), "nick=" + event->mConcernedPlayer->getNick());
565                 sendMsg(event->mConcernedPlayer, event->mPacket);
566                 break;
567 
568             case ServerNotificationType::entitySlapped:
569                 // This message should not be sent by human players (they are notified asynchronously)
570                 OD_ASSERT_TRUE_MSG(!event->mConcernedPlayer->getIsHuman(), "nick=" + event->mConcernedPlayer->getNick());
571                 sendMsg(event->mConcernedPlayer, event->mPacket);
572                 break;
573 
574             case ServerNotificationType::exit:
575                 running = false;
576                 stopServer();
577                 break;
578 
579             default:
580                 sendMsg(event->mConcernedPlayer, event->mPacket);
581                 break;
582         }
583 
584         delete event;
585         event = nullptr;
586     }
587 }
588 
processClientNotifications(ODSocketClient * clientSocket)589 bool ODServer::processClientNotifications(ODSocketClient* clientSocket)
590 {
591     if (!clientSocket)
592         return false;
593 
594     GameMap* gameMap = mGameMap;
595 
596     ODPacket packetReceived;
597 
598     ODSocketClient::ODComStatus status = clientSocket->recv(packetReceived);
599 
600     // If the client closed the connection
601     if (status != ODSocketClient::ODComStatus::OK)
602     {
603         // If a client disconnects during seat configuration, we delete him from the list
604         if(mServerState != ServerState::StateConfiguration)
605             return (status != ODSocketClient::ODComStatus::Error);
606 
607         // If the client is in a state where he has been notified to the other clients,
608         // we notify his deconnexion
609         if(std::string("ready").compare(clientSocket->getState()) != 0)
610             return (status != ODSocketClient::ODComStatus::Error);
611 
612         OD_LOG_INF("Disconnected player: " + clientSocket->getPlayer()->getNick());
613         // We notify
614         uint32_t nbPlayers = 1;
615         ODPacket packetSend;
616         packetSend << ServerNotificationType::removePlayers << nbPlayers;
617         int32_t id = clientSocket->getPlayer()->getId();
618         packetSend << id;
619         sendMsg(nullptr, packetSend);
620 
621         ODSocketClient* otherHumanConnected = nullptr;
622         for(Seat* seat : gameMap->getSeats())
623         {
624             if(seat->getConfigPlayerId() == id)
625             {
626                 seat->setConfigPlayerId(-1);
627                 if(clientSocket->getPlayer() == mPlayerConfig)
628                     mPlayerConfig = nullptr;
629             }
630 
631             if(seat->getConfigPlayerId() < Seat::PLAYER_ID_HUMAN_MIN)
632                 continue;
633 
634             otherHumanConnected = getClientFromPlayerId(seat->getConfigPlayerId());
635         }
636         if((mPlayerConfig == nullptr) &&
637            (otherHumanConnected != nullptr))
638         {
639             mPlayerConfig = otherHumanConnected->getPlayer();
640             ODPacket packetSend;
641             packetSend << ServerNotificationType::playerConfigChange;
642             otherHumanConnected->send(packetSend);
643 
644             OD_LOG_INF("Changing game host to " + mPlayerConfig->getNick());
645         }
646         return (status != ODSocketClient::ODComStatus::Error);
647     }
648 
649     ClientNotificationType clientCommand;
650     OD_ASSERT_TRUE(packetReceived >> clientCommand);
651 
652     OD_LOG_DBG("processClientNotifications type=" + ClientNotification::typeString(clientCommand));
653     switch(clientCommand)
654     {
655         case ClientNotificationType::hello:
656         {
657             if(std::string("connected").compare(clientSocket->getState()) != 0)
658                 return false;
659             std::string version;
660             OD_ASSERT_TRUE(packetReceived >> version);
661 
662             // If the version is different, we refuse the client
663             if(version.compare(std::string("OpenDungeons V ") + ODApplication::VERSION) != 0)
664             {
665                 OD_LOG_INF("Server rejected client. Application version mismatch: required= "
666                     + ODApplication::VERSION + ", received=" + version);
667                 return false;
668             }
669 
670             // Tell the client to load the given map
671             OD_LOG_INF("Level sent to client: " + gameMap->getLevelName());
672             clientSocket->setState("loadLevel");
673             int32_t mapSizeX = gameMap->getMapSizeX();
674             int32_t mapSizeY = gameMap->getMapSizeY();
675 
676             ODPacket packet;
677             packet << ServerNotificationType::loadLevel;
678             packet << version;
679             packet << mapSizeX << mapSizeY;
680             // Map infos
681             packet << gameMap->getLevelName();
682             packet << gameMap->getLevelDescription();
683             packet << gameMap->getLevelMusicFile();
684             packet << gameMap->getLevelFightMusicFile();
685 
686             packet << gameMap->getTileSetName();
687 
688             int32_t nb;
689             // Seats
690             const std::vector<Seat*>& seats = gameMap->getSeats();
691             nb = seats.size();
692             packet << nb;
693             for(Seat* seat : seats)
694                 seat->exportToPacket(packet);
695 
696             // Creature definitions
697             nb = gameMap->numClassDescriptions();
698             packet << nb;
699             for(int32_t i = 0; i < nb; ++i)
700             {
701                 const CreatureDefinition* def = gameMap->getClassDescription(i);
702                 packet << def;
703             }
704 
705             // Weapons
706             nb = gameMap->numWeapons();
707             packet << nb;
708             for(int32_t i = 0; i < nb; ++i)
709             {
710                 const Weapon* def = gameMap->getWeapon(i);
711                 packet << def;
712             }
713 
714             // Tiles
715             std::vector<Tile*> goldTiles;
716             std::vector<Tile*> rockTiles;
717             std::vector<Tile*> gemTiles;
718             for (int xxx = 0; xxx < mapSizeX; ++xxx)
719             {
720                 for (int yyy = 0; yyy < mapSizeY; ++yyy)
721                 {
722                     Tile* tile = gameMap->getTile(xxx,yyy);
723                     switch(tile->getType())
724                     {
725                         case TileType::gold:
726                             goldTiles.push_back(tile);
727                             break;
728                         case TileType::rock:
729                             rockTiles.push_back(tile);
730                             break;
731                         case TileType::gem:
732                             gemTiles.push_back(tile);
733                             break;
734                         default:
735                             // Per default, tiles are dirt and don't need to be notified
736                             break;
737                     }
738                 }
739             }
740 
741             nb = goldTiles.size();
742             packet << nb;
743             for(Tile* tile : goldTiles)
744             {
745                 gameMap->tileToPacket(packet, tile);
746             }
747 
748             nb = rockTiles.size();
749             packet << nb;
750             for(Tile* tile : rockTiles)
751             {
752                 gameMap->tileToPacket(packet, tile);
753             }
754 
755             nb = gemTiles.size();
756             packet << nb;
757             for(Tile* tile : gemTiles)
758             {
759                 gameMap->tileToPacket(packet, tile);
760             }
761 
762             clientSocket->send(packet);
763             break;
764         }
765 
766         case ClientNotificationType::levelOK:
767         {
768             if(std::string("loadLevel").compare(clientSocket->getState()) != 0)
769                 return false;
770 
771             clientSocket->setState("nick");
772             // Tell the client to give us their nickname
773             ODPacket packetSend;
774             packetSend << ServerNotificationType::pickNick << mServerMode;
775             clientSocket->send(packetSend);
776             break;
777         }
778 
779         case ClientNotificationType::setNick:
780         {
781             if(std::string("nick").compare(clientSocket->getState()) != 0)
782                 return false;
783 
784             // Pick nick
785             std::string clientNick;
786             OD_ASSERT_TRUE(packetReceived >> clientNick);
787 
788             // NOTE : playerId 0 is reserved for inactive players and 1 is reserved for AI
789             int32_t playerId = mUniqueNumberPlayer + Seat::PLAYER_ID_HUMAN_MIN;
790             mUniqueNumberPlayer++;
791             Player* curPlayer = new Player(gameMap, playerId);
792             curPlayer->setNick(clientNick);
793             curPlayer->setIsHuman(true);
794             clientSocket->setPlayer(curPlayer);
795             clientSocket->setState("ready");
796 
797             OD_LOG_INF("Player id: " + Helper::toString(playerId) + " nickname is: " + clientNick);
798 
799             if(mServerMode != ServerMode::ModeEditor)
800                 break;
801 
802             // On editor mode, we configure automatically seats
803             mServerState = ServerState::StateGame;
804             const std::vector<Seat*>& seats = gameMap->getSeats();
805             if(seats.empty())
806             {
807                 OD_LOG_ERR("unexpected empty seats in gamemap");
808                 break;
809             }
810 
811             // By default, the first player to connect is the one allowed to configure game
812             if(mPlayerConfig == nullptr)
813             {
814                 mPlayerConfig = curPlayer;
815                 ODPacket packetSend;
816                 packetSend << ServerNotificationType::playerConfigChange;
817                 clientSocket->send(packetSend);
818             }
819 
820             Seat* seat = seats[0];
821             seat->setPlayer(curPlayer);
822             //This makes sure the player is deleted on exit.
823             gameMap->addPlayer(curPlayer);
824             ODPacket packetSend;
825             packetSend << ServerNotificationType::clientAccepted << ODApplication::turnsPerSecond;
826             int32_t nbPlayers = 1;
827             packetSend << nbPlayers;
828             const std::string& nick = clientSocket->getPlayer()->getNick();
829             int32_t id = clientSocket->getPlayer()->getId();
830             int32_t seatId = seat->getId();
831             int32_t teamId = 0;
832             seat->setMapSize(gameMap->getMapSizeX(), gameMap->getMapSizeY());
833             packetSend << nick << id << seatId << teamId;
834             clientSocket->send(packetSend);
835 
836             packetSend.clear();
837             packetSend << ServerNotificationType::startGameMode << seatId << mServerMode;
838             clientSocket->send(packetSend);
839             mSeatsConfigured = true;
840             break;
841         }
842 
843         case ClientNotificationType::readyForSeatConfiguration:
844         {
845             if(std::string("ready").compare(clientSocket->getState()) != 0)
846                 return false;
847 
848             // By default, the first player to connect is the one allowed to configure game
849             if(mPlayerConfig == nullptr)
850             {
851                 mPlayerConfig = clientSocket->getPlayer();
852                 OD_LOG_INF("New player host: " + mPlayerConfig->getNick());
853                 ODPacket packetSend;
854                 packetSend << ServerNotificationType::playerConfigChange;
855                 clientSocket->send(packetSend);
856             }
857 
858             ODPacket packetSend;
859             OD_LOG_INF("New player: " + clientSocket->getPlayer()->getNick());
860             // We notify to the newly connected player all the currently connected players (including himself)
861             uint32_t nbPlayers = mSockClients.size();
862             packetSend << ServerNotificationType::addPlayers << nbPlayers;
863             for (ODSocketClient* client : mSockClients)
864             {
865                 const std::string& nick = client->getPlayer()->getNick();
866                 int32_t id = client->getPlayer()->getId();
867                 packetSend << nick << id;
868             }
869             clientSocket->send(packetSend);
870 
871             // Then, we notify the newly connected client to every client
872             const std::string& clientNick = clientSocket->getPlayer()->getNick();
873             int32_t clientPlayerId = clientSocket->getPlayer()->getId();
874             packetSend.clear();
875             nbPlayers = 1;
876             packetSend << ServerNotificationType::addPlayers << nbPlayers;
877             packetSend << clientNick << clientPlayerId;
878             for (ODSocketClient* client : mSockClients)
879             {
880                 if(clientSocket == client)
881                     continue;
882 
883                 client->send(packetSend);
884             }
885 
886             // Then we look for the first available human seat and assign the player there (if available)
887             Seat* seatToUse = nullptr;
888             // If we find an unused human only seat, we use it. If not, we take the first choosable
889             for(Seat* seat : gameMap->getSeats())
890             {
891                 if(seat->isRogueSeat())
892                     continue;
893                 if((seat->getPlayerType().compare(Seat::PLAYER_TYPE_HUMAN) != 0) &&
894                    (seat->getPlayerType().compare(Seat::PLAYER_TYPE_CHOICE) != 0))
895                 {
896                     continue;
897                 }
898 
899                 if(seat->getConfigPlayerId() != -1)
900                     continue;
901 
902                 //Suitable seat
903                 if(seat->getPlayerType().compare(Seat::PLAYER_TYPE_HUMAN) == 0)
904                 {
905                     seatToUse = seat;
906                     break;
907                 }
908 
909                 if(seatToUse == nullptr)
910                     seatToUse = seat;
911             }
912 
913             if(seatToUse != nullptr)
914             {
915                 OD_LOG_INF("Player: " + clientSocket->getPlayer()->getNick() + " on seat " + Helper::toString(seatToUse->getId()));
916                 seatToUse->setConfigPlayerId(clientSocket->getPlayer()->getId());
917             }
918             fireSeatConfigurationRefresh();
919 
920             break;
921         }
922 
923         case ClientNotificationType::seatConfigurationRefresh:
924         {
925             const std::vector<std::string>& factions = ConfigManager::getSingleton().getFactions();
926             for(Seat* seat : gameMap->getSeats())
927             {
928                 // Rogue seat do not have to be configured
929                 if(seat->isRogueSeat())
930                     continue;
931 
932                 int seatId;
933                 bool isSet;
934                 OD_ASSERT_TRUE(packetReceived >> seatId);
935                 OD_ASSERT_TRUE_MSG(seatId == seat->getId(), "seatId=" + Helper::toString(seatId) + ", seat->getId()=" + Helper::toString(seat->getId()));
936 
937                 OD_ASSERT_TRUE(packetReceived >> isSet);
938                 int32_t factionIndex = -1;
939                 if(isSet)
940                 {
941                     OD_ASSERT_TRUE(packetReceived >> factionIndex);
942                     if(static_cast<uint32_t>(factionIndex) >= factions.size())
943                         factionIndex = -1;
944                 }
945                 seat->setConfigFactionIndex(factionIndex);
946 
947                 OD_ASSERT_TRUE(packetReceived >> isSet);
948                 int32_t playerId = -1;
949                 if(isSet)
950                 {
951                     OD_ASSERT_TRUE(packetReceived >> playerId);
952                 }
953                 seat->setConfigPlayerId(playerId);
954 
955                 OD_ASSERT_TRUE(packetReceived >> isSet);
956                 int32_t teamId = -1;
957                 if(isSet)
958                 {
959                     OD_ASSERT_TRUE(packetReceived >> teamId);
960                 }
961                 seat->setConfigTeamId(teamId);
962             }
963             fireSeatConfigurationRefresh();
964             break;
965         }
966 
967         case ClientNotificationType::seatConfigurationSet:
968         {
969             // We change server state to make sure no new client will be accepted
970             if(mServerState != ServerState::StateConfiguration)
971             {
972                 OD_LOG_ERR("Wrong server state=" + Helper::toString(static_cast<int>(mServerState)));
973                 break;
974             }
975 
976             bool isConfigured = true;
977             for(Seat* seat : gameMap->getSeats())
978             {
979                 // Rogue seat do not have to be configured
980                 if(seat->isRogueSeat())
981                     continue;
982 
983                 int seatId = seat->getId();
984                 if(seat->getConfigPlayerId() == -1)
985                 {
986                     OD_LOG_ERR("player not configured seatId=" + Helper::toString(seatId) + ", ConfigPlayerId=" + Helper::toString(seat->getConfigPlayerId()));
987                     isConfigured = false;
988                     break;
989                 }
990                 if(seat->getConfigTeamId() == -1)
991                 {
992                     OD_LOG_ERR("player not configured seatId=" + Helper::toString(seatId) + ", ConfigTeamId=" + Helper::toString(seat->getConfigTeamId()));
993                     isConfigured = false;
994                     break;
995                 }
996                 if(seat->getConfigFactionIndex() == -1)
997                 {
998                     OD_LOG_ERR("player not configured seatId=" + Helper::toString(seatId) + ", ConfigFactionIndex=" + Helper::toString(seat->getConfigFactionIndex()));
999                     isConfigured = false;
1000                     break;
1001                 }
1002             }
1003 
1004             // If configuration is not complete, we don't go further
1005             if(!isConfigured)
1006                 break;
1007 
1008             mServerState = ServerState::StateGame;
1009 
1010             const std::vector<std::string>& factions = ConfigManager::getSingleton().getFactions();
1011             for(Seat* seat : gameMap->getSeats())
1012             {
1013                 // Rogue seat do not have to be configured
1014                 if(seat->isRogueSeat())
1015                     continue;
1016 
1017                 seat->setFaction(factions[seat->getConfigFactionIndex()]);
1018 
1019                 int seatId = seat->getId();
1020                 int32_t playerId = seat->getConfigPlayerId();
1021                 if(playerId == Seat::PLAYER_TYPE_INACTIVE_ID)
1022                 {
1023                     // It is an inactive player
1024                     Player* inactivePlayer = new Player(gameMap, 0);
1025                     inactivePlayer->setNick("Inactive AI " + Helper::toString(seatId));
1026                     gameMap->addPlayer(inactivePlayer);
1027                     seat->setPlayer(inactivePlayer);
1028                 }
1029                 else if(playerId < Seat::PLAYER_ID_HUMAN_MIN)
1030                 {
1031                     // It is an AI
1032                     KeeperAIType aiType = Seat::playerIdToAIType(playerId);
1033                     if(aiType >= KeeperAIType::nbAI)
1034                     {
1035                         OD_LOG_ERR("Wrong value for keeper seatId=" + Helper::toString(seat->getId())
1036                             + ", ConfigPlayerId=" + Helper::toString(playerId));
1037 
1038                         // Default to normal
1039                         aiType = KeeperAIType::normal;
1040                     }
1041                     // We set player id = 0 for AI players. ID is only used during seat configuration phase
1042                     // During the game, one should use the seat ID to identify a player
1043                     Player* aiPlayer = new Player(gameMap, 0);
1044                     aiPlayer->setNick("Keeper AI " + KeeperAITypes::toString(aiType) + " " + Helper::toString(seatId));
1045                     gameMap->addPlayer(aiPlayer);
1046                     seat->setPlayer(aiPlayer);
1047                     gameMap->assignAI(*aiPlayer, aiType);
1048                 }
1049                 else
1050                 {
1051                     // Human player
1052                     for (ODSocketClient* client : mSockClients)
1053                     {
1054                         if((client->getState().compare("ready") == 0) &&
1055                            (client->getPlayer()->getId() == seat->getConfigPlayerId()))
1056                         {
1057                             seat->setPlayer(client->getPlayer());
1058                             gameMap->addPlayer(client->getPlayer());
1059                             break;
1060                         }
1061                     }
1062                 }
1063                 seat->setTeamId(seat->getConfigTeamId());
1064             }
1065 
1066             // Now, we can disconnect the players that were not configured
1067             std::vector<ODSocketClient*> clientsToRemove;
1068             for (ODSocketClient* client : mSockClients)
1069             {
1070                 if(client->getPlayer()->getSeat() == nullptr)
1071                     clientsToRemove.push_back(client);
1072             }
1073 
1074             if(!clientsToRemove.empty())
1075             {
1076                 ODPacket packetSend;
1077                 packetSend << ServerNotificationType::clientRejected;
1078                 for(ODSocketClient* client : clientsToRemove)
1079                 {
1080                     Player* player = client->getPlayer();
1081                     OD_LOG_INF("Rejecting player id="
1082                         + Helper::toString(player->getId())
1083                         + ", nick=" + player->getNick());
1084                     client->setState("rejected");
1085                     client->send(packetSend);
1086                     delete player;
1087                     client->setPlayer(nullptr);
1088                 }
1089             }
1090 
1091             ODPacket packetSend;
1092             packetSend << ServerNotificationType::clientAccepted << ODApplication::turnsPerSecond;
1093             const std::vector<Player*>& players = gameMap->getPlayers();
1094             int32_t nbPlayers = players.size();
1095             packetSend << nbPlayers;
1096             for (Player* player : players)
1097             {
1098                 packetSend << player->getNick() << player->getId()
1099                     << player->getSeat()->getId() << player->getSeat()->getTeamId();
1100                 player->getSeat()->setMapSize(gameMap->getMapSizeX(), gameMap->getMapSizeY());
1101             }
1102             sendMsg(nullptr, packetSend);
1103 
1104             for (ODSocketClient* client : mSockClients)
1105             {
1106                 if(!client->isConnected() || (client->getPlayer() == nullptr))
1107                     continue;
1108 
1109                 ODPacket packetSend;
1110                 int seatId = client->getPlayer()->getSeat()->getId();
1111                 packetSend << ServerNotificationType::startGameMode << seatId << mServerMode;
1112                 client->send(packetSend);
1113             }
1114 
1115             for(Seat* seat : gameMap->getSeats())
1116             {
1117                 // We initialize the seats
1118                 seat->initSeat();
1119             }
1120 
1121             mSeatsConfigured = true;
1122             gameMap->notifySeatsConfigured();
1123             break;
1124         }
1125 
1126         case ClientNotificationType::chat:
1127         {
1128             // TODO : handle chat for everybody/allies/player
1129             // As chat message do not interfere with GameMap, it is OK to send
1130             // them directly to the clients instead of queuing a ServerNotification
1131             // to the Server
1132             std::string chatMsg;
1133             OD_ASSERT_TRUE(packetReceived >> chatMsg);
1134             int32_t seatId = -1;
1135             if(clientSocket->getPlayer()->getSeat() != nullptr)
1136                 seatId = clientSocket->getPlayer()->getSeat()->getId();
1137 
1138             ODPacket packetSend;
1139             const std::string& playerNick = clientSocket->getPlayer()->getNick();
1140             ServerNotification notif(ServerNotificationType::chat, nullptr);
1141             notif.mPacket << playerNick << chatMsg << seatId;
1142             sendAsyncMsg(notif);
1143             break;
1144         }
1145 
1146         case ClientNotificationType::askEntityPickUp:
1147         {
1148             std::string entityName;
1149             GameEntityType entityType;
1150             OD_ASSERT_TRUE(packetReceived >> entityType >> entityName);
1151 
1152             Player *player = clientSocket->getPlayer();
1153             GameEntity* entity = gameMap->getEntityFromTypeAndName(entityType, entityName);
1154             if(entity == nullptr)
1155             {
1156                 OD_LOG_ERR("entityType=" + Helper::toString(static_cast<int32_t>(entityType)) + ", entityName=" + entityName);
1157                 break;
1158             }
1159             bool allowPickup = entity->tryPickup(player->getSeat());
1160             if(!allowPickup)
1161             {
1162                 OD_LOG_INF("player=" + player->getNick()
1163                         + " could not pickup entity entityType="
1164                         + Helper::toString(static_cast<int32_t>(entityType))
1165                         + ", entityName=" + entityName);
1166                 break;
1167             }
1168 
1169             player->pickUpEntity(entity);
1170             break;
1171         }
1172 
1173         case ClientNotificationType::askHandDrop:
1174         {
1175             Player *player = clientSocket->getPlayer();
1176             Tile* tile = gameMap->tileFromPacket(packetReceived);
1177             if(tile == nullptr)
1178             {
1179                 OD_LOG_ERR("player seatId=" + Helper::toString(player->getSeat()->getId())
1180                     + " send wrong tile");
1181                 break;
1182             }
1183             if(!player->isDropHandPossible(tile, 0))
1184             {
1185                 OD_LOG_ERR("player seatId=" + Helper::toString(player->getSeat()->getId())
1186                     + " could not drop entity in hand on tile "
1187                     + Tile::displayAsString(tile));
1188                 break;
1189             }
1190             player->dropHand(tile, 0);
1191             break;
1192         }
1193 
1194         case ClientNotificationType::askPickupWorker:
1195         {
1196             Player *player = clientSocket->getPlayer();
1197             Creature* creature = gameMap->getWorkerToPickupBySeat(player->getSeat());
1198             if(creature == nullptr)
1199                 break;
1200 
1201             player->pickUpEntity(creature);
1202             break;
1203         }
1204 
1205         case ClientNotificationType::askPickupFighter:
1206         {
1207             Player *player = clientSocket->getPlayer();
1208             Creature* creature = gameMap->getFighterToPickupBySeat(player->getSeat());
1209             if(creature == nullptr)
1210                 break;
1211 
1212             player->pickUpEntity(creature);
1213             break;
1214         }
1215 
1216         case ClientNotificationType::askMarkTiles:
1217         {
1218             int x1, y1, x2, y2;
1219             bool isDigSet;
1220             Player* player = clientSocket->getPlayer();
1221 
1222             OD_ASSERT_TRUE(packetReceived >> x1 >> y1 >> x2 >> y2 >> isDigSet);
1223             std::vector<Tile*> tiles = gameMap->rectangularRegion(x1, y1, x2, y2);
1224             player->markTilesForDigging(isDigSet, tiles, true);
1225 
1226             break;
1227         }
1228 
1229         case ClientNotificationType::askSlapEntity:
1230         {
1231             GameEntityType entityType;
1232             std::string entityName;
1233             Player* player = clientSocket->getPlayer();
1234             OD_ASSERT_TRUE(packetReceived >> entityType >> entityName);
1235             GameEntity* entity = gameMap->getEntityFromTypeAndName(entityType, entityName);
1236             if(entity == nullptr)
1237             {
1238                 OD_LOG_WRN("entityType=" + Helper::toString(static_cast<int32_t>(entityType)) + ", entityName=" + entityName);
1239                 break;
1240             }
1241 
1242             if(!entity->canSlap(player->getSeat()))
1243             {
1244                 OD_LOG_INF("player seatId=" + Helper::toString(player->getSeat()->getId())
1245                     + " could not slap entity entityType="
1246                     + Helper::toString(static_cast<int32_t>(entityType))
1247                     + ", entityName=" + entityName);
1248                 break;
1249             }
1250 
1251             OD_LOG_INF("player seatId=" + Helper::toString(player->getSeat()->getId()) + " slapped entity " + entity->getName());
1252             entity->slap();
1253 
1254             ServerNotification notif(ServerNotificationType::entitySlapped, player);
1255             sendAsyncMsg(notif);
1256             break;
1257         }
1258 
1259         case ClientNotificationType::askBuildRoom:
1260         {
1261             RoomType type;
1262 
1263             OD_ASSERT_TRUE(packetReceived >> type);
1264             Player* player = clientSocket->getPlayer();
1265 
1266             // We check if the room is available. It is not normal to receive a message
1267             // asking to build an unbuildable room since the client should only display
1268             // available rooms
1269             if(!SkillManager::isRoomAvailable(type, player->getSeat()))
1270             {
1271                 OD_LOG_INF("WARNING: player seatId=" + Helper::toString(player->getSeat()->getId())
1272                     + " asked to build a room not available: " + RoomManager::getRoomNameFromRoomType(type));
1273                 break;
1274             }
1275 
1276             if(!RoomManager::buildRoom(gameMap, type, player, packetReceived))
1277             {
1278                 OD_LOG_INF("WARNING: player seatId=" + Helper::toString(player->getSeat()->getId())
1279                     + " couldn't build room: " + RoomManager::getRoomNameFromRoomType(type));
1280                 break;
1281             }
1282             break;
1283         }
1284 
1285         case ClientNotificationType::askSellRoomTiles:
1286         {
1287             Player* player = clientSocket->getPlayer();
1288             RoomManager::sellRoomTiles(gameMap, player, packetReceived);
1289             break;
1290         }
1291 
1292         case ClientNotificationType::editorAskDestroyRoomTiles:
1293         {
1294             if(mServerMode != ServerMode::ModeEditor)
1295             {
1296                 OD_LOG_ERR("Received editor command while wrong mode mode"
1297                     + Helper::toString(static_cast<int>(mServerMode)));
1298                 break;
1299             }
1300 
1301             RoomManager::sellRoomTilesEditor(gameMap, packetReceived);
1302             break;
1303         }
1304 
1305         case ClientNotificationType::askBuildTrap:
1306         {
1307             TrapType type;
1308 
1309             OD_ASSERT_TRUE(packetReceived >> type);
1310             Player* player = clientSocket->getPlayer();
1311 
1312             // We check if the trap is available. It is not normal to receive a message
1313             // asking to build an unbuildable trap since the client should only display
1314             // available rooms
1315             if(!SkillManager::isTrapAvailable(type, player->getSeat()))
1316             {
1317                 OD_LOG_INF("WARNING: player seatId=" + Helper::toString(player->getSeat()->getId())
1318                     + " asked to build a trap not available: " + TrapManager::getTrapNameFromTrapType(type));
1319                 break;
1320             }
1321 
1322             if(!TrapManager::buildTrap(gameMap, type, player, packetReceived))
1323             {
1324                 OD_LOG_INF("WARNING: player seatId=" + Helper::toString(player->getSeat()->getId())
1325                     + " couldn't build trap: " + TrapManager::getTrapNameFromTrapType(type));
1326                 break;
1327             }
1328 
1329             // If the player is human and do not own a workshop, we warn him
1330             if(!player->getIsHuman())
1331                 break;
1332             if(player->getHasLost())
1333                 break;
1334 
1335             std::vector<Room*> rooms = gameMap->getRoomsByTypeAndSeat(RoomType::workshop, player->getSeat());
1336             if(!rooms.empty())
1337                 break;
1338 
1339             ServerNotification *serverNotification = new ServerNotification(
1340                 ServerNotificationType::chatServer, player);
1341 
1342             std::string msg = "You need a workshop to craft the trap!";
1343             serverNotification->mPacket << msg << EventShortNoticeType::genericGameInfo;
1344             ODServer::getSingleton().queueServerNotification(serverNotification);
1345             break;
1346         }
1347 
1348         case ClientNotificationType::askCastSpell:
1349         {
1350             SpellType spellType;
1351 
1352             OD_ASSERT_TRUE(packetReceived >> spellType);
1353             Player* player = clientSocket->getPlayer();
1354 
1355             // We check if the spell is available. It is not normal to receive a message
1356             // asking to cast an uncastable spell since the client should only display
1357             // available spells
1358             if(!SkillManager::isSpellAvailable(spellType, player->getSeat()))
1359             {
1360                 OD_LOG_WRN("player " + player->getNick()
1361                     + " asked to cast a spell not available: " + SpellManager::getSpellNameFromSpellType(spellType));
1362                 break;
1363             }
1364 
1365             uint32_t cooldown = player->getSpellCooldownTurns(spellType);
1366             if(cooldown > 0)
1367             {
1368                 OD_LOG_WRN("player " + player->getNick()
1369                     + " asked to cast a spell " + SpellManager::getSpellNameFromSpellType(spellType) + " before end of cooldown: "
1370                     + Helper::toString(cooldown));
1371                 break;
1372             }
1373 
1374             OD_LOG_INF("Player id: " + Helper::toString(player->getSeat()->getId()) + " casts spell " + SpellManager::getSpellNameFromSpellType(spellType));
1375 
1376             if(!SpellManager::castSpell(gameMap, spellType, player, packetReceived))
1377                 break;
1378 
1379             uint32_t newCooldown = SpellManager::getSpellCooldown(spellType);
1380             player->setSpellCooldownTurns(spellType, newCooldown);
1381             break;
1382         }
1383 
1384         case ClientNotificationType::askSellTrapTiles:
1385         {
1386             Player* player = clientSocket->getPlayer();
1387             TrapManager::sellTrapTiles(gameMap, player->getSeat(), packetReceived);
1388             break;
1389         }
1390 
1391         case ClientNotificationType::askSetPlayerSettings:
1392         {
1393             Seat* playerSeat = clientSocket->getPlayer()->getSeat();
1394             bool koCreatures;
1395             OD_ASSERT_TRUE(packetReceived >> koCreatures);
1396             playerSeat->setPlayerSettings(koCreatures);
1397             break;
1398         }
1399 
1400         case ClientNotificationType::editorAskDestroyTrapTiles:
1401         {
1402             if(mServerMode != ServerMode::ModeEditor)
1403             {
1404                 OD_LOG_ERR("Received editor command while wrong mode mode"
1405                     + Helper::toString(static_cast<int>(mServerMode)));
1406                 break;
1407             }
1408 
1409             TrapManager::sellTrapTilesEditor(gameMap, packetReceived);
1410             break;
1411         }
1412 
1413         case ClientNotificationType::ackNewTurn:
1414         {
1415             int64_t turn;
1416             OD_ASSERT_TRUE(packetReceived >> turn);
1417             clientSocket->setLastTurnAck(turn);
1418             break;
1419         }
1420 
1421         case ClientNotificationType::askCreatureInfos:
1422         {
1423             std::string name;
1424             bool refreshEachTurn;
1425             OD_ASSERT_TRUE(packetReceived >> name >> refreshEachTurn);
1426             std::vector<std::string>& creatures = mCreaturesInfoWanted[clientSocket];
1427 
1428             std::vector<std::string>::iterator it = std::find(creatures.begin(), creatures.end(), name);
1429             if(refreshEachTurn && (it == creatures.end()))
1430             {
1431                 creatures.push_back(name);
1432             }
1433             else if(!refreshEachTurn && (it != creatures.end()))
1434                 creatures.erase(it);
1435 
1436             break;
1437         }
1438 
1439         case ClientNotificationType::askSaveMap:
1440         {
1441             Player* player = clientSocket->getPlayer();
1442             // Only the player allowed to configure the game can save games
1443             if(mPlayerConfig != player)
1444                 break;
1445 
1446             // We only allow to save game if launching in client+server mode
1447             if(ODClient::getSingletonPtr() == nullptr)
1448                 break;
1449             if(!ODClient::getSingleton().isConnected())
1450                 break;
1451 
1452             const boost::filesystem::path levelPath(gameMap->getLevelFileName());
1453             std::string fileLevel = levelPath.filename().string();
1454             // In editor mode, we don't allow a player to have creatures in hand while saving map
1455             if((mServerMode == ServerMode::ModeEditor) &&
1456                 (player->numObjectsInHand() > 0))
1457             {
1458                 // We cannot save the map
1459                 std::string msg = "Map could not be saved because player hand is not empty";
1460                 ServerNotification notif(ServerNotificationType::chatServer, player);
1461                 notif.mPacket << msg << EventShortNoticeType::genericGameInfo;
1462                 sendAsyncMsg(notif);
1463                 break;
1464             }
1465 
1466             boost::filesystem::path levelSave;
1467             if(mServerMode == ServerMode::ModeEditor)
1468             {
1469                 // In editor mode, we save in the original folder
1470                 levelSave = levelPath;
1471 
1472                 // If the level was not a custom one, we save it as a custom one now.
1473                 // Note: We don't compare for official levels path, as they may be relative and unreliable.
1474                 std::string levelStr = levelSave.string();
1475                 ResourceManager& resMgr = ResourceManager::getSingleton();
1476                 bool skirmishLevelType = (levelStr.find("skirmish") != std::string::npos);
1477                 if (skirmishLevelType) {
1478                     if (levelStr.find(resMgr.getUserLevelPathSkirmish()) == std::string::npos) {
1479                         levelSave = boost::filesystem::path(resMgr.getUserLevelPathSkirmish() + fileLevel);
1480                     }
1481                 }
1482                 else if (levelStr.find(resMgr.getUserLevelPathMultiplayer()) == std::string::npos) {
1483                     levelSave = boost::filesystem::path(resMgr.getUserLevelPathMultiplayer() + fileLevel);
1484                 }
1485                 std::cout << levelSave.string() << std::endl;
1486             }
1487             else
1488             {
1489                 // We save in the saved game folder
1490                 static std::locale loc(std::wcout.getloc(), new boost::posix_time::time_facet("%Y-%m-%d_%H%M%S"));
1491 
1492                 std::ostringstream ss;
1493                 ss.imbue(loc);
1494                 ss << boost::posix_time::second_clock::local_time() << "-";
1495                 switch(mServerMode)
1496                 {
1497                     case ServerMode::ModeGameSinglePlayer:
1498                         ss << SAVEGAME_SKIRMISH_PREFIX;
1499                         ss << fileLevel;
1500                         break;
1501                     case ServerMode::ModeGameMultiPlayer:
1502                         ss << SAVEGAME_MULTIPLAYER_PREFIX;
1503                         ss << fileLevel;
1504                         break;
1505                     case ServerMode::ModeGameLoaded:
1506                     {
1507                         // We look for the Skirmish or multiplayer prefix and keep it.
1508                         uint32_t indexSk = fileLevel.find(SAVEGAME_SKIRMISH_PREFIX);
1509                         uint32_t indexMp = fileLevel.find(SAVEGAME_MULTIPLAYER_PREFIX);
1510                         if((indexSk != std::string::npos) && (indexMp == std::string::npos))
1511                         {
1512                             // Skirmish savegame
1513                             ss << SAVEGAME_SKIRMISH_PREFIX;
1514                             ss << fileLevel.substr(indexSk + SAVEGAME_SKIRMISH_PREFIX.length());
1515 
1516                         }
1517                         else if((indexSk == std::string::npos) && (indexMp != std::string::npos))
1518                         {
1519                             // Multiplayer savegame
1520                             ss << SAVEGAME_MULTIPLAYER_PREFIX;
1521                             ss << fileLevel.substr(indexMp + SAVEGAME_MULTIPLAYER_PREFIX.length());
1522                         }
1523                         else if((indexSk != std::string::npos) && (indexMp != std::string::npos))
1524                         {
1525                             // We found both prefixes. That can happen if the name contains the other
1526                             // prefix. Because of filename construction, we know that the lowest is the good
1527                             if(indexSk < indexMp)
1528                             {
1529                                 ss << SAVEGAME_SKIRMISH_PREFIX;
1530                                 ss << fileLevel.substr(indexSk + SAVEGAME_SKIRMISH_PREFIX.length());
1531                             }
1532                             else
1533                             {
1534                                 ss << SAVEGAME_MULTIPLAYER_PREFIX;
1535                                 ss << fileLevel.substr(indexMp + SAVEGAME_MULTIPLAYER_PREFIX.length());
1536                             }
1537                         }
1538                         else
1539                         {
1540                             // We couldn't find any prefix. That's not normal
1541                             OD_LOG_ERR("fileLevel=" + fileLevel);
1542                             ss << fileLevel;
1543                         }
1544                         break;
1545                     }
1546                     default:
1547                         OD_LOG_ERR("mode=" + Helper::toString(static_cast<int>(mServerMode)));
1548                         ss << fileLevel;
1549                         break;
1550                 }
1551                 std::string savePath = ResourceManager::getSingleton().getSaveGamePath() + ss.str();
1552                 levelSave = boost::filesystem::path(savePath);
1553             }
1554 
1555             // If the file exists, we make a backup
1556             if (boost::filesystem::exists(levelSave))
1557                 boost::filesystem::rename(levelSave, levelSave.string() + ".bak");
1558 
1559             std::string msg = "Map saved successfully as: " + levelSave.string();
1560             if (!MapHandler::writeGameMapToFile(levelSave.string(), *gameMap))
1561             {
1562                 msg = "Couldn't not save map file as: " + levelSave.string() + "\nPlease check logs.";
1563             }
1564             // We notify all the players that the game was saved successfully
1565             ServerNotification notif(ServerNotificationType::chatServer, nullptr);
1566             notif.mPacket << msg << EventShortNoticeType::genericGameInfo;
1567             sendAsyncMsg(notif);
1568             break;
1569         }
1570 
1571         case ClientNotificationType::editorAskChangeTiles:
1572         {
1573             if(mServerMode != ServerMode::ModeEditor)
1574             {
1575                 OD_LOG_ERR("Received editor command while wrong mode mode" + Helper::toString(static_cast<int>(mServerMode)));
1576                 break;
1577             }
1578             int x1, y1, x2, y2;
1579             TileType tileType;
1580             double tileFullness;
1581             int seatId;
1582 
1583             OD_ASSERT_TRUE(packetReceived >> x1 >> y1 >> x2 >> y2 >> tileType >> tileFullness >> seatId);
1584             std::vector<Tile*> selectedTiles = gameMap->rectangularRegion(x1, y1, x2, y2);
1585             std::vector<Tile*> affectedTiles;
1586             Seat* seat = nullptr;
1587             if(seatId != -1)
1588                 seat = gameMap->getSeatById(seatId);
1589 
1590             for(Tile* tile : selectedTiles)
1591             {
1592                 // We do not change tiles where there is something
1593                 if((tile->numEntitiesInTile() > 0) &&
1594                    ((tileFullness > 0.0) || (tileType == TileType::lava) || (tileType == TileType::water)))
1595                     continue;
1596                 if(tile->getCoveringBuilding() != nullptr)
1597                     continue;
1598 
1599                 affectedTiles.push_back(tile);
1600                 tile->setType(tileType);
1601                 tile->setFullness(tileFullness);
1602                 if(seat != nullptr)
1603                     tile->claimTile(seat);
1604                 else
1605                     tile->unclaimTile();
1606 
1607                 tile->computeTileVisual();
1608             }
1609             if(!affectedTiles.empty())
1610             {
1611                 uint32_t nbTiles = affectedTiles.size();
1612                 const std::vector<Seat*>& seats = gameMap->getSeats();
1613                 for(Seat* seat : seats)
1614                 {
1615                     if(seat->getPlayer() == nullptr)
1616                         continue;
1617                     if(!seat->getPlayer()->getIsHuman())
1618                         continue;
1619 
1620                     ServerNotification notif(ServerNotificationType::refreshTiles, seat->getPlayer());
1621                     notif.mPacket << nbTiles;
1622                     for(Tile* tile : affectedTiles)
1623                     {
1624                         gameMap->tileToPacket(notif.mPacket, tile);
1625                         seat->updateTileStateForSeat(tile, false);
1626                         tile->exportToPacketForUpdate(notif.mPacket, seat);
1627 
1628                     }
1629                     sendAsyncMsg(notif);
1630                 }
1631             }
1632             break;
1633         }
1634 
1635         case ClientNotificationType::editorAskBuildRoom:
1636         {
1637             if(mServerMode != ServerMode::ModeEditor)
1638             {
1639                 OD_LOG_ERR("Received editor command while wrong mode mode"
1640                     + Helper::toString(static_cast<int>(mServerMode)));
1641                 break;
1642             }
1643             RoomType type;
1644 
1645             OD_ASSERT_TRUE(packetReceived >> type);
1646 
1647             Player* player = clientSocket->getPlayer();
1648             if(!RoomManager::buildRoomEditor(gameMap, type, packetReceived))
1649             {
1650                 OD_LOG_INF("WARNING: player seatId=" + Helper::toString(player->getSeat()->getId())
1651                     + " couldn't build room: " + RoomManager::getRoomNameFromRoomType(type));
1652                 break;
1653             }
1654             break;
1655         }
1656 
1657         case ClientNotificationType::editorAskBuildTrap:
1658         {
1659             if(mServerMode != ServerMode::ModeEditor)
1660             {
1661                 OD_LOG_ERR("Received editor command while wrong mode mode"
1662                     + Helper::toString(static_cast<int>(mServerMode)));
1663                 break;
1664             }
1665             TrapType type;
1666 
1667             OD_ASSERT_TRUE(packetReceived >> type);
1668 
1669             Player* player = clientSocket->getPlayer();
1670             if(!TrapManager::buildTrapEditor(gameMap, type, packetReceived))
1671             {
1672                 OD_LOG_INF("WARNING: player seatId=" + Helper::toString(player->getSeat()->getId())
1673                     + " couldn't build trap: " + TrapManager::getTrapNameFromTrapType(type));
1674                 break;
1675             }
1676             break;
1677         }
1678 
1679         case ClientNotificationType::editorCreateWorker:
1680         {
1681             if(mServerMode != ServerMode::ModeEditor)
1682             {
1683                 OD_LOG_ERR("Received editor command while wrong mode mode" + Helper::toString(static_cast<int>(mServerMode)));
1684                 break;
1685             }
1686             Player* player = clientSocket->getPlayer();
1687             int seatId;
1688             OD_ASSERT_TRUE(packetReceived >> seatId);
1689             Seat* seatCreature = gameMap->getSeatById(seatId);
1690             if(seatCreature == nullptr)
1691             {
1692                 OD_LOG_ERR("seatId=" + Helper::toString(seatId));
1693                 break;
1694             }
1695 
1696             const CreatureDefinition *classToSpawn = ConfigManager::getSingleton().getCreatureDefinitionDefaultWorker();
1697             if(classToSpawn == nullptr)
1698             {
1699                 OD_LOG_ERR("unexpected null classToSpawn for getCreatureDefinitionDefaultWorker");
1700                 break;
1701             }
1702             Creature* newCreature = new Creature(gameMap, classToSpawn, seatCreature);
1703             newCreature->addToGameMap();
1704             newCreature->setPosition(Ogre::Vector3(0.0, 0.0, 0.0));
1705             // In editor mode, every player has vision
1706             for(Seat* seat : gameMap->getSeats())
1707             {
1708                 if(seat->getPlayer() == nullptr)
1709                     continue;
1710                 if(!seat->getPlayer()->getIsHuman())
1711                     continue;
1712 
1713                 newCreature->addSeatWithVision(seat, true);
1714             }
1715 
1716             player->pickUpEntity(newCreature);
1717             break;
1718         }
1719 
1720         case ClientNotificationType::editorCreateFighter:
1721         {
1722             if(mServerMode != ServerMode::ModeEditor)
1723             {
1724                 OD_LOG_ERR("Received editor command while wrong mode=" + Helper::toString(static_cast<int>(mServerMode)));
1725                 break;
1726             }
1727             Player* player = clientSocket->getPlayer();
1728             int seatId;
1729             std::string className;
1730             OD_ASSERT_TRUE(packetReceived >> seatId >> className);
1731             Seat* seatCreature = gameMap->getSeatById(seatId);
1732             if(seatCreature == nullptr)
1733             {
1734                 OD_LOG_ERR("seatId=" + Helper::toString(seatId));
1735                 break;
1736             }
1737             const CreatureDefinition *classToSpawn = gameMap->getClassDescription(className);
1738             if(classToSpawn == nullptr)
1739             {
1740                 OD_LOG_ERR("Couldn't spawn creature class=" + className);
1741                 break;
1742             }
1743             Creature* newCreature = new Creature(gameMap, classToSpawn, seatCreature);
1744             newCreature->addToGameMap();
1745             newCreature->setPosition(Ogre::Vector3(0.0, 0.0, 0.0));
1746             // In editor mode, every player has vision
1747             for(Seat* seat : gameMap->getSeats())
1748             {
1749                 if(seat->getPlayer() == nullptr)
1750                     continue;
1751                 if(!seat->getPlayer()->getIsHuman())
1752                     continue;
1753 
1754                 newCreature->addSeatWithVision(seat, true);
1755             }
1756 
1757             player->pickUpEntity(newCreature);
1758             break;
1759         }
1760 
1761         case ClientNotificationType::askSetSkillTree:
1762         {
1763             Player* player = clientSocket->getPlayer();
1764             uint32_t nbItems;
1765             OD_ASSERT_TRUE(packetReceived >> nbItems);
1766             std::vector<SkillType> skills;
1767             while(nbItems > 0)
1768             {
1769                 nbItems--;
1770                 SkillType skill;
1771                 OD_ASSERT_TRUE(packetReceived >> skill);
1772                 skills.push_back(skill);
1773             }
1774 
1775             player->getSeat()->setSkillTree(skills);
1776             break;
1777         }
1778 
1779         case ClientNotificationType::askExecuteConsoleCommand:
1780         {
1781             uint32_t nbArgs;
1782             std::string str;
1783             std::vector<std::string> args;
1784             OD_ASSERT_TRUE(packetReceived >> nbArgs);
1785             while(nbArgs > 0)
1786             {
1787                 --nbArgs;
1788                 OD_ASSERT_TRUE(packetReceived >> str);
1789                 args.push_back(str);
1790             }
1791             Player* player = clientSocket->getPlayer();
1792             handleConsoleCommand(player, gameMap, args);
1793             break;
1794         }
1795 
1796         case ClientNotificationType::editorAskCreateMapLight:
1797         {
1798             Player* player = clientSocket->getPlayer();
1799             MapLight* mapLight = new MapLight(gameMap, true);
1800             mapLight->setName(gameMap->nextUniqueNameMapLight());
1801             mapLight->addToGameMap();
1802             mapLight->setPosition(Ogre::Vector3(0.0, 0.0, 3.75));
1803             // In editor mode, every player has vision
1804             for(Seat* seat : gameMap->getSeats())
1805             {
1806                 if(seat->getPlayer() == nullptr)
1807                     continue;
1808                 if(!seat->getPlayer()->getIsHuman())
1809                     continue;
1810 
1811                 mapLight->addSeatWithVision(seat, true);
1812             }
1813             player->pickUpEntity(mapLight);
1814             break;
1815         }
1816 
1817         default:
1818         {
1819             OD_LOG_ERR("Unhandled command received from client:"
1820                 + Helper::toString(static_cast<int>(clientCommand)));
1821         }
1822     }
1823 
1824     return true;
1825 }
1826 
notifyNewConnection(sf::TcpListener & sockListener)1827 ODSocketClient* ODServer::notifyNewConnection(sf::TcpListener& sockListener)
1828 {
1829     ODSocketClient* newClient = new ODSocketClient;
1830     sf::Socket::Status status = sockListener.accept(newClient->getSockClient());
1831     if (status != sf::Socket::Done)
1832     {
1833         OD_LOG_ERR("Error while listening to socket status=" + Helper::toString(static_cast<uint32_t>(status)));
1834         delete newClient;
1835         return nullptr;
1836     }
1837 
1838     switch(mServerState)
1839     {
1840         case ServerState::StateNone:
1841         {
1842             // It is not normal to receive new connexions while not connected. We are in an unexpected state
1843             OD_LOG_ERR("Unexpected none server mode");
1844             delete newClient;
1845             return nullptr;
1846         }
1847         case ServerState::StateConfiguration:
1848         {
1849             newClient->setState("connected");
1850             return newClient;
1851         }
1852         case ServerState::StateGame:
1853         {
1854             // TODO : handle re-connexion if a client was disconnected and tries to reconnect
1855             OD_LOG_WRN("Received a reconnexion from a client while in game state");
1856             delete newClient;
1857             return nullptr;
1858         }
1859         default:
1860             OD_LOG_ERR("Unexpected server state=" + Helper::toString(static_cast<uint32_t>(mServerState)));
1861             break;
1862     }
1863 
1864     delete newClient;
1865     return nullptr;
1866 }
1867 
notifyClientMessage(ODSocketClient * clientSocket)1868 bool ODServer::notifyClientMessage(ODSocketClient *clientSocket)
1869 {
1870     bool ret = processClientNotifications(clientSocket);
1871     if(!ret)
1872     {
1873         std::string nick = clientSocket->getPlayer() ? clientSocket->getPlayer()->getNick() : std::string();
1874         std::string message = nick.empty() ?
1875                               "Client disconnected state=" + clientSocket->getState() :
1876                               "Client (" + nick + ") disconnected state=" + clientSocket->getState();
1877         OD_LOG_INF(message);
1878         if(std::string("ready").compare(clientSocket->getState()) == 0)
1879         {
1880             for(Player* player : mGameMap->getPlayers())
1881             {
1882                 if(!player->getIsHuman())
1883                     continue;
1884 
1885                 ServerNotification *serverNotification = new ServerNotification(
1886                     ServerNotificationType::chatServer, player);
1887                 std::string msg = nick.empty() ?
1888                                   "A client disconnected." :
1889                                   nick + " disconnected.";
1890                 serverNotification->mPacket << msg << EventShortNoticeType::genericGameInfo;
1891                 queueServerNotification(serverNotification);
1892             }
1893         }
1894 
1895         if(mSeatsConfigured)
1896         {
1897             mDisconnectedPlayers.push_back(clientSocket->getPlayer());
1898         }
1899         // TODO : wait at least 1 minute if the client reconnects if deconnexion happens during game
1900     }
1901     return ret;
1902 }
1903 
stopServer()1904 void ODServer::stopServer()
1905 {
1906     // We start by stopping server to make sure no new message comes
1907     ODSocketServer::stopServer();
1908 
1909     mServerState = ServerState::StateNone;
1910     mSeatsConfigured = false;
1911     mDisconnectedPlayers.clear();
1912     mPlayerConfig = nullptr;
1913 
1914     // Now that the server is stopped, we can remove all pending messages
1915     while(!mServerNotificationQueue.empty())
1916     {
1917         delete mServerNotificationQueue.front();
1918         mServerNotificationQueue.pop_front();
1919     }
1920     mGameMap->clearAll();
1921 }
1922 
notifyExit()1923 void ODServer::notifyExit()
1924 {
1925     while(!mServerNotificationQueue.empty())
1926     {
1927         delete mServerNotificationQueue.front();
1928         mServerNotificationQueue.pop_front();
1929     }
1930 
1931     ServerNotification* exitServerNotification = new ServerNotification(
1932         ServerNotificationType::exit, nullptr);
1933     queueServerNotification(exitServerNotification);
1934 }
1935 
getClientFromPlayer(Player * player)1936 ODSocketClient* ODServer::getClientFromPlayer(Player* player)
1937 {
1938     for (ODSocketClient* client : mSockClients)
1939     {
1940         if(client->getPlayer() == player)
1941             return client;
1942     }
1943 
1944     return nullptr;
1945 }
1946 
getClientFromPlayerId(int32_t playerId)1947 ODSocketClient* ODServer::getClientFromPlayerId(int32_t playerId)
1948 {
1949     for (ODSocketClient* client : mSockClients)
1950     {
1951         if(client->getPlayer()->getId() == playerId)
1952             return client;
1953     }
1954 
1955     return nullptr;
1956 }
1957 
waitEndGame()1958 bool ODServer::waitEndGame()
1959 {
1960     // If the server is not launched, we don't allow to wait for end of game
1961     if(mServerState == ServerState::StateNone)
1962         return false;
1963 
1964     mThread->wait();
1965     return true;
1966 }
1967 
fireSeatConfigurationRefresh()1968 void ODServer::fireSeatConfigurationRefresh()
1969 {
1970     ODPacket packetSend;
1971     packetSend << ServerNotificationType::seatConfigurationRefresh;
1972     for(Seat* seat : mGameMap->getSeats())
1973     {
1974         // Rogue seat do not have to be configured
1975         if(seat->isRogueSeat())
1976             continue;
1977 
1978         int seatId = seat->getId();
1979         packetSend << seatId;
1980 
1981         int32_t factionIndex = seat->getConfigFactionIndex();
1982         if(factionIndex == -1)
1983         {
1984             packetSend << false;
1985         }
1986         else
1987         {
1988             packetSend << true;
1989             packetSend << factionIndex;
1990         }
1991 
1992         int32_t playerId = seat->getConfigPlayerId();
1993         if(playerId == -1)
1994         {
1995             packetSend << false;
1996         }
1997         else
1998         {
1999             packetSend << true;
2000             packetSend << playerId;
2001         }
2002 
2003         int32_t teamId = seat->getConfigTeamId();
2004         if(teamId == -1)
2005         {
2006             packetSend << false;
2007         }
2008         else
2009         {
2010             packetSend << true;
2011             packetSend << teamId;
2012         }
2013     }
2014     sendMsg(nullptr, packetSend);
2015 }
2016 
getNetworkPort() const2017 int32_t ODServer::getNetworkPort() const
2018 {
2019     int32_t port = ResourceManager::getSingleton().getForcedNetworkPort();
2020     if(port != -1)
2021         return port;
2022 
2023     return ConfigManager::getSingleton().getNetworkPort();
2024 }
2025 
printConsoleMsg(const std::string & text)2026 void ODServer::printConsoleMsg(const std::string& text)
2027 {
2028     OD_LOG_INF("Console:" + text);
2029 }
2030 
operator <<(ODPacket & os,const EventShortNoticeType & type)2031 ODPacket& operator<<(ODPacket& os, const EventShortNoticeType& type)
2032 {
2033     os << static_cast<int32_t>(type);
2034     return os;
2035 }
2036 
operator >>(ODPacket & is,EventShortNoticeType & type)2037 ODPacket& operator>>(ODPacket& is, EventShortNoticeType& type)
2038 {
2039     int32_t tmp;
2040     OD_ASSERT_TRUE(is >> tmp);
2041     type = static_cast<EventShortNoticeType>(tmp);
2042     return is;
2043 }
2044