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/Player.h"
19 
20 #include "creatureaction/CreatureAction.h"
21 #include "entities/Creature.h"
22 #include "entities/GameEntityType.h"
23 #include "entities/Tile.h"
24 #include "game/SkillManager.h"
25 #include "game/Seat.h"
26 #include "gamemap/GameMap.h"
27 #include "gamemap/Pathfinding.h"
28 #include "render/RenderManager.h"
29 #include "rooms/Room.h"
30 #include "rooms/RoomType.h"
31 #include "sound/SoundEffectsManager.h"
32 #include "spells/SpellManager.h"
33 #include "spells/SpellType.h"
34 #include "traps/Trap.h"
35 #include "network/ODPacket.h"
36 #include "network/ODServer.h"
37 #include "network/ServerNotification.h"
38 #include "utils/Helper.h"
39 #include "utils/LogManager.h"
40 #include "utils/Random.h"
41 #include "ODApplication.h"
42 
43 #include <cmath>
44 
45 //! \brief The number of seconds the local player must stay out of danger to trigger the calm music again.
46 const float BATTLE_TIME_COUNT = 10.0f;
47 //! \brief Minimum distance to consider that a new event of the same type is in another place
48 //! Note that the distance is squared (100 would mean 10 tiles)
49 const int MIN_DIST_EVENT_SQUARED = 100;
50 
51 //! \brief The number of seconds the local player will not be notified again if the skill queue is empty
52 const float NO_RESEARCH_TIME_COUNT = 60.0f;
53 
54 //! \brief The number of seconds the local player will not be notified again if he has no worker
55 const float NO_WORKER_TIME_COUNT = 60.0f;
56 
57 //! \brief The number of seconds the local player will not be notified again if no treasury is available
58 const float NO_TREASURY_TIME_COUNT = 30.0f;
59 
60 //! \brief The number of seconds the local player will not be notified again if a creature cannot find place in a dormitory
61 const float CREATURE_CANNOT_FIND_BED_TIME_COUNT = 30.0f;
62 
63 //! \brief The number of seconds the local player will not be notified again if a creature cannot find place in a dormitory
64 const float CREATURE_CANNOT_FIND_FOOD_TIME_COUNT = 30.0f;
65 
Player(GameMap * gameMap,int32_t id)66 Player::Player(GameMap* gameMap, int32_t id) :
67     mId(id),
68     mGameMap(gameMap),
69     mSeat(nullptr),
70     mIsHuman(false),
71     mNoSkillInQueueTime(0.0f),
72     mNoWorkerTime(0.0f),
73     mNoTreasuryAvailableTime(0.0f),
74     mCreatureCannotFindBed(0.0f),
75     mCreatureCannotFindFood(0.0f),
76     mHasLost(false),
77     mSpellsCooldown(std::vector<PlayerSpellData>(static_cast<uint32_t>(SpellType::nbSpells), PlayerSpellData(0, 0.0f))),
78     mWorkersActions(std::vector<uint32_t>(static_cast<uint32_t>(CreatureActionType::nb), 0))
79 {
80 }
81 
numCreaturesInHand(const Seat * seat) const82 unsigned int Player::numCreaturesInHand(const Seat* seat) const
83 {
84     unsigned int cpt = 0;
85     for(GameEntity* entity : mObjectsInHand)
86     {
87         if(entity->getObjectType() != GameEntityType::creature)
88             continue;
89 
90         if(seat != nullptr && entity->getSeat() != seat)
91             continue;
92 
93         ++cpt;
94     }
95     return cpt;
96 }
97 
numObjectsInHand() const98 unsigned int Player::numObjectsInHand() const
99 {
100     return mObjectsInHand.size();
101 }
102 
addEntityToHand(GameEntity * entity)103 void Player::addEntityToHand(GameEntity *entity)
104 {
105     if (mObjectsInHand.empty())
106     {
107         mObjectsInHand.push_back(entity);
108         return;
109     }
110 
111     // creaturesInHand.push_front(c);
112     // Since vectors have no push_front method,
113     // we need to move all of the elements in the vector back one
114     // and then add this one to the beginning.
115     mObjectsInHand.push_back(nullptr);
116     for (unsigned int j = mObjectsInHand.size() - 1; j > 0; --j)
117         mObjectsInHand[j] = mObjectsInHand[j - 1];
118 
119     mObjectsInHand[0] = entity;
120 }
121 
clearObjectsInHand()122 void Player::clearObjectsInHand()
123 {
124     mObjectsInHand.clear();
125 }
126 
operator <<(ODPacket & os,const PlayerEventType & type)127 ODPacket& operator<<(ODPacket& os, const PlayerEventType& type)
128 {
129     os << static_cast<int32_t>(type);
130     return os;
131 }
132 
operator >>(ODPacket & is,PlayerEventType & type)133 ODPacket& operator>>(ODPacket& is, PlayerEventType& type)
134 {
135     int32_t tmp;
136     OD_ASSERT_TRUE(is >> tmp);
137     type = static_cast<PlayerEventType>(tmp);
138     return is;
139 }
140 
updateEvents(const std::vector<PlayerEvent * > & events)141 void Player::updateEvents(const std::vector<PlayerEvent*>& events)
142 {
143     for(PlayerEvent* event : mEvents)
144         delete event;
145 
146     mEvents = events;
147 }
148 
getNextEvent(uint32_t & index) const149 const PlayerEvent* Player::getNextEvent(uint32_t& index) const
150 {
151     if(mEvents.empty())
152         return nullptr;
153 
154     index = (index + 1) % mEvents.size();
155 
156     return mEvents[index];
157 }
158 
getSpellCooldownTurns(SpellType spellType) const159 uint32_t Player::getSpellCooldownTurns(SpellType spellType) const
160 {
161     uint32_t spellIndex = static_cast<uint32_t>(spellType);
162     if(spellIndex >= mSpellsCooldown.size())
163     {
164         OD_LOG_ERR("seatId=" + Helper::toString(getId()) + ", spellType=" + SpellManager::getSpellNameFromSpellType(spellType));
165         return 0;
166     }
167 
168     return mSpellsCooldown.at(spellIndex).mCooldownNbTurnPending;
169 }
170 
getSpellCooldownSmooth(SpellType spellType) const171 float Player::getSpellCooldownSmooth(SpellType spellType) const
172 {
173     uint32_t spellIndex = static_cast<uint32_t>(spellType);
174     if(spellIndex >= mSpellsCooldown.size())
175     {
176         OD_LOG_ERR("seatId=" + Helper::toString(getId()) + ", spellType=" + SpellManager::getSpellNameFromSpellType(spellType));
177         return 0;
178     }
179 
180     const PlayerSpellData& cooldown = mSpellsCooldown.at(spellIndex);
181     uint32_t cooldownTurns = cooldown.mCooldownNbTurnPending;
182     if(cooldownTurns <= 0)
183         return 0.0f;
184 
185     uint32_t maxCooldownTurns = SpellManager::getSpellCooldown(spellType);
186     if(maxCooldownTurns <= 0)
187         return 0.0f;
188 
189     float cooldownTime = static_cast<float>(cooldownTurns - 1) / ODApplication::turnsPerSecond;
190     cooldownTime += cooldown.mCooldownTimePendingTurn;
191     float maxCooldownTime = static_cast<float>(maxCooldownTurns) / ODApplication::turnsPerSecond;
192 
193     return cooldownTime / maxCooldownTime;
194 }
195 
getSpellCooldownSmoothTime(SpellType spellType) const196 float Player::getSpellCooldownSmoothTime(SpellType spellType) const
197 {
198     uint32_t spellIndex = static_cast<uint32_t>(spellType);
199     if(spellIndex >= mSpellsCooldown.size())
200     {
201         OD_LOG_ERR("seatId=" + Helper::toString(getId()) + ", spellType=" + SpellManager::getSpellNameFromSpellType(spellType));
202         return 0;
203     }
204 
205     const PlayerSpellData& cooldown = mSpellsCooldown.at(spellIndex);
206     uint32_t cooldownTurns = cooldown.mCooldownNbTurnPending;
207     if(cooldownTurns <= 0)
208         return 0.0f;
209 
210     float cooldownTime = static_cast<float>(cooldownTurns - 1) / ODApplication::turnsPerSecond;
211     cooldownTime += cooldown.mCooldownTimePendingTurn;
212 
213     return cooldownTime;
214 }
215 
decreaseSpellCooldowns()216 void Player::decreaseSpellCooldowns()
217 {
218     for(PlayerSpellData& cooldown : mSpellsCooldown)
219     {
220         if(cooldown.mCooldownNbTurnPending <= 0)
221             continue;
222 
223         --cooldown.mCooldownNbTurnPending;
224         cooldown.mCooldownTimePendingTurn = 1.0f / ODApplication::turnsPerSecond;
225     }
226 }
227 
frameStarted(float timeSinceLastFrame)228 void Player::frameStarted(float timeSinceLastFrame)
229 {
230     // Update the smooth spell cooldown
231     for(PlayerSpellData& cooldown : mSpellsCooldown)
232     {
233         if(cooldown.mCooldownNbTurnPending <= 0)
234             continue;
235         if(timeSinceLastFrame > cooldown.mCooldownTimePendingTurn)
236         {
237             cooldown.mCooldownTimePendingTurn = 0.0f;
238             continue;
239         }
240 
241         cooldown.mCooldownTimePendingTurn -= timeSinceLastFrame;
242     }
243 }
244 
getPlayerEventFromPacket(GameMap * gameMap,ODPacket & is)245 PlayerEvent* PlayerEvent::getPlayerEventFromPacket(GameMap* gameMap, ODPacket& is)
246 {
247     PlayerEvent* event = new PlayerEvent;
248     event->importFromPacket(gameMap, is);
249     return event;
250 }
251 
exportPlayerEventToPacket(PlayerEvent * event,GameMap * gameMap,ODPacket & is)252 void PlayerEvent::exportPlayerEventToPacket(PlayerEvent* event, GameMap* gameMap, ODPacket& is)
253 {
254     event->exportToPacket(gameMap, is);
255 }
256 
exportToPacket(GameMap * gameMap,ODPacket & os)257 void PlayerEvent::exportToPacket(GameMap* gameMap, ODPacket& os)
258 {
259     os << mType;
260     gameMap->tileToPacket(os, mTile);
261 }
262 
importFromPacket(GameMap * gameMap,ODPacket & is)263 void PlayerEvent::importFromPacket(GameMap* gameMap, ODPacket& is)
264 {
265     OD_ASSERT_TRUE(is >> mType);
266     mTile = gameMap->tileFromPacket(is);
267 }
268 
pickUpEntity(GameEntity * entity)269 void Player::pickUpEntity(GameEntity *entity)
270 {
271     if(!entity->tryPickup(getSeat()))
272     {
273         OD_LOG_ERR("player seatId" + Helper::toString(getSeat()->getId()) + " couldn't pickup entity=" + entity->getName());
274         return;
275     }
276 
277     OD_LOG_INF("player seatId=" + Helper::toString(getSeat()->getId()) + " picked up " + entity->getName());
278     entity->pickup();
279 
280     // Start tracking this creature as being in this player's hand
281     addEntityToHand(entity);
282 
283     if (mGameMap->isServerGameMap())
284     {
285         entity->firePickupEntity(this);
286         return;
287     }
288 
289     if (this != mGameMap->getLocalPlayer())
290     {
291         OD_LOG_ERR("cannot pickup entity player seat=" + Helper::toString(getSeat()->getId()) + ", localPlayer seat id=" + Helper::toString(mGameMap->getLocalPlayer()->getSeat()->getId()) + ", entity=" + entity->getName());
292         return;
293     }
294     RenderManager::getSingleton().rrPickUpEntity(entity, this);
295 }
296 
297 
isDropHandPossible(Tile * t,unsigned int index)298 bool Player::isDropHandPossible(Tile *t, unsigned int index)
299 {
300     // if we have a creature to drop
301     if (index >= mObjectsInHand.size())
302     {
303         OD_LOG_ERR("seatId=" + Helper::toString(getSeat()->getId()) + ", index=" + Helper::toString(index) + ", size=" + Helper::toString(mObjectsInHand.size()));
304         return false;
305     }
306 
307     GameEntity* entity = mObjectsInHand[index];
308     return entity->tryDrop(getSeat(), t);
309 }
310 
dropHand(Tile * t,unsigned int index)311 void Player::dropHand(Tile *t, unsigned int index)
312 {
313     // Add the creature to the map
314     if(index >= mObjectsInHand.size())
315     {
316         OD_LOG_ERR("seatId=" + Helper::toString(getSeat()->getId()) + ", index=" + Helper::toString(index) + ", size=" + Helper::toString(mObjectsInHand.size()));
317         return;
318     }
319 
320     GameEntity *entity = mObjectsInHand[index];
321     mObjectsInHand.erase(mObjectsInHand.begin() + index);
322 
323     Ogre::Vector3 pos(static_cast<Ogre::Real>(t->getX()),
324        static_cast<Ogre::Real>(t->getY()), entity->getPosition().z);
325     if(mGameMap->isServerGameMap())
326     {
327         entity->drop(pos);
328         entity->fireDropEntity(this, t);
329         return;
330     }
331 
332     entity->correctDropPosition(pos);
333     OD_LOG_INF("player seatId=" + Helper::toString(getSeat()->getId()) + " drop " + entity->getName() + " on tile=" + Tile::displayAsString(t));
334     entity->drop(pos);
335 
336     // If this is the result of another player dropping the creature it is currently not visible so we need to create a mesh for it
337     //cout << "\nthis:  " << this << "\nme:  " << gameMap->getLocalPlayer() << endl;
338     //cout.flush();
339     if (this != mGameMap->getLocalPlayer())
340     {
341         OD_LOG_ERR("cannot pickup entity player seat=" + Helper::toString(getSeat()->getId()) + ", localPlayer seat id=" + Helper::toString(mGameMap->getLocalPlayer()->getSeat()->getId()) + ", entity=" + entity->getName());
342         return;
343     }
344     // Send a render request to rearrange the creatures in the hand to move them all forward 1 place
345     RenderManager::getSingleton().rrDropHand(entity, this);
346 }
347 
rotateHand(Direction d)348 void Player::rotateHand(Direction d)
349 {
350     if(mObjectsInHand.size() > 1)
351     {
352         if(d == Direction::left)
353         {
354             std::rotate(mObjectsInHand.begin(), mObjectsInHand.begin() + 1, mObjectsInHand.end());
355         }
356         else
357         {
358             std::rotate(mObjectsInHand.begin(), mObjectsInHand.end() - 1, mObjectsInHand.end());
359         }
360         // Send a render request to move the entity into the "hand"
361         RenderManager::getSingleton().rrRotateHand(this);
362     }
363 }
364 
notifyNoMoreDungeonTemple()365 void Player::notifyNoMoreDungeonTemple()
366 {
367     if(mHasLost)
368         return;
369 
370     mHasLost = true;
371     OD_LOG_INF("Player seatId=" + Helper::toString(getSeat()->getId()) + " lost");
372 
373     // We check if there is still a player in the team with a dungeon temple. If yes, we notify the player he lost his dungeon
374     // if no, we notify the team they lost
375     std::vector<Room*> dungeonTemples = mGameMap->getRoomsByType(RoomType::dungeonTemple);
376     bool hasTeamLost = true;
377     for(Room* dungeonTemple : dungeonTemples)
378     {
379         if(getSeat()->isAlliedSeat(dungeonTemple->getSeat()))
380         {
381             hasTeamLost = false;
382             break;
383         }
384     }
385 
386     if(hasTeamLost)
387     {
388         // This message will be sent in 1v1 or multiplayer so it should not talk about team. If we want to be
389         // more precise, we shall handle the case
390         std::vector<Seat*> seats;
391         for(Seat* seat : mGameMap->getSeats())
392         {
393             if(seat->getPlayer() == nullptr)
394                 continue;
395             if(!seat->getPlayer()->getIsHuman())
396                 continue;
397             if(!getSeat()->isAlliedSeat(seat))
398                 continue;
399 
400             seats.push_back(seat);
401 
402             ServerNotification *serverNotification = new ServerNotification(
403                 ServerNotificationType::chatServer, seat->getPlayer());
404             serverNotification->mPacket << "You lost the game" << EventShortNoticeType::majorGameEvent;
405             ODServer::getSingleton().queueServerNotification(serverNotification);
406         }
407         mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::Lost);
408     }
409     else
410     {
411         std::vector<Seat*> seats;
412         for(Seat* seat : mGameMap->getSeats())
413         {
414             if(seat->getPlayer() == nullptr)
415                 continue;
416             if(!seat->getPlayer()->getIsHuman())
417                 continue;
418             if(!getSeat()->isAlliedSeat(seat))
419                 continue;
420 
421             if(this == seat->getPlayer())
422             {
423                 // For the current player, we send the defeat message
424                 ServerNotification *serverNotification = new ServerNotification(
425                     ServerNotificationType::chatServer, seat->getPlayer());
426                 serverNotification->mPacket << "You lost" << EventShortNoticeType::majorGameEvent;
427                 ODServer::getSingleton().queueServerNotification(serverNotification);
428 
429                 mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::Defeat);
430                 continue;
431             }
432 
433             seats.push_back(seat);
434 
435             ServerNotification *serverNotification = new ServerNotification(
436                 ServerNotificationType::chatServer, seat->getPlayer());
437             serverNotification->mPacket << "An ally has lost" << EventShortNoticeType::majorGameEvent;
438             ODServer::getSingleton().queueServerNotification(serverNotification);
439         }
440         mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::AllyDefeated);
441     }
442 }
443 
notifyTeamFighting(Player * player,Tile * tile)444 void Player::notifyTeamFighting(Player* player, Tile* tile)
445 {
446     // We check if there is a fight event currently near this tile. If yes, we update
447     // the time event. If not, we create a new fight event
448     bool isFirstFight = true;
449     for(PlayerEvent* event : mEvents)
450     {
451         if(event->getType() != PlayerEventType::fight)
452             continue;
453 
454         // There is already another fight event. We check the distance
455         isFirstFight = false;
456         if(Pathfinding::squaredDistanceTile(*tile, *event->getTile()) < MIN_DIST_EVENT_SQUARED)
457         {
458             // A similar event is already on nearly. We only update it
459             event->setTimeRemain(BATTLE_TIME_COUNT);
460             return;
461         }
462     }
463 
464     if(isFirstFight)
465     {
466         ServerNotification *serverNotification = new ServerNotification(
467             ServerNotificationType::playerFighting, this);
468         serverNotification->mPacket << player->getId();
469         ODServer::getSingleton().queueServerNotification(serverNotification);
470 
471         std::vector<Seat*> seats;
472         seats.push_back(getSeat());
473         mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::WeAreUnderAttack);
474     }
475 
476     // We add the fight event
477     mEvents.push_back(new PlayerEvent(PlayerEventType::fight, tile, BATTLE_TIME_COUNT));
478     fireEvents();
479 }
480 
notifyNoSkillInQueue()481 void Player::notifyNoSkillInQueue()
482 {
483     if(mNoSkillInQueueTime == 0.0f)
484     {
485         mNoSkillInQueueTime = NO_RESEARCH_TIME_COUNT;
486 
487         std::string chatMsg = "Your skill queue is empty, while there are still skills that could be unlocked.";
488         ServerNotification *serverNotification = new ServerNotification(
489             ServerNotificationType::chatServer, this);
490         serverNotification->mPacket << chatMsg << EventShortNoticeType::genericGameInfo;
491         ODServer::getSingleton().queueServerNotification(serverNotification);
492     }
493 }
494 
notifyNoWorker()495 void Player::notifyNoWorker()
496 {
497     if(mNoWorkerTime > 0.0f)
498         return;
499 
500     mNoWorkerTime = NO_WORKER_TIME_COUNT;
501 
502     std::string chatMsg = "You have no worker to fulfill your dark wishes.";
503     ServerNotification *serverNotification = new ServerNotification(
504         ServerNotificationType::chatServer, this);
505     serverNotification->mPacket << chatMsg << EventShortNoticeType::genericGameInfo;
506     ODServer::getSingleton().queueServerNotification(serverNotification);
507 }
508 
notifyNoTreasuryAvailable()509 void Player::notifyNoTreasuryAvailable()
510 {
511     if(mNoTreasuryAvailableTime == 0.0f)
512     {
513         mNoTreasuryAvailableTime = NO_TREASURY_TIME_COUNT;
514 
515         std::string chatMsg = "No treasury available. You should build a bigger one.";
516         ServerNotification *serverNotification = new ServerNotification(
517             ServerNotificationType::chatServer, this);
518         serverNotification->mPacket << chatMsg << EventShortNoticeType::genericGameInfo;
519         ODServer::getSingleton().queueServerNotification(serverNotification);
520     }
521 }
522 
notifyCreatureCannotFindBed(Creature & creature)523 void Player::notifyCreatureCannotFindBed(Creature& creature)
524 {
525     if(mCreatureCannotFindBed <= 0.0f)
526     {
527         mCreatureCannotFindBed = CREATURE_CANNOT_FIND_BED_TIME_COUNT;
528 
529         std::string chatMsg = creature.getName() + " cannot find room for a bed";
530         ServerNotification *serverNotification = new ServerNotification(
531             ServerNotificationType::chatServer, this);
532         serverNotification->mPacket << chatMsg << EventShortNoticeType::genericGameInfo;
533         ODServer::getSingleton().queueServerNotification(serverNotification);
534 
535         std::vector<Seat*> seats;
536         seats.push_back(getSeat());
537         mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::CreatureNoBed);
538     }
539 }
540 
notifyCreatureCannotFindFood(Creature & creature)541 void Player::notifyCreatureCannotFindFood(Creature& creature)
542 {
543     if(mCreatureCannotFindFood <= 0.0f)
544     {
545         mCreatureCannotFindFood = CREATURE_CANNOT_FIND_FOOD_TIME_COUNT;
546 
547         std::string chatMsg = creature.getName() + " cannot find food";
548         ServerNotification *serverNotification = new ServerNotification(
549             ServerNotificationType::chatServer, this);
550         serverNotification->mPacket << chatMsg << EventShortNoticeType::genericGameInfo;
551         ODServer::getSingleton().queueServerNotification(serverNotification);
552 
553         std::vector<Seat*> seats;
554         seats.push_back(getSeat());
555         mGameMap->fireRelativeSound(seats, SoundRelativeKeeperStatements::CreatureNoFood);
556     }
557 }
558 
fireEvents()559 void Player::fireEvents()
560 {
561     // On server side, we update the client
562     if(!mGameMap->isServerGameMap())
563         return;
564 
565     ServerNotification *serverNotification = new ServerNotification(
566         ServerNotificationType::playerEvents, this);
567     uint32_t nbItems = mEvents.size();
568     serverNotification->mPacket << nbItems;
569     for(PlayerEvent* event : mEvents)
570     {
571         PlayerEvent::exportPlayerEventToPacket(event, mGameMap,
572             serverNotification->mPacket);
573     }
574 
575     ODServer::getSingleton().queueServerNotification(serverNotification);
576 }
577 
578 
markTilesForDigging(bool marked,const std::vector<Tile * > & tiles,bool asyncMsg)579 void Player::markTilesForDigging(bool marked, const std::vector<Tile*>& tiles, bool asyncMsg)
580 {
581     if(tiles.empty())
582         return;
583 
584     std::vector<Tile*> tilesMark;
585     // If human player, we notify marked tiles
586     if(getIsHuman())
587     {
588         for(Tile* tile : tiles)
589         {
590             if(!marked)
591             {
592                 getSeat()->tileMarkedDiggingNotifiedToPlayer(tile, marked);
593                 tile->setMarkedForDigging(marked, this);
594                 tilesMark.push_back(tile);
595 
596                 continue;
597             }
598 
599             // If the tile is diggable for the client, we mark it for him
600             if(getSeat()->isTileDiggableForClient(tile))
601             {
602                 getSeat()->tileMarkedDiggingNotifiedToPlayer(tile, marked);
603                 tilesMark.push_back(tile);
604             }
605 
606             // If the tile can be marked on server side, we mark it
607             if(!tile->isDiggable(getSeat()))
608                 continue;
609 
610             tile->setMarkedForDigging(marked, this);
611         }
612     }
613     else
614     {
615         for(Tile* tile : tiles)
616         {
617             if(!marked)
618             {
619                 getSeat()->tileMarkedDiggingNotifiedToPlayer(tile, marked);
620                 tile->setMarkedForDigging(marked, this);
621                 continue;
622             }
623 
624             // If the tile can be marked on server side, we mark it
625             if(!tile->isDiggable(getSeat()))
626                 continue;
627 
628             tile->setMarkedForDigging(marked, this);
629         }
630         return;
631     }
632 
633     // If no tile to mark, we do not send the message
634     if(tilesMark.empty())
635         return;
636 
637     // On client side, we ask to mark the tile
638     if(!asyncMsg)
639     {
640         ServerNotification* serverNotification = new ServerNotification(
641             ServerNotificationType::markTiles, this);
642         uint32_t nbTiles = tilesMark.size();
643         serverNotification->mPacket << marked << nbTiles;
644         for(Tile* tile : tilesMark)
645         {
646             // On client side, we ask to mark the tile
647             mGameMap->tileToPacket(serverNotification->mPacket, tile);
648         }
649         ODServer::getSingleton().queueServerNotification(serverNotification);
650     }
651     else
652     {
653         ServerNotification serverNotification(
654             ServerNotificationType::markTiles, this);
655         uint32_t nbTiles = tilesMark.size();
656         serverNotification.mPacket << marked << nbTiles;
657         for(Tile* tile : tilesMark)
658             mGameMap->tileToPacket(serverNotification.mPacket, tile);
659 
660         ODServer::getSingleton().sendAsyncMsg(serverNotification);
661     }
662 }
663 
upkeepPlayer(double timeSinceLastUpkeep)664 void Player::upkeepPlayer(double timeSinceLastUpkeep)
665 {
666     decreaseSpellCooldowns();
667 
668     // Specific stuff for human players
669     if(!getIsHuman())
670         return;
671 
672     // Handle fighting time
673     bool wasFightHappening = false;
674     bool isFightHappening = false;
675     bool isEventListUpdated = false;
676     for(auto it = mEvents.begin(); it != mEvents.end();)
677     {
678         PlayerEvent* event = *it;
679         if(event->getType() != PlayerEventType::fight)
680         {
681             ++it;
682             continue;
683         }
684 
685         wasFightHappening = true;
686 
687         float timeRemain = event->getTimeRemain();
688         if(timeRemain > timeSinceLastUpkeep)
689         {
690             isFightHappening = true;
691             timeRemain -= timeSinceLastUpkeep;
692             event->setTimeRemain(timeRemain);
693             ++it;
694             continue;
695         }
696 
697         // This event is outdated, we remove it
698         isEventListUpdated = true;
699         delete event;
700         it = mEvents.erase(it);
701     }
702 
703     if(wasFightHappening && !isFightHappening)
704     {
705         // Notify the player he is no longer under attack.
706         ServerNotification *serverNotification = new ServerNotification(
707             ServerNotificationType::playerNoMoreFighting, this);
708         ODServer::getSingleton().queueServerNotification(serverNotification);
709     }
710 
711     // Do not notify skill queue empty if no library
712     if(getIsHuman() &&
713        !getHasLost() &&
714        (getSeat()->getNbRooms(RoomType::library) > 0))
715     {
716         if(mNoSkillInQueueTime > timeSinceLastUpkeep)
717             mNoSkillInQueueTime -= timeSinceLastUpkeep;
718         else
719         {
720             mNoSkillInQueueTime = 0.0f;
721 
722             // Reprint the warning if there is still no skill being done
723             if(getSeat() != nullptr && !getSeat()->isSkilling() && !SkillManager::isAllSkillsDoneForSeat(getSeat()))
724                 notifyNoSkillInQueue();
725         }
726     }
727 
728     if(getIsHuman() &&
729        !getHasLost())
730     {
731         if(mNoWorkerTime > timeSinceLastUpkeep)
732             mNoWorkerTime -= timeSinceLastUpkeep;
733         else
734         {
735             mNoWorkerTime = 0.0f;
736 
737             if(getSeat()->getNumCreaturesWorkers() <= 0)
738                 notifyNoWorker();
739         }
740     }
741 
742     if(mNoTreasuryAvailableTime > 0.0f)
743     {
744         if(mNoTreasuryAvailableTime > timeSinceLastUpkeep)
745             mNoTreasuryAvailableTime -= timeSinceLastUpkeep;
746         else
747             mNoTreasuryAvailableTime = 0.0f;
748     }
749 
750     if(mCreatureCannotFindBed > 0.0f)
751     {
752         if(mCreatureCannotFindBed > timeSinceLastUpkeep)
753             mCreatureCannotFindBed -= timeSinceLastUpkeep;
754         else
755             mCreatureCannotFindBed = 0.0f;
756     }
757 
758     if(mCreatureCannotFindFood > 0.0f)
759     {
760         if(mCreatureCannotFindFood > timeSinceLastUpkeep)
761             mCreatureCannotFindFood -= timeSinceLastUpkeep;
762         else
763             mCreatureCannotFindFood = 0.0f;
764     }
765 
766     if(isEventListUpdated)
767         fireEvents();
768 }
769 
setSpellCooldownTurns(SpellType spellType,uint32_t cooldown)770 void Player::setSpellCooldownTurns(SpellType spellType, uint32_t cooldown)
771 {
772     uint32_t spellIndex = static_cast<uint32_t>(spellType);
773     if(spellIndex >= mSpellsCooldown.size())
774     {
775         OD_LOG_ERR("seatId=" + Helper::toString(getId()) + ", spellType=" + SpellManager::getSpellNameFromSpellType(spellType));
776         return;
777     }
778 
779     mSpellsCooldown[spellIndex] = PlayerSpellData(cooldown, 1.0f / ODApplication::turnsPerSecond);
780 
781     if(mGameMap->isServerGameMap() && getIsHuman())
782     {
783         ServerNotification *serverNotification = new ServerNotification(
784             ServerNotificationType::setSpellCooldown, this);
785         serverNotification->mPacket << spellType << cooldown;
786         ODServer::getSingleton().queueServerNotification(serverNotification);
787     }
788 }
789 
notifyWorkerAction(Creature & worker,CreatureActionType actionType)790 void Player::notifyWorkerAction(Creature& worker, CreatureActionType actionType)
791 {
792     uint32_t index = static_cast<uint32_t>(actionType);
793     if(index >= mWorkersActions.size())
794     {
795         OD_LOG_ERR("Invalid index seatId=" + Helper::toString(getId()) + ", value=" + Helper::toString(index) + ", size=" + Helper::toString(mWorkersActions.size()));
796         return;
797     }
798 
799     mWorkersActions[index]++;
800 }
801 
notifyWorkerStopsAction(Creature & worker,CreatureActionType actionType)802 void Player::notifyWorkerStopsAction(Creature& worker, CreatureActionType actionType)
803 {
804     uint32_t index = static_cast<uint32_t>(actionType);
805     if(index >= mWorkersActions.size())
806     {
807         OD_LOG_ERR("Invalid index seatId=" + Helper::toString(getId()) + ", value=" + Helper::toString(index) + ", size=" + Helper::toString(mWorkersActions.size()));
808         return;
809     }
810 
811     // Sanity check
812     if(mWorkersActions[index] <= 0)
813     {
814         OD_LOG_ERR("No worker doing action seatId=" + Helper::toString(getId()) + ", action=" + CreatureAction::toString(actionType));
815         return;
816     }
817 
818 
819     mWorkersActions[index]--;
820 }
821 
getNbWorkersDoing(CreatureActionType actionType) const822 uint32_t Player::getNbWorkersDoing(CreatureActionType actionType) const
823 {
824     uint32_t index = static_cast<uint32_t>(actionType);
825     if(index >= mWorkersActions.size())
826     {
827         OD_LOG_ERR("Invalid index seatId=" + Helper::toString(getId()) + ", value=" + Helper::toString(index) + ", size=" + Helper::toString(mWorkersActions.size()));
828         return 0;
829     }
830 
831     return mWorkersActions.at(index);
832 }
833 
getWorkerPreferredActions(Creature & worker) const834 std::vector<CreatureActionType> Player::getWorkerPreferredActions(Creature& worker) const
835 {
836     std::vector<CreatureActionType> ret;
837     // We want to have more or less 40% workers digging, 40% claiming ground tiles and 20% claiming wall tiles
838     // Concerning carrying stuff, most workers should try unless more than 20% are already carrying.
839     uint32_t nbWorkersDigging = getNbWorkersDoing(CreatureActionType::searchTileToDig);
840     uint32_t nbWorkersClaimingGround = getNbWorkersDoing(CreatureActionType::searchGroundTileToClaim);
841     uint32_t nbWorkersClaimingWall = getNbWorkersDoing(CreatureActionType::searchWallTileToClaim);
842     uint32_t nbWorkersCarrying = getNbWorkersDoing(CreatureActionType::searchEntityToCarry);
843     // For the total number of workers, we consider only those doing something in the wanted list (and not
844     // the ones fighting or having nothing to do) + the one we are considering
845     uint32_t nbWorkersTotal = nbWorkersDigging + nbWorkersClaimingGround
846             + nbWorkersClaimingWall + nbWorkersCarrying + 1;
847 
848     double percent;
849     bool isCarryAdded = false;
850     percent = static_cast<double>(nbWorkersCarrying) / static_cast<double>(nbWorkersTotal);
851     if(percent <= 0.2)
852     {
853         isCarryAdded = true;
854         ret.push_back(CreatureActionType::searchEntityToCarry);
855     }
856 
857     bool isClaimWallAdded = false;
858     percent = static_cast<double>(nbWorkersDigging + nbWorkersClaimingGround) / static_cast<double>(nbWorkersTotal);
859     if(percent > 0.8)
860     {
861         isClaimWallAdded = true;
862         ret.push_back(CreatureActionType::searchWallTileToClaim);
863     }
864 
865     bool digTileFirst = false;
866     if(nbWorkersDigging < nbWorkersClaimingGround)
867         digTileFirst = true;
868     else if(nbWorkersDigging == nbWorkersClaimingGround)
869         digTileFirst = (Random::Uint(0,1) == 0);
870 
871     if(digTileFirst)
872     {
873         ret.push_back(CreatureActionType::searchTileToDig);
874         ret.push_back(CreatureActionType::searchGroundTileToClaim);
875     }
876     else
877     {
878         ret.push_back(CreatureActionType::searchGroundTileToClaim);
879         ret.push_back(CreatureActionType::searchTileToDig);
880     }
881 
882     if(!isClaimWallAdded)
883         ret.push_back(CreatureActionType::searchWallTileToClaim);
884     if(!isCarryAdded)
885         ret.push_back(CreatureActionType::searchEntityToCarry);
886 
887     return ret;
888 }
889