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