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