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 "entities/Creature.h"
19
20 #include "creatureaction/CreatureAction.h"
21 #include "creatureaction/CreatureActionClaimGroundTile.h"
22 #include "creatureaction/CreatureActionClaimWallTile.h"
23 #include "creatureaction/CreatureActionDigTile.h"
24 #include "creatureaction/CreatureActionFight.h"
25 #include "creatureaction/CreatureActionFindHome.h"
26 #include "creatureaction/CreatureActionFlee.h"
27 #include "creatureaction/CreatureActionGetFee.h"
28 #include "creatureaction/CreatureActionGrabEntity.h"
29 #include "creatureaction/CreatureActionLeaveDungeon.h"
30 #include "creatureaction/CreatureActionSearchEntityToCarry.h"
31 #include "creatureaction/CreatureActionSearchFood.h"
32 #include "creatureaction/CreatureActionSearchGroundTileToClaim.h"
33 #include "creatureaction/CreatureActionSearchJob.h"
34 #include "creatureaction/CreatureActionSearchTileToDig.h"
35 #include "creatureaction/CreatureActionSearchWallTileToClaim.h"
36 #include "creatureaction/CreatureActionSleep.h"
37 #include "creatureaction/CreatureActionStealFreeGold.h"
38 #include "creatureaction/CreatureActionWalkToTile.h"
39 #include "creaturebehaviour/CreatureBehaviour.h"
40 #include "creatureeffect/CreatureEffect.h"
41 #include "creatureeffect/CreatureEffectManager.h"
42 #include "creatureeffect/CreatureEffectSlap.h"
43 #include "creaturemood/CreatureMood.h"
44 #include "creaturemood/CreatureMoodManager.h"
45 #include "creatureskill/CreatureSkill.h"
46 #include "entities/ChickenEntity.h"
47 #include "entities/CreatureDefinition.h"
48 #include "entities/GameEntityType.h"
49 #include "entities/Tile.h"
50 #include "entities/TreasuryObject.h"
51 #include "entities/Weapon.h"
52 #include "game/Player.h"
53 #include "game/Skill.h"
54 #include "game/SkillType.h"
55 #include "game/Seat.h"
56 #include "gamemap/GameMap.h"
57 #include "gamemap/Pathfinding.h"
58 #include "giftboxes/GiftBoxSkill.h"
59 #include "network/ODClient.h"
60 #include "network/ODServer.h"
61 #include "network/ServerNotification.h"
62 #include "render/CreatureOverlayStatus.h"
63 #include "render/RenderManager.h"
64 #include "rooms/RoomCrypt.h"
65 #include "rooms/RoomDormitory.h"
66 #include "sound/SoundEffectsManager.h"
67 #include "spells/Spell.h"
68 #include "spells/SpellType.h"
69 #include "traps/Trap.h"
70 #include "utils/ConfigManager.h"
71 #include "utils/Helper.h"
72 #include "utils/LogManager.h"
73 #include "utils/MakeUnique.h"
74 #include "utils/Random.h"
75
76 #include <CEGUI/Event.h>
77 #include <CEGUI/System.h>
78 #include <CEGUI/UDim.h>
79 #include <CEGUI/Vector.h>
80 #include <CEGUI/WindowManager.h>
81 #include <CEGUI/Window.h>
82 #include <CEGUI/widgets/FrameWindow.h>
83 #include <CEGUI/widgets/PushButton.h>
84
85 #include <OgreQuaternion.h>
86 #include <OgreVector3.h>
87 #include <OgreVector2.h>
88
89 #include <cmath>
90 #include <algorithm>
91
92 #if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
93 #define snprintf_is_banned_in_OD_code _snprintf
94 #endif
95
96 static const Ogre::Real CANNON_MISSILE_HEIGHT = 0.3;
97
98 const int32_t Creature::NB_TURNS_BEFORE_CHECKING_TASK = 15;
99 const uint32_t Creature::NB_OVERLAY_HEALTH_VALUES = 8;
100
101 enum CreatureMoodEnum
102 {
103 Angry = 0x0001,
104 Furious = 0x0002,
105 GetFee = 0x0004,
106 LeaveDungeon = 0x0008,
107 KoDeath = 0x0010,
108 Hungry = 0x0020,
109 Tired = 0x0040,
110 KoTemp = 0x0080,
111 InJail = 0x0100,
112 // To know if a creature is KO
113 KoDeathOrTemp = KoTemp | KoDeath,
114 // Mood filters for creatures in prison that every player will see
115 MoodPrisonFiltersAllPlayers = InJail,
116 // Mood filters for creatures in prison that prison allied will see
117 MoodPrisonFiltersPrisonAllies = KoTemp | InJail
118 };
119
CreatureParticuleEffect(Creature & creature,const std::string & name,const std::string & script,uint32_t nbTurnsEffect,CreatureEffect * effect)120 CreatureParticuleEffect::CreatureParticuleEffect(Creature& creature, const std::string& name, const std::string& script, uint32_t nbTurnsEffect,
121 CreatureEffect* effect) :
122 EntityParticleEffect(name, script, nbTurnsEffect),
123 mEffect(effect),
124 mCreature(creature)
125 {
126 if(mEffect == nullptr)
127 {
128 OD_LOG_ERR("null effect on creature=" + mCreature.getName() + ", name=" + name + ", script=" + script);
129 return;
130 }
131
132 mEffect->startEffect(mCreature);
133 }
134
~CreatureParticuleEffect()135 CreatureParticuleEffect::~CreatureParticuleEffect()
136 {
137 if(mEffect != nullptr)
138 {
139 mEffect->releaseEffect(mCreature);
140 delete mEffect;
141 }
142
143 mEffect = nullptr;
144 }
145
Creature(GameMap * gameMap,const CreatureDefinition * definition,Seat * seat,Ogre::Vector3 position)146 Creature::Creature(GameMap* gameMap, const CreatureDefinition* definition, Seat* seat, Ogre::Vector3 position) :
147 MovableGameEntity (gameMap),
148 mPhysicalDefense (3.0),
149 mMagicalDefense (1.5),
150 mElementDefense (0.0),
151 mModifierStrength (1.0),
152 mWeaponL (nullptr),
153 mWeaponR (nullptr),
154 mHomeTile (nullptr),
155 mDefinition (definition),
156 mHasVisualDebuggingEntities (false),
157 mWakefulness (100.0),
158 mHunger (0.0),
159 mLevel (1),
160 mHp (10.0),
161 mMaxHP (10.0),
162 mExp (0.0),
163 mGroundSpeed (1.0),
164 mWaterSpeed (0.0),
165 mLavaSpeed (0.0),
166 mDigRate (0.0),
167 mClaimRate (0.0),
168 mDeathCounter (0),
169 mJobCooldown (0),
170 mGoldFee (0),
171 mGoldCarried (0),
172 mSkillTypeDropDeath (SkillType::nullSkillType),
173 mWeaponDropDeath ("none"),
174 mStatsWindow (nullptr),
175 mNbTurnsWithoutBattle (0),
176 mCarriedEntity (nullptr),
177 mMoodCooldownTurns (0),
178 mMoodValue (CreatureMoodLevel::Neutral),
179 mMoodPoints (0),
180 mNbTurnFurious (-1),
181 mOverlayHealthValue (0),
182 mOverlayMoodValue (0),
183 mOverlayStatus (nullptr),
184 mNeedFireRefresh (false),
185 mDropCooldown (0),
186 mSpeedModifier (1.0),
187 mKoTurnCounter (0),
188 mSeatPrison (nullptr),
189 mNbTurnsTorture (0),
190 mNbTurnsPrison (0),
191 mActiveSlapsCount (0)
192
193 {
194 //TODO: This should be set in initialiser list in parent classes
195 setSeat(seat);
196 mPosition = position;
197 setMeshName(definition->getMeshName());
198 setName(getGameMap()->nextUniqueNameCreature(definition->getClassName()));
199
200 mMaxHP = mDefinition->getMinHp();
201 setHP(mMaxHP);
202
203 mGroundSpeed = mDefinition->getMoveSpeedGround();
204 mWaterSpeed = mDefinition->getMoveSpeedWater();
205 mLavaSpeed = mDefinition->getMoveSpeedLava();
206
207 mDigRate = mDefinition->getDigRate();
208 mClaimRate = mDefinition->getClaimRate();
209
210 // Fighting stats
211 mPhysicalDefense = mDefinition->getPhysicalDefense();
212 mMagicalDefense = mDefinition->getMagicalDefense();
213 mElementDefense = mDefinition->getElementDefense();
214
215 if(mDefinition->getWeaponSpawnL().compare("none") != 0)
216 mWeaponL = gameMap->getWeapon(mDefinition->getWeaponSpawnL());
217
218 if(mDefinition->getWeaponSpawnR().compare("none") != 0)
219 mWeaponR = gameMap->getWeapon(mDefinition->getWeaponSpawnR());
220
221 setupDefinition(*gameMap, *ConfigManager::getSingleton().getCreatureDefinitionDefaultWorker());
222 }
223
Creature(GameMap * gameMap)224 Creature::Creature(GameMap* gameMap) :
225 MovableGameEntity (gameMap),
226 mPhysicalDefense (3.0),
227 mMagicalDefense (1.5),
228 mElementDefense (0.0),
229 mModifierStrength (1.0),
230 mWeaponL (nullptr),
231 mWeaponR (nullptr),
232 mHomeTile (nullptr),
233 mDefinition (nullptr),
234 mHasVisualDebuggingEntities (false),
235 mWakefulness (100.0),
236 mHunger (0.0),
237 mLevel (1),
238 mHp (10.0),
239 mMaxHP (10.0),
240 mExp (0.0),
241 mGroundSpeed (1.0),
242 mWaterSpeed (0.0),
243 mLavaSpeed (0.0),
244 mDigRate (0.0),
245 mClaimRate (0.0),
246 mDeathCounter (0),
247 mJobCooldown (0),
248 mGoldFee (0),
249 mGoldCarried (0),
250 mSkillTypeDropDeath (SkillType::nullSkillType),
251 mWeaponDropDeath ("none"),
252 mStatsWindow (nullptr),
253 mNbTurnsWithoutBattle (0),
254 mCarriedEntity (nullptr),
255 mMoodCooldownTurns (0),
256 mMoodValue (CreatureMoodLevel::Neutral),
257 mMoodPoints (0),
258 mNbTurnFurious (-1),
259 mOverlayHealthValue (0),
260 mOverlayMoodValue (0),
261 mOverlayStatus (nullptr),
262 mNeedFireRefresh (false),
263 mDropCooldown (0),
264 mSpeedModifier (1.0),
265 mKoTurnCounter (0),
266 mSeatPrison (nullptr),
267 mNbTurnsTorture (0),
268 mNbTurnsPrison (0),
269 mActiveSlapsCount (0)
270 {
271 }
272
~Creature()273 Creature::~Creature()
274 {
275 }
276
createMeshLocal()277 void Creature::createMeshLocal()
278 {
279 MovableGameEntity::createMeshLocal();
280 if(!getIsOnServerMap())
281 {
282 RenderManager::getSingleton().rrCreateCreature(this);
283
284 // By default, we set the creature in idle state
285 RenderManager::getSingleton().rrSetObjectAnimationState(this, EntityAnimation::idle_anim, true);
286 }
287
288 createMeshWeapons();
289 }
290
destroyMeshLocal()291 void Creature::destroyMeshLocal()
292 {
293 destroyMeshWeapons();
294 MovableGameEntity::destroyMeshLocal();
295 if(getIsOnServerMap())
296 return;
297
298 destroyStatsWindow();
299 RenderManager::getSingleton().rrDestroyCreature(this);
300 }
301
createMeshWeapons()302 void Creature::createMeshWeapons()
303 {
304 if(getIsOnServerMap())
305 return;
306
307 if(mWeaponL != nullptr)
308 RenderManager::getSingleton().rrCreateWeapon(this, mWeaponL, "L");
309
310 if(mWeaponR != nullptr)
311 RenderManager::getSingleton().rrCreateWeapon(this, mWeaponR, "R");
312 }
313
destroyMeshWeapons()314 void Creature::destroyMeshWeapons()
315 {
316 if(getIsOnServerMap())
317 return;
318
319 if(mWeaponL != nullptr)
320 RenderManager::getSingleton().rrDestroyWeapon(this, mWeaponL, "L");
321
322 if(mWeaponR != nullptr)
323 RenderManager::getSingleton().rrDestroyWeapon(this, mWeaponR, "R");
324 }
325
getObjectType() const326 GameEntityType Creature::getObjectType() const
327 {
328 return GameEntityType::creature;
329 }
330
addToGameMap()331 void Creature::addToGameMap()
332 {
333 getGameMap()->addCreature(this);
334 getGameMap()->addAnimatedObject(this);
335 getGameMap()->addClientUpkeepEntity(this);
336
337 if(!getIsOnServerMap())
338 return;
339
340 getGameMap()->addActiveObject(this);
341 }
342
removeFromGameMap()343 void Creature::removeFromGameMap()
344 {
345 fireEntityRemoveFromGameMap();
346 removeEntityFromPositionTile();
347 getGameMap()->removeCreature(this);
348 getGameMap()->removeAnimatedObject(this);
349 getGameMap()->removeClientUpkeepEntity(this);
350
351 if(!getIsOnServerMap())
352 return;
353
354 // If the creature has a homeTile where it sleeps, its bed needs to be destroyed.
355 if (getHomeTile() != nullptr)
356 {
357 RoomDormitory* home = static_cast<RoomDormitory*>(getHomeTile()->getCoveringBuilding());
358 home->releaseTileForSleeping(getHomeTile(), this);
359 }
360
361 fireRemoveEntityToSeatsWithVision();
362 getGameMap()->removeActiveObject(this);
363 }
364
getCreatureStreamFormat()365 std::string Creature::getCreatureStreamFormat()
366 {
367 std::string format = MovableGameEntity::getMovableGameEntityStreamFormat();
368 if(!format.empty())
369 format += "\t";
370
371 format += "ClassName\tLevel\tCurrentXP\tCurrentHP\tCurrentWakefulness"
372 "\tCurrentHunger\tGoldToDeposit\tLeftWeapon\tRightWeapon\tCarriedSkill\tCarriedWeapon"
373 "\tNbCreatureEffects\tN*CreatureEffects";
374
375 return format;
376 }
377
exportToStream(std::ostream & os) const378 void Creature::exportToStream(std::ostream& os) const
379 {
380 MovableGameEntity::exportToStream(os);
381 os << mDefinition->getClassName() << "\t";
382 os << getLevel() << "\t" << mExp << "\t";
383 if(getHP() < mMaxHP)
384 os << getHP();
385 else
386 os << "max";
387 os << "\t" << mWakefulness << "\t" << mHunger << "\t" << mGoldCarried;
388
389 // Check creature weapons
390 if(mWeaponL != nullptr)
391 os << "\t" << mWeaponL->getName();
392 else
393 os << "\tnone";
394
395 if(mWeaponR != nullptr)
396 os << "\t" << mWeaponR->getName();
397 else
398 os << "\tnone";
399
400 os << "\t" << Skills::toString(mSkillTypeDropDeath);
401
402 os << "\t" << mWeaponDropDeath;
403
404 uint32_t nbEffects = mEntityParticleEffects.size();
405 os << "\t" << nbEffects;
406 for(EntityParticleEffect* effect : mEntityParticleEffects)
407 {
408 // We only save creature particle effects. The other are expected to be re-created
409 // automatically (for example if it is a permanent effect for a creature)
410 if(effect->getEntityParticleEffectType() != EntityParticleEffectType::creature)
411 continue;
412
413 CreatureParticuleEffect* creatureParticuleEffect = static_cast<CreatureParticuleEffect*>(effect);
414 os << "\t";
415 CreatureEffectManager::write(*creatureParticuleEffect->mEffect, os);
416 }
417 }
418
importFromStream(std::istream & is)419 bool Creature::importFromStream(std::istream& is)
420 {
421 // Beware: A generic class name might be used here so we shouldn't use mDefinition
422 // here as it is not set yet (for example, default worker will be available only after
423 // seat lobby configuration)
424 if(!MovableGameEntity::importFromStream(is))
425 return false;
426 std::string tempString;
427
428 if(!(is >> mDefinitionString))
429 return false;
430 if(!(is >> mLevel))
431 return false;
432 if(!(is >> mExp))
433 return false;
434 if(!(is >> mHpString))
435 return false;
436 if(!(is >> mWakefulness))
437 return false;
438 if(!(is >> mHunger))
439 return false;
440 if(!(is >> mGoldCarried))
441 return false;
442 if(!(is >> tempString))
443 return false;
444 if(tempString != "none")
445 {
446 mWeaponL = getGameMap()->getWeapon(tempString);
447 if(mWeaponL == nullptr)
448 {
449 OD_LOG_ERR("Unknown weapon name=" + tempString);
450 }
451 }
452
453 if(!(is >> tempString))
454 return false;
455 if(tempString != "none")
456 {
457 mWeaponR = getGameMap()->getWeapon(tempString);
458 if(mWeaponR == nullptr)
459 {
460 OD_LOG_ERR("Unknown weapon name=" + tempString);
461 }
462 }
463
464 if(!(is >> tempString))
465 return false;
466 mSkillTypeDropDeath = Skills::fromString(tempString);
467
468 if(!(is >> mWeaponDropDeath))
469 return false;
470
471 mLevel = std::min(MAX_LEVEL, mLevel);
472
473 uint32_t nbEffects;
474 if(!(is >> nbEffects))
475 return false;
476 while(nbEffects > 0)
477 {
478 --nbEffects;
479 CreatureEffect* effect = CreatureEffectManager::load(is);
480 if(effect == nullptr)
481 continue;
482
483 addCreatureEffect(effect);
484 }
485
486 return true;
487 }
488
buildStats()489 void Creature::buildStats()
490 {
491 // Get the base value
492 mMaxHP = mDefinition->getMinHp();
493 mDigRate = mDefinition->getDigRate();
494 mClaimRate = mDefinition->getClaimRate();
495 mGroundSpeed = mDefinition->getMoveSpeedGround();
496 mWaterSpeed = mDefinition->getMoveSpeedWater();
497 mLavaSpeed = mDefinition->getMoveSpeedLava();
498
499 mPhysicalDefense = mDefinition->getPhysicalDefense();
500 mMagicalDefense = mDefinition->getMagicalDefense();
501 mElementDefense = mDefinition->getElementDefense();
502
503 // Improve the stats to the current level
504 double multiplier = mLevel - 1;
505 if (multiplier <= 0.0)
506 return;
507
508 mMaxHP += mDefinition->getHpPerLevel() * multiplier;
509 mDigRate += mDefinition->getDigRatePerLevel() * multiplier;
510 mClaimRate += mDefinition->getClaimRatePerLevel() * multiplier;
511 mGroundSpeed += mDefinition->getGroundSpeedPerLevel() * multiplier;
512 mWaterSpeed += mDefinition->getWaterSpeedPerLevel() * multiplier;
513 mLavaSpeed += mDefinition->getLavaSpeedPerLevel() * multiplier;
514
515 mPhysicalDefense += mDefinition->getPhysicalDefPerLevel() * multiplier;
516 mMagicalDefense += mDefinition->getMagicalDefPerLevel() * multiplier;
517 mElementDefense += mDefinition->getElementDefPerLevel() * multiplier;
518 }
519
getCreatureFromStream(GameMap * gameMap,std::istream & is)520 Creature* Creature::getCreatureFromStream(GameMap* gameMap, std::istream& is)
521 {
522 Creature* creature = new Creature(gameMap);
523 if(!creature->importFromStream(is))
524 {
525 delete creature;
526 return nullptr;
527 }
528 return creature;
529 }
530
getCreatureFromPacket(GameMap * gameMap,ODPacket & is)531 Creature* Creature::getCreatureFromPacket(GameMap* gameMap, ODPacket& is)
532 {
533 Creature* creature = new Creature(gameMap);
534 creature->importFromPacket(is);
535 return creature;
536 }
537
exportToPacket(ODPacket & os,const Seat * seat) const538 void Creature::exportToPacket(ODPacket& os, const Seat* seat) const
539 {
540 MovableGameEntity::exportToPacket(os, seat);
541 const std::string& className = mDefinition->getClassName();
542 os << className;
543 os << mLevel;
544 os << mExp;
545
546 os << mHp;
547 os << mMaxHP;
548
549 os << mDigRate;
550 os << mClaimRate;
551 os << mWakefulness;
552 os << mHunger;
553
554 os << mGroundSpeed;
555 os << mWaterSpeed;
556 os << mLavaSpeed;
557
558 os << mPhysicalDefense;
559 os << mMagicalDefense;
560 os << mElementDefense;
561 os << mOverlayHealthValue;
562
563 // Only allied players should see creature mood (except some states)
564 uint32_t moodValue = 0;
565 if(seat->isAlliedSeat(getSeat()))
566 moodValue = mOverlayMoodValue;
567 else if(mSeatPrison != nullptr)
568 {
569 if(mSeatPrison->isAlliedSeat(seat))
570 moodValue = mOverlayMoodValue & CreatureMoodEnum::MoodPrisonFiltersPrisonAllies;
571 else
572 moodValue = mOverlayMoodValue & CreatureMoodEnum::MoodPrisonFiltersAllPlayers;
573 }
574
575 os << moodValue;
576 os << mSpeedModifier;
577
578 if(mWeaponL != nullptr)
579 os << mWeaponL->getName();
580 else
581 os << "none";
582
583 if(mWeaponR != nullptr)
584 os << mWeaponR->getName();
585 else
586 os << "none";
587 }
588
importFromPacket(ODPacket & is)589 void Creature::importFromPacket(ODPacket& is)
590 {
591 MovableGameEntity::importFromPacket(is);
592 std::string tempString;
593
594 OD_ASSERT_TRUE(is >> mDefinitionString);
595
596 OD_ASSERT_TRUE(is >> mLevel);
597 OD_ASSERT_TRUE(is >> mExp);
598
599 OD_ASSERT_TRUE(is >> mHp);
600 OD_ASSERT_TRUE(is >> mMaxHP);
601
602 OD_ASSERT_TRUE(is >> mDigRate);
603 OD_ASSERT_TRUE(is >> mClaimRate);
604 OD_ASSERT_TRUE(is >> mWakefulness);
605 OD_ASSERT_TRUE(is >> mHunger);
606
607 OD_ASSERT_TRUE(is >> mGroundSpeed);
608 OD_ASSERT_TRUE(is >> mWaterSpeed);
609 OD_ASSERT_TRUE(is >> mLavaSpeed);
610
611 OD_ASSERT_TRUE(is >> mPhysicalDefense);
612 OD_ASSERT_TRUE(is >> mMagicalDefense);
613 OD_ASSERT_TRUE(is >> mElementDefense);
614
615 OD_ASSERT_TRUE(is >> mOverlayHealthValue);
616 OD_ASSERT_TRUE(is >> mOverlayMoodValue);
617 OD_ASSERT_TRUE(is >> mSpeedModifier);
618
619 OD_ASSERT_TRUE(is >> tempString);
620 if(tempString != "none")
621 {
622 mWeaponL = getGameMap()->getWeapon(tempString);
623 if(mWeaponL == nullptr)
624 {
625 OD_LOG_ERR("Unknown weapon name=" + tempString);
626 }
627 }
628
629 OD_ASSERT_TRUE(is >> tempString);
630 if(tempString != "none")
631 {
632 mWeaponR = getGameMap()->getWeapon(tempString);
633 if(mWeaponR == nullptr)
634 {
635 OD_LOG_ERR("Unknown weapon name=" + tempString);
636 }
637 }
638
639 setupDefinition(*getGameMap(), *ConfigManager::getSingleton().getCreatureDefinitionDefaultWorker());
640 }
641
setPosition(const Ogre::Vector3 & v)642 void Creature::setPosition(const Ogre::Vector3& v)
643 {
644 MovableGameEntity::setPosition(v);
645 if(mCarriedEntity != nullptr)
646 mCarriedEntity->notifyCarryMove(v);
647 }
648
setHP(double nHP)649 void Creature::setHP(double nHP)
650 {
651 if (nHP > mMaxHP)
652 mHp = mMaxHP;
653 else
654 mHp = nHP;
655
656 computeCreatureOverlayHealthValue();
657 }
658
heal(double hp)659 void Creature::heal(double hp)
660 {
661 mHp = std::min(mHp + hp, mMaxHP);
662
663 computeCreatureOverlayHealthValue();
664 }
665
isAlive() const666 bool Creature::isAlive() const
667 {
668 if(!getIsOnServerMap())
669 return mOverlayHealthValue < (NB_OVERLAY_HEALTH_VALUES - 1);
670
671 return mHp > 0.0;
672 }
673
update(Ogre::Real timeSinceLastFrame)674 void Creature::update(Ogre::Real timeSinceLastFrame)
675 {
676 Tile* previousPositionTile = getPositionTile();
677 // Update movements, direction, ...
678 MovableGameEntity::update(timeSinceLastFrame);
679
680 // Update the visual debugging entities
681 //if we are standing in a different tile than we were last turn
682 if (mHasVisualDebuggingEntities &&
683 getIsOnServerMap() &&
684 (getPositionTile() != previousPositionTile))
685 {
686 computeVisualDebugEntities();
687 }
688
689 if(getOverlayStatus() != nullptr)
690 {
691 getOverlayStatus()->update(timeSinceLastFrame);
692 }
693 }
694
computeVisibleTiles()695 void Creature::computeVisibleTiles()
696 {
697 // dead Creatures do not give vision
698 if (getHP() <= 0.0)
699 return;
700
701 // KO Creatures do not give vision
702 if (isKo())
703 return;
704
705 // creatures in jail do not give vision
706 if (mSeatPrison != nullptr)
707 return;
708
709 if (!getIsOnMap())
710 return;
711
712 // Look at the surrounding area
713 updateTilesInSight();
714 for(Tile* tile : mVisibleTiles)
715 tile->notifyVision(getSeat());
716 }
717
setLevel(unsigned int level)718 void Creature::setLevel(unsigned int level)
719 {
720 // Reset XP once the level has been acquired.
721 mLevel = std::min(MAX_LEVEL, level);
722 mExp = 0.0;
723
724 buildStats();
725
726 mNeedFireRefresh = true;
727 }
728
dropCarriedEquipment()729 void Creature::dropCarriedEquipment()
730 {
731 fireCreatureSound(CreatureSound::Die);
732 clearActionQueue();
733 clearDestinations(EntityAnimation::die_anim, false, false);
734
735 // We drop what we are carrying
736 Tile* myTile = getPositionTile();
737 if(myTile == nullptr)
738 {
739 OD_LOG_ERR("name=" + getName() + ", position=" + Helper::toString(getPosition()));
740 return;
741 }
742
743 if(mGoldCarried > 0)
744 {
745 TreasuryObject* obj = new TreasuryObject(getGameMap(), mGoldCarried);
746 obj->addToGameMap();
747 Ogre::Vector3 spawnPosition(static_cast<Ogre::Real>(myTile->getX()),
748 static_cast<Ogre::Real>(myTile->getY()), 0.0f);
749 obj->createMesh();
750 obj->setPosition(spawnPosition);
751 mGoldCarried = 0;
752 }
753
754 if(mSkillTypeDropDeath != SkillType::nullSkillType)
755 {
756 GiftBoxSkill* skillEntity = new GiftBoxSkill(getGameMap(),
757 "DroppedBy" + getName(), mSkillTypeDropDeath);
758 skillEntity->addToGameMap();
759 Ogre::Vector3 spawnPosition(static_cast<Ogre::Real>(myTile->getX()),
760 static_cast<Ogre::Real>(myTile->getY()), 0.0f);
761 skillEntity->createMesh();
762 skillEntity->setPosition(spawnPosition);
763 mSkillTypeDropDeath = SkillType::nullSkillType;
764 }
765
766 // TODO: drop weapon when available
767 }
768
doUpkeep()769 void Creature::doUpkeep()
770 {
771 // If the creature is in jail, we check if it is still standing on it (if not picked up). If
772 // not, it is free
773 if((mSeatPrison != nullptr) &&
774 getIsOnMap())
775 {
776 Tile* myTile = getPositionTile();
777 if(myTile == nullptr)
778 {
779 OD_LOG_ERR("name=" + getName() + ", position=" + Helper::toString(getPosition()));
780 return;
781 }
782
783 // We check if the creature is in containment
784 Room* roomPrison = myTile->getCoveringRoom();
785 if((roomPrison == nullptr) ||
786 (!roomPrison->isInContainment(*this)))
787 {
788 // it is not standing on a jail. It is free
789 mSeatPrison = nullptr;
790 mNeedFireRefresh = true;
791 }
792 }
793
794 // The creature may be killed while temporary KO
795 if((mKoTurnCounter != 0) && !isAlive())
796 mKoTurnCounter = 0;
797
798 // If the creature is KO to death or dead, we remove its particle effects
799 if(!mEntityParticleEffects.empty() &&
800 ((mKoTurnCounter < 0) || !isAlive()))
801 {
802 for(EntityParticleEffect* effect : mEntityParticleEffects)
803 {
804 delete effect;
805 }
806 mEntityParticleEffects.clear();
807 }
808
809 // We apply creature effects if any
810 for(auto it = mEntityParticleEffects.begin(); it != mEntityParticleEffects.end();)
811 {
812 CreatureParticuleEffect* effect = static_cast<CreatureParticuleEffect*>(*it);
813 if(effect->mEffect->upkeepEffect(*this))
814 {
815 ++it;
816 continue;
817 }
818
819 delete effect;
820 it = mEntityParticleEffects.erase(it);
821 }
822
823 // if creature is not on map (picked up or being carried), we do nothing
824 if(!getIsOnMap())
825 return;
826
827 // If the creature is temporary KO, it should do nothing
828 if(mKoTurnCounter > 0)
829 {
830 --mKoTurnCounter;
831 if(mKoTurnCounter > 0)
832 return;
833
834 computeCreatureOverlayMoodValue();
835 return;
836 }
837
838 if(mKoTurnCounter < 0)
839 {
840 // If the counter reaches 0, the creature is dead
841 ++mKoTurnCounter;
842 if(mKoTurnCounter < 0)
843 return;
844
845 mHp = 0;
846 computeCreatureOverlayHealthValue();
847 computeCreatureOverlayMoodValue();
848 }
849
850 // Handle creature death
851 if (!isAlive())
852 {
853 // Let the creature lay dead on the ground for a few turns before removing it from the GameMap.
854 if (mDeathCounter == 0)
855 {
856 OD_LOG_INF("Creature=" + getName() + " RIP");
857
858 dropCarriedEquipment();
859 }
860 else if (mDeathCounter >= ConfigManager::getSingleton().getCreatureDeathCounter())
861 {
862 // Remove the creature from the game map and into the deletion queue, it will be deleted
863 // when it is safe, i.e. all other pointers to it have been wiped from the program.
864 removeFromGameMap();
865 deleteYourself();
866 }
867
868 ++mDeathCounter;
869 return;
870 }
871
872 // If the creature is in jail, it should not auto heal or do anything
873 if(mSeatPrison != nullptr)
874 {
875 Tile* myTile = getPositionTile();
876 if(myTile == nullptr)
877 {
878 OD_LOG_ERR("name=" + getName() + ", position=" + Helper::toString(getPosition()));
879 return;
880 }
881
882 // If the creature is in a containment room, it should use it
883 Room* roomPrison = myTile->getCoveringRoom();
884 if((roomPrison != nullptr) && (decreaseJobCooldown()))
885 roomPrison->useRoom(*this, true);
886
887 return;
888 }
889
890 // If we are not standing somewhere on the map, do nothing.
891 if (getPositionTile() == nullptr)
892 {
893 OD_LOG_ERR("creature=" + getName() + " not on map position=" + Helper::toString(getPosition()));
894 return;
895 }
896
897 // Check to see if we have earned enough experience to level up.
898 checkLevelUp();
899
900
901 // Heal.
902 mHp += mDefinition->getHpHealPerTurn();
903 if (mHp > getMaxHp())
904 mHp = getMaxHp();
905
906 computeCreatureOverlayHealthValue();
907
908 // Rogue creatures are not affected by wakefulness/hunger
909 if(!getSeat()->isRogueSeat())
910 {
911 decreaseWakefulness(mDefinition->getWakefulnessLostPerTurn());
912
913 increaseHunger(mDefinition->getHungerGrowthPerTurn());
914 }
915
916 mVisibleEnemyObjects = getVisibleEnemyObjects();
917 mVisibleAlliedObjects = getVisibleAlliedObjects();
918 mReachableAlliedObjects = getReachableAttackableObjects(mVisibleAlliedObjects);
919
920 // Check if we should compute mood
921 if(mMoodCooldownTurns > 0)
922 {
923 --mMoodCooldownTurns;
924 }
925 // Rogue creatures do not have mood
926 else if(!getSeat()->isRogueSeat())
927 {
928 computeMood();
929 computeCreatureOverlayMoodValue();
930 mMoodCooldownTurns = Random::Int(0, 5);
931 }
932
933 if(mMoodValue < CreatureMoodLevel::Furious)
934 {
935 mNbTurnFurious = -1;
936 }
937 else
938 {
939 // If the creature is furious for too long, it will become rogue
940 if(mNbTurnFurious < 0)
941 mNbTurnFurious = 0;
942 else
943 ++mNbTurnFurious;
944
945 if(mNbTurnFurious >= ConfigManager::getSingleton().getNbTurnsFuriousMax())
946 {
947 // We couldn't leave the dungeon in time, we become rogue
948 fireChatMsgBecameRogue();
949
950 Seat* rogueSeat = getGameMap()->getSeatRogue();
951 changeSeat(rogueSeat);
952 }
953 }
954
955 ++mNbTurnsWithoutBattle;
956
957 bool isWarmUp = false;
958 // We use creature skills if we can
959 for(CreatureSkillData& skillData : mSkillData)
960 {
961 if(skillData.mWarmup > 0)
962 {
963 --skillData.mWarmup;
964 isWarmUp = true;
965 }
966
967 if(skillData.mCooldown > 0)
968 {
969 --skillData.mCooldown;
970 continue;
971 }
972
973 if(!skillData.mSkill->canBeUsedBy(this))
974 continue;
975
976 if(!skillData.mSkill->tryUseSupport(*getGameMap(), this))
977 continue;
978
979 skillData.mCooldown = skillData.mSkill->getCooldownNbTurns();
980 skillData.mWarmup = skillData.mSkill->getWarmupNbTurns();
981
982 if(skillData.mWarmup > 0)
983 isWarmUp = true;
984 }
985
986 // If a warmup is active, we do nothing
987 if(isWarmUp)
988 return;
989
990 decidePrioritaryAction();
991
992 // The loopback variable allows creatures to begin processing a new
993 // action immediately after some other action happens.
994 bool loopBack = false;
995 unsigned int loops = 0;
996
997 mActionTry.clear();
998
999 do
1000 {
1001 ++loops;
1002 loopBack = false;
1003
1004 if (mActions.empty())
1005 loopBack = handleIdleAction();
1006 else
1007 {
1008 std::function<bool()> func = mActions.back().get()->action();
1009 loopBack = func();
1010 }
1011 } while (loopBack && loops < 20);
1012
1013 if(!mActions.empty())
1014 mActions.back().get()->increaseNbTurnActive();
1015
1016 for(std::unique_ptr<CreatureAction>& creatureAction : mActions)
1017 creatureAction.get()->increaseNbTurn();
1018
1019 if(loops >= 20)
1020 {
1021 OD_LOG_INF("> 20 loops in Creature::doUpkeep name:" + getName() +
1022 " seat id: " + Helper::toString(getSeat()->getId()) + ". Breaking out..");
1023 }
1024 }
1025
decidePrioritaryAction()1026 void Creature::decidePrioritaryAction()
1027 {
1028 for(const CreatureBehaviour* behaviour : getDefinition()->getCreatureBehaviours())
1029 {
1030 if(!behaviour->processBehaviour(*this))
1031 return;
1032 }
1033 }
1034
handleIdleAction()1035 bool Creature::handleIdleAction()
1036 {
1037 setAnimationState(EntityAnimation::idle_anim);
1038
1039 if (mDefinition->isWorker())
1040 {
1041 // Decide what to do
1042 std::vector<CreatureActionType> workerActions = getSeat()->getPlayer()->getWorkerPreferredActions(*this);
1043 for(CreatureActionType actionType : workerActions)
1044 {
1045 if(hasActionBeenTried(actionType))
1046 continue;
1047
1048 switch(actionType)
1049 {
1050 case CreatureActionType::searchEntityToCarry:
1051 pushAction(Utils::make_unique<CreatureActionSearchEntityToCarry>(*this, false));
1052 return true;
1053 case CreatureActionType::searchGroundTileToClaim:
1054 pushAction(Utils::make_unique<CreatureActionSearchGroundTileToClaim>(*this, false));
1055 return true;
1056 case CreatureActionType::searchTileToDig:
1057 pushAction(Utils::make_unique<CreatureActionSearchTileToDig>(*this, false));
1058 return true;
1059 case CreatureActionType::searchWallTileToClaim:
1060 pushAction(Utils::make_unique<CreatureActionSearchWallTileToClaim>(*this, false));
1061 return true;
1062 default:
1063 OD_LOG_ERR("name=" + getName() + ", unexpected worker action=" + CreatureAction::toString(actionType));
1064 break;
1065 }
1066 }
1067 }
1068
1069 // We check if we are looking for our fee
1070 if(!mDefinition->isWorker() &&
1071 !hasActionBeenTried(CreatureActionType::getFee) &&
1072 (mGoldFee > 0))
1073 {
1074 pushAction(Utils::make_unique<CreatureActionGetFee>(*this));
1075 return true;
1076 }
1077
1078 // We check if there is a go to war spell reachable
1079 std::vector<Spell*> callToWars = getGameMap()->getSpellsBySeatAndType(getSeat(), SpellType::callToWar);
1080 if(!callToWars.empty())
1081 {
1082 std::vector<Spell*> reachableCallToWars;
1083 for(Spell* callToWar : callToWars)
1084 {
1085 if(!callToWar->getIsOnMap())
1086 continue;
1087
1088 Tile* callToWarTile = callToWar->getPositionTile();
1089 if(callToWarTile == nullptr)
1090 continue;
1091
1092 if (!getGameMap()->pathExists(this, getPositionTile(), callToWarTile))
1093 continue;
1094
1095 reachableCallToWars.push_back(callToWar);
1096 }
1097
1098 if(!reachableCallToWars.empty())
1099 {
1100 // We go there
1101 uint32_t index = Random::Uint(0,reachableCallToWars.size()-1);
1102 Spell* callToWar = reachableCallToWars[index];
1103 Tile* callToWarTile = callToWar->getPositionTile();
1104 std::list<Tile*> tempPath = getGameMap()->path(this, callToWarTile);
1105 // If we are 5 tiles from the call to war, we don't go there
1106 if(tempPath.size() >= 5)
1107 {
1108 std::vector<Ogre::Vector3> path;
1109 tileToVector3(tempPath, path, true, 0.0);
1110 setWalkPath(EntityAnimation::walk_anim, EntityAnimation::idle_anim, true, true, path);
1111 pushAction(Utils::make_unique<CreatureActionWalkToTile>(*this));
1112 return false;
1113 }
1114 }
1115 }
1116
1117 // Check to see if we have found a "home" tile where we can sleep. Even if we are not sleepy,
1118 // we want to have a bed
1119 if (!mDefinition->isWorker() &&
1120 !hasActionBeenTried(CreatureActionType::findHome) &&
1121 (mHomeTile == nullptr) &&
1122 (Random::Double(0.0, 1.0) < 0.5))
1123 {
1124 pushAction(Utils::make_unique<CreatureActionFindHome>(*this, false));
1125 return true;
1126 }
1127
1128 // If we are sleepy, we go to sleep
1129 if (!mDefinition->isWorker() &&
1130 !hasActionBeenTried(CreatureActionType::sleep) &&
1131 (mHomeTile != nullptr) &&
1132 (Random::Double(20.0, 30.0) > mWakefulness))
1133 {
1134 pushAction(Utils::make_unique<CreatureActionSleep>(*this));
1135 return true;
1136 }
1137
1138 // If we are hungry, we go to eat
1139 if (!mDefinition->isWorker() &&
1140 !hasActionBeenTried(CreatureActionType::searchFood) &&
1141 (Random::Double(70.0, 80.0) < mHunger))
1142 {
1143 pushAction(Utils::make_unique<CreatureActionSearchFood>(*this, false));
1144 return true;
1145 }
1146
1147 // We try to steal some gold if there is some on the ground
1148 // Later, we might want to add a creature definition parameter to make some
1149 // creatures more likely to steal gold than others
1150 if (!mDefinition->isWorker() &&
1151 !hasActionBeenTried(CreatureActionType::stealFreeGold) &&
1152 (Random::Uint(0, 10) > 8))
1153 {
1154 pushAction(Utils::make_unique<CreatureActionStealFreeGold>(*this));
1155 return true;
1156 }
1157
1158 // Otherwise, we try to work
1159 if (!mDefinition->isWorker() &&
1160 !hasActionBeenTried(CreatureActionType::searchJob) &&
1161 (Random::Double(0.0, 1.0) < 0.4))
1162 {
1163 pushAction(Utils::make_unique<CreatureActionSearchJob>(*this, false));
1164 return true;
1165 }
1166
1167 // Any creature.
1168
1169 // Workers should move around randomly at large jumps. Non-workers either wander short distances or follow workers.
1170 Tile* tileDest = nullptr;
1171 // Define reachable tiles from the tiles within radius
1172 std::vector<Tile*> reachableTiles;
1173 for (Tile* tile: mTilesWithinSightRadius)
1174 {
1175 if (getGameMap()->pathExists(this, getPositionTile(), tile))
1176 reachableTiles.push_back(tile);
1177 }
1178
1179 if (!mDefinition->isWorker())
1180 {
1181 // Non-workers only.
1182
1183 // Check to see if we want to try to follow a worker around or if we want to try to explore.
1184 double r = Random::Double(0.0, 1.0);
1185 if (r < 0.7)
1186 {
1187 bool workerFound = false;
1188 // Try to find a worker to follow around.
1189 for (unsigned int i = 0; !workerFound && i < mReachableAlliedObjects.size(); ++i)
1190 {
1191 // Check to see if we found a worker.
1192 if (mReachableAlliedObjects[i]->getObjectType() == GameEntityType::creature
1193 && static_cast<Creature*>(mReachableAlliedObjects[i])->mDefinition->isWorker())
1194 {
1195 // We found a worker so find a tile near the worker to walk to. See if the worker is digging.
1196 Tile* tempTile = mReachableAlliedObjects[i]->getCoveredTile(0);
1197 if (static_cast<Creature*>(mReachableAlliedObjects[i])->isActionInList(CreatureActionType::digTile))
1198 {
1199 // Worker is digging, get near it since it could expose enemies.
1200 int x = static_cast<int>(static_cast<double>(tempTile->getX()) + 3.0
1201 * Random::gaussianRandomDouble());
1202 int y = static_cast<int>(static_cast<double>(tempTile->getY()) + 3.0
1203 * Random::gaussianRandomDouble());
1204 tileDest = getGameMap()->getTile(x, y);
1205 }
1206 else
1207 {
1208 // Worker is not digging, wander a bit farther around the worker.
1209 int x = static_cast<int>(static_cast<double>(tempTile->getX()) + 8.0
1210 * Random::gaussianRandomDouble());
1211 int y = static_cast<int>(static_cast<double>(tempTile->getY()) + 8.0
1212 * Random::gaussianRandomDouble());
1213 tileDest = getGameMap()->getTile(x, y);
1214 }
1215 workerFound = true;
1216 }
1217
1218 // If there are no workers around, choose tiles far away to "roam" the dungeon.
1219 if (!workerFound)
1220 {
1221 if (!reachableTiles.empty())
1222 {
1223 tileDest = reachableTiles[static_cast<unsigned int>(Random::Double(0.6, 0.8)
1224 * (reachableTiles.size() - 1))];
1225 }
1226 }
1227 }
1228 }
1229 else
1230 {
1231 // Randomly choose a tile near where we are standing to walk to.
1232 if (!reachableTiles.empty())
1233 {
1234 unsigned int tileIndex = static_cast<unsigned int>(reachableTiles.size()
1235 * Random::Double(0.1, 0.3));
1236 tileDest = reachableTiles[tileIndex];
1237 }
1238 }
1239 }
1240 else
1241 {
1242 // Workers only.
1243
1244 // Choose a tile far away from our current position to wander to.
1245 if (!reachableTiles.empty())
1246 {
1247 tileDest = reachableTiles[Random::Uint(reachableTiles.size() / 2,
1248 reachableTiles.size() - 1)];
1249 }
1250 }
1251
1252 if(setDestination(tileDest))
1253 return false;
1254
1255 return true;
1256 }
1257
searchBestTargetInList(const std::vector<GameEntity * > & listObjects,const std::vector<Tile * > & tilesFilter,GameEntity * & attackedEntity,Tile * & attackedTile,Tile * & positionTile,CreatureSkillData * & creatureSkillData)1258 bool Creature::searchBestTargetInList(const std::vector<GameEntity*>& listObjects, const std::vector<Tile*>& tilesFilter, GameEntity*& attackedEntity,
1259 Tile*& attackedTile, Tile*& positionTile, CreatureSkillData*& creatureSkillData)
1260 {
1261 Tile* myTile = getPositionTile();
1262 if(myTile == nullptr)
1263 {
1264 OD_LOG_ERR("name=" + getName() + ", position=" + Helper::toString(getPosition()));
1265 return false;
1266 }
1267
1268 GameEntity* entityFlee = nullptr;
1269 // Closest creature
1270 GameEntity* entityAttack = nullptr;
1271 Tile* tileAttack = nullptr;
1272 CreatureSkillData* skillData = nullptr;
1273 Tile* tilePosition = nullptr;
1274 int closestDist = -1;
1275 // We try to attack creatures first
1276 for(GameEntity* entity : listObjects)
1277 {
1278 GameEntity* entityAttackCheck = nullptr;
1279 Tile* tileAttackCheck = nullptr;
1280 CreatureSkillData* skillDataCheck = nullptr;
1281 int closestDistCheck = closestDist;
1282 // We check if this creature is closer than the other one (if any)
1283 std::vector<Tile*> coveredTiles = entity->getCoveredTiles();
1284 for(Tile* tile : coveredTiles)
1285 {
1286 if(std::find(mVisibleTiles.begin(), mVisibleTiles.end(), tile) == mVisibleTiles.end())
1287 continue;
1288
1289 int dist = Pathfinding::squaredDistanceTile(*tile, *myTile);
1290 if((closestDistCheck != -1) && (dist >= closestDistCheck))
1291 continue;
1292
1293 // We found a tile closer
1294 // Note that we don't break because if this entity is on more than 1 tile,
1295 // we want to attack the closest tile
1296 closestDistCheck = dist;
1297 entityAttackCheck = entity;
1298 tileAttackCheck = tile;
1299 }
1300
1301 if((entityAttackCheck == nullptr) || (tileAttackCheck == nullptr))
1302 continue;
1303
1304 // We check if we are supposed to flee from this entity
1305 if((entityFlee == nullptr) && entityAttackCheck->isDangerous(this, closestDistCheck))
1306 entityFlee = entityAttackCheck;
1307
1308 // If we found a suitable enemy, we check if we can attack it
1309 double skillRangeMax = 0.0;
1310 for(CreatureSkillData& skillDataTmp : mSkillData)
1311 {
1312 if(skillDataTmp.mCooldown > 0)
1313 continue;
1314
1315 if(!skillDataTmp.mSkill->canBeUsedBy(this))
1316 continue;
1317
1318 double skillRange = skillDataTmp.mSkill->getRangeMax(this, entityAttackCheck);
1319 if(skillRange <= 0)
1320 continue;
1321 if(skillRange < skillRangeMax)
1322 continue;
1323
1324 skillRangeMax = skillRange;
1325 skillDataCheck = &skillDataTmp;
1326 }
1327 if(skillRangeMax <= 0)
1328 continue;
1329
1330 // Check if we can attack from our position
1331 int rangeTarget = Pathfinding::squaredDistanceTile(*tileAttackCheck, *myTile);
1332 if(rangeTarget <= (skillRangeMax * skillRangeMax))
1333 {
1334 // We can attack
1335 if((closestDist == -1) || (rangeTarget < closestDist))
1336 {
1337 tilePosition = myTile;
1338 entityAttack = entityAttackCheck;
1339 tileAttack = tileAttackCheck;
1340 skillData = skillDataCheck;
1341 closestDist = rangeTarget;
1342 }
1343 continue;
1344 }
1345
1346 // We check if we can attack from somewhere. To do that, we check
1347 // from the target point of view if there is a tile with visibility within range
1348 int skillRangeMaxInt = static_cast<int>(skillRangeMax);
1349 int skillRangeMaxIntSquared = skillRangeMaxInt * skillRangeMaxInt;
1350 int bestScoreAttack = -1;
1351 std::vector<Tile*> tiles;
1352 if(tilesFilter.empty())
1353 tiles = getGameMap()->visibleTiles(tileAttackCheck->getX(), tileAttackCheck->getY(), skillRangeMaxInt);
1354 else
1355 {
1356 float radiusSquared = skillRangeMaxInt * skillRangeMaxInt;
1357 for(Tile* tile : tilesFilter)
1358 {
1359 float dist = Pathfinding::squaredDistanceTile(*tileAttackCheck, *tile);
1360 if(dist > radiusSquared)
1361 continue;
1362
1363 tiles.push_back(tile);
1364 }
1365 }
1366 for(Tile* tile : tiles)
1367 {
1368 if(tile->isFullTile())
1369 continue;
1370
1371 if(!getGameMap()->pathExists(this, myTile, tile))
1372 continue;
1373
1374 int distFoeTmp = Pathfinding::squaredDistanceTile(*tile, *tileAttackCheck);
1375 int distAttackTmp = Pathfinding::squaredDistanceTile(*tile, *myTile);
1376 // We compute a score for each tile. We will choose the best one. Note that we try to be as close as possible
1377 // from the fightIdleDist but by walking the less possible. We need to find a compromise
1378 int scoreAttack = std::abs(skillRangeMaxIntSquared - distFoeTmp) * 2 + distAttackTmp;
1379 if((bestScoreAttack != -1) && (bestScoreAttack <= scoreAttack))
1380 continue;
1381
1382 // We found a better target
1383 bestScoreAttack = scoreAttack;
1384 tilePosition = tile;
1385 entityAttack = entityAttackCheck;
1386 tileAttack = tileAttackCheck;
1387 skillData = skillDataCheck;
1388 closestDist = closestDistCheck;
1389 // We don't break because there might be a better spot
1390 }
1391 }
1392
1393 // If there is a dangerous entity and we cannot attack, we should try to get away
1394 if((entityFlee != nullptr) && (skillData == nullptr))
1395 {
1396 // Let's try to run to the closest spot at the distance closest to the fight idle distance
1397 Tile* tileEntityFlee = entityFlee->getPositionTile();
1398 if(tileEntityFlee == nullptr)
1399 {
1400 OD_LOG_ERR("entity=" + entityFlee->getName() + ", position=" + Helper::toString(entityFlee->getPosition()));
1401 return false;
1402 }
1403
1404 int bestScoreFlee = -1;
1405 int32_t fightIdleDist = getDefinition()->getFightIdleDist();
1406 Tile* fleeTile = nullptr;
1407 std::vector<Tile*> tiles;
1408 if(tilesFilter.empty())
1409 tiles = getGameMap()->visibleTiles(tileEntityFlee->getX(), tileEntityFlee->getY(), fightIdleDist);
1410 else
1411 {
1412 float radiusSquared = fightIdleDist * fightIdleDist;
1413 for(Tile* tile : tilesFilter)
1414 {
1415 float dist = Pathfinding::squaredDistanceTile(*tileEntityFlee, *tile);
1416 if(dist > radiusSquared)
1417 continue;
1418
1419 tiles.push_back(tile);
1420 }
1421 }
1422 int32_t fightIdleDistSquared = fightIdleDist * fightIdleDist;
1423 for(Tile* tile : tiles)
1424 {
1425 if(tile->isFullTile())
1426 continue;
1427
1428 if(!getGameMap()->pathExists(this, myTile, tile))
1429 continue;
1430
1431 int distFoeTmp = Pathfinding::squaredDistanceTile(*tile, *tileEntityFlee);
1432 int fleeDistTmp = Pathfinding::squaredDistanceTile(*tile, *myTile);
1433 // We compute a score for each tile. We will choose the best one. Note that we try to be as close as possible
1434 // from the fightIdleDist but by walking the less possible. We need to find a compromise
1435 int scoreFlee = std::abs(fightIdleDistSquared - distFoeTmp) * 2 + fleeDistTmp;
1436 if((bestScoreFlee != -1) && (bestScoreFlee <= scoreFlee))
1437 continue;
1438
1439 bestScoreFlee = scoreFlee;
1440 fleeTile = tile;
1441 }
1442
1443 if(fleeTile == nullptr)
1444 return false;
1445
1446 attackedEntity = nullptr;
1447 attackedTile = nullptr;
1448 positionTile = fleeTile;
1449 }
1450 else if ((entityAttack == nullptr) ||
1451 (tileAttack == nullptr) ||
1452 (tilePosition == nullptr))
1453 {
1454 // We couldn't find an entity to attack
1455 return false;
1456 }
1457 else
1458 {
1459 attackedEntity = entityAttack;
1460 attackedTile = tileAttack;
1461 creatureSkillData = skillData;
1462 positionTile = tilePosition;
1463 }
1464
1465 return true;
1466 }
1467
engageAlliedNaturalEnemy(Creature & attackerCreature)1468 void Creature::engageAlliedNaturalEnemy(Creature& attackerCreature)
1469 {
1470 Tile* myTile = getPositionTile();
1471 if(myTile == nullptr)
1472 {
1473 OD_LOG_ERR("name=" + getName() + ", pos=" + Helper::toString(getPosition()));
1474 return;
1475 }
1476
1477 // If we are already fighting, do nothing
1478 if(isActionInList(CreatureActionType::fight))
1479 return;
1480
1481 // When fighting a natural enemy, we always fight to death
1482 // We want to notify the player that his creatures are fighting
1483 fightCreature(attackerCreature, false, true);
1484 }
1485
getMoveSpeed() const1486 double Creature::getMoveSpeed() const
1487 {
1488 return getMoveSpeed(getPositionTile());
1489 }
1490
getMoveSpeed(Tile * tile) const1491 double Creature::getMoveSpeed(Tile* tile) const
1492 {
1493 if(tile == nullptr)
1494 {
1495 OD_LOG_ERR("creature=" + getName());
1496 return 1.0;
1497 }
1498
1499 if(getIsOnServerMap())
1500 {
1501 // Check if the covering building allows this creature to go through
1502 if(tile->getCoveringBuilding() != nullptr)
1503 return tile->getCoveringBuilding()->getCreatureSpeed(this, tile);
1504 else
1505 return tile->getCreatureSpeedDefault(this);
1506 }
1507 else
1508 {
1509 if(tile->getHasBridge())
1510 return getMoveSpeedGround();
1511 else
1512 return tile->getCreatureSpeedDefault(this);
1513 }
1514 }
1515
getPhysicalDefense() const1516 double Creature::getPhysicalDefense() const
1517 {
1518 double defense = mPhysicalDefense;
1519 if (mWeaponL != nullptr)
1520 defense += mWeaponL->getPhysicalDefense();
1521 if (mWeaponR != nullptr)
1522 defense += mWeaponR->getPhysicalDefense();
1523
1524 return defense;
1525 }
1526
getMagicalDefense() const1527 double Creature::getMagicalDefense() const
1528 {
1529 double defense = mMagicalDefense;
1530 if (mWeaponL != nullptr)
1531 defense += mWeaponL->getMagicalDefense();
1532 if (mWeaponR != nullptr)
1533 defense += mWeaponR->getMagicalDefense();
1534
1535 return defense;
1536 }
1537
getElementDefense() const1538 double Creature::getElementDefense() const
1539 {
1540 double defense = mElementDefense;
1541 if (mWeaponL != nullptr)
1542 defense += mWeaponL->getElementDefense();
1543 if (mWeaponR != nullptr)
1544 defense += mWeaponR->getElementDefense();
1545
1546 return defense;
1547 }
1548
checkLevelUp()1549 void Creature::checkLevelUp()
1550 {
1551 if (getLevel() >= MAX_LEVEL)
1552 return;
1553
1554 // Check the returned value.
1555 double newXP = mDefinition->getXPNeededWhenLevel(getLevel());
1556
1557 // An error occurred
1558 if (newXP <= 0.0)
1559 {
1560 OD_LOG_ERR("creature=" + getName() + ", newXP=" + Helper::toString(newXP));
1561 return;
1562 }
1563
1564 if (mExp < newXP)
1565 return;
1566
1567 setLevel(mLevel + 1);
1568 }
1569
exportToPacketForUpdate(ODPacket & os,const Seat * seat) const1570 void Creature::exportToPacketForUpdate(ODPacket& os, const Seat* seat) const
1571 {
1572 MovableGameEntity::exportToPacketForUpdate(os, seat);
1573
1574 int seatId = getSeat()->getId();
1575 os << mLevel;
1576 os << seatId;
1577 os << mOverlayHealthValue;
1578
1579 // Only allied players should see creature mood (except some states)
1580 uint32_t moodValue = 0;
1581 if(seat->isAlliedSeat(getSeat()))
1582 moodValue = mOverlayMoodValue;
1583 else if(mSeatPrison != nullptr)
1584 {
1585 if(mSeatPrison->isAlliedSeat(seat))
1586 moodValue = mOverlayMoodValue & CreatureMoodEnum::MoodPrisonFiltersPrisonAllies;
1587 else
1588 moodValue = mOverlayMoodValue & CreatureMoodEnum::MoodPrisonFiltersAllPlayers;
1589 }
1590
1591 os << moodValue;
1592 os << mGroundSpeed;
1593 os << mWaterSpeed;
1594 os << mLavaSpeed;
1595 os << mSpeedModifier;
1596
1597 int seatPrisonId = -1;
1598 if(mSeatPrison != nullptr)
1599 seatPrisonId = mSeatPrison->getId();
1600
1601 os << seatPrisonId;
1602 }
1603
updateFromPacket(ODPacket & is)1604 void Creature::updateFromPacket(ODPacket& is)
1605 {
1606 MovableGameEntity::updateFromPacket(is);
1607
1608 int seatId;
1609 OD_ASSERT_TRUE(is >> mLevel);
1610 OD_ASSERT_TRUE(is >> seatId);
1611 OD_ASSERT_TRUE(is >> mOverlayHealthValue);
1612 OD_ASSERT_TRUE(is >> mOverlayMoodValue);
1613 OD_ASSERT_TRUE(is >> mGroundSpeed);
1614 OD_ASSERT_TRUE(is >> mWaterSpeed);
1615 OD_ASSERT_TRUE(is >> mLavaSpeed);
1616 OD_ASSERT_TRUE(is >> mSpeedModifier);
1617
1618 // We do not scale the creature if it is picked up (because it is already not at its normal size). It will be
1619 // resized anyway when dropped
1620 if(getIsOnMap())
1621 RenderManager::getSingleton().rrScaleCreature(*this);
1622
1623 if(getSeat()->getId() != seatId)
1624 {
1625 Seat* seat = getGameMap()->getSeatById(seatId);
1626 if(seat == nullptr)
1627 {
1628 OD_LOG_ERR("Creature " + getName() + ", wrong seatId=" + Helper::toString(seatId));
1629 }
1630 else
1631 {
1632 setSeat(seat);
1633 }
1634 }
1635
1636 OD_ASSERT_TRUE(is >> seatId);
1637 if(seatId == -1)
1638 mSeatPrison = nullptr;
1639 else
1640 {
1641 mSeatPrison = getGameMap()->getSeatById(seatId);
1642 if(mSeatPrison == nullptr)
1643 {
1644 OD_LOG_ERR("Creature " + getName() + ", wrong seatId=" + Helper::toString(seatId));
1645 }
1646 }
1647 }
1648
updateTilesInSight()1649 void Creature::updateTilesInSight()
1650 {
1651 Tile* posTile = getPositionTile();
1652 if (posTile == nullptr)
1653 return;
1654
1655 // The tiles with sight radius without constraints
1656 mTilesWithinSightRadius = getGameMap()->circularRegion(posTile->getX(), posTile->getY(), mDefinition->getSightRadius());
1657
1658 // Only the tiles the creature can "see".
1659 mVisibleTiles = getGameMap()->visibleTiles(posTile->getX(), posTile->getY(), mDefinition->getSightRadius());
1660 }
1661
getVisibleEnemyObjects()1662 std::vector<GameEntity*> Creature::getVisibleEnemyObjects()
1663 {
1664 return getVisibleForce(getSeat(), true);
1665 }
1666
getReachableAttackableObjects(const std::vector<GameEntity * > & objectsToCheck)1667 std::vector<GameEntity*> Creature::getReachableAttackableObjects(const std::vector<GameEntity*>& objectsToCheck)
1668 {
1669 std::vector<GameEntity*> tempVector;
1670 Tile* myTile = getPositionTile();
1671
1672 // Loop over the vector of objects we are supposed to check.
1673 for (unsigned int i = 0; i < objectsToCheck.size(); ++i)
1674 {
1675 // Try to find a valid path from the tile this creature is in to the nearest tile where the current target object is.
1676 GameEntity* entity = objectsToCheck[i];
1677 // We only consider alive objects
1678 if(entity->getHP(nullptr) <= 0)
1679 continue;
1680
1681 Tile* objectTile = entity->getCoveredTile(0);
1682 if (getGameMap()->pathExists(this, myTile, objectTile))
1683 tempVector.push_back(objectsToCheck[i]);
1684 }
1685
1686 return tempVector;
1687 }
1688
getCreaturesFromList(const std::vector<GameEntity * > & objectsToCheck,bool workersOnly)1689 std::vector<GameEntity*> Creature::getCreaturesFromList(const std::vector<GameEntity*> &objectsToCheck, bool workersOnly)
1690 {
1691 std::vector<GameEntity*> tempVector;
1692
1693 // Loop over the vector of objects we are supposed to check.
1694 for (std::vector<GameEntity*>::const_iterator it = objectsToCheck.begin(); it != objectsToCheck.end(); ++it)
1695 {
1696 // Try to find a valid path from the tile this creature is in to the nearest tile where the current target object is.
1697 GameEntity* entity = *it;
1698 // We only consider alive objects
1699 if(entity->getObjectType() != GameEntityType::creature)
1700 continue;
1701
1702 if(workersOnly && !static_cast<Creature*>(entity)->getDefinition()->isWorker())
1703 continue;
1704
1705 tempVector.push_back(entity);
1706 }
1707
1708 return tempVector;
1709 }
1710
getVisibleAlliedObjects()1711 std::vector<GameEntity*> Creature::getVisibleAlliedObjects()
1712 {
1713 return getVisibleForce(getSeat(), false);
1714 }
1715
getVisibleForce(Seat * seat,bool invert)1716 std::vector<GameEntity*> Creature::getVisibleForce(Seat* seat, bool invert)
1717 {
1718 return getGameMap()->getVisibleForce(mVisibleTiles, seat, invert);
1719 }
1720
computeVisualDebugEntities()1721 void Creature::computeVisualDebugEntities()
1722 {
1723 if(!getIsOnServerMap())
1724 return;
1725
1726 mHasVisualDebuggingEntities = true;
1727
1728 updateTilesInSight();
1729
1730 ServerNotification *serverNotification = new ServerNotification(
1731 ServerNotificationType::refreshCreatureVisDebug, nullptr);
1732
1733 const std::string& name = getName();
1734 serverNotification->mPacket << name;
1735 serverNotification->mPacket << true;
1736 if(getIsOnMap())
1737 {
1738 uint32_t nbTiles = mVisibleTiles.size();
1739 serverNotification->mPacket << nbTiles;
1740
1741 for (Tile* tile : mVisibleTiles)
1742 getGameMap()->tileToPacket(serverNotification->mPacket, tile);
1743 }
1744 else
1745 {
1746 uint32_t nbTiles = 0;
1747 serverNotification->mPacket << nbTiles;
1748 }
1749
1750 ODServer::getSingleton().queueServerNotification(serverNotification);
1751 }
1752
refreshVisualDebugEntities(const std::vector<Tile * > & tiles)1753 void Creature::refreshVisualDebugEntities(const std::vector<Tile*>& tiles)
1754 {
1755 if(getIsOnServerMap())
1756 return;
1757
1758 mHasVisualDebuggingEntities = true;
1759
1760 for (Tile* tile : tiles)
1761 {
1762 // We check if the visual debug is already on this tile
1763 if(std::find(mVisualDebugEntityTiles.begin(), mVisualDebugEntityTiles.end(), tile) != mVisualDebugEntityTiles.end())
1764 continue;
1765
1766 RenderManager::getSingleton().rrCreateCreatureVisualDebug(this, tile);
1767
1768 mVisualDebugEntityTiles.push_back(tile);
1769 }
1770
1771 // now, we check if visual debug should be removed from a tile
1772 for (std::vector<Tile*>::iterator it = mVisualDebugEntityTiles.begin(); it != mVisualDebugEntityTiles.end();)
1773 {
1774 Tile* tile = *it;
1775 if(std::find(tiles.begin(), tiles.end(), tile) != tiles.end())
1776 {
1777 ++it;
1778 continue;
1779 }
1780
1781 it = mVisualDebugEntityTiles.erase(it);
1782
1783 RenderManager::getSingleton().rrDestroyCreatureVisualDebug(this, tile);
1784 }
1785 }
1786
stopComputeVisualDebugEntities()1787 void Creature::stopComputeVisualDebugEntities()
1788 {
1789 if(!getIsOnServerMap())
1790 return;
1791
1792 mHasVisualDebuggingEntities = false;
1793
1794 ServerNotification *serverNotification = new ServerNotification(
1795 ServerNotificationType::refreshCreatureVisDebug, nullptr);
1796 const std::string& name = getName();
1797 serverNotification->mPacket << name;
1798 serverNotification->mPacket << false;
1799 ODServer::getSingleton().queueServerNotification(serverNotification);
1800 }
1801
destroyVisualDebugEntities()1802 void Creature::destroyVisualDebugEntities()
1803 {
1804 if(getIsOnServerMap())
1805 return;
1806
1807 mHasVisualDebuggingEntities = false;
1808
1809 for (Tile* tile : mVisualDebugEntityTiles)
1810 {
1811 if (tile == nullptr)
1812 continue;
1813
1814 RenderManager::getSingleton().rrDestroyCreatureVisualDebug(this, tile);
1815 }
1816 mVisualDebugEntityTiles.clear();
1817 }
1818
getCoveredTiles()1819 std::vector<Tile*> Creature::getCoveredTiles()
1820 {
1821 std::vector<Tile*> tempVector;
1822 tempVector.push_back(getPositionTile());
1823 return tempVector;
1824 }
1825
getCoveredTile(int index)1826 Tile* Creature::getCoveredTile(int index)
1827 {
1828 if(index > 0)
1829 {
1830 OD_LOG_ERR("name=" + getName() + ", index=" + Helper::toString(index));
1831 return nullptr;
1832 }
1833
1834 return getPositionTile();
1835 }
1836
numCoveredTiles() const1837 uint32_t Creature::numCoveredTiles() const
1838 {
1839 if(getPositionTile() == nullptr)
1840 return 0;
1841
1842 return 1;
1843 }
1844
CloseStatsWindow(const CEGUI::EventArgs &)1845 bool Creature::CloseStatsWindow(const CEGUI::EventArgs& /*e*/)
1846 {
1847 destroyStatsWindow();
1848 return true;
1849 }
1850
createStatsWindow()1851 void Creature::createStatsWindow()
1852 {
1853 if (mStatsWindow != nullptr)
1854 return;
1855
1856 ClientNotification *clientNotification = new ClientNotification(
1857 ClientNotificationType::askCreatureInfos);
1858 std::string name = getName();
1859 clientNotification->mPacket << name << true;
1860 ODClient::getSingleton().queueClientNotification(clientNotification);
1861
1862 CEGUI::WindowManager* wmgr = CEGUI::WindowManager::getSingletonPtr();
1863 CEGUI::Window* rootWindow = CEGUI::System::getSingleton().getDefaultGUIContext().getRootWindow();
1864
1865 mStatsWindow = wmgr->createWindow("OD/FrameWindow", std::string("CreatureStatsWindows_") + getName());
1866 mStatsWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.3, 0), CEGUI::UDim(0.3, 0)));
1867 mStatsWindow->setSize(CEGUI::USize(CEGUI::UDim(0, 380), CEGUI::UDim(0, 400)));
1868
1869 CEGUI::Window* textWindow = wmgr->createWindow("OD/StaticText", "TextDisplay");
1870 textWindow->setPosition(CEGUI::UVector2(CEGUI::UDim(0.05, 0), CEGUI::UDim(0.1, 0)));
1871 textWindow->setSize(CEGUI::USize(CEGUI::UDim(0.9, 0), CEGUI::UDim(0.85, 0)));
1872 textWindow->setProperty("FrameEnabled", "False");
1873 textWindow->setProperty("BackgroundEnabled", "False");
1874
1875 // We want to close the window when the cross is clicked
1876 mStatsWindow->subscribeEvent(CEGUI::FrameWindow::EventCloseClicked,
1877 CEGUI::Event::Subscriber(&Creature::CloseStatsWindow, this));
1878
1879 // Set the window title
1880 mStatsWindow->setText(getName() + " (" + getDefinition()->getClassName() + ")");
1881
1882 mStatsWindow->addChild(textWindow);
1883 rootWindow->addChild(mStatsWindow);
1884 mStatsWindow->show();
1885
1886 updateStatsWindow("Loading...");
1887 }
1888
destroyStatsWindow()1889 void Creature::destroyStatsWindow()
1890 {
1891 if (mStatsWindow != nullptr)
1892 {
1893 ClientNotification *clientNotification = new ClientNotification(
1894 ClientNotificationType::askCreatureInfos);
1895 std::string name = getName();
1896 clientNotification->mPacket << name << false;
1897 ODClient::getSingleton().queueClientNotification(clientNotification);
1898
1899 mStatsWindow->destroy();
1900 mStatsWindow = nullptr;
1901 }
1902 }
1903
updateStatsWindow(const std::string & txt)1904 void Creature::updateStatsWindow(const std::string& txt)
1905 {
1906 if (mStatsWindow == nullptr)
1907 return;
1908
1909 CEGUI::Window* textWindow = mStatsWindow->getChild("TextDisplay");
1910 textWindow->setText(txt);
1911 }
1912
getStatsText()1913 std::string Creature::getStatsText()
1914 {
1915 // The creatures are not refreshed at each turn so this information is relevant in the server
1916 // GameMap only
1917 const std::string formatTitleOn = "[font='MedievalSharp-12'][colour='CCBBBBFF']";
1918 const std::string formatTitleOff = "[font='MedievalSharp-10'][colour='FFFFFFFF']";
1919
1920 std::stringstream tempSS;
1921 tempSS << formatTitleOn << "Characteristics" << formatTitleOff << std::endl;
1922 tempSS << "Level: " << getLevel() << std::endl;
1923 tempSS << "Experience: " << mExp << std::endl;
1924 tempSS << "HP: " << getHP() << " / " << mMaxHP << std::endl;
1925 tempSS << "Gold: " << mGoldCarried << std::endl;
1926 if (!getDefinition()->isWorker())
1927 {
1928 tempSS << "Wakefulness: " << mWakefulness << std::endl;
1929 tempSS << "Hunger: " << mHunger << std::endl;
1930 }
1931 tempSS << "Move speed (G/W/L): " << getMoveSpeedGround() << " / "
1932 << getMoveSpeedWater() << " / " << getMoveSpeedLava() << std::endl;
1933 tempSS << "Weapons:" << std::endl;
1934 if(mWeaponL == nullptr)
1935 tempSS << " - Left: none" << std::endl;
1936 else
1937 tempSS << " - Left: " << mWeaponL->getName() << " | Damage (P/M/E): " << mWeaponL->getPhysicalDamage()
1938 << " / " << mWeaponL->getMagicalDamage() << " / " << mWeaponL->getElementDamage() << std::endl;
1939 if(mWeaponR == nullptr)
1940 tempSS << " - Right: none" << std::endl;
1941 else
1942 tempSS << " - Right: " << mWeaponR->getName() << " | Damage (P/M/E): " << mWeaponR->getPhysicalDamage()
1943 << " / " << mWeaponR->getMagicalDamage() << " / " << mWeaponR->getElementDamage() << std::endl;
1944 tempSS << "Defense (P/M/E): " << getPhysicalDefense() << " / " << getMagicalDefense() << " / " << getElementDefense() << std::endl;
1945 if (getDefinition()->isWorker())
1946 {
1947 tempSS << "Dig rate: " << getDigRate() << std::endl;
1948 tempSS << "Dance rate: " << mClaimRate << std::endl;
1949 }
1950
1951 tempSS << formatTitleOn << "\nDebugging information" << formatTitleOff << std::endl;
1952 tempSS << "Seat and team IDs: " << getSeat()->getId() << " / " << getSeat()->getTeamId() << std::endl;
1953 tempSS << "Position: " << Helper::toString(getPosition()) << std::endl;
1954 tempSS << "Actions:";
1955 for(const std::unique_ptr<CreatureAction>& ca : mActions)
1956 {
1957 tempSS << " " << CreatureAction::toString(ca.get()->getType());
1958 }
1959 tempSS << std::endl;
1960 tempSS << "Destinations:";
1961 for(const Ogre::Vector3& dest : mWalkQueue)
1962 {
1963 tempSS << " " << Helper::toStringWithoutZ(dest);
1964 }
1965 tempSS << std::endl;
1966 tempSS << "Mood: " << CreatureMood::toString(mMoodValue) << std::endl;
1967 tempSS << "Mood points: " << Helper::toString(mMoodPoints) << std::endl;
1968 return tempSS.str();
1969 }
1970
takeDamage(GameEntity * attacker,double absoluteDamage,double physicalDamage,double magicalDamage,double elementDamage,Tile * tileTakingDamage,bool ko)1971 double Creature::takeDamage(GameEntity* attacker, double absoluteDamage, double physicalDamage, double magicalDamage, double elementDamage,
1972 Tile *tileTakingDamage, bool ko)
1973 {
1974 mNbTurnsWithoutBattle = 0;
1975 physicalDamage = std::max(physicalDamage - getPhysicalDefense(), 0.0);
1976 magicalDamage = std::max(magicalDamage - getMagicalDefense(), 0.0);
1977 elementDamage = std::max(elementDamage - getElementDefense(), 0.0);
1978 double damageDone = std::min(mHp, absoluteDamage + physicalDamage + magicalDamage + elementDamage);
1979 mHp -= damageDone;
1980 if(mHp <= 0)
1981 {
1982 // If the attacking entity is a creature and its seat is configured to KO creatures
1983 // instead of killing, we KO
1984 if(ko)
1985 {
1986 mHp = 1.0;
1987 mKoTurnCounter = -ConfigManager::getSingleton().getNbTurnsKoCreatureAttacked();
1988 OD_LOG_INF("creature=" + getName() + " has been KO by " + attacker->getName());
1989 dropCarriedEquipment();
1990 }
1991 }
1992
1993 computeCreatureOverlayHealthValue();
1994 computeCreatureOverlayMoodValue();
1995
1996 if(!isAlive())
1997 fireEntityDead();
1998
1999 if(!getIsOnServerMap())
2000 return damageDone;
2001
2002 Player* player = getGameMap()->getPlayerBySeat(getSeat());
2003 if (player == nullptr)
2004 return damageDone;
2005
2006 // If we are a worker attacked by a worker, we fight. Otherwise, we flee (if it is a fighter, a trap,
2007 // or whatever)
2008 if(!getDefinition()->isWorker())
2009 return damageDone;
2010
2011 bool shouldFlee = true;
2012 if((attacker != nullptr) &&
2013 (attacker->getObjectType() == GameEntityType::creature))
2014 {
2015 Creature* creatureAttacking = static_cast<Creature*>(attacker);
2016 if(creatureAttacking->getDefinition()->isWorker())
2017 {
2018 // We do not flee because of this attack
2019 shouldFlee = false;
2020 }
2021 }
2022
2023 if(shouldFlee)
2024 {
2025 flee();
2026 return damageDone;
2027 }
2028 return damageDone;
2029 }
2030
receiveExp(double experience)2031 void Creature::receiveExp(double experience)
2032 {
2033 if (experience < 0)
2034 return;
2035
2036 mExp += experience;
2037 }
2038
useAttack(CreatureSkillData & skillData,GameEntity & entityAttack,Tile & tileAttack,bool ko,bool notifyPlayerIfHit)2039 void Creature::useAttack(CreatureSkillData& skillData, GameEntity& entityAttack,
2040 Tile& tileAttack, bool ko, bool notifyPlayerIfHit)
2041 {
2042 // Turn to face the entity we are attacking and set the animation state to Attack.
2043 const Ogre::Vector3& pos = getPosition();
2044 Ogre::Vector3 walkDirection(tileAttack.getX() - pos.x, tileAttack.getY() - pos.y, 0);
2045 walkDirection.normalise();
2046 setAnimationState(EntityAnimation::attack_anim, false, walkDirection, true);
2047 fireCreatureSound(CreatureSound::Attack);
2048 setNbTurnsWithoutBattle(0);
2049
2050 // Calculate how much damage we do.
2051 Tile* myTile = getPositionTile();
2052 float range = Pathfinding::distanceTile(*myTile, tileAttack);
2053
2054 // We use the skill
2055 skillData.mSkill->tryUseFight(*getGameMap(), this, range,
2056 &entityAttack, &tileAttack, ko, notifyPlayerIfHit);
2057 skillData.mWarmup = skillData.mSkill->getWarmupNbTurns();
2058 skillData.mCooldown = skillData.mSkill->getCooldownNbTurns();
2059
2060 // Fighting is tiring
2061 decreaseWakefulness(0.5);
2062 // but gives experience
2063 receiveExp(1.5);
2064 }
2065
isActionInList(CreatureActionType action) const2066 bool Creature::isActionInList(CreatureActionType action) const
2067 {
2068 for (const std::unique_ptr<CreatureAction>& ca : mActions)
2069 {
2070 if (ca.get()->getType() == action)
2071 return true;
2072 }
2073 return false;
2074 }
2075
clearActionQueue()2076 void Creature::clearActionQueue()
2077 {
2078 mActions.clear();
2079 }
2080
hasActionBeenTried(CreatureActionType actionType) const2081 bool Creature::hasActionBeenTried(CreatureActionType actionType) const
2082 {
2083 if(std::find(mActionTry.begin(), mActionTry.end(), actionType) == mActionTry.end())
2084 return false;
2085
2086 return true;
2087 }
2088
pushAction(std::unique_ptr<CreatureAction> && action)2089 void Creature::pushAction(std::unique_ptr<CreatureAction>&& action)
2090 {
2091 CreatureActionType actionType = action.get()->getType();
2092 if(std::find(mActionTry.begin(), mActionTry.end(), actionType) == mActionTry.end())
2093 {
2094 mActionTry.push_back(actionType);
2095 }
2096
2097 mActions.emplace_back(std::move(action));
2098 }
2099
popAction()2100 void Creature::popAction()
2101 {
2102 if(mActions.empty())
2103 {
2104 OD_LOG_ERR("name=" + getName() + ", trying to pop empty action list");
2105 return;
2106 }
2107
2108 mActions.pop_back();
2109 }
2110
tryPickup(Seat * seat)2111 bool Creature::tryPickup(Seat* seat)
2112 {
2113 if(!getIsOnMap())
2114 return false;
2115
2116 // Cannot pick up dead creatures
2117 if (!getGameMap()->isInEditorMode() && !isAlive())
2118 return false;
2119
2120 if(!getGameMap()->isInEditorMode() && (mSeatPrison == nullptr) && !getSeat()->canOwnedCreatureBePickedUpBy(seat))
2121 return false;
2122
2123 if(!getGameMap()->isInEditorMode() && (mSeatPrison != nullptr) && !mSeatPrison->canOwnedCreatureBePickedUpBy(seat))
2124 return false;
2125
2126 // KO creatures cannot be picked up
2127 if(isKo())
2128 return false;
2129
2130 return true;
2131 }
2132
pickup()2133 void Creature::pickup()
2134 {
2135 // Stop the creature walking and set it off the map to prevent the AI from running on it.
2136 removeEntityFromPositionTile();
2137 clearDestinations(EntityAnimation::idle_anim, true, true);
2138 clearActionQueue();
2139
2140 if(!getIsOnServerMap())
2141 return;
2142
2143 if(getHasVisualDebuggingEntities())
2144 computeVisualDebugEntities();
2145
2146 fireCreatureSound(CreatureSound::Pickup);
2147 }
2148
canGoThroughTile(Tile * tile) const2149 bool Creature::canGoThroughTile(Tile* tile) const
2150 {
2151 if(tile == nullptr)
2152 return false;
2153
2154 return getMoveSpeed(tile) > 0.0;
2155 }
2156
tryDrop(Seat * seat,Tile * tile)2157 bool Creature::tryDrop(Seat* seat, Tile* tile)
2158 {
2159 // check whether the tile is a ground tile ...
2160 if(tile->isFullTile())
2161 return false;
2162
2163 // In editor mode, we allow creatures to be dropped anywhere they can walk
2164 if(getGameMap()->isInEditorMode() && canGoThroughTile(tile))
2165 return true;
2166
2167 // we cannot drop a creature on a tile we don't see
2168 if(!seat->hasVisionOnTile(tile))
2169 return false;
2170
2171 // If it is a worker, he can be dropped on dirt
2172 if (getDefinition()->isWorker() && (tile->getTileVisual() == TileVisual::dirtGround || tile->getTileVisual() == TileVisual::goldGround))
2173 return true;
2174
2175 // Every creature can be dropped on allied claimed tiles
2176 if(tile->isClaimedForSeat(seat))
2177 return true;
2178
2179 return false;
2180 }
2181
drop(const Ogre::Vector3 & v)2182 void Creature::drop(const Ogre::Vector3& v)
2183 {
2184 setPosition(v);
2185 if(!getIsOnServerMap())
2186 {
2187 mDropCooldown = 2;
2188 return;
2189 }
2190
2191 if(getHasVisualDebuggingEntities())
2192 computeVisualDebugEntities();
2193
2194 fireCreatureSound(CreatureSound::Drop);
2195
2196 // The creature is temporary KO
2197 mKoTurnCounter = mDefinition->getTurnsStunDropped();
2198 computeCreatureOverlayMoodValue();
2199
2200 // Action queue should be empty but it shouldn't hurt
2201 clearActionQueue();
2202
2203 // In editor mode, we do not check for forced actions
2204 if(getGameMap()->isInEditorMode())
2205 return;
2206
2207 if(mDefinition->isWorker())
2208 {
2209 // If a worker is dropped, he will search in the tile he is and in the 4 neighboor tiles.
2210 // 1 - If the tile he is in a treasury and he is carrying gold, he should deposit it
2211 // 2 - if one of the 4 neighboor tiles is marked, he will dig
2212 // 3 - if there is a carryable entity where it is dropped, it should try to carry it
2213 // 4 - if the the tile he is in is not claimed and one of the neigbboor tiles is claimed, he will claim
2214 // 5 - if the the tile he is in is claimed and one of the neigbboor tiles is not claimed, he will claim
2215 // 6 - If the tile he is in is claimed and one of the neigbboor tiles is a not claimed wall, he will claim
2216 Tile* position = getPositionTile();
2217 Seat* seat = getSeat();
2218 Tile* tileMarkedDig = nullptr;
2219 Tile* tileMarkedDigPos = nullptr;
2220 Tile* tileToClaim = nullptr;
2221 Tile* tileWallNotClaimed = nullptr;
2222 for (Tile* tile : position->getAllNeighbors())
2223 {
2224 if(tileMarkedDig == nullptr &&
2225 tile->getMarkedForDigging(getGameMap()->getPlayerBySeat(seat))
2226 )
2227 {
2228 // Check if there is room for digging
2229 std::vector<Tile*> tiles;
2230 tile->canWorkerDig(*this, tiles);
2231 // We search for the closest neighbor tile (may be not the position
2232 // tile if the player drops several workers at the same tile)
2233 float distBest = -1;
2234 for (Tile* neigh : tiles)
2235 {
2236 float dist = Pathfinding::squaredDistanceTile(*position, *neigh);
2237 if((distBest != -1) && (distBest <= dist))
2238 continue;
2239
2240 distBest = dist;
2241 tileMarkedDig = tile;
2242 tileMarkedDigPos = neigh;
2243 }
2244 }
2245 else if(tileToClaim == nullptr &&
2246 tile->isClaimedForSeat(seat) &&
2247 position->isGroundClaimable(seat)
2248 )
2249 {
2250 tileToClaim = position;
2251 }
2252 else if(tileToClaim == nullptr &&
2253 position->isClaimedForSeat(seat) &&
2254 tile->isGroundClaimable(seat)
2255 )
2256 {
2257 tileToClaim = tile;
2258 }
2259 else if(tileWallNotClaimed == nullptr &&
2260 position->isClaimedForSeat(seat) &&
2261 tile->isWallClaimable(seat)
2262 )
2263 {
2264 tileWallNotClaimed = tile;
2265 }
2266 }
2267
2268 // We try to deposit gold if we are on a room while carrying gold
2269 if((mGoldCarried > 0) && (mDigRate > 0.0) &&
2270 (position->getCoveringRoom() != nullptr))
2271 {
2272 int deposited = position->getCoveringRoom()->depositGold(mGoldCarried, position);
2273 if(deposited > 0)
2274 {
2275 mGoldCarried -= deposited;
2276 return;
2277 }
2278 }
2279
2280 std::vector<GameEntity*> carryable;
2281 position->fillWithCarryableEntities(this, carryable);
2282
2283 // Now, we can decide
2284 if((tileMarkedDig != nullptr) && (tileMarkedDigPos != nullptr) && (mDigRate > 0.0))
2285 {
2286 pushAction(Utils::make_unique<CreatureActionSearchTileToDig>(*this, true));
2287 pushAction(Utils::make_unique<CreatureActionDigTile>(*this, *tileMarkedDig, *tileMarkedDigPos));
2288 return;
2289 }
2290
2291 if(!carryable.empty())
2292 {
2293 // We look for the most important entity to carry
2294 GameEntity* entityToCarry = carryable[0];
2295 for(GameEntity* entity : carryable)
2296 {
2297 if(entity->getEntityCarryType(this) <= entityToCarry->getEntityCarryType(this))
2298 continue;
2299
2300 entityToCarry = entity;
2301 }
2302
2303 pushAction(Utils::make_unique<CreatureActionGrabEntity>(*this, *entityToCarry));
2304 return;
2305 }
2306
2307 if((tileToClaim != nullptr) && (mClaimRate > 0.0))
2308 {
2309 pushAction(Utils::make_unique<CreatureActionSearchGroundTileToClaim>(*this, true));
2310 pushAction(Utils::make_unique<CreatureActionClaimGroundTile>(*this, *tileToClaim));
2311 return;
2312 }
2313
2314 if((tileWallNotClaimed != nullptr) && (mClaimRate > 0.0))
2315 {
2316 pushAction(Utils::make_unique<CreatureActionSearchWallTileToClaim>(*this, true));
2317 pushAction(Utils::make_unique<CreatureActionClaimWallTile>(*this, *tileWallNotClaimed));
2318 return;
2319 }
2320
2321 // We couldn't find why we were dropped here. Let's behave as usual
2322 return;
2323 }
2324
2325 // Fighters
2326 // If we are dropped on a tile with a building, we notify it so that it
2327 // can do some special actions
2328 Tile* tile = getPositionTile();
2329 if((tile != nullptr) &&
2330 (tile->getCoveringBuilding() != nullptr))
2331 {
2332 Building* building = tile->getCoveringBuilding();
2333 building->creatureDropped(*this);
2334 return;
2335 }
2336 }
2337
resizeMeshAfterDrop()2338 bool Creature::resizeMeshAfterDrop()
2339 {
2340 RenderManager::getSingleton().rrScaleCreature(*this);
2341 return false;
2342 }
2343
setDestination(Tile * tile)2344 bool Creature::setDestination(Tile* tile)
2345 {
2346 if(tile == nullptr)
2347 return false;
2348
2349 Tile *posTile = getPositionTile();
2350 if(posTile == nullptr)
2351 return false;
2352
2353 std::list<Tile*> result = getGameMap()->path(this, tile);
2354
2355 std::vector<Ogre::Vector3> path;
2356 tileToVector3(result, path, true, 0.0);
2357 setWalkPath(EntityAnimation::walk_anim, EntityAnimation::idle_anim, true, true, path);
2358 pushAction(Utils::make_unique<CreatureActionWalkToTile>(*this));
2359 return true;
2360 }
2361
wanderRandomly(const std::string & animationState)2362 bool Creature::wanderRandomly(const std::string& animationState)
2363 {
2364 // We pick randomly a visible tile far away (at the end of visible tiles)
2365 if(mTilesWithinSightRadius.empty())
2366 return false;
2367
2368 // Add reachable tiles only before searching for one of them
2369 std::vector<Tile*> reachableTiles;
2370 for (Tile* tile: mTilesWithinSightRadius)
2371 {
2372 if (getGameMap()->pathExists(this, getPositionTile(), tile))
2373 reachableTiles.push_back(tile);
2374 }
2375
2376 if (reachableTiles.empty())
2377 return false;
2378
2379 Tile* tileDestination = reachableTiles[Random::Uint(0, reachableTiles.size() - 1)];
2380 setDestination(tileDestination);
2381 return false;
2382 }
2383
isAttackable(Tile * tile,Seat * seat) const2384 bool Creature::isAttackable(Tile* tile, Seat* seat) const
2385 {
2386 if(mHp <= 0.0)
2387 return false;
2388
2389 // KO Creature to death creatures are not a threat and cannot be attacked. However, temporary KO can be
2390 if(mKoTurnCounter < 0)
2391 return false;
2392
2393 // Creatures in prison are not a treat and cannot be attacked
2394 if(mSeatPrison != nullptr)
2395 return false;
2396
2397 return true;
2398 }
2399
getEntityCarryType(Creature * carrier)2400 EntityCarryType Creature::getEntityCarryType(Creature* carrier)
2401 {
2402 // Workers cannot be carried to crypt/prison
2403 if(getDefinition()->isWorker())
2404 return EntityCarryType::notCarryable;
2405
2406 // KO to death entities can be carried
2407 if(mKoTurnCounter < 0)
2408 return EntityCarryType::koCreature;
2409
2410 // Dead creatures are carryable
2411 if(getHP() <= 0.0)
2412 return EntityCarryType::corpse;
2413
2414 return EntityCarryType::notCarryable;
2415 }
2416
notifyEntityCarryOn(Creature * carrier)2417 void Creature::notifyEntityCarryOn(Creature* carrier)
2418 {
2419 removeEntityFromPositionTile();
2420 }
2421
notifyEntityCarryOff(const Ogre::Vector3 & position)2422 void Creature::notifyEntityCarryOff(const Ogre::Vector3& position)
2423 {
2424 mPosition = position;
2425 addEntityToPositionTile();
2426 }
2427
carryEntity(GameEntity * carriedEntity)2428 void Creature::carryEntity(GameEntity* carriedEntity)
2429 {
2430 if(!getIsOnServerMap())
2431 return;
2432
2433 OD_ASSERT_TRUE(carriedEntity != nullptr);
2434 OD_ASSERT_TRUE(mCarriedEntity == nullptr);
2435 mCarriedEntity = nullptr;
2436 if(carriedEntity == nullptr)
2437 return;
2438
2439 // We remove the carried entity from the clients gamemaps as well as the carrier
2440 // and we send the carrier creation message (that will embed the carried)
2441 carriedEntity->fireRemoveEntityToSeatsWithVision();
2442 // We only notify seats that already had vision. We copy the seats with vision list
2443 // because fireRemoveEntityToSeatsWithVision will empty it.
2444 std::vector<Seat*> seatsWithVision = mSeatsWithVisionNotified;
2445 // We remove ourself and send the creation
2446 fireRemoveEntityToSeatsWithVision();
2447 mCarriedEntity = carriedEntity;
2448 notifySeatsWithVision(seatsWithVision);
2449 }
2450
releaseCarriedEntity()2451 void Creature::releaseCarriedEntity()
2452 {
2453 if(!getIsOnServerMap())
2454 return;
2455
2456 GameEntity* carriedEntity = mCarriedEntity;
2457 mCarriedEntity = nullptr;
2458 if(carriedEntity == nullptr)
2459 {
2460 OD_LOG_ERR("name=" + getName());
2461 return;
2462 }
2463
2464 for(Seat* seat : mSeatsWithVisionNotified)
2465 {
2466 if(seat->getPlayer() == nullptr)
2467 continue;
2468 if(!seat->getPlayer()->getIsHuman())
2469 continue;
2470
2471 ServerNotification* serverNotification = new ServerNotification(
2472 ServerNotificationType::releaseCarriedEntity, seat->getPlayer());
2473 serverNotification->mPacket << getName() << carriedEntity->getObjectType();
2474 serverNotification->mPacket << carriedEntity->getName();
2475 serverNotification->mPacket << mPosition;
2476 ODServer::getSingleton().queueServerNotification(serverNotification);
2477 }
2478 }
2479
canSlap(Seat * seat)2480 bool Creature::canSlap(Seat* seat)
2481 {
2482 Tile* tile = getPositionTile();
2483 if(tile == nullptr)
2484 {
2485 OD_LOG_ERR("entityName=" + getName());
2486 return false;
2487 }
2488
2489 if(mDropCooldown > 0)
2490 return false;
2491
2492 if(getGameMap()->isInEditorMode())
2493 return true;
2494
2495 if(getHP() <= 0.0)
2496 return false;
2497
2498 // If the creature is in prison, it can be slapped by the jail owner only
2499 if(mSeatPrison != nullptr)
2500 return (mSeatPrison == seat);
2501
2502 // Only the owning player can slap a creature
2503 if(getSeat() != seat)
2504 return false;
2505
2506 return true;
2507 }
2508
slap()2509 void Creature::slap()
2510 {
2511 if(!getIsOnServerMap())
2512 return;
2513
2514 fireCreatureSound(CreatureSound::Slap);
2515
2516 // In editor mode, we remove the creature
2517 if(getGameMap()->isInEditorMode())
2518 {
2519 removeFromGameMap();
2520 deleteYourself();
2521 return;
2522 }
2523
2524 CreatureEffectSlap* effect = new CreatureEffectSlap(
2525 ConfigManager::getSingleton().getSlapEffectDuration(), "");
2526 addCreatureEffect(effect);
2527 mHp -= mMaxHP * ConfigManager::getSingleton().getSlapDamagePercent() / 100.0;
2528 computeCreatureOverlayHealthValue();
2529 }
2530
fireAddEntity(Seat * seat,bool async)2531 void Creature::fireAddEntity(Seat* seat, bool async)
2532 {
2533 if(async)
2534 {
2535 ServerNotification serverNotification(
2536 ServerNotificationType::addEntity, seat->getPlayer());
2537 exportHeadersToPacket(serverNotification.mPacket);
2538 exportToPacket(serverNotification.mPacket, seat);
2539 ODServer::getSingleton().sendAsyncMsg(serverNotification);
2540
2541 if(mCarriedEntity != nullptr)
2542 {
2543 OD_LOG_ERR("Trying to fire add creature in async mode name=" + getName() + " while carrying " + mCarriedEntity->getName());
2544 }
2545 return;
2546 }
2547
2548 ServerNotification* serverNotification = new ServerNotification(
2549 ServerNotificationType::addEntity, seat->getPlayer());
2550 exportHeadersToPacket(serverNotification->mPacket);
2551 exportToPacket(serverNotification->mPacket, seat);
2552 ODServer::getSingleton().queueServerNotification(serverNotification);
2553
2554 if(mCarriedEntity != nullptr)
2555 {
2556 mCarriedEntity->addSeatWithVision(seat, false);
2557
2558 serverNotification = new ServerNotification(
2559 ServerNotificationType::carryEntity, seat->getPlayer());
2560 serverNotification->mPacket << getName() << mCarriedEntity->getObjectType();
2561 serverNotification->mPacket << mCarriedEntity->getName();
2562 ODServer::getSingleton().queueServerNotification(serverNotification);
2563 }
2564 }
2565
fireRemoveEntity(Seat * seat)2566 void Creature::fireRemoveEntity(Seat* seat)
2567 {
2568 // If we are carrying an entity, we release it first, then we can remove it and us
2569 if(mCarriedEntity != nullptr)
2570 {
2571 ServerNotification* serverNotification = new ServerNotification(
2572 ServerNotificationType::releaseCarriedEntity, seat->getPlayer());
2573 serverNotification->mPacket << getName() << mCarriedEntity->getObjectType();
2574 serverNotification->mPacket << mCarriedEntity->getName();
2575 serverNotification->mPacket << mPosition;
2576 ODServer::getSingleton().queueServerNotification(serverNotification);
2577
2578 mCarriedEntity->removeSeatWithVision(seat);
2579 }
2580
2581 const std::string& name = getName();
2582 ServerNotification *serverNotification = new ServerNotification(
2583 ServerNotificationType::removeEntity, seat->getPlayer());
2584 GameEntityType type = getObjectType();
2585 serverNotification->mPacket << type;
2586 serverNotification->mPacket << name;
2587 ODServer::getSingleton().queueServerNotification(serverNotification);
2588 }
2589
fireCreatureRefreshIfNeeded()2590 void Creature::fireCreatureRefreshIfNeeded()
2591 {
2592 if(!mNeedFireRefresh)
2593 return;
2594
2595 mNeedFireRefresh = false;
2596 for(Seat* seat : mSeatsWithVisionNotified)
2597 {
2598 if(seat->getPlayer() == nullptr)
2599 continue;
2600 if(!seat->getPlayer()->getIsHuman())
2601 continue;
2602
2603 const std::string& name = getName();
2604 ServerNotification *serverNotification = new ServerNotification(
2605 ServerNotificationType::entitiesRefresh, seat->getPlayer());
2606 uint32_t nbCreature = 1;
2607 serverNotification->mPacket << nbCreature;
2608 serverNotification->mPacket << GameEntityType::creature;
2609 serverNotification->mPacket << name;
2610 exportToPacketForUpdate(serverNotification->mPacket, seat);
2611 ODServer::getSingleton().queueServerNotification(serverNotification);
2612 }
2613 }
2614
fireChatMsgTookFee(int goldTaken)2615 void Creature::fireChatMsgTookFee(int goldTaken)
2616 {
2617 if(getSeat()->getPlayer() == nullptr)
2618 return;
2619 if(!getSeat()->getPlayer()->getIsHuman())
2620 return;
2621 if(getSeat()->getPlayer()->getHasLost())
2622 return;
2623
2624 ServerNotification *serverNotification = new ServerNotification(
2625 ServerNotificationType::chatServer, getSeat()->getPlayer());
2626 std::string msg;
2627 // We don't display the same message if we have taken all our fee or only a part of it
2628 if(getGoldFee() <= 0)
2629 msg = getName() + " took its fee: " + Helper::toString(goldTaken);
2630 else
2631 msg = getName() + " took " + Helper::toString(goldTaken) + " from its fee";
2632
2633 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
2634 ODServer::getSingleton().queueServerNotification(serverNotification);
2635 }
2636
fireChatMsgLeftDungeon()2637 void Creature::fireChatMsgLeftDungeon()
2638 {
2639 if(getSeat()->getPlayer() == nullptr)
2640 return;
2641 if(!getSeat()->getPlayer()->getIsHuman())
2642 return;
2643 if(getSeat()->getPlayer()->getHasLost())
2644 return;
2645
2646 ServerNotification *serverNotification = new ServerNotification(
2647 ServerNotificationType::chatServer, getSeat()->getPlayer());
2648 std::string msg = getName() + " left your dungeon";
2649 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
2650 ODServer::getSingleton().queueServerNotification(serverNotification);
2651 }
2652
fireChatMsgLeavingDungeon()2653 void Creature::fireChatMsgLeavingDungeon()
2654 {
2655 if(getSeat()->getPlayer() == nullptr)
2656 return;
2657 if(!getSeat()->getPlayer()->getIsHuman())
2658 return;
2659 if(getSeat()->getPlayer()->getHasLost())
2660 return;
2661
2662 ServerNotification *serverNotification = new ServerNotification(
2663 ServerNotificationType::chatServer, getSeat()->getPlayer());
2664 std::string msg = getName() + " is leaving your dungeon";
2665 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
2666 ODServer::getSingleton().queueServerNotification(serverNotification);
2667 }
2668
fireChatMsgBecameRogue()2669 void Creature::fireChatMsgBecameRogue()
2670 {
2671 if(getSeat()->getPlayer() == nullptr)
2672 return;
2673 if(!getSeat()->getPlayer()->getIsHuman())
2674 return;
2675 if(getSeat()->getPlayer()->getHasLost())
2676 return;
2677
2678 ServerNotification *serverNotification = new ServerNotification(
2679 ServerNotificationType::chatServer, getSeat()->getPlayer());
2680 std::string msg = getName() + " is not under your control anymore !";
2681 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
2682 ODServer::getSingleton().queueServerNotification(serverNotification);
2683 }
2684
fireChatMsgUnhappy()2685 void Creature::fireChatMsgUnhappy()
2686 {
2687 if(getSeat()->getPlayer() == nullptr)
2688 return;
2689 if(!getSeat()->getPlayer()->getIsHuman())
2690 return;
2691 if(getSeat()->getPlayer()->getHasLost())
2692 return;
2693
2694 ServerNotification *serverNotification = new ServerNotification(
2695 ServerNotificationType::chatServer, getSeat()->getPlayer());
2696 std::string msg = getName() + " is unhappy !";
2697 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
2698 ODServer::getSingleton().queueServerNotification(serverNotification);
2699 }
2700
fireChatMsgFurious()2701 void Creature::fireChatMsgFurious()
2702 {
2703 if(getSeat()->getPlayer() == nullptr)
2704 return;
2705 if(!getSeat()->getPlayer()->getIsHuman())
2706 return;
2707 if(getSeat()->getPlayer()->getHasLost())
2708 return;
2709
2710 ServerNotification *serverNotification = new ServerNotification(
2711 ServerNotificationType::chatServer, getSeat()->getPlayer());
2712 std::string msg = getName() + " is furious !";
2713 serverNotification->mPacket << msg << EventShortNoticeType::aboutCreatures;
2714 ODServer::getSingleton().queueServerNotification(serverNotification);
2715 }
2716
setupDefinition(GameMap & gameMap,const CreatureDefinition & defaultWorkerCreatureDefinition)2717 void Creature::setupDefinition(GameMap& gameMap, const CreatureDefinition& defaultWorkerCreatureDefinition)
2718 {
2719 bool setHpToStrHp = false;
2720 if(mDefinition == nullptr)
2721 {
2722 // If the classname corresponds to the default worker CreatureDefinition, we use
2723 // the dedicated class. The correct one will be set after the seat is initialized
2724 if(!mDefinitionString.empty() && mDefinitionString.compare(ConfigManager::DefaultWorkerCreatureDefinition) != 0)
2725 {
2726 mDefinition = gameMap.getClassDescription(mDefinitionString);
2727 }
2728 else
2729 {
2730 // If we are in editor mode, we take the default worker class. Otherwise, we take
2731 // the default worker from the seat faction
2732 if(gameMap.isInEditorMode() || !mSeat)
2733 mDefinition = &defaultWorkerCreatureDefinition;
2734 else
2735 mDefinition = getSeat()->getWorkerClassToSpawn();
2736 }
2737
2738 if(mDefinition == nullptr)
2739 {
2740 OD_LOG_ERR("Definition=" + mDefinitionString);
2741 return;
2742 }
2743
2744 if(getIsOnServerMap())
2745 {
2746 setHpToStrHp = true;
2747
2748 // name
2749 if (getName().compare("autoname") == 0)
2750 {
2751 std::string name = getGameMap()->nextUniqueNameCreature(mDefinition->getClassName());
2752 setName(name);
2753 }
2754 }
2755 }
2756
2757 if(getIsOnServerMap())
2758 {
2759 for(const CreatureSkill* skill : mDefinition->getCreatureSkills())
2760 {
2761 CreatureSkillData skillData(skill, skill->getCooldownNbTurns(), 0);
2762 mSkillData.push_back(skillData);
2763 }
2764 }
2765
2766 buildStats();
2767
2768 // Now, the max hp is known. If needed, we set it
2769 if(setHpToStrHp)
2770 {
2771 if(mHpString.compare("max") == 0)
2772 mHp = mMaxHP;
2773 else
2774 mHp = Helper::toDouble(mHpString);
2775
2776 computeCreatureOverlayHealthValue();
2777 }
2778 }
2779
fireCreatureSound(CreatureSound sound)2780 void Creature::fireCreatureSound(CreatureSound sound)
2781 {
2782 Tile* posTile = getPositionTile();
2783 if(posTile == nullptr)
2784 return;
2785
2786 std::string soundFamily;
2787 switch(sound)
2788 {
2789 case CreatureSound::Pickup:
2790 soundFamily = getDefinition()->getSoundFamilyPickup();
2791 break;
2792 case CreatureSound::Drop:
2793 soundFamily = getDefinition()->getSoundFamilyDrop();
2794 break;
2795 case CreatureSound::Attack:
2796 soundFamily = getDefinition()->getSoundFamilyAttack();
2797 break;
2798 case CreatureSound::Die:
2799 soundFamily = getDefinition()->getSoundFamilyDie();
2800 break;
2801 case CreatureSound::Slap:
2802 soundFamily = getDefinition()->getSoundFamilySlap();
2803 break;
2804 case CreatureSound::Dig:
2805 soundFamily = "Default/Dig";
2806 break;
2807 default:
2808 OD_LOG_ERR("Wrong CreatureSound value=" + Helper::toString(static_cast<uint32_t>(sound)));
2809 return;
2810 }
2811
2812 std::string soundComplete = "Creatures/" + soundFamily;
2813 for(Seat* seat : mSeatsWithVisionNotified)
2814 {
2815 if(seat->getPlayer() == nullptr)
2816 continue;
2817 if(!seat->getPlayer()->getIsHuman())
2818 continue;
2819
2820 ServerNotification *serverNotification = new ServerNotification(
2821 ServerNotificationType::playSpatialSound, seat->getPlayer());
2822 serverNotification->mPacket << soundComplete << posTile->getX() << posTile->getY();
2823 ODServer::getSingleton().queueServerNotification(serverNotification);
2824 }
2825 }
2826
itsPayDay()2827 void Creature::itsPayDay()
2828 {
2829 // Rogue creatures do not have to be paid
2830 if(getSeat()->isRogueSeat())
2831 return;
2832
2833 mGoldFee += mDefinition->getFee(getLevel());
2834 }
2835
increaseHunger(double value)2836 void Creature::increaseHunger(double value)
2837 {
2838 if(getSeat()->isRogueSeat())
2839 return;
2840
2841 mHunger = std::min(100.0, mHunger + value);
2842 }
2843
decreaseWakefulness(double value)2844 void Creature::decreaseWakefulness(double value)
2845 {
2846 if(getSeat()->isRogueSeat())
2847 return;
2848
2849 mWakefulness = std::max(0.0, mWakefulness - value);
2850 }
2851
computeMood()2852 void Creature::computeMood()
2853 {
2854 mMoodPoints = CreatureMoodManager::computeCreatureMoodModifiers(*this);
2855
2856 CreatureMoodLevel oldMoodValue = mMoodValue;
2857 mMoodValue = CreatureMoodManager::getCreatureMoodLevel(mMoodPoints);
2858 if(mMoodValue == oldMoodValue)
2859 return;
2860
2861 if((mMoodValue >= CreatureMoodLevel::Furious) &&
2862 (oldMoodValue < CreatureMoodLevel::Furious))
2863 {
2864 // We became unhappy
2865 fireChatMsgFurious();
2866 }
2867 else if((mMoodValue > CreatureMoodLevel::Neutral) &&
2868 (oldMoodValue <= CreatureMoodLevel::Neutral))
2869 {
2870 // We became unhappy
2871 fireChatMsgUnhappy();
2872 }
2873 }
2874
computeCreatureOverlayHealthValue()2875 void Creature::computeCreatureOverlayHealthValue()
2876 {
2877 if(!getIsOnServerMap())
2878 return;
2879
2880 uint32_t value = 0;
2881 double hp = getHP();
2882 // Note that we make a special case for hp = 0 to avoid errors due to roundness
2883 if(hp <= 0)
2884 {
2885 value = NB_OVERLAY_HEALTH_VALUES - 1;
2886 }
2887 else
2888 {
2889 uint32_t nbSteps = NB_OVERLAY_HEALTH_VALUES - 2;
2890 double healthStep = getMaxHp() / static_cast<double>(nbSteps);
2891 double tmpHealth = getMaxHp();
2892 for(value = 0; value < nbSteps; ++value)
2893 {
2894 if(hp >= tmpHealth)
2895 break;
2896
2897 tmpHealth -= healthStep;
2898 }
2899 }
2900
2901 if(mOverlayHealthValue != value)
2902 {
2903 mOverlayHealthValue = value;
2904 mNeedFireRefresh = true;
2905 }
2906 }
2907
computeCreatureOverlayMoodValue()2908 void Creature::computeCreatureOverlayMoodValue()
2909 {
2910 if(!getIsOnServerMap())
2911 return;
2912
2913 uint32_t value = 0;
2914 // The creature mood applies only if the creature is alive
2915 if(isAlive())
2916 {
2917 if(mMoodValue == CreatureMoodLevel::Angry)
2918 value |= CreatureMoodEnum::Angry;
2919 else if(mMoodValue == CreatureMoodLevel::Furious)
2920 value |= CreatureMoodEnum::Furious;
2921
2922 if(isActionInList(CreatureActionType::getFee))
2923 value |= CreatureMoodEnum::GetFee;
2924
2925 if(isActionInList(CreatureActionType::leaveDungeon))
2926 value |= CreatureMoodEnum::LeaveDungeon;
2927
2928 if(mKoTurnCounter < 0)
2929 value |= CreatureMoodEnum::KoDeath;
2930 else if(mKoTurnCounter > 0)
2931 value |= CreatureMoodEnum::KoTemp;
2932
2933 if(isHungry())
2934 value |= CreatureMoodEnum::Hungry;
2935
2936 if(isTired())
2937 value |= CreatureMoodEnum::Tired;
2938
2939 if(mSeatPrison != nullptr)
2940 value |= CreatureMoodEnum::InJail;
2941 }
2942
2943 if(mOverlayMoodValue != value)
2944 {
2945 mOverlayMoodValue = value;
2946 mNeedFireRefresh = true;
2947 }
2948 }
2949
addCreatureEffect(CreatureEffect * effect)2950 void Creature::addCreatureEffect(CreatureEffect* effect)
2951 {
2952 std::string effectName = nextParticleSystemsName();
2953
2954 OD_LOG_INF("Added CreatureEffect name=" + effectName + " on creature=" + getName());
2955
2956 CreatureParticuleEffect* particleEffect = new CreatureParticuleEffect(*this, effectName, effect->getParticleEffectScript(),
2957 effect->getNbTurnsEffect(), effect);
2958 mEntityParticleEffects.push_back(particleEffect);
2959
2960 mNeedFireRefresh = true;
2961 }
2962
isHurt() const2963 bool Creature::isHurt() const
2964 {
2965 //On server side, we test HP
2966 if(getIsOnServerMap())
2967 return getHP() < getMaxHp();
2968
2969 // On client side, we test overlay value. 0 represents full health
2970 return mOverlayHealthValue > 0;
2971 }
2972
isKo() const2973 bool Creature::isKo() const
2974 {
2975 if(getIsOnServerMap())
2976 return mKoTurnCounter != 0;
2977
2978 // On client side, we test mood overlay value
2979 return (mOverlayMoodValue & KoDeathOrTemp) != 0;
2980 }
2981
isKoDeath() const2982 bool Creature::isKoDeath() const
2983 {
2984 if(getIsOnServerMap())
2985 return mKoTurnCounter < 0;
2986
2987 // On client side, we test mood overlay value
2988 return (mOverlayMoodValue & CreatureMoodEnum::KoDeath) != 0;
2989 }
2990
isKoTemp() const2991 bool Creature::isKoTemp() const
2992 {
2993 if(getIsOnServerMap())
2994 return mKoTurnCounter > 0;
2995
2996 // On client side, we test mood overlay value
2997 return (mOverlayMoodValue & CreatureMoodEnum::KoTemp) != 0;
2998 }
2999
isInPrison() const3000 bool Creature::isInPrison() const
3001 {
3002 return mSeatPrison != nullptr;
3003 }
3004
correctEntityMovePosition(Ogre::Vector3 & position)3005 void Creature::correctEntityMovePosition(Ogre::Vector3& position)
3006 {
3007 static const double offset = 0.3;
3008 if(position.x > 0)
3009 position.x += Random::Double(-offset, offset);
3010
3011 if(position.y > 0)
3012 position.y += Random::Double(-offset, offset);
3013
3014 if(position.z > 0)
3015 position.z += Random::Double(-offset, offset);
3016 }
3017
checkWalkPathValid()3018 void Creature::checkWalkPathValid()
3019 {
3020 bool stop = false;
3021 for(const Ogre::Vector3& dest : mWalkQueue)
3022 {
3023 Tile* tile = getGameMap()->getTile(Helper::round(dest.x), Helper::round(dest.y));
3024 if(tile == nullptr)
3025 {
3026 stop = true;
3027 break;
3028 }
3029
3030 if(!canGoThroughTile(tile))
3031 {
3032 stop = true;
3033 break;
3034 }
3035 }
3036
3037 if(!stop)
3038 return;
3039
3040 // There is an unpassable tile in our way. We stop what we are doing
3041 clearDestinations(EntityAnimation::idle_anim, true, true);
3042 }
3043
setJobCooldown(int val)3044 void Creature::setJobCooldown(int val)
3045 {
3046 // If the creature has been slapped, its cooldown is decreased
3047 if(hasSlapEffect())
3048 val = Helper::round(static_cast<float>(val) * 0.8f);
3049
3050 mJobCooldown = val;
3051 }
3052
isTired() const3053 bool Creature::isTired() const
3054 {
3055 if(getIsOnServerMap())
3056 return mWakefulness <= 20.0;
3057
3058 return (mOverlayMoodValue & CreatureMoodEnum::Tired) != 0;
3059 }
3060
isHungry() const3061 bool Creature::isHungry() const
3062 {
3063 if(getIsOnServerMap())
3064 return mHunger >= 80.0;
3065
3066 return (mOverlayMoodValue & CreatureMoodEnum::Hungry) != 0;
3067 }
3068
resetKoTurns()3069 void Creature::resetKoTurns()
3070 {
3071 mKoTurnCounter = 0;
3072 mNeedFireRefresh = true;
3073 }
3074
setInJail(Room * prison)3075 void Creature::setInJail(Room* prison)
3076 {
3077 if(prison == nullptr)
3078 {
3079 if(mSeatPrison == nullptr)
3080 return;
3081
3082 mSeatPrison = nullptr;
3083 mNeedFireRefresh = true;
3084 return;
3085 }
3086
3087 // Creature is set in prison
3088 if(mSeatPrison == prison->getSeat())
3089 return;
3090
3091 mSeatPrison = prison->getSeat();
3092 mNeedFireRefresh = true;
3093 }
3094
isDangerous(const Creature * creature,int distance) const3095 bool Creature::isDangerous(const Creature* creature, int distance) const
3096 {
3097 if(getDefinition()->isWorker())
3098 return false;
3099
3100 return true;
3101 }
3102
clientUpkeep()3103 void Creature::clientUpkeep()
3104 {
3105 MovableGameEntity::clientUpkeep();
3106 if(mDropCooldown > 0)
3107 --mDropCooldown;
3108 }
3109
setMoveSpeedModifier(double modifier)3110 void Creature::setMoveSpeedModifier(double modifier)
3111 {
3112 mSpeedModifier = modifier;
3113
3114 mGroundSpeed = mDefinition->getMoveSpeedGround();
3115 mWaterSpeed = mDefinition->getMoveSpeedWater();
3116 mLavaSpeed = mDefinition->getMoveSpeedLava();
3117
3118 double multiplier = mLevel - 1;
3119 if (multiplier > 0.0)
3120 {
3121 mGroundSpeed += mDefinition->getGroundSpeedPerLevel() * multiplier;
3122 mWaterSpeed += mDefinition->getWaterSpeedPerLevel() * multiplier;
3123 mLavaSpeed += mDefinition->getLavaSpeedPerLevel() * multiplier;
3124 }
3125
3126 mGroundSpeed *= mSpeedModifier;
3127 mWaterSpeed *= mSpeedModifier;
3128 mLavaSpeed *= mSpeedModifier;
3129 mNeedFireRefresh = true;
3130 }
3131
clearMoveSpeedModifier()3132 void Creature::clearMoveSpeedModifier()
3133 {
3134 setMoveSpeedModifier(1.0);
3135 }
3136
setDefenseModifier(double phy,double mag,double ele)3137 void Creature::setDefenseModifier(double phy, double mag, double ele)
3138 {
3139 mPhysicalDefense = mDefinition->getPhysicalDefense();
3140 mMagicalDefense = mDefinition->getMagicalDefense();
3141 mElementDefense = mDefinition->getElementDefense();
3142
3143 mPhysicalDefense += phy;
3144 mMagicalDefense += mag;
3145 mElementDefense += ele;
3146
3147 // Improve the stats to the current level
3148 double multiplier = mLevel - 1;
3149 if (multiplier <= 0.0)
3150 return;
3151
3152 mPhysicalDefense += mDefinition->getPhysicalDefPerLevel() * multiplier;
3153 mMagicalDefense += mDefinition->getMagicalDefPerLevel() * multiplier;
3154 mElementDefense += mDefinition->getElementDefPerLevel() * multiplier;
3155
3156 mNeedFireRefresh = true;
3157 }
3158
clearDefenseModifier()3159 void Creature::clearDefenseModifier()
3160 {
3161 setDefenseModifier(0.0, 0.0, 0.0);
3162 }
3163
setStrengthModifier(double modifier)3164 void Creature::setStrengthModifier(double modifier)
3165 {
3166 mModifierStrength = modifier;
3167 // Since strength is not used on client side, no need to send it
3168 }
3169
clearStrengthModifier()3170 void Creature::clearStrengthModifier()
3171 {
3172 setStrengthModifier(1.0);
3173 }
3174
isWarmup() const3175 bool Creature::isWarmup() const
3176 {
3177 for(const CreatureSkillData& skillData : mSkillData)
3178 {
3179 if(skillData.mWarmup > 0)
3180 return true;
3181 }
3182
3183 return false;
3184 }
3185
fight()3186 void Creature::fight()
3187 {
3188 clearDestinations(EntityAnimation::idle_anim, true, true);
3189 clearActionQueue();
3190 bool ko = getSeat()->getKoCreatures();
3191 pushAction(Utils::make_unique<CreatureActionFight>(*this, nullptr, ko, true));
3192 }
3193
fightCreature(Creature & creature,bool ko,bool notifyPlayerIfHit)3194 void Creature::fightCreature(Creature& creature, bool ko, bool notifyPlayerIfHit)
3195 {
3196 clearDestinations(EntityAnimation::idle_anim, true, true);
3197 clearActionQueue();
3198 pushAction(Utils::make_unique<CreatureActionFight>(*this, &creature, ko, notifyPlayerIfHit));
3199 }
3200
flee()3201 void Creature::flee()
3202 {
3203 clearDestinations(EntityAnimation::idle_anim, true, true);
3204 clearActionQueue();
3205 pushAction(Utils::make_unique<CreatureActionFlee>(*this));
3206 }
3207
sleep()3208 void Creature::sleep()
3209 {
3210 clearDestinations(EntityAnimation::idle_anim, true, true);
3211 clearActionQueue();
3212 pushAction(Utils::make_unique<CreatureActionSleep>(*this));
3213 }
3214
leaveDungeon()3215 void Creature::leaveDungeon()
3216 {
3217 clearDestinations(EntityAnimation::idle_anim, true, true);
3218 clearActionQueue();
3219 pushAction(Utils::make_unique<CreatureActionLeaveDungeon>(*this));
3220 }
3221
needsToEat(bool forced) const3222 bool Creature::needsToEat(bool forced) const
3223 {
3224 if (forced && getHunger() < 5.0)
3225 return false;
3226
3227 if (!forced && (getHunger() <= Random::Double(0.0, 15.0)))
3228 return false;
3229
3230 return true;
3231 }
3232
changeSeat(Seat * newSeat)3233 void Creature::changeSeat(Seat* newSeat)
3234 {
3235 OD_LOG_INF("creature=" + getName() + " changes side from seatId=" + Helper::toString(getSeat()->getId()) + " to seatId=" + Helper::toString(newSeat->getId()));
3236 OD_ASSERT_TRUE_MSG(getSeat() != newSeat, "creature=" + getName() + ", seatId=" + Helper::toString(newSeat->getId()));
3237 setSeat(newSeat);
3238 mMoodValue = CreatureMoodLevel::Neutral;
3239 mMoodPoints = 0;
3240 mWakefulness = 100;
3241 mHunger = 0;
3242 mNbTurnsTorture = 0;
3243 mNbTurnsPrison = 0;
3244 mActiveSlapsCount = 0;
3245 clearDestinations(EntityAnimation::idle_anim, true, true);
3246 clearActionQueue();
3247 mNeedFireRefresh = true;
3248 if (getHomeTile() != nullptr)
3249 {
3250 RoomDormitory* home = static_cast<RoomDormitory*>(getHomeTile()->getCoveringBuilding());
3251 home->releaseTileForSleeping(getHomeTile(), this);
3252 }
3253 }
3254