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