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 "game/Seat.h"
19 
20 #include "ai/KeeperAIType.h"
21 #include "entities/Building.h"
22 #include "entities/CreatureDefinition.h"
23 #include "entities/GameEntityType.h"
24 #include "entities/Tile.h"
25 #include "game/Player.h"
26 #include "game/Skill.h"
27 #include "game/SkillManager.h"
28 #include "game/SkillType.h"
29 #include "gamemap/GameMap.h"
30 #include "goals/Goal.h"
31 #include "network/ODServer.h"
32 #include "network/ServerNotification.h"
33 #include "render/RenderManager.h"
34 #include "rooms/Room.h"
35 #include "rooms/RoomManager.h"
36 #include "rooms/RoomType.h"
37 #include "sound/SoundEffectsManager.h"
38 #include "spawnconditions/SpawnCondition.h"
39 #include "traps/Trap.h"
40 #include "traps/TrapManager.h"
41 #include "utils/ConfigManager.h"
42 #include "utils/Helper.h"
43 #include "utils/LogManager.h"
44 #include "utils/Random.h"
45 
46 #include <istream>
47 #include <ostream>
48 
49 const std::string Seat::PLAYER_TYPE_HUMAN = "Human";
50 const std::string Seat::PLAYER_TYPE_AI = "AI";
51 const std::string Seat::PLAYER_TYPE_INACTIVE = "Inactive";
52 const std::string Seat::PLAYER_TYPE_CHOICE = "Choice";
53 const std::string Seat::PLAYER_FACTION_CHOICE = "Choice";
54 
55 const int32_t Seat::PLAYER_TYPE_INACTIVE_ID = 0;
56 const int32_t Seat::PLAYER_ID_HUMAN_MIN = static_cast<int32_t>(KeeperAIType::nbAI) + Seat::PLAYER_TYPE_INACTIVE_ID + 1;
57 
58 
TileStateNotified()59 TileStateNotified::TileStateNotified():
60     mTileVisual(TileVisual::nullTileVisual),
61     mSeatIdOwner(-1),
62     mMarkedForDigging(false),
63     mVisionTurnLast(false),
64     mVisionTurnCurrent(false),
65     mBuilding(nullptr)
66 {
67 }
68 
69 
Seat(GameMap * gameMap)70 Seat::Seat(GameMap* gameMap) :
71     mGameMap(gameMap),
72     mPlayer(nullptr),
73     mGoldMined(0),
74     mDefaultWorkerClass(nullptr),
75     mTeamIndex(0),
76     mIsDebuggingVision(false),
77     mSkillPoints(0),
78     mCurrentSkill(nullptr),
79     mGuiSkillNeedsRefresh(false),
80     mConfigPlayerId(-1),
81     mConfigTeamId(-1),
82     mConfigFactionIndex(-1),
83     mKoCreatures(false)
84 {
85 }
86 
addGoal(Goal * g)87 void Seat::addGoal(Goal* g)
88 {
89     mUncompleteGoals.push_back(g);
90 }
91 
numUncompleteGoals()92 unsigned int Seat::numUncompleteGoals()
93 {
94     unsigned int tempUnsigned = mUncompleteGoals.size();
95 
96     return tempUnsigned;
97 }
98 
getUncompleteGoal(unsigned int index)99 Goal* Seat::getUncompleteGoal(unsigned int index)
100 {
101     if (index >= mUncompleteGoals.size())
102         return nullptr;
103 
104     return mUncompleteGoals[index];
105 }
106 
clearUncompleteGoals()107 void Seat::clearUncompleteGoals()
108 {
109     mUncompleteGoals.clear();
110 }
111 
clearCompletedGoals()112 void Seat::clearCompletedGoals()
113 {
114     mCompletedGoals.clear();
115 }
116 
numCompletedGoals()117 unsigned int Seat::numCompletedGoals()
118 {
119     return mCompletedGoals.size();
120 }
121 
getCompletedGoal(unsigned int index)122 Goal* Seat::getCompletedGoal(unsigned int index)
123 {
124     if (index >= mCompletedGoals.size())
125         return nullptr;
126 
127     return mCompletedGoals[index];
128 }
129 
numFailedGoals()130 unsigned int Seat::numFailedGoals()
131 {
132     return mFailedGoals.size();
133 }
134 
getFailedGoal(unsigned int index)135 Goal* Seat::getFailedGoal(unsigned int index)
136 {
137     if (index >= mFailedGoals.size())
138         return nullptr;
139 
140     return mFailedGoals[index];
141 }
142 
checkAllCompletedGoals()143 unsigned int Seat::checkAllCompletedGoals()
144 {
145     // Loop over the goals vector and move any goals that have been met to the completed goals vector.
146     std::vector<Goal*>::iterator currentGoal = mCompletedGoals.begin();
147     while (currentGoal != mCompletedGoals.end())
148     {
149         // Start by checking if this previously met goal has now been unmet.
150         if ((*currentGoal)->isUnmet(*this, *mGameMap))
151         {
152             mUncompleteGoals.push_back(*currentGoal);
153 
154             currentGoal = mCompletedGoals.erase(currentGoal);
155 
156             //Signal that the list of goals has changed.
157             mHasGoalsChanged = true;
158         }
159         else
160         {
161             // Next check to see if this previously met goal has now been failed.
162             if ((*currentGoal)->isFailed(*this, *mGameMap))
163             {
164                 mFailedGoals.push_back(*currentGoal);
165 
166                 std::vector<Seat*> seats;
167                 seats.push_back(this);
168                 mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::GoalFailed);
169 
170                 currentGoal = mCompletedGoals.erase(currentGoal);
171 
172                 //Signal that the list of goals has changed.
173                 mHasGoalsChanged = true;
174             }
175             else
176             {
177                 ++currentGoal;
178             }
179         }
180     }
181 
182     return numCompletedGoals();
183 }
184 
isAlliedSeat(const Seat * seat) const185 bool Seat::isAlliedSeat(const Seat *seat) const
186 {
187     return getTeamId() == seat->getTeamId();
188 }
189 
canOwnedCreatureBePickedUpBy(const Seat * seat) const190 bool Seat::canOwnedCreatureBePickedUpBy(const Seat* seat) const
191 {
192     // Note : if we want to allow players to pickup allied creatures, we can do that here.
193     if(this == seat)
194         return true;
195 
196     return false;
197 }
198 
canOwnedTileBeClaimedBy(const Seat * seat) const199 bool Seat::canOwnedTileBeClaimedBy(const Seat* seat) const
200 {
201     if(getTeamId() != seat->getTeamId())
202         return true;
203 
204     return false;
205 }
206 
canOwnedCreatureUseRoomFrom(const Seat * seat) const207 bool Seat::canOwnedCreatureUseRoomFrom(const Seat* seat) const
208 {
209     if(this == seat)
210         return true;
211 
212     return false;
213 }
214 
canBuildingBeDestroyedBy(const Seat * seat) const215 bool Seat::canBuildingBeDestroyedBy(const Seat* seat) const
216 {
217     if(this == seat)
218         return true;
219 
220     return false;
221 }
222 
addAlliedSeat(Seat * seat)223 void Seat::addAlliedSeat(Seat* seat)
224 {
225     mAlliedSeats.push_back(seat);
226 }
227 
clearTilesWithVision()228 void Seat::clearTilesWithVision()
229 {
230     if(mPlayer == nullptr)
231         return;
232     if(!mPlayer->getIsHuman())
233         return;
234 
235     for(std::vector<TileStateNotified>& vec : mTilesStates)
236     {
237         for(TileStateNotified& p : vec)
238         {
239             p.mVisionTurnLast = p.mVisionTurnCurrent;
240             p.mVisionTurnCurrent = false;
241         }
242     }
243 }
244 
notifyVisionOnTile(Tile * tile)245 void Seat::notifyVisionOnTile(Tile* tile)
246 {
247     if(mPlayer == nullptr)
248         return;
249     if(!mPlayer->getIsHuman())
250         return;
251 
252     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
253     {
254         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
255         return;
256     }
257     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
258     {
259         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
260         return;
261     }
262 
263     TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
264     tileState.mVisionTurnCurrent = true;
265 }
266 
notifyTileClaimedByEnemy(Tile * tile)267 void Seat::notifyTileClaimedByEnemy(Tile* tile)
268 {
269     if(mPlayer == nullptr)
270         return;
271     if(!mPlayer->getIsHuman())
272         return;
273 
274     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
275     {
276         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
277         return;
278     }
279     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
280     {
281         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
282         return;
283     }
284 
285     TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
286 
287     // By default, we set the tile like if it was not claimed anymore
288     tileState.mSeatIdOwner = -1;
289     tileState.mTileVisual = TileVisual::dirtGround;
290     tileState.mVisionTurnCurrent = true;
291 }
292 
getFactionFromLine(const std::string & line)293 const std::string Seat::getFactionFromLine(const std::string& line)
294 {
295     const uint32_t indexFactionInLine = 3;
296     std::vector<std::string> elems = Helper::split(line, '\t');
297     if(elems.size() > indexFactionInLine)
298         return elems[indexFactionInLine];
299 
300     OD_LOG_ERR("line=" + line);
301     return std::string();
302 }
303 
takeMana(double mana)304 bool Seat::takeMana(double mana)
305 {
306     if(mana > mMana)
307         return false;
308 
309     mMana -= mana;
310     return true;
311 }
312 
sortForMapSave(Seat * s1,Seat * s2)313 bool Seat::sortForMapSave(Seat* s1, Seat* s2)
314 {
315     return s1->mId < s2->mId;
316 }
317 
setPlayer(Player * player)318 void Seat::setPlayer(Player* player)
319 {
320     if(mPlayer != nullptr)
321     {
322         OD_LOG_ERR("A player=" + mPlayer->getNick() + " already on seat id="
323             + Helper::toString(getId()) + ", newNick=" + player->getNick());
324     }
325 
326     mPlayer = player;
327     player->mSeat = this;
328 }
329 
hasVisionOnTile(Tile * tile)330 bool Seat::hasVisionOnTile(Tile* tile)
331 {
332     if(!mGameMap->isServerGameMap())
333     {
334         // On client side, we check only for the local player
335         if(this != mGameMap->getLocalPlayer()->getSeat())
336             return false;
337 
338         return tile->getLocalPlayerHasVision();
339     }
340 
341     // AI players have vision on every tile
342     if(mPlayer == nullptr)
343         return true;
344     if(!mPlayer->getIsHuman())
345         return true;
346 
347     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
348     {
349         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
350         return false;
351     }
352     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
353     {
354         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
355         return false;
356     }
357 
358     TileStateNotified& stateTile = mTilesStates[tile->getX()][tile->getY()];
359 
360     return stateTile.mVisionTurnCurrent;
361 }
362 
initSeat()363 void Seat::initSeat()
364 {
365     if(getPlayer() == nullptr)
366         return;
367 
368     ConfigManager& config = ConfigManager::getSingleton();
369 
370     if(isRogueSeat())
371     {
372         std::string defaultWorkerClass = config.getRogueWorkerClass();
373         mDefaultWorkerClass = mGameMap->getClassDescription(defaultWorkerClass);
374         OD_ASSERT_TRUE_MSG(mDefaultWorkerClass != nullptr, "No valid default worker class for rogue seat: " + defaultWorkerClass);
375         return;
376     }
377 
378     // Spawn pool initialisation
379     const std::vector<std::string>& pool = config.getFactionSpawnPool(mFaction);
380     OD_ASSERT_TRUE_MSG(!pool.empty(), "Empty spawn pool seatId=" + Helper::toString(mId) + ", faction=" + mFaction);
381     for(const std::string& defName : pool)
382     {
383         const CreatureDefinition* def = mGameMap->getClassDescription(defName);
384         if(def == nullptr)
385         {
386             OD_LOG_ERR("defName=" + defName);
387             continue;
388         }
389 
390         mSpawnPool.push_back(std::pair<const CreatureDefinition*, bool>(def, false));
391     }
392 
393     // Get the default worker class
394     std::string defaultWorkerClass = config.getFactionWorkerClass(mFaction);
395     mDefaultWorkerClass = mGameMap->getClassDescription(defaultWorkerClass);
396     OD_ASSERT_TRUE_MSG(mDefaultWorkerClass != nullptr, "No valid default worker class for seatId=" + Helper::toString(mId) + ", faction: " + mFaction);
397 
398     // We use a temporary vector to allow the corresponding functions to check the vector validity
399     // and reject its content if it is not valid
400     std::vector<SkillType> skills = mSkillDone;
401     mSkillDone.clear();
402     setSkillsDone(skills);
403     skills = mSkillPending;
404     mSkillPending.clear();
405     setSkillTree(skills);
406 
407     // We restore the tiles if any
408     if(!mTilesStateLoaded.empty())
409     {
410         std::vector<Tile*> tilesRefresh;
411         std::vector<Tile*> tilesMark;
412 
413         for(std::pair<std::pair<int, int> const, TileStateNotified>& p : mTilesStateLoaded)
414         {
415             Tile* tile = mGameMap->getTile(p.first.first, p.first.second);
416             if(tile == nullptr)
417             {
418                 OD_LOG_ERR("tile=" + Tile::displayAsString(tile));
419                 continue;
420             }
421 
422             // We check if the tile is marked
423             if(p.second.mMarkedForDigging)
424                 tilesMark.push_back(tile);
425 
426             // Other tiles than goldFull, dirtFull and rockFull need to be notified to refresh
427             switch(p.second.mTileVisual)
428             {
429                 case TileVisual::nullTileVisual:
430                 case TileVisual::goldFull:
431                 case TileVisual::dirtFull:
432                 case TileVisual::rockFull:
433                     break;
434                 default:
435                     tilesRefresh.push_back(tile);
436                     break;
437             }
438         }
439 
440         if(!tilesRefresh.empty())
441         {
442             ServerNotification *serverNotification = new ServerNotification(
443                 ServerNotificationType::refreshTiles, getPlayer());
444             uint32_t nbTiles = tilesRefresh.size();
445             serverNotification->mPacket << nbTiles;
446             for(Tile* tile : tilesRefresh)
447             {
448                 std::pair<int, int> tileCoords(tile->getX(), tile->getY());
449                 TileStateNotified& tileState = mTilesStateLoaded[tileCoords];
450 
451                 // We set the tile visual to make sure the tile state is exported if
452                 // game is saved again
453                 if(tile->getX() >= static_cast<int>(mTilesStates.size()))
454                 {
455                     OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
456                     continue;
457                 }
458                 if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
459                 {
460                     OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
461                     continue;
462                 }
463                 mTilesStates[tile->getX()][tile->getY()] = tileState;
464 
465                 // Then, we export tile state to the client
466                 mGameMap->tileToPacket(serverNotification->mPacket, tile);
467                 tile->exportToPacketForUpdate(serverNotification->mPacket, this);
468             }
469             ODServer::getSingleton().queueServerNotification(serverNotification);
470         }
471 
472         getPlayer()->markTilesForDigging(true, tilesMark, false);
473     }
474 }
475 
setMapSize(int x,int y)476 void Seat::setMapSize(int x, int y)
477 {
478     if(mPlayer == nullptr)
479         return;
480     if(!mPlayer->getIsHuman())
481         return;
482 
483     mTilesStates = std::vector<std::vector<TileStateNotified>>(x, std::vector<TileStateNotified>(y));
484     // By default, we know that rock (ground & full) will be set as rock full tiles,
485     // gold (ground & full) will be set as gold full tiles,
486     // other tiles will be set as dirt full tiles
487     for(int xxx = 0; xxx < x; ++xxx)
488     {
489         for(int yyy = 0; yyy < y; ++yyy)
490         {
491             Tile* tile = mGameMap->getTile(xxx, yyy);
492             if(tile == nullptr)
493                 continue;
494 
495             if(tile->getType() == TileType::gold)
496             {
497                 mTilesStates[xxx][yyy].mTileVisual = TileVisual::goldFull;
498                 continue;
499             }
500 
501             if(tile->getType() == TileType::rock)
502             {
503                 mTilesStates[xxx][yyy].mTileVisual = TileVisual::rockFull;
504                 continue;
505             }
506 
507             mTilesStates[xxx][yyy].mTileVisual = TileVisual::dirtFull;
508         }
509     }
510 }
511 
checkAllGoals()512 unsigned int Seat::checkAllGoals()
513 {
514     // Loop over the goals vector and move any goals that have been met to the completed goals vector.
515     std::vector<Goal*> goalsToAdd;
516     std::vector<Goal*>::iterator currentGoal = mUncompleteGoals.begin();
517     while (currentGoal != mUncompleteGoals.end())
518     {
519         Goal* goal = *currentGoal;
520         // Start by checking if the goal has been met by this seat.
521         if (goal->isMet(*this, *mGameMap))
522         {
523             mCompletedGoals.push_back(goal);
524 
525             // Add any subgoals upon completion to the list of outstanding goals.
526             for (unsigned int i = 0; i < goal->numSuccessSubGoals(); ++i)
527                 goalsToAdd.push_back(goal->getSuccessSubGoal(i));
528 
529             currentGoal = mUncompleteGoals.erase(currentGoal);
530 
531             mHasGoalsChanged = true;
532 
533             // Tells the player an objective has been met.
534             if((mGameMap->getTurnNumber() > 5) &&
535                (getPlayer() != nullptr) &&
536                getPlayer()->getIsHuman() &&
537                !getPlayer()->getHasLost())
538             {
539                 ServerNotification *serverNotification = new ServerNotification(
540                     ServerNotificationType::chatServer, getPlayer());
541 
542                 serverNotification->mPacket << "You have met an objective." << EventShortNoticeType::aboutObjectives;
543                 ODServer::getSingleton().queueServerNotification(serverNotification);
544 
545                 std::vector<Seat*> seats;
546                 seats.push_back(this);
547                 mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::GoalMet);
548             }
549         }
550         else
551         {
552             // If the goal has not been met, check to see if it cannot be met in the future.
553             if (goal->isFailed(*this, *mGameMap))
554             {
555                 mFailedGoals.push_back(goal);
556 
557                 // Add any subgoals upon completion to the list of outstanding goals.
558                 for (unsigned int i = 0; i < goal->numFailureSubGoals(); ++i)
559                     goalsToAdd.push_back(goal->getFailureSubGoal(i));
560 
561                 currentGoal = mUncompleteGoals.erase(currentGoal);
562                 mHasGoalsChanged = true;
563 
564                 // Tells the player an objective has been failed.
565                 if((mGameMap->getTurnNumber() > 5) &&
566                    (getPlayer() != nullptr) &&
567                    getPlayer()->getIsHuman() &&
568                    !getPlayer()->getHasLost())
569                 {
570                     ServerNotification *serverNotification = new ServerNotification(
571                         ServerNotificationType::chatServer, getPlayer());
572 
573                     serverNotification->mPacket << "You have FAILED an objective!" << EventShortNoticeType::majorGameEvent;
574                     ODServer::getSingleton().queueServerNotification(serverNotification);
575 
576                     std::vector<Seat*> seats;
577                     seats.push_back(this);
578                     mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::GoalFailed);
579                 }
580             }
581             else
582             {
583                 // The goal has not been met but has also not been definitively failed, continue on to the next goal in the list.
584                 ++currentGoal;
585             }
586         }
587     }
588 
589     for(std::vector<Goal*>::iterator it = goalsToAdd.begin(); it != goalsToAdd.end(); ++it)
590     {
591         Goal* goal = *it;
592         mUncompleteGoals.push_back(goal);
593     }
594 
595     return numUncompleteGoals();
596 }
597 
notifyChangedVisibleTiles()598 void Seat::notifyChangedVisibleTiles()
599 {
600     if(mPlayer == nullptr)
601         return;
602     if(!mPlayer->getIsHuman())
603         return;
604 
605     std::vector<Tile*> tilesToNotify;
606     int xMax = static_cast<int>(mTilesStates.size());
607     for(int xxx = 0; xxx < xMax; ++xxx)
608     {
609         int yMax = static_cast<int>(mTilesStates[xxx].size());
610         for(int yyy = 0; yyy < yMax; ++yyy)
611         {
612             if(!mTilesStates[xxx][yyy].mVisionTurnCurrent)
613                 continue;
614 
615             Tile* tile = mGameMap->getTile(xxx, yyy);
616             if(!tile->hasChangedForSeat(this))
617                 continue;
618 
619             tilesToNotify.push_back(tile);
620             tile->changeNotifiedForSeat(this);
621         }
622     }
623 
624     if(tilesToNotify.empty())
625         return;
626 
627     uint32_t nbTiles = tilesToNotify.size();
628     ServerNotification *serverNotification = new ServerNotification(
629         ServerNotificationType::refreshTiles, getPlayer());
630     serverNotification->mPacket << nbTiles;
631     for(Tile* tile : tilesToNotify)
632     {
633         mGameMap->tileToPacket(serverNotification->mPacket, tile);
634         updateTileStateForSeat(tile, false);
635         tile->exportToPacketForUpdate(serverNotification->mPacket, this);
636     }
637     ODServer::getSingleton().queueServerNotification(serverNotification);
638 }
639 
stopVisualDebugEntities()640 void Seat::stopVisualDebugEntities()
641 {
642     if(mGameMap->isServerGameMap())
643         return;
644 
645     mIsDebuggingVision = false;
646 
647     for (Tile* tile : mVisualDebugEntityTiles)
648     {
649         if (tile == nullptr)
650             continue;
651 
652         RenderManager::getSingleton().rrDestroySeatVisionVisualDebug(getId(), tile);
653     }
654     mVisualDebugEntityTiles.clear();
655 }
656 
refreshVisualDebugEntities(const std::vector<Tile * > & tiles)657 void Seat::refreshVisualDebugEntities(const std::vector<Tile*>& tiles)
658 {
659     if(mGameMap->isServerGameMap())
660         return;
661 
662     mIsDebuggingVision = true;
663 
664     for (Tile* tile : tiles)
665     {
666         // We check if the visual debug is already on this tile
667         if(std::find(mVisualDebugEntityTiles.begin(), mVisualDebugEntityTiles.end(), tile) != mVisualDebugEntityTiles.end())
668             continue;
669 
670         RenderManager::getSingleton().rrCreateSeatVisionVisualDebug(getId(), tile);
671 
672         mVisualDebugEntityTiles.push_back(tile);
673     }
674 
675     // now, we check if visual debug should be removed from a tile
676     for (std::vector<Tile*>::iterator it = mVisualDebugEntityTiles.begin(); it != mVisualDebugEntityTiles.end();)
677     {
678         Tile* tile = *it;
679         if(std::find(tiles.begin(), tiles.end(), tile) != tiles.end())
680         {
681             ++it;
682             continue;
683         }
684 
685         it = mVisualDebugEntityTiles.erase(it);
686 
687         RenderManager::getSingleton().rrDestroySeatVisionVisualDebug(getId(), tile);
688     }
689 }
690 
toggleSeatVisualDebug()691 void Seat::toggleSeatVisualDebug()
692 {
693     if(!mGameMap->isServerGameMap())
694         return;
695 
696     // Visual debugging do not work for AI players (otherwise, we would have to use
697     // mTilesStates for them which would be memory consuming)
698     if(mPlayer == nullptr)
699         return;
700     if(!mPlayer->getIsHuman())
701         return;
702 
703     mIsDebuggingVision = !mIsDebuggingVision;
704 }
705 
refreshSeatVisualDebug()706 void Seat::refreshSeatVisualDebug()
707 {
708     int seatId = getId();
709     if(mIsDebuggingVision)
710     {
711         std::vector<Tile*> tiles;
712         int xMax = static_cast<int>(mTilesStates.size());
713         for(int xxx = 0; xxx < xMax; ++xxx)
714         {
715             int yMax = static_cast<int>(mTilesStates[xxx].size());
716             for(int yyy = 0; yyy < yMax; ++yyy)
717             {
718                 if(!mTilesStates[xxx][yyy].mVisionTurnCurrent)
719                     continue;
720 
721                 Tile* tile = mGameMap->getTile(xxx, yyy);
722                 tiles.push_back(tile);
723             }
724         }
725         uint32_t nbTiles = tiles.size();
726         ServerNotification *serverNotification = new ServerNotification(
727             ServerNotificationType::refreshSeatVisDebug, nullptr);
728         serverNotification->mPacket << seatId;
729         serverNotification->mPacket << true;
730         serverNotification->mPacket << nbTiles;
731         for(Tile* tile : tiles)
732         {
733             mGameMap->tileToPacket(serverNotification->mPacket, tile);
734         }
735         ODServer::getSingleton().queueServerNotification(serverNotification);
736     }
737     else
738     {
739         ServerNotification *serverNotification = new ServerNotification(
740             ServerNotificationType::refreshSeatVisDebug, nullptr);
741         serverNotification->mPacket << seatId;
742         serverNotification->mPacket << false;
743         ODServer::getSingleton().queueServerNotification(serverNotification);
744     }
745 }
746 
sendVisibleTiles()747 void Seat::sendVisibleTiles()
748 {
749     if(!mGameMap->isServerGameMap())
750         return;
751 
752     if(getPlayer() == nullptr)
753         return;
754 
755     if(!getPlayer()->getIsHuman())
756         return;
757 
758     uint32_t nbTiles;
759     ServerNotification *serverNotification = new ServerNotification(
760         ServerNotificationType::refreshVisibleTiles, getPlayer());
761     std::vector<Tile*> tilesVisionGained;
762     std::vector<Tile*> tilesVisionLost;
763     // Tiles we gained vision
764     int xMax = static_cast<int>(mTilesStates.size());
765     for(int xxx = 0; xxx < xMax; ++xxx)
766     {
767         int yMax = static_cast<int>(mTilesStates[xxx].size());
768         for(int yyy = 0; yyy < yMax; ++yyy)
769         {
770             if(mTilesStates[xxx][yyy].mVisionTurnCurrent == mTilesStates[xxx][yyy].mVisionTurnLast)
771                 continue;
772 
773             Tile* tile = mGameMap->getTile(xxx, yyy);
774             if(mTilesStates[xxx][yyy].mVisionTurnCurrent)
775             {
776                 // Vision gained
777                 tilesVisionGained.push_back(tile);
778             }
779             else
780             {
781                 // Vision lost
782                 tilesVisionLost.push_back(tile);
783             }
784         }
785     }
786 
787     // Notify tiles we gained vision
788     nbTiles = tilesVisionGained.size();
789     serverNotification->mPacket << nbTiles;
790     for(Tile* tile : tilesVisionGained)
791     {
792         mGameMap->tileToPacket(serverNotification->mPacket, tile);
793     }
794 
795     // Notify tiles we lost vision
796     nbTiles = tilesVisionLost.size();
797     serverNotification->mPacket << nbTiles;
798     for(Tile* tile : tilesVisionLost)
799     {
800         mGameMap->tileToPacket(serverNotification->mPacket, tile);
801     }
802     ODServer::getSingleton().queueServerNotification(serverNotification);
803 }
804 
computeSeatBeginTurn()805 void Seat::computeSeatBeginTurn()
806 {
807     if(mPlayer != nullptr)
808     {
809         std::fill(mNbRooms.begin(), mNbRooms.end(), 0);
810         for(Room* room : mGameMap->getRooms())
811         {
812             if(room->getSeat() != this)
813                 continue;
814 
815             if(room->getHP(nullptr) <= 0.0)
816                 continue;
817 
818             uint32_t index = static_cast<uint32_t>(room->getType());
819             if(index >= mNbRooms.size())
820             {
821                 OD_LOG_ERR("wrong index=" + Helper::toString(index) + ", size=" + Helper::toString(mNbRooms.size()));
822                 return;
823             }
824             ++mNbRooms[index];
825         }
826     }
827 }
828 
829 
createRogueSeat(GameMap * gameMap)830 Seat* Seat::createRogueSeat(GameMap* gameMap)
831 {
832     Seat* seat = new Seat(gameMap);
833     seat->mId = 0;
834     seat->mTeamId = 0;
835     seat->mAvailableTeamIds.push_back(0);
836     seat->mPlayerType = PLAYER_TYPE_INACTIVE;
837     seat->mStartingX = 0;
838     seat->mStartingY = 0;
839     seat->mGold = 0;
840     seat->mGoldMax = 0;
841     seat->mGoldMined = 0;
842     seat->mColorId = "0";
843     seat->mMana = 0;
844 
845     // In editor, we do not add the player on rogue seat because that's where the human player will be since that's the
846     // only seat we are sure to exist
847     if(!gameMap->isInEditorMode())
848     {
849         Player* inactivePlayer = new Player(gameMap, 0);
850         inactivePlayer->setNick("Inactive rogue AI");
851         gameMap->addPlayer(inactivePlayer);
852         seat->setPlayer(inactivePlayer);
853     }
854 
855     return seat;
856 }
857 
858 
importSeatFromStream(std::istream & is)859 bool Seat::importSeatFromStream(std::istream& is)
860 {
861     std::string str;
862     OD_ASSERT_TRUE(is >> str);
863     if(str != "seatId")
864     {
865         OD_LOG_INF("WARNING: expected seatId and read " + str);
866         return false;
867     }
868     OD_ASSERT_TRUE(is >> mId);
869     if(mId == 0)
870     {
871         OD_LOG_INF("WARNING: Forbidden seatId used");
872         return false;
873     }
874 
875     OD_ASSERT_TRUE(is >> str);
876     if(str != "teamId")
877     {
878         OD_LOG_INF("WARNING: expected teamId and read " + str);
879         return false;
880     }
881     OD_ASSERT_TRUE(is >> str);
882 
883     std::vector<std::string> teamIds = Helper::split(str, '/');
884     for(const std::string& strTeamId : teamIds)
885     {
886         int teamId = Helper::toInt(strTeamId);
887         if(teamId == 0)
888         {
889             OD_LOG_INF("WARNING: forbidden teamId in seat id=" + Helper::toString(mId));
890             continue;
891         }
892 
893         mAvailableTeamIds.push_back(teamId);
894     }
895 
896     OD_ASSERT_TRUE(is >> str);
897     if(str != "player")
898     {
899         OD_LOG_INF("WARNING: expected player and read " + str);
900         return false;
901     }
902     OD_ASSERT_TRUE(is >> mPlayerType);
903 
904     OD_ASSERT_TRUE(is >> str);
905     if(str != "faction")
906     {
907         OD_LOG_INF("WARNING: expected faction and read " + str);
908         return false;
909     }
910     OD_ASSERT_TRUE(is >> mFaction);
911 
912     OD_ASSERT_TRUE(is >> str);
913     if(str != "startingX")
914     {
915         OD_LOG_INF("WARNING: expected startingX and read " + str);
916         return false;
917     }
918     OD_ASSERT_TRUE(is >> mStartingX);
919 
920     OD_ASSERT_TRUE(is >> str);
921     if(str != "startingY")
922     {
923         OD_LOG_INF("WARNING: expected startingY and read " + str);
924         return false;
925     }
926     OD_ASSERT_TRUE(is >> mStartingY);
927 
928     OD_ASSERT_TRUE(is >> str);
929     if(str != "colorId")
930     {
931         OD_LOG_INF("WARNING: expected colorId and read " + str);
932         return false;
933     }
934     OD_ASSERT_TRUE(is >> mColorId);
935 
936     OD_ASSERT_TRUE(is >> str);
937     if(str != "gold")
938     {
939         OD_LOG_INF("WARNING: expected gold and read " + str);
940         return false;
941     }
942     OD_ASSERT_TRUE(is >> mGold);
943 
944     OD_ASSERT_TRUE(is >> str);
945     if(str != "goldMined")
946     {
947         OD_LOG_INF("WARNING: expected goldMined and read " + str);
948         return false;
949     }
950     OD_ASSERT_TRUE(is >> mGoldMined);
951 
952     OD_ASSERT_TRUE(is >> str);
953     if(str != "mana")
954     {
955         OD_LOG_INF("WARNING: expected mana and read " + str);
956         return false;
957     }
958     OD_ASSERT_TRUE(is >> mMana);
959 
960     mColorValue = ConfigManager::getSingleton().getColorFromId(mColorId);
961 
962     uint32_t nbSkill = static_cast<uint32_t>(SkillType::countSkill);
963     OD_ASSERT_TRUE(is >> str);
964     if(str != "[SkillDone]")
965     {
966         OD_LOG_INF("WARNING: expected [SkillDone] and read " + str);
967         return false;
968     }
969     while(true)
970     {
971         OD_ASSERT_TRUE(is >> str);
972         if(str == "[/SkillDone]")
973             break;
974 
975         for(uint32_t i = 0; i < nbSkill; ++i)
976         {
977             SkillType type = static_cast<SkillType>(i);
978             if(type == SkillType::nullSkillType)
979                 continue;
980             if(str.compare(Skills::toString(type)) != 0)
981                 continue;
982 
983             if(std::find(mSkillDone.begin(), mSkillDone.end(), type) != mSkillDone.end())
984                 break;
985 
986             // We found a valid skill
987             mSkillDone.push_back(type);
988 
989             break;
990         }
991     }
992 
993     OD_ASSERT_TRUE(is >> str);
994     if(str != "[SkillNotAllowed]")
995     {
996         OD_LOG_INF("WARNING: expected [SkillNotAllowed] and read " + str);
997         return false;
998     }
999 
1000     while(true)
1001     {
1002         OD_ASSERT_TRUE(is >> str);
1003         if(str == "[/SkillNotAllowed]")
1004             break;
1005 
1006         for(uint32_t i = 0; i < nbSkill; ++i)
1007         {
1008             SkillType type = static_cast<SkillType>(i);
1009             if(type == SkillType::nullSkillType)
1010                 continue;
1011             if(str.compare(Skills::toString(type)) != 0)
1012                 continue;
1013 
1014             // We do not allow a skill to be done and not allowed
1015             if(std::find(mSkillDone.begin(), mSkillDone.end(), type) != mSkillDone.end())
1016                 break;
1017             if(std::find(mSkillNotAllowed.begin(), mSkillNotAllowed.end(), type) != mSkillNotAllowed.end())
1018                 break;
1019 
1020             // We found a valid skill
1021             mSkillNotAllowed.push_back(type);
1022 
1023             break;
1024         }
1025     }
1026 
1027     OD_ASSERT_TRUE(is >> str);
1028     if(str != "[SkillPending]")
1029     {
1030         OD_LOG_INF("WARNING: expected [SkillPending] and read " + str);
1031         return false;
1032     }
1033 
1034     while(true)
1035     {
1036         OD_ASSERT_TRUE(is >> str);
1037         if(str == "[/SkillPending]")
1038             break;
1039 
1040         for(uint32_t i = 0; i < nbSkill; ++i)
1041         {
1042             SkillType type = static_cast<SkillType>(i);
1043             if(type == SkillType::nullSkillType)
1044                 continue;
1045             if(str.compare(Skills::toString(type)) != 0)
1046                 continue;
1047 
1048             // We do not allow skills already done or not allowed
1049             if(std::find(mSkillDone.begin(), mSkillDone.end(), type) != mSkillDone.end())
1050                 break;
1051             if(std::find(mSkillNotAllowed.begin(), mSkillNotAllowed.end(), type) != mSkillNotAllowed.end())
1052                 break;
1053             if(std::find(mSkillPending.begin(), mSkillPending.end(), type) != mSkillPending.end())
1054                 break;
1055 
1056             // We found a valid skill
1057             mSkillPending.push_back(type);
1058         }
1059     }
1060 
1061     // Note: At this point, we are reading seats but the tiles may change during map load.
1062     // That's why we cannot keep pointers to tiles. However, map size is already set
1063     // so we can use it
1064     uint32_t nb = static_cast<uint32_t>(TileVisual::countTileVisual);
1065     for(uint32_t k = 0; k < nb; ++k)
1066     {
1067         TileVisual tileVisual = static_cast<TileVisual>(k);
1068         // Full dirt tiles, full gold tiles and full rock tiles are automatically
1069         // set so we don't have to bother about them
1070         switch(tileVisual)
1071         {
1072             case TileVisual::nullTileVisual:
1073             case TileVisual::goldFull:
1074             case TileVisual::dirtFull:
1075             case TileVisual::rockFull:
1076                 continue;
1077 
1078             default:
1079                 break;
1080         }
1081         int ret = readTilesVisualInitialStates(tileVisual, is);
1082         if(ret == 0)
1083             return true;
1084         else if(ret == -1)
1085             return false;
1086     }
1087 
1088     OD_ASSERT_TRUE(is >> str);
1089     if(str != "[markedTiles]")
1090     {
1091         OD_LOG_INF("WARNING: expected [markedTiles] and read " + str);
1092         return false;
1093     }
1094 
1095     while(true)
1096     {
1097         OD_ASSERT_TRUE(is >> str);
1098         if(str == "[/markedTiles]")
1099             break;
1100 
1101         std::pair<int, int> tilecoords;
1102         tilecoords.first = Helper::toInt(str);
1103         OD_ASSERT_TRUE(is >> str);
1104         tilecoords.second = Helper::toInt(str);
1105 
1106         TileStateNotified& tileState = mTilesStateLoaded[tilecoords];
1107         tileState.mMarkedForDigging = true;
1108     }
1109 
1110     // The next line should be a seat end tag
1111     OD_ASSERT_TRUE(is >> str);
1112     if(str != "[/Seat]")
1113     {
1114         OD_LOG_INF("WARNING: expected [/Seat] and read " + str);
1115         return false;
1116     }
1117 
1118     return true;
1119 }
1120 
1121 
exportSeatToStream(std::ostream & os) const1122 bool Seat::exportSeatToStream(std::ostream& os) const
1123 {
1124     os << "seatId\t";
1125     os << mId;
1126     os << std::endl;
1127     // If the team id is set, we save it. Otherwise, we save all the available team ids
1128     // That way, save map will work in both editor and in game.
1129     os << "teamId\t";
1130     if(mTeamId != -1)
1131     {
1132         os << mTeamId;
1133     }
1134     else
1135     {
1136         int cpt = 0;
1137         for(int teamId : mAvailableTeamIds)
1138         {
1139             if(cpt > 0)
1140                 os << "/";
1141 
1142             os << teamId;
1143             ++cpt;
1144         }
1145     }
1146     os << std::endl;
1147 
1148     // On editor, we write the original player type. If we are saving a game, we keep the assigned type
1149     if((mGameMap->isInEditorMode()) ||
1150         (getPlayer() == nullptr))
1151     {
1152         os << "player\t";
1153         os << mPlayerType;
1154         os << std::endl;
1155     }
1156     else
1157     {
1158         os << "player\t";
1159         if(getPlayer()->getIsHuman())
1160             os << PLAYER_TYPE_HUMAN;
1161         else
1162             os << PLAYER_TYPE_AI;
1163 
1164         os << std::endl;
1165     }
1166 
1167     os << "faction\t";
1168     os << mFaction;
1169     os << std::endl;
1170 
1171     os << "startingX\t";
1172     os << mStartingX;
1173     os << std::endl;
1174 
1175     os << "startingY\t";
1176     os << mStartingY;
1177     os << std::endl;
1178 
1179     os << "colorId\t";
1180     os << mColorId;
1181     os << std::endl;
1182 
1183     os << "gold\t";
1184     os << mGold;
1185     os << std::endl;
1186 
1187     os << "goldMined\t";
1188     os << mGoldMined;
1189     os << std::endl;
1190 
1191     os << "mana\t";
1192     os << mMana;
1193     os << std::endl;
1194 
1195     os << "[SkillDone]" << std::endl;
1196     for(SkillType type : mSkillDone)
1197     {
1198         os << Skills::toString(type) << std::endl;
1199     }
1200     os << "[/SkillDone]" << std::endl;
1201 
1202     os << "[SkillNotAllowed]" << std::endl;
1203     for(SkillType type : mSkillNotAllowed)
1204     {
1205         os << Skills::toString(type) << std::endl;
1206     }
1207     os << "[/SkillNotAllowed]" << std::endl;
1208 
1209     os << "[SkillPending]" << std::endl;
1210     for(SkillType type : mSkillPending)
1211     {
1212         os << Skills::toString(type) << std::endl;
1213     }
1214     os << "[/SkillPending]" << std::endl;
1215 
1216     // In editor mode, we don't save tile states
1217     if(mGameMap->isInEditorMode())
1218         return true;
1219 
1220     // Tile states are only saved for human players
1221     if((getPlayer() == nullptr) ||
1222         (!getPlayer()->getIsHuman()))
1223     {
1224         return true;
1225     }
1226 
1227     // We save the visible tiles last state
1228     uint32_t nb = static_cast<uint32_t>(TileVisual::countTileVisual);
1229     for(uint32_t k = 0; k < nb; ++k)
1230     {
1231         TileVisual tileVisual = static_cast<TileVisual>(k);
1232         // Full dirt tiles, full gold tiles and full rock tiles are automatically
1233         // set so we don't have to bother about them
1234         switch(tileVisual)
1235         {
1236             case TileVisual::nullTileVisual:
1237             case TileVisual::goldFull:
1238             case TileVisual::dirtFull:
1239             case TileVisual::rockFull:
1240                 continue;
1241 
1242             default:
1243                 break;
1244         }
1245         exportTilesVisualInitialStates(tileVisual, os);
1246     }
1247 
1248     os << "[markedTiles]" << std::endl;
1249     for(uint32_t xxx = 0; xxx < mTilesStates.size(); ++xxx)
1250     {
1251         for(uint32_t yyy = 0; yyy < mTilesStates[xxx].size(); ++yyy)
1252         {
1253             const TileStateNotified& tileState = mTilesStates[xxx][yyy];
1254             if(!tileState.mMarkedForDigging)
1255                 continue;
1256 
1257             os << xxx << "\t" << yyy << std::endl;
1258         }
1259     }
1260     os << "[/markedTiles]" << std::endl;
1261 
1262     return true;
1263 }
1264 
exportTilesVisualInitialStates(TileVisual tileVisual,std::ostream & os) const1265 void Seat::exportTilesVisualInitialStates(TileVisual tileVisual, std::ostream& os) const
1266 {
1267     os << "[" + Tile::tileVisualToString(tileVisual) + "]" << std::endl;
1268 
1269     for(uint32_t xxx = 0; xxx < mTilesStates.size(); ++xxx)
1270     {
1271         for(uint32_t yyy = 0; yyy < mTilesStates[xxx].size(); ++yyy)
1272         {
1273             const TileStateNotified& tileState = mTilesStates[xxx][yyy];
1274             if(tileState.mTileVisual != tileVisual)
1275                 continue;
1276 
1277             os << xxx << "\t" << yyy << "\t" << tileState.mSeatIdOwner << std::endl;
1278         }
1279     }
1280 
1281     os << "[/" + Tile::tileVisualToString(tileVisual) + "]" << std::endl;
1282 }
1283 
addSkill(SkillType type)1284 bool Seat::addSkill(SkillType type)
1285 {
1286     if(std::find(mSkillDone.begin(), mSkillDone.end(), type) != mSkillDone.end())
1287         return false;
1288 
1289     std::vector<SkillType> skillDone = mSkillDone;
1290     skillDone.push_back(type);
1291     setSkillsDone(skillDone);
1292 
1293     // Tells the player a new room/trap/spell is available.
1294     if((getPlayer() != nullptr) &&
1295        getPlayer()->getIsHuman() &&
1296        !getPlayer()->getHasLost())
1297     {
1298         ServerNotification *serverNotification = new ServerNotification(
1299             ServerNotificationType::chatServer, getPlayer());
1300 
1301         std::string msg = Skills::skillTypeToPlayerVisibleString(type) + " is now available.";
1302         serverNotification->mPacket << msg << EventShortNoticeType::aboutSkills;
1303         ODServer::getSingleton().queueServerNotification(serverNotification);
1304     }
1305 
1306     return true;
1307 }
1308 
isSkillDone(SkillType type) const1309 bool Seat::isSkillDone(SkillType type) const
1310 {
1311     for(SkillType skillDone : mSkillDone)
1312     {
1313         if(skillDone == type)
1314             return true;
1315     }
1316 
1317     return false;
1318 }
1319 
isSkillPending(SkillType skillType) const1320 uint32_t Seat::isSkillPending(SkillType skillType) const
1321 {
1322     uint32_t queueNumber = 1;
1323     for (SkillType pending : mSkillPending)
1324     {
1325         if (pending == skillType)
1326             return queueNumber;
1327         ++queueNumber;
1328     }
1329     return 0;
1330 }
1331 
getFirstSkillPending() const1332 SkillType Seat::getFirstSkillPending() const
1333 {
1334     if(mSkillPending.empty())
1335         return SkillType::nullSkillType;
1336 
1337     return mSkillPending.at(0);
1338 }
1339 
addSkillPoints(int32_t points)1340 void Seat::addSkillPoints(int32_t points)
1341 {
1342     // Even if we are not searching anything, we allow to bring back a skill book if
1343     // we find any
1344     mSkillPoints += points;
1345     if(mCurrentSkill == nullptr)
1346     {
1347         mCurrentSkillType = SkillType::nullSkillType;
1348         return;
1349     }
1350 
1351     if(mSkillPoints < mCurrentSkill->getNeededSkillPoints())
1352     {
1353         mCurrentSkillType = mCurrentSkill->getType();
1354         mCurrentSkillProgress = static_cast<float>(mSkillPoints) / static_cast<float>(mCurrentSkill->getNeededSkillPoints());
1355         return;
1356     }
1357 
1358     // The current skill is complete. We add it to the available skill list
1359     mSkillPoints -= mCurrentSkill->getNeededSkillPoints();
1360     addSkill(mCurrentSkill->getType());
1361 
1362     // We set the next skill
1363     setNextSkill(mCurrentSkill->getType());
1364 }
1365 
getCurrentSkillProgress(SkillType & type,float & progress) const1366 bool Seat::getCurrentSkillProgress(SkillType& type, float& progress) const
1367 {
1368     if(mCurrentSkillType == SkillType::nullSkillType)
1369         return false;
1370 
1371     type = mCurrentSkillType;
1372     progress = mCurrentSkillProgress;
1373     return true;
1374 }
1375 
setNextSkill(SkillType skilledType)1376 void Seat::setNextSkill(SkillType skilledType)
1377 {
1378     if(!mGameMap->isServerGameMap())
1379         return;
1380 
1381     mCurrentSkill = nullptr;
1382     if(mSkillPending.empty())
1383     {
1384         mCurrentSkillType = SkillType::nullSkillType;
1385 
1386         // Notify the player that no skill is in the queue if there are still available skills
1387         if(SkillManager::isAllSkillsDoneForSeat(this))
1388             return;
1389 
1390         if(getPlayer() == nullptr)
1391             return;
1392         if(!getPlayer()->getIsHuman())
1393             return;
1394         if(getPlayer()->getHasLost())
1395             return;
1396         if(getNbRooms(RoomType::library) <= 0)
1397             return;
1398 
1399         getPlayer()->notifyNoSkillInQueue();
1400         return;
1401     }
1402 
1403     // We search for the first pending skill we don't own a corresponding SkillEntity
1404     SkillType skillType = SkillType::nullSkillType;
1405     for(SkillType pending : mSkillPending)
1406     {
1407         if(pending == skilledType)
1408             continue;
1409 
1410         skillType = pending;
1411 
1412         if(skillType != SkillType::nullSkillType)
1413             break;
1414     }
1415 
1416     if(skillType == SkillType::nullSkillType)
1417     {
1418         mCurrentSkillType = SkillType::nullSkillType;
1419         return;
1420     }
1421 
1422     // We have found a fitting skill. We retrieve the corresponding Skill
1423     // object and start working on that
1424     mCurrentSkill = SkillManager::getSkill(skillType);
1425     if(mCurrentSkill == nullptr)
1426     {
1427         mCurrentSkillType = SkillType::nullSkillType;
1428         return;
1429     }
1430 
1431     mCurrentSkillType = mCurrentSkill->getType();
1432     mCurrentSkillProgress = static_cast<float>(mSkillPoints) / static_cast<float>(mCurrentSkill->getNeededSkillPoints());
1433 }
1434 
setSkillsDone(const std::vector<SkillType> & skills)1435 void Seat::setSkillsDone(const std::vector<SkillType>& skills)
1436 {
1437     mSkillDone = skills;
1438     // We remove the skills done from the pending skills (if it was there,
1439     // which may not be true if the skill list changed after creating the
1440     // skillEntity for example)
1441     for(SkillType type : skills)
1442     {
1443         auto skill = std::find(mSkillPending.begin(), mSkillPending.end(), type);
1444         if(skill == mSkillPending.end())
1445             continue;
1446 
1447         mSkillPending.erase(skill);
1448     }
1449 
1450     if(mGameMap->isServerGameMap())
1451     {
1452         if((getPlayer() != nullptr) && getPlayer()->getIsHuman())
1453         {
1454             // We notify the client
1455             ServerNotification *serverNotification = new ServerNotification(
1456                 ServerNotificationType::skillsDone, getPlayer());
1457 
1458             uint32_t nbItems = mSkillDone.size();
1459             serverNotification->mPacket << nbItems;
1460             for(SkillType skill : mSkillDone)
1461                 serverNotification->mPacket << skill;
1462 
1463             ODServer::getSingleton().queueServerNotification(serverNotification);
1464         }
1465     }
1466     else
1467     {
1468         // We notify the mode that the available skills changed. This way, it will
1469         // be able to update the UI as needed
1470         mGuiSkillNeedsRefresh = true;
1471     }
1472 }
1473 
setSkillTree(const std::vector<SkillType> & skills)1474 void Seat::setSkillTree(const std::vector<SkillType>& skills)
1475 {
1476     if(mGameMap->isServerGameMap())
1477     {
1478         // We check if all the skills in the vector are allowed. If not, we don't update the list
1479         std::vector<SkillType> skillsDoneInTree = mSkillDone;
1480         for(SkillType skillType : skills)
1481         {
1482             // We check if the skill is allowed
1483             if(std::find(mSkillNotAllowed.begin(), mSkillNotAllowed.end(), skillType) != mSkillNotAllowed.end())
1484             {
1485                 // Invalid skill. This might be allowed in the gui to enter invalid
1486                 // values. In this case, we should remove the assert
1487                 OD_LOG_ERR("Unallowed skill: " + Skills::toString(skillType));
1488                 return;
1489             }
1490             const Skill* skill = SkillManager::getSkill(skillType);
1491             if(skill == nullptr)
1492             {
1493                 // We found an unknown skill
1494                 OD_LOG_ERR("Unknown skill: " + Skills::toString(skillType));
1495                 return;
1496             }
1497 
1498             if(!skill->canBeSkilled(skillsDoneInTree))
1499             {
1500                 // Invalid skill. This might happen if the level has a skill pending with a non skillable dependency.
1501                 // In this case, we don't use the skill tree
1502                 OD_LOG_ERR("Unallowed skill: " + Skills::toString(skillType));
1503                 return;
1504             }
1505 
1506             // This skill is valid. We add it in the list and we check if the next one also is
1507             skillsDoneInTree.push_back(skillType);
1508         }
1509 
1510         mSkillPending = skills;
1511         if((getPlayer() != nullptr) && getPlayer()->getIsHuman())
1512         {
1513             // We notify the client
1514             ServerNotification *serverNotification = new ServerNotification(
1515                 ServerNotificationType::skillTree, getPlayer());
1516 
1517             uint32_t nbItems = mSkillPending.size();
1518             serverNotification->mPacket << nbItems;
1519             for(SkillType skill : mSkillPending)
1520                 serverNotification->mPacket << skill;
1521 
1522             ODServer::getSingleton().queueServerNotification(serverNotification);
1523         }
1524 
1525         // We start working on the skill tree
1526         setNextSkill(SkillType::nullSkillType);
1527     }
1528     else
1529     {
1530         // On client side, no need to check if the skill tree is allowed
1531         mSkillPending = skills;
1532 
1533         // Makes the client gui update.
1534         mGuiSkillNeedsRefresh = true;
1535     }
1536 }
1537 
updateTileStateForSeat(Tile * tile,bool hideSeatId)1538 void Seat::updateTileStateForSeat(Tile* tile, bool hideSeatId)
1539 {
1540     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
1541     {
1542         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1543         return;
1544     }
1545     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
1546     {
1547         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1548         return;
1549     }
1550 
1551     TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
1552     tileState.mTileVisual = tile->getTileVisual();
1553     switch(tileState.mTileVisual)
1554     {
1555         case TileVisual::claimedFull:
1556         case TileVisual::claimedGround:
1557             if(tile->getSeat() == nullptr)
1558             {
1559                 OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1560             }
1561             else
1562             {
1563                 tileState.mSeatIdOwner = tile->getSeat()->getId();
1564             }
1565             break;
1566         case TileVisual::waterGround:
1567         case TileVisual::lavaGround:
1568             if(tile->getCoveringBuilding() != nullptr)
1569             {
1570                 if(tile->getSeat() == nullptr)
1571                 {
1572                     OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1573                 }
1574                 else
1575                 {
1576                     tileState.mSeatIdOwner = tile->getSeat()->getId();
1577                 }
1578             }
1579             break;
1580         default:
1581             tileState.mSeatIdOwner = -1;
1582             break;
1583     }
1584 
1585     if(tile->getCoveringBuilding() == tileState.mBuilding)
1586         return;
1587 
1588     // If we are hiding seat id, we do not notify the building about vision
1589     // so that it doesn't send the building seat id
1590     if((tileState.mBuilding != nullptr) && !hideSeatId)
1591         tileState.mBuilding->notifySeatVision(tile, this);
1592 
1593     if((tile->getCoveringBuilding() != nullptr) &&
1594         (tile->getCoveringBuilding()->isTileVisibleForSeat(tile, this)))
1595     {
1596         tileState.mBuilding = tile->getCoveringBuilding();
1597         if(!hideSeatId)
1598             tileState.mBuilding->notifySeatVision(tile, this);
1599     }
1600     else
1601     {
1602         tileState.mBuilding = nullptr;
1603     }
1604 }
1605 
setVisibleBuildingOnTile(Building * building,Tile * tile)1606 void Seat::setVisibleBuildingOnTile(Building* building, Tile* tile)
1607 {
1608     if(getPlayer() == nullptr)
1609         return;
1610     if(!getPlayer()->getIsHuman())
1611         return;
1612 
1613     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
1614     {
1615         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1616         return;
1617     }
1618     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
1619     {
1620         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1621         return;
1622     }
1623 
1624     TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
1625 
1626     if(building == tileState.mBuilding)
1627         return;
1628 
1629     tileState.mBuilding = building;
1630     tileState.mSeatIdOwner = building->getSeat()->getId();
1631 }
1632 
exportTileToPacket(ODPacket & os,const Tile * tile,bool hideSeatId) const1633 void Seat::exportTileToPacket(ODPacket& os, const Tile* tile,
1634         bool hideSeatId) const
1635 {
1636     if(getPlayer() == nullptr)
1637     {
1638         OD_LOG_ERR("SeatId=" + Helper::toString(getId()));
1639         return;
1640     }
1641     if(!getPlayer()->getIsHuman())
1642     {
1643         OD_LOG_ERR("SeatId=" + Helper::toString(getId()));
1644         return;
1645     }
1646 
1647     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
1648     {
1649         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1650         return;
1651     }
1652     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
1653     {
1654         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1655         return;
1656     }
1657 
1658     const TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
1659 
1660     int tileSeatId = -1;
1661     // We only pass the tile seat to the client if the tile is fully claimed
1662     if(!hideSeatId)
1663     {
1664         switch(tileState.mTileVisual)
1665         {
1666             case TileVisual::claimedGround:
1667             case TileVisual::claimedFull:
1668                 tileSeatId = tileState.mSeatIdOwner;
1669                 break;
1670             case TileVisual::waterGround:
1671             case TileVisual::lavaGround:
1672                 if(tileState.mBuilding != nullptr)
1673                     tileSeatId = tileState.mSeatIdOwner;
1674                 break;
1675             default:
1676                 break;
1677         }
1678     }
1679 
1680     std::string meshName;
1681 
1682     if((tileState.mBuilding != nullptr) &&
1683        !tileState.mBuilding->getMeshName().empty())
1684     {
1685         meshName = tileState.mBuilding->getMeshName() + ".mesh";
1686     }
1687     else
1688     {
1689         // We set an empty mesh so that the client can compute the tile itself
1690         meshName.clear();
1691     }
1692     bool isRoom = false;
1693     bool isTrap = false;
1694     bool displayTileMesh = true;
1695     bool colorCustomMesh = false;
1696     bool hasBridge = false;
1697 
1698     uint32_t refundPriceRoom = 0;
1699     uint32_t refundPriceTrap = 0;
1700     if(tileState.mBuilding != nullptr)
1701     {
1702         displayTileMesh = tileState.mBuilding->displayTileMesh();
1703         colorCustomMesh = tileState.mBuilding->colorCustomMesh();
1704 
1705         if(tileState.mBuilding->getObjectType() == GameEntityType::room)
1706         {
1707             isRoom = true;
1708             Room* room = static_cast<Room*>(tileState.mBuilding);
1709             if(room->getSeat() == this)
1710                 refundPriceRoom = (RoomManager::costPerTile(room->getType()) / 2);
1711 
1712             hasBridge = room->isBridge();
1713         }
1714         else if(tileState.mBuilding->getObjectType() == GameEntityType::trap)
1715         {
1716             isTrap = true;
1717             Trap* trap = static_cast<Trap*>(tileState.mBuilding);
1718             if(trap->getSeat() == this)
1719                 refundPriceTrap = (TrapManager::costPerTile(trap->getType()) / 2);
1720         }
1721     }
1722     os << isRoom;
1723     os << isTrap;
1724     os << refundPriceRoom;
1725     os << refundPriceTrap;
1726     os << displayTileMesh;
1727     os << colorCustomMesh;
1728     os << hasBridge;
1729     os << tileSeatId;
1730     os << meshName;
1731     os << tileState.mTileVisual;
1732 }
1733 
notifyBuildingRemovedFromGameMap(Building * building,Tile * tile)1734 void Seat::notifyBuildingRemovedFromGameMap(Building* building, Tile* tile)
1735 {
1736     if(getPlayer() == nullptr)
1737         return;
1738     if(!getPlayer()->getIsHuman())
1739         return;
1740 
1741     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
1742     {
1743         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1744         return;
1745     }
1746     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
1747     {
1748         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1749         return;
1750     }
1751 
1752     TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
1753     if(tileState.mBuilding == building)
1754         tileState.mBuilding = nullptr;
1755 }
1756 
tileMarkedDiggingNotifiedToPlayer(Tile * tile,bool isDigSet)1757 void Seat::tileMarkedDiggingNotifiedToPlayer(Tile* tile, bool isDigSet)
1758 {
1759     if(getPlayer() == nullptr)
1760         return;
1761     if(!getPlayer()->getIsHuman())
1762         return;
1763 
1764     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
1765     {
1766         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1767         return;
1768     }
1769     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
1770     {
1771         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1772         return;
1773     }
1774 
1775     TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
1776     tileState.mMarkedForDigging = isDigSet;
1777 }
1778 
isTileDiggableForClient(Tile * tile) const1779 bool Seat::isTileDiggableForClient(Tile* tile) const
1780 {
1781     if(!getPlayer()->getIsHuman())
1782         return false;
1783     if(tile->getX() >= static_cast<int>(mTilesStates.size()))
1784     {
1785         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1786         return false;
1787     }
1788     if(tile->getY() >= static_cast<int>(mTilesStates[tile->getX()].size()))
1789     {
1790         OD_LOG_ERR("Tile=" + Tile::displayAsString(tile));
1791         return false;
1792     }
1793 
1794     const TileStateNotified& tileState = mTilesStates[tile->getX()][tile->getY()];
1795     // Handle non claimed
1796     switch(tileState.mTileVisual)
1797     {
1798         case TileVisual::claimedGround:
1799         case TileVisual::dirtGround:
1800         case TileVisual::goldGround:
1801         case TileVisual::lavaGround:
1802         case TileVisual::waterGround:
1803         case TileVisual::rockGround:
1804         case TileVisual::gemGround:
1805         case TileVisual::rockFull:
1806             return false;
1807         case TileVisual::goldFull:
1808         case TileVisual::dirtFull:
1809         case TileVisual::gemFull:
1810             return true;
1811         default:
1812             break;
1813     }
1814 
1815     // Should be claimed tile
1816     if(tileState.mTileVisual != TileVisual::claimedFull)
1817     {
1818         OD_LOG_ERR("mTileVisual=" + Tile::tileVisualToString(tileState.mTileVisual));
1819         return false;
1820     }
1821 
1822     // It is claimed. If it is by the given seat team, it can be dug
1823     Seat* seat = mGameMap->getSeatById(tileState.mSeatIdOwner);
1824     if(!canOwnedTileBeClaimedBy(seat))
1825         return true;
1826 
1827     return false;
1828 }
1829 
getNextFighterClassToSpawn(const GameMap & gameMap,const ConfigManager & configManager)1830 const CreatureDefinition* Seat::getNextFighterClassToSpawn(const GameMap& gameMap, const ConfigManager& configManager)
1831 {
1832     std::vector<std::pair<const CreatureDefinition*, int32_t> > defSpawnable;
1833     int32_t nbPointsTotal = 0;
1834     for(std::pair<const CreatureDefinition*, bool>& def : mSpawnPool)
1835     {
1836         // Only check for fighter creatures.
1837         if (!def.first || def.first->isWorker())
1838             continue;
1839 
1840         const std::vector<const SpawnCondition*>& conditions = configManager.getCreatureSpawnConditions(def.first);
1841         int32_t nbPointsConditions = 0;
1842         for(const SpawnCondition* condition : conditions)
1843         {
1844             int32_t nbPointsCondition = 0;
1845             if(!condition->computePointsForSeat(gameMap, *this, nbPointsCondition))
1846             {
1847                 nbPointsConditions = -1;
1848                 break;
1849             }
1850             nbPointsConditions += nbPointsCondition;
1851         }
1852 
1853         // Check if the creature can spawn. nbPointsConditions < 0 can happen if a condition is not met or if there are too many
1854         // negative points. In both cases, we don't want the creature to spawn
1855         if(nbPointsConditions < 0)
1856             continue;
1857 
1858         // Check if it is the first time this conditions have been fulfilled. If yes, we force this creature to spawn
1859         if(!def.second && !conditions.empty())
1860         {
1861             def.second = true;
1862             std::vector<Seat*> seats;
1863             seats.push_back(this);
1864             mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::CreatureNew);
1865             return def.first;
1866         }
1867         nbPointsConditions += configManager.getBaseSpawnPoint();
1868         defSpawnable.push_back(std::pair<const CreatureDefinition*, int32_t>(def.first, nbPointsConditions));
1869         nbPointsTotal += nbPointsConditions;
1870     }
1871 
1872     if(defSpawnable.empty())
1873         return nullptr;
1874 
1875     // We choose randomly a creature to spawn according to their points
1876     int32_t cpt = Random::Int(0, nbPointsTotal - 1);
1877     for(std::pair<const CreatureDefinition*, int32_t>& def : defSpawnable)
1878     {
1879         if(cpt < def.second)
1880             return def.first;
1881 
1882         cpt -= def.second;
1883     }
1884 
1885     // It is not normal to come here
1886     OD_LOG_ERR("seatId=" + Helper::toString(getId()));
1887     return nullptr;
1888 }
1889 
readTilesVisualInitialStates(TileVisual tileVisual,std::istream & is)1890 int Seat::readTilesVisualInitialStates(TileVisual tileVisual, std::istream& is)
1891 {
1892     // We check if it is the Seat end tag
1893     std::string str;
1894     OD_ASSERT_TRUE(is >> str);
1895     if (str == "[/Seat]")
1896         return 0;
1897 
1898     if(str != "[" + Tile::tileVisualToString(tileVisual) + "]")
1899     {
1900         OD_LOG_INF("WARNING: expected [" + Tile::tileVisualToString(tileVisual) + "] and read " + str);
1901         return -1;
1902     }
1903 
1904 
1905     while(true)
1906     {
1907         OD_ASSERT_TRUE(is >> str);
1908         if(str == "[/" + Tile::tileVisualToString(tileVisual) + "]")
1909             break;
1910 
1911         std::pair<int, int> tilecoords;
1912         tilecoords.first = Helper::toInt(str);
1913 
1914         OD_ASSERT_TRUE(is >> str);
1915         tilecoords.second = Helper::toInt(str);
1916 
1917         int seatId;
1918         OD_ASSERT_TRUE(is >> seatId);
1919 
1920         TileStateNotified& tileState = mTilesStateLoaded[tilecoords];
1921         tileState.mTileVisual = tileVisual;
1922         tileState.mSeatIdOwner = seatId;
1923     }
1924 
1925     return 1;
1926 }
1927 
setPlayerSettings(bool koCreatures)1928 void Seat::setPlayerSettings(bool koCreatures)
1929 {
1930     mKoCreatures = koCreatures;
1931     if(!mGameMap->isServerGameMap())
1932         return;
1933 
1934     if(getPlayer() == nullptr)
1935         return;
1936 
1937     // We send a message to the client to update his settings
1938     ServerNotification *serverNotification = new ServerNotification(
1939         ServerNotificationType::setPlayerSettings, getPlayer());
1940 
1941     serverNotification->mPacket << mKoCreatures;
1942     ODServer::getSingleton().queueServerNotification(serverNotification);
1943 }
1944 
playerIdToAIType(int32_t playerId)1945 KeeperAIType Seat::playerIdToAIType(int32_t playerId)
1946 {
1947     int32_t value = playerId - Seat::PLAYER_TYPE_INACTIVE_ID - 1;
1948     if(value >= static_cast<int32_t>(KeeperAIType::nbAI))
1949         return KeeperAIType::nbAI;
1950 
1951     return static_cast<KeeperAIType>(value);
1952 }
1953 
aITypeToPlayerId(KeeperAIType type)1954 int32_t Seat::aITypeToPlayerId(KeeperAIType type)
1955 {
1956     return static_cast<int32_t>(type) + Seat::PLAYER_TYPE_INACTIVE_ID + 1;
1957 }
1958