1/**
2 * The Forgotten Server - a free and open-source MMORPG server emulator
3 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20#include "otpch.h"
21
22#include "configmanager.h"
23#include "creature.h"
24#include "game.h"
25#include "monster.h"
26#include "scheduler.h"
27
28double Creature::speedA = 857.36;
29double Creature::speedB = 261.29;
30double Creature::speedC = -4795.01;
31
32extern Game g_game;
33extern ConfigManager g_config;
34extern CreatureEvents *g_creatureEvents;
35
36Creature::Creature() { onIdleStatus(); }
37
38Creature::~Creature() {
39 for (Creature *summon : summons) {
40 summon->setAttackedCreature(nullptr);
41 summon->removeMaster();
42 }
43
44 for (Condition *condition : conditions) {
45 condition->endCondition(this);
46 delete condition;
47 }
48}
49
50bool Creature::canSee(const Position &myPos, const Position &pos,
51 int32_t viewRangeX, int32_t viewRangeY) {
52 if (myPos.z <= 7) {
53 // we are on ground level or above (7 -> 0)
54 // view is from 7 -> 0
55 if (pos.z > 7) {
56 return false;
57 }
58 } else if (myPos.z >= 8) {
59 // we are underground (8 -> 15)
60 // view is +/- 2 from the floor we stand on
61 if (Position::getDistanceZ(myPos, pos) > 2) {
62 return false;
63 }
64 }
65
66 const int_fast32_t offsetz = myPos.getZ() - pos.getZ();
67 return (pos.getX() >= myPos.getX() - viewRangeX + offsetz) &&
68 (pos.getX() <= myPos.getX() + viewRangeX + offsetz) &&
69 (pos.getY() >= myPos.getY() - viewRangeY + offsetz) &&
70 (pos.getY() <= myPos.getY() + viewRangeY + offsetz);
71}
72
73bool Creature::canSee(const Position &pos) const {
74 return canSee(getPosition(), pos, Map::maxViewportX, Map::maxViewportY);
75}
76
77bool Creature::canSeeCreature(const Creature *creature) const {
78 if (!canSeeInvisibility() && creature->isInvisible()) {
79 return false;
80 }
81 return true;
82}
83
84void Creature::setSkull(Skulls_t newSkull) {
85 skull = newSkull;
86 g_game.updateCreatureSkull(this);
87}
88
89int64_t Creature::getTimeSinceLastMove() const {
90 if (lastStep) {
91 return OTSYS_TIME() - lastStep;
92 }
93 return std::numeric_limits<int64_t>::max();
94}
95
96int32_t Creature::getWalkDelay(Direction dir) const {
97 if (lastStep == 0) {
98 return 0;
99 }
100
101 int64_t ct = OTSYS_TIME();
102 int64_t stepDuration = getStepDuration(dir);
103 return stepDuration - (ct - lastStep);
104}
105
106int32_t Creature::getWalkDelay() const {
107 // Used for auto-walking
108 if (lastStep == 0) {
109 return 0;
110 }
111
112 int64_t ct = OTSYS_TIME();
113 int64_t stepDuration = getStepDuration() * lastStepCost;
114 return stepDuration - (ct - lastStep);
115}
116
117void Creature::onThink(uint32_t interval) {
118 if (!isMapLoaded && useCacheMap()) {
119 isMapLoaded = true;
120 updateMapCache();
121 }
122
123 if (followCreature && master != followCreature &&
124 !canSeeCreature(followCreature)) {
125 onCreatureDisappear(followCreature, false);
126 }
127
128 if (attackedCreature && master != attackedCreature &&
129 !canSeeCreature(attackedCreature)) {
130 onCreatureDisappear(attackedCreature, false);
131 }
132
133 blockTicks += interval;
134 if (blockTicks >= 1000) {
135 blockCount = std::min<uint32_t>(blockCount + 1, 2);
136 blockTicks = 0;
137 }
138
139 if (followCreature) {
140 walkUpdateTicks += interval;
141 if (forceUpdateFollowPath || walkUpdateTicks >= 2000) {
142 walkUpdateTicks = 0;
143 forceUpdateFollowPath = false;
144 isUpdatingPath = true;
145 }
146 }
147
148 if (isUpdatingPath) {
149 isUpdatingPath = false;
150 goToFollowCreature();
151 }
152
153 // scripting event - onThink
154 const CreatureEventList &thinkEvents =
155 getCreatureEvents(CREATURE_EVENT_THINK);
156 for (CreatureEvent *thinkEvent : thinkEvents) {
157 thinkEvent->executeOnThink(this, interval);
158 }
159}
160
161void Creature::onAttacking(uint32_t interval) {
162 if (!attackedCreature) {
163 return;
164 }
165
166 onAttacked();
167 attackedCreature->onAttacked();
168
169 if (g_game.isSightClear(getPosition(), attackedCreature->getPosition(),
170 true)) {
171 doAttacking(interval);
172 }
173}
174
175void Creature::onIdleStatus() {
176 if (getHealth() > 0) {
177 damageMap.clear();
178 lastHitCreatureId = 0;
179 }
180}
181
182void Creature::onWalk() {
183 if (getWalkDelay() <= 0) {
184 // Nota: Hemos eliminado el revert prematuro al caminar para permitir
185 // que las animaciones de ataque se completen fluidamente (Premium look).
186
187 Player *player = getPlayer();
188 Direction dir;
189 uint32_t flags = FLAG_IGNOREFIELDDAMAGE;
190 if (getNextStep(dir, flags)) {
191 ReturnValue ret = g_game.internalMoveCreature(this, dir, flags);
192 if (player &&
193 ret != RETURNVALUE_NOERROR) { // it's player and walk path is invalid
194 if (tryToFixAutoWalk(dir)) { // correction for auto walking
195 ret = g_game.internalMoveCreature(this, dir, flags);
196 }
197 }
198 if (ret != RETURNVALUE_NOERROR) {
199 if (player) {
200 player->sendCancelMessage(ret);
201 player->sendCancelWalk();
202 player->sendNewCancelWalk();
203 }
204
205 forceUpdateFollowPath = true;
206 }
207 } else {
208 if (listWalkDir.empty()) {
209 onWalkComplete();
210 }
211
212 stopEventWalk();
213 }
214 }
215
216 if (cancelNextWalk) {
217 listWalkDir.clear();
218 onWalkAborted();
219 cancelNextWalk = false;
220 }
221
222 if (eventWalk != 0) {
223 eventWalk = 0;
224 addEventWalk();
225 }
226}
227
228void Creature::onWalk(Direction &dir) {
229 if (hasCondition(CONDITION_DRUNK)) {
230 uint32_t r = uniform_random(0, 20);
231 if (r <= DIRECTION_DIAGONAL_MASK) {
232 if (r < DIRECTION_DIAGONAL_MASK) {
233 dir = static_cast<Direction>(r);
234 }
235 g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, "Hicks!", false);
236 }
237 }
238}
239
240bool Creature::getNextStep(Direction &dir, uint32_t &) {
241 if (listWalkDir.empty()) {
242 return false;
243 }
244
245 dir = listWalkDir.front();
246 listWalkDir.pop_front();
247 onWalk(dir);
248 return true;
249}
250
251void Creature::startAutoWalk(const std::list<Direction> &listDir) {
252 listWalkDir = listDir;
253 addEventWalk();
254}
255
256void Creature::addEventWalk() {
257 cancelNextWalk = false;
258
259 if (getStepSpeed() <= 0) {
260 return;
261 }
262
263 if (eventWalk != 0) {
264 return;
265 }
266
267 int64_t ticks = getEventStepTicks();
268 if (ticks <= 0) {
269 return;
270 }
271
272 // Take first step right away, but still queue the next
273 if (ticks == 1) {
274 g_game.checkCreatureWalk(getID());
275 }
276
277 eventWalk = g_scheduler.addEvent(createSchedulerTask(
278 ticks, std::bind(&Game::checkCreatureWalk, &g_game, getID())));
279}
280
281void Creature::stopEventWalk() {
282 if (eventWalk != 0) {
283 g_scheduler.stopEvent(eventWalk);
284 eventWalk = 0;
285 }
286}
287
288void Creature::updateMapCache() {
289 Tile *tile;
290 const Position &myPos = getPosition();
291 Position pos(0, 0, myPos.z);
292
293 for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) {
294 for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) {
295 pos.x = myPos.getX() + x;
296 pos.y = myPos.getY() + y;
297 tile = g_game.map.getTile(pos);
298 updateTileCache(tile, pos);
299 }
300 }
301}
302
303void Creature::updateTileCache(const Tile *tile, int32_t dx, int32_t dy) {
304 if (std::abs(dx) <= maxWalkCacheWidth && std::abs(dy) <= maxWalkCacheHeight) {
305 localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx] =
306 tile && tile->queryAdd(0, *this, 1,
307 FLAG_PATHFINDING | FLAG_IGNOREFIELDDAMAGE) ==
308 RETURNVALUE_NOERROR;
309 }
310}
311
312void Creature::updateTileCache(const Tile *tile, const Position &pos) {
313 const Position &myPos = getPosition();
314 if (pos.z == myPos.z) {
315 int32_t dx = Position::getOffsetX(pos, myPos);
316 int32_t dy = Position::getOffsetY(pos, myPos);
317 updateTileCache(tile, dx, dy);
318 }
319}
320
321int32_t Creature::getWalkCache(const Position &pos) const {
322 if (!useCacheMap()) {
323 return 2;
324 }
325
326 const Position &myPos = getPosition();
327 if (myPos.z != pos.z) {
328 return 0;
329 }
330
331 if (pos == myPos) {
332 return 1;
333 }
334
335 int32_t dx = Position::getOffsetX(pos, myPos);
336 if (std::abs(dx) <= maxWalkCacheWidth) {
337 int32_t dy = Position::getOffsetY(pos, myPos);
338 if (std::abs(dy) <= maxWalkCacheHeight) {
339 if (localMapCache[maxWalkCacheHeight + dy][maxWalkCacheWidth + dx]) {
340 return 1;
341 } else {
342 return 0;
343 }
344 }
345 }
346
347 // out of range
348 return 2;
349}
350
351void Creature::onAddTileItem(const Tile *tile, const Position &pos) {
352 if (isMapLoaded && pos.z == getPosition().z) {
353 updateTileCache(tile, pos);
354 }
355}
356
357void Creature::onUpdateTileItem(const Tile *tile, const Position &pos,
358 const Item *, const ItemType &oldType,
359 const Item *, const ItemType &newType) {
360 if (!isMapLoaded) {
361 return;
362 }
363
364 if (oldType.blockSolid || oldType.blockPathFind || newType.blockPathFind ||
365 newType.blockSolid) {
366 if (pos.z == getPosition().z) {
367 updateTileCache(tile, pos);
368 }
369 }
370}
371
372void Creature::onRemoveTileItem(const Tile *tile, const Position &pos,
373 const ItemType &iType, const Item *) {
374 if (!isMapLoaded) {
375 return;
376 }
377
378 if (iType.blockSolid || iType.blockPathFind || iType.isGroundTile()) {
379 if (pos.z == getPosition().z) {
380 updateTileCache(tile, pos);
381 }
382 }
383}
384
385void Creature::onCreatureAppear(Creature *creature, bool isLogin) {
386 if (creature == this) {
387 if (useCacheMap()) {
388 isMapLoaded = true;
389 updateMapCache();
390 }
391
392 if (isLogin) {
393 setLastPosition(getPosition());
394 }
395 } else if (isMapLoaded) {
396 if (creature->getPosition().z == getPosition().z) {
397 updateTileCache(creature->getTile(), creature->getPosition());
398 }
399 }
400}
401
402void Creature::onRemoveCreature(Creature *creature, bool) {
403 onCreatureDisappear(creature, true);
404 if (creature == this) {
405 if (master && !master->isRemoved()) {
406 setMaster(nullptr);
407 }
408 } else if (isMapLoaded) {
409 if (creature->getPosition().z == getPosition().z) {
410 updateTileCache(creature->getTile(), creature->getPosition());
411 }
412 }
413}
414
415void Creature::onCreatureDisappear(const Creature *creature, bool isLogout) {
416 if (attackedCreature == creature) {
417 setAttackedCreature(nullptr);
418 onAttackedCreatureDisappear(isLogout);
419 }
420
421 if (followCreature == creature) {
422 setFollowCreature(nullptr);
423 onFollowCreatureDisappear(isLogout);
424 }
425}
426
427void Creature::onChangeZone(ZoneType_t zone) {
428 if (attackedCreature && zone == ZONE_PROTECTION) {
429 onCreatureDisappear(attackedCreature, false);
430 }
431}
432
433void Creature::onAttackedCreatureChangeZone(ZoneType_t zone) {
434 if (zone == ZONE_PROTECTION) {
435 onCreatureDisappear(attackedCreature, false);
436 }
437}
438
439void Creature::onCreatureMove(Creature *creature, const Tile *newTile,
440 const Position &newPos, const Tile *oldTile,
441 const Position &oldPos, bool teleport) {
442 if (creature == this) {
443 lastStep = OTSYS_TIME();
444 lastStepCost = 1;
445
446 if (!teleport) {
447 if (oldPos.z != newPos.z) {
448 // floor change extra cost
449 lastStepCost = 2;
450 } else if (Position::getDistanceX(newPos, oldPos) >= 1 &&
451 Position::getDistanceY(newPos, oldPos) >= 1) {
452 // diagonal extra cost
453 lastStepCost = 3;
454 }
455 } else {
456 stopEventWalk();
457 }
458
459 if (!summons.empty()) {
460 // check if any of our summons is out of range (+/- 2 floors or 30 tiles
461 // away)
462 std::forward_list<Creature *> despawnList;
463 for (Creature *summon : summons) {
464 const Position &pos = summon->getPosition();
465 if (Position::getDistanceZ(newPos, pos) > 2 ||
466 (std::max<int32_t>(Position::getDistanceX(newPos, pos),
467 Position::getDistanceY(newPos, pos)) > 30)) {
468 despawnList.push_front(summon);
469 }
470 }
471
472 for (Creature *despawnCreature : despawnList) {
473 g_game.removeCreature(despawnCreature, true);
474 }
475 }
476
477 if (newTile->getZone() != oldTile->getZone()) {
478 onChangeZone(getZone());
479 }
480
481 // update map cache
482 if (isMapLoaded) {
483 if (teleport || oldPos.z != newPos.z) {
484 updateMapCache();
485 } else {
486 const Position &myPos = getPosition();
487
488 if (oldPos.y > newPos.y) { // north
489 // shift y south
490 for (int32_t y = mapWalkHeight - 1; --y >= 0;) {
491 memcpy(localMapCache[y + 1], localMapCache[y],
492 sizeof(localMapCache[y]));
493 }
494
495 // update 0
496 for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) {
497 Tile *cacheTile = g_game.map.getTile(
498 myPos.getX() + x, myPos.getY() - maxWalkCacheHeight, myPos.z);
499 updateTileCache(cacheTile, x, -maxWalkCacheHeight);
500 }
501 } else if (oldPos.y < newPos.y) { // south
502 // shift y north
503 for (int32_t y = 0; y <= mapWalkHeight - 2; ++y) {
504 memcpy(localMapCache[y], localMapCache[y + 1],
505 sizeof(localMapCache[y]));
506 }
507
508 // update mapWalkHeight - 1
509 for (int32_t x = -maxWalkCacheWidth; x <= maxWalkCacheWidth; ++x) {
510 Tile *cacheTile = g_game.map.getTile(
511 myPos.getX() + x, myPos.getY() + maxWalkCacheHeight, myPos.z);
512 updateTileCache(cacheTile, x, maxWalkCacheHeight);
513 }
514 }
515
516 if (oldPos.x < newPos.x) { // east
517 // shift y west
518 int32_t starty = 0;
519 int32_t endy = mapWalkHeight - 1;
520 int32_t dy = Position::getDistanceY(oldPos, newPos);
521
522 if (dy < 0) {
523 endy += dy;
524 } else if (dy > 0) {
525 starty = dy;
526 }
527
528 for (int32_t y = starty; y <= endy; ++y) {
529 for (int32_t x = 0; x <= mapWalkWidth - 2; ++x) {
530 localMapCache[y][x] = localMapCache[y][x + 1];
531 }
532 }
533
534 // update mapWalkWidth - 1
535 for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) {
536 Tile *cacheTile = g_game.map.getTile(myPos.x + maxWalkCacheWidth,
537 myPos.y + y, myPos.z);
538 updateTileCache(cacheTile, maxWalkCacheWidth, y);
539 }
540 } else if (oldPos.x > newPos.x) { // west
541 // shift y east
542 int32_t starty = 0;
543 int32_t endy = mapWalkHeight - 1;
544 int32_t dy = Position::getDistanceY(oldPos, newPos);
545
546 if (dy < 0) {
547 endy += dy;
548 } else if (dy > 0) {
549 starty = dy;
550 }
551
552 for (int32_t y = starty; y <= endy; ++y) {
553 for (int32_t x = mapWalkWidth - 1; --x >= 0;) {
554 localMapCache[y][x + 1] = localMapCache[y][x];
555 }
556 }
557
558 // update 0
559 for (int32_t y = -maxWalkCacheHeight; y <= maxWalkCacheHeight; ++y) {
560 Tile *cacheTile = g_game.map.getTile(myPos.x - maxWalkCacheWidth,
561 myPos.y + y, myPos.z);
562 updateTileCache(cacheTile, -maxWalkCacheWidth, y);
563 }
564 }
565
566 updateTileCache(oldTile, oldPos);
567 }
568 }
569 } else {
570 if (isMapLoaded) {
571 const Position &myPos = getPosition();
572
573 if (newPos.z == myPos.z) {
574 updateTileCache(newTile, newPos);
575 }
576
577 if (oldPos.z == myPos.z) {
578 updateTileCache(oldTile, oldPos);
579 }
580 }
581 }
582
583 if (creature == followCreature || (creature == this && followCreature)) {
584 if (hasFollowPath) {
585 isUpdatingPath = true;
586 }
587
588 if (newPos.z != oldPos.z || !canSee(followCreature->getPosition())) {
589 onCreatureDisappear(followCreature, false);
590 }
591 }
592
593 if (creature == attackedCreature || (creature == this && attackedCreature)) {
594 if (newPos.z != oldPos.z || !canSee(attackedCreature->getPosition())) {
595 onCreatureDisappear(attackedCreature, false);
596 } else {
597 if (hasExtraSwing()) {
598 // our target is moving lets see if we can get in hit
599 g_dispatcher.addTask(createTask(
600 std::bind(&Game::checkCreatureAttack, &g_game, getID())));
601 }
602
603 if (newTile->getZone() != oldTile->getZone()) {
604 onAttackedCreatureChangeZone(attackedCreature->getZone());
605 }
606 }
607 }
608}
609
610void Creature::onDeath() {
611 bool lastHitUnjustified = false;
612 bool mostDamageUnjustified = false;
613 Creature *lastHitCreature = g_game.getCreatureByID(lastHitCreatureId);
614 Creature *lastHitCreatureMaster;
615 if (lastHitCreature) {
616 lastHitUnjustified = lastHitCreature->onKilledCreature(this);
617 lastHitCreatureMaster = lastHitCreature->getMaster();
618 } else {
619 lastHitCreatureMaster = nullptr;
620 }
621
622 Creature *mostDamageCreature = nullptr;
623
624 const int64_t timeNow = OTSYS_TIME();
625 const uint32_t inFightTicks = g_config.getNumber(ConfigManager::PZ_LOCKED);
626 int32_t mostDamage = 0;
627 std::map<Creature *, uint64_t> experienceMap;
628 for (const auto &it : damageMap) {
629 if (Creature *attacker = g_game.getCreatureByID(it.first)) {
630 CountBlock_t cb = it.second;
631 if ((cb.total > mostDamage && (timeNow - cb.ticks <= inFightTicks))) {
632 mostDamage = cb.total;
633 mostDamageCreature = attacker;
634 }
635
636 if (attacker != this) {
637 uint64_t gainExp = getGainedExperience(attacker);
638 if (Player *attackerPlayer = attacker->getPlayer()) {
639 attackerPlayer->removeAttacked(getPlayer());
640
641 Party *party = attackerPlayer->getParty();
642 if (party && party->getLeader() &&
643 party->isSharedExperienceActive() &&
644 party->isSharedExperienceEnabled()) {
645 attacker = party->getLeader();
646 }
647 }
648
649 auto tmpIt = experienceMap.find(attacker);
650 if (tmpIt == experienceMap.end()) {
651 experienceMap[attacker] = gainExp;
652 } else {
653 tmpIt->second += gainExp;
654 }
655 }
656 }
657 }
658
659 for (const auto &it : experienceMap) {
660 it.first->onGainExperience(it.second, this);
661 }
662
663 if (mostDamageCreature) {
664 if (mostDamageCreature != lastHitCreature &&
665 mostDamageCreature != lastHitCreatureMaster) {
666 Creature *mostDamageCreatureMaster = mostDamageCreature->getMaster();
667 if (lastHitCreature != mostDamageCreatureMaster &&
668 (lastHitCreatureMaster == nullptr ||
669 mostDamageCreatureMaster != lastHitCreatureMaster)) {
670 mostDamageUnjustified =
671 mostDamageCreature->onKilledCreature(this, false);
672 }
673 }
674 }
675
676 bool droppedCorpse = dropCorpse(lastHitCreature, mostDamageCreature,
677 lastHitUnjustified, mostDamageUnjustified);
678 death(lastHitCreature);
679
680 if (master) {
681 setMaster(nullptr);
682 }
683
684 if (droppedCorpse) {
685 g_game.removeCreature(this, false);
686 }
687}
688
689bool Creature::dropCorpse(Creature *lastHitCreature,
690 Creature *mostDamageCreature, bool lastHitUnjustified,
691 bool mostDamageUnjustified) {
692 if (!lootDrop && getMonster()) {
693 if (master) {
694 // scripting event - onDeath
695 const CreatureEventList &deathEvents =
696 getCreatureEvents(CREATURE_EVENT_DEATH);
697 for (CreatureEvent *deathEvent : deathEvents) {
698 deathEvent->executeOnDeath(this, nullptr, lastHitCreature,
699 mostDamageCreature, lastHitUnjustified,
700 mostDamageUnjustified);
701 }
702 }
703
704 g_game.addMagicEffect(getPosition(), CONST_ME_POFF);
705 } else {
706 Item *splash;
707 switch (getRace()) {
708 case RACE_VENOM:
709 splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_SLIME);
710 break;
711
712 case RACE_BLOOD:
713 splash = Item::CreateItem(ITEM_FULLSPLASH, FLUID_BLOOD);
714 break;
715
716 default:
717 splash = nullptr;
718 break;
719 }
720
721 Tile *tile = getTile();
722
723 if (splash) {
724 g_game.internalAddItem(tile, splash, INDEX_WHEREEVER, FLAG_NOLIMIT);
725 g_game.startDecay(splash);
726 }
727
728 Item *corpse = getCorpse(lastHitCreature, mostDamageCreature);
729 if (corpse) {
730 g_game.internalAddItem(tile, corpse, INDEX_WHEREEVER, FLAG_NOLIMIT);
731 g_game.startDecay(corpse);
732 }
733
734 // scripting event - onDeath
735 for (CreatureEvent *deathEvent : getCreatureEvents(CREATURE_EVENT_DEATH)) {
736 deathEvent->executeOnDeath(this, corpse, lastHitCreature,
737 mostDamageCreature, lastHitUnjustified,
738 mostDamageUnjustified);
739 }
740
741 if (corpse) {
742 dropLoot(corpse->getContainer(), lastHitCreature);
743 }
744 }
745
746 return true;
747}
748
749bool Creature::hasBeenAttacked(uint32_t attackerId) {
750 auto it = damageMap.find(attackerId);
751 if (it == damageMap.end()) {
752 return false;
753 }
754 return (OTSYS_TIME() - it->second.ticks) <=
755 g_config.getNumber(ConfigManager::PZ_LOCKED);
756}
757
758Item *Creature::getCorpse(Creature *, Creature *) {
759 return Item::CreateItem(getLookCorpse());
760}
761
762void Creature::changeHealth(int32_t healthChange,
763 bool sendHealthChange /* = true*/) {
764 int32_t oldHealth = health;
765
766 if (healthChange > 0) {
767 health += std::min<int32_t>(healthChange, getMaxHealth() - health);
768 } else {
769 health = std::max<int32_t>(0, health + healthChange);
770 }
771
772 if (sendHealthChange && oldHealth != health) {
773 g_game.addCreatureHealth(this);
774 }
775}
776
777void Creature::gainHealth(Creature *healer, int32_t healthGain) {
778 changeHealth(healthGain);
779 if (healer) {
780 healer->onTargetCreatureGainHealth(this, healthGain);
781 }
782}
783
784void Creature::drainHealth(Creature *attacker, int32_t damage) {
785 changeHealth(-damage, false);
786
787 if (attacker) {
788 attacker->onAttackedCreatureDrainHealth(this, damage);
789 }
790}
791
792BlockType_t Creature::blockHit(Creature *attacker, CombatType_t combatType,
793 int32_t &damage, bool checkDefense /* = false */,
794 bool checkArmor /* = false */,
795 bool /* field = false */) {
796 BlockType_t blockType = BLOCK_NONE;
797
798 if (isImmune(combatType)) {
799 damage = 0;
800 blockType = BLOCK_IMMUNITY;
801 } else if (checkDefense || checkArmor) {
802 bool hasDefense = false;
803
804 if (blockCount > 0) {
805 --blockCount;
806 hasDefense = true;
807 }
808
809 if (checkDefense && hasDefense && canUseDefense) {
810 int32_t defense = getDefense();
811 damage -= uniform_random(defense / 2, defense);
812 if (damage <= 0) {
813 damage = 0;
814 blockType = BLOCK_DEFENSE;
815 checkArmor = false;
816 }
817 }
818
819 if (checkArmor) {
820 int32_t armor = getArmor();
821 if (armor > 3) {
822 damage -= uniform_random(armor / 2, armor - (armor % 2 + 1));
823 } else if (armor > 0) {
824 --damage;
825 }
826
827 if (damage <= 0) {
828 damage = 0;
829 blockType = BLOCK_ARMOR;
830 }
831 }
832
833 if (hasDefense && blockType != BLOCK_NONE) {
834 onBlockHit();
835 }
836 }
837
838 if (attacker) {
839 attacker->onAttackedCreature(this);
840 attacker->onAttackedCreatureBlockHit(blockType);
841 }
842
843 onAttacked();
844 return blockType;
845}
846
847bool Creature::setAttackedCreature(Creature *creature) {
848 if (creature) {
849 const Position &creaturePos = creature->getPosition();
850 if (creaturePos.z != getPosition().z || !canSee(creaturePos)) {
851 attackedCreature = nullptr;
852 return false;
853 }
854
855 attackedCreature = creature;
856 onAttackedCreature(attackedCreature);
857 attackedCreature->onAttacked();
858 } else {
859 attackedCreature = nullptr;
860 if (isAttackPulsing) {
861 revertAttackOutfit();
862 }
863 }
864
865 for (Creature *summon : summons) {
866 summon->setAttackedCreature(creature);
867 }
868 return true;
869}
870
871void Creature::getPathSearchParams(const Creature *,
872 FindPathParams &fpp) const {
873 fpp.fullPathSearch = !hasFollowPath;
874 fpp.clearSight = true;
875 fpp.maxSearchDist = 12;
876 fpp.minTargetDist = 1;
877 fpp.maxTargetDist = 1;
878}
879
880void Creature::goToFollowCreature() {
881 if (followCreature) {
882 FindPathParams fpp;
883 getPathSearchParams(followCreature, fpp);
884
885 Monster *monster = getMonster();
886 if (monster && !monster->getMaster() &&
887 (monster->isFleeing() || fpp.maxTargetDist > 1)) {
888 Direction dir = DIRECTION_NONE;
889
890 if (monster->isFleeing()) {
891 monster->getDistanceStep(followCreature->getPosition(), dir, true);
892 } else { // maxTargetDist > 1
893 if (!monster->getDistanceStep(followCreature->getPosition(), dir)) {
894 // if we can't get anything then let the A* calculate
895 listWalkDir.clear();
896 if (getPathTo(followCreature->getPosition(), listWalkDir, fpp)) {
897 hasFollowPath = true;
898 startAutoWalk(listWalkDir);
899 } else {
900 hasFollowPath = false;
901 }
902 return;
903 }
904 }
905
906 if (dir != DIRECTION_NONE) {
907 listWalkDir.clear();
908 listWalkDir.push_front(dir);
909
910 hasFollowPath = true;
911 startAutoWalk(listWalkDir);
912 }
913 } else {
914 listWalkDir.clear();
915 if (getPathTo(followCreature->getPosition(), listWalkDir, fpp)) {
916 hasFollowPath = true;
917 startAutoWalk(listWalkDir);
918 } else {
919 hasFollowPath = false;
920 }
921 }
922 }
923
924 onFollowCreatureComplete(followCreature);
925}
926
927bool Creature::setFollowCreature(Creature *creature) {
928 if (followCreature == creature) {
929 return true;
930 }
931
932 if (creature) {
933 const Position &creaturePos = creature->getPosition();
934 if (creaturePos.z != getPosition().z || !canSee(creaturePos)) {
935 followCreature = nullptr;
936 return false;
937 }
938
939 if (!listWalkDir.empty()) {
940 listWalkDir.clear();
941 onWalkAborted();
942 }
943
944 hasFollowPath = false;
945 forceUpdateFollowPath = false;
946 followCreature = creature;
947 isUpdatingPath = true;
948 } else {
949 isUpdatingPath = false;
950 followCreature = nullptr;
951 }
952
953 onFollowCreature(creature);
954 return true;
955}
956
957double Creature::getDamageRatio(Creature *attacker) const {
958 uint32_t totalDamage = 0;
959 uint32_t attackerDamage = 0;
960
961 for (const auto &it : damageMap) {
962 const CountBlock_t &cb = it.second;
963 totalDamage += cb.total;
964 if (it.first == attacker->getID()) {
965 attackerDamage += cb.total;
966 }
967 }
968
969 if (totalDamage == 0) {
970 return 0;
971 }
972
973 return (static_cast<double>(attackerDamage) / totalDamage);
974}
975
976uint64_t Creature::getGainedExperience(Creature *attacker) const {
977 return std::floor(getDamageRatio(attacker) * getLostExperience());
978}
979
980void Creature::addDamagePoints(Creature *attacker, int32_t damagePoints) {
981 if (damagePoints <= 0) {
982 return;
983 }
984
985 uint32_t attackerId = attacker->id;
986
987 auto it = damageMap.find(attackerId);
988 if (it == damageMap.end()) {
989 CountBlock_t cb;
990 cb.ticks = OTSYS_TIME();
991 cb.total = damagePoints;
992 damageMap[attackerId] = cb;
993 } else {
994 it->second.total += damagePoints;
995 it->second.ticks = OTSYS_TIME();
996 }
997
998 lastHitCreatureId = attackerId;
999}
1000
1001void Creature::onAddCondition(ConditionType_t type) {
1002 if (type == CONDITION_PARALYZE && hasCondition(CONDITION_HASTE)) {
1003 removeCondition(CONDITION_HASTE);
1004 } else if (type == CONDITION_HASTE && hasCondition(CONDITION_PARALYZE)) {
1005 removeCondition(CONDITION_PARALYZE);
1006 }
1007}
1008
1009void Creature::onAddCombatCondition(ConditionType_t) {
1010 //
1011}
1012
1013void Creature::onEndCondition(ConditionType_t) {
1014 //
1015}
1016
1017void Creature::onTickCondition(ConditionType_t type, bool &bRemove) {
1018 const MagicField *field = getTile()->getFieldItem();
1019 if (!field) {
1020 return;
1021 }
1022
1023 switch (type) {
1024 case CONDITION_FIRE:
1025 bRemove = (field->getCombatType() != COMBAT_FIREDAMAGE);
1026 break;
1027 case CONDITION_ENERGY:
1028 bRemove = (field->getCombatType() != COMBAT_ENERGYDAMAGE);
1029 break;
1030 case CONDITION_POISON:
1031 bRemove = (field->getCombatType() != COMBAT_EARTHDAMAGE);
1032 break;
1033 case CONDITION_FREEZING:
1034 bRemove = (field->getCombatType() != COMBAT_ICEDAMAGE);
1035 break;
1036 case CONDITION_DAZZLED:
1037 bRemove = (field->getCombatType() != COMBAT_HOLYDAMAGE);
1038 break;
1039 case CONDITION_CURSED:
1040 bRemove = (field->getCombatType() != COMBAT_DEATHDAMAGE);
1041 break;
1042 case CONDITION_DROWN:
1043 bRemove = (field->getCombatType() != COMBAT_DROWNDAMAGE);
1044 break;
1045 case CONDITION_BLEEDING:
1046 bRemove = (field->getCombatType() != COMBAT_PHYSICALDAMAGE);
1047 break;
1048 default:
1049 break;
1050 }
1051}
1052
1053void Creature::onCombatRemoveCondition(Condition *condition) {
1054 removeCondition(condition);
1055}
1056
1057void Creature::onAttacked() {
1058 //
1059}
1060
1061void Creature::onAttackedCreatureDrainHealth(Creature *target, int32_t points) {
1062 target->addDamagePoints(this, points);
1063}
1064
1065bool Creature::onKilledCreature(Creature *target, bool) {
1066 if (master) {
1067 master->onKilledCreature(target);
1068 }
1069
1070 // scripting event - onKill
1071 const CreatureEventList &killEvents = getCreatureEvents(CREATURE_EVENT_KILL);
1072 for (CreatureEvent *killEvent : killEvents) {
1073 killEvent->executeOnKill(this, target);
1074 }
1075 return false;
1076}
1077
1078void Creature::onGainExperience(uint64_t gainExp, Creature *target) {
1079 if (gainExp == 0 || !master) {
1080 return;
1081 }
1082
1083 gainExp /= 2;
1084 master->onGainExperience(gainExp, target);
1085
1086 SpectatorVec spectators;
1087 g_game.map.getSpectators(spectators, position, false, true);
1088 if (spectators.empty()) {
1089 return;
1090 }
1091
1092 TextMessage message(
1093 MESSAGE_EXPERIENCE_OTHERS,
1094 ucfirst(getNameDescription()) + " gained " + std::to_string(gainExp) +
1095 (gainExp != 1 ? " experience points." : " experience point."));
1096 message.position = position;
1097 message.primary.color = TEXTCOLOR_WHITE_EXP;
1098 message.primary.value = gainExp;
1099
1100 for (Creature *spectator : spectators) {
1101 spectator->getPlayer()->sendTextMessage(message);
1102 }
1103}
1104
1105bool Creature::setMaster(Creature *newMaster) {
1106 if (!newMaster && !master) {
1107 return false;
1108 }
1109
1110 if (newMaster) {
1111 incrementReferenceCounter();
1112 newMaster->summons.push_back(this);
1113 }
1114
1115 Creature *oldMaster = master;
1116 master = newMaster;
1117
1118 if (oldMaster) {
1119 auto summon =
1120 std::find(oldMaster->summons.begin(), oldMaster->summons.end(), this);
1121 if (summon != oldMaster->summons.end()) {
1122 oldMaster->summons.erase(summon);
1123 decrementReferenceCounter();
1124 }
1125 }
1126 return true;
1127}
1128
1129bool Creature::addCondition(Condition *condition, bool force /* = false*/) {
1130 if (condition == nullptr) {
1131 return false;
1132 }
1133
1134 if (!force && condition->getType() == CONDITION_HASTE &&
1135 hasCondition(CONDITION_PARALYZE)) {
1136 int64_t walkDelay = getWalkDelay();
1137 if (walkDelay > 0) {
1138 g_scheduler.addEvent(createSchedulerTask(
1139 walkDelay,
1140 std::bind(&Game::forceAddCondition, &g_game, getID(), condition)));
1141 return false;
1142 }
1143 }
1144
1145 Condition *prevCond = getCondition(condition->getType(), condition->getId(),
1146 condition->getSubId());
1147 if (prevCond) {
1148 prevCond->addCondition(this, condition);
1149 delete condition;
1150 return true;
1151 }
1152
1153 if (condition->startCondition(this)) {
1154 conditions.push_back(condition);
1155 onAddCondition(condition->getType());
1156 return true;
1157 }
1158
1159 delete condition;
1160 return false;
1161}
1162
1163bool Creature::addCombatCondition(Condition *condition) {
1164 // Caution: condition variable could be deleted after the call to addCondition
1165 ConditionType_t type = condition->getType();
1166
1167 if (!addCondition(condition)) {
1168 return false;
1169 }
1170
1171 onAddCombatCondition(type);
1172 return true;
1173}
1174
1175void Creature::removeCondition(ConditionType_t type, bool force /* = false*/) {
1176 auto it = conditions.begin(), end = conditions.end();
1177 while (it != end) {
1178 Condition *condition = *it;
1179 if (condition->getType() != type) {
1180 ++it;
1181 continue;
1182 }
1183
1184 if (!force && type == CONDITION_PARALYZE) {
1185 int64_t walkDelay = getWalkDelay();
1186 if (walkDelay > 0) {
1187 g_scheduler.addEvent(createSchedulerTask(
1188 walkDelay,
1189 std::bind(&Game::forceRemoveCondition, &g_game, getID(), type)));
1190 return;
1191 }
1192 }
1193
1194 it = conditions.erase(it);
1195
1196 condition->endCondition(this);
1197 delete condition;
1198
1199 onEndCondition(type);
1200 }
1201}
1202
1203void Creature::removeCondition(ConditionType_t type, ConditionId_t conditionId,
1204 bool force /* = false*/) {
1205 auto it = conditions.begin(), end = conditions.end();
1206 while (it != end) {
1207 Condition *condition = *it;
1208 if (condition->getType() != type || condition->getId() != conditionId) {
1209 ++it;
1210 continue;
1211 }
1212
1213 if (!force && type == CONDITION_PARALYZE) {
1214 int64_t walkDelay = getWalkDelay();
1215 if (walkDelay > 0) {
1216 g_scheduler.addEvent(createSchedulerTask(
1217 walkDelay,
1218 std::bind(&Game::forceRemoveCondition, &g_game, getID(), type)));
1219 return;
1220 }
1221 }
1222
1223 it = conditions.erase(it);
1224
1225 condition->endCondition(this);
1226 delete condition;
1227
1228 onEndCondition(type);
1229 }
1230}
1231
1232void Creature::removeCombatCondition(ConditionType_t type) {
1233 std::vector<Condition *> removeConditions;
1234 for (Condition *condition : conditions) {
1235 if (condition->getType() == type) {
1236 removeConditions.push_back(condition);
1237 }
1238 }
1239
1240 for (Condition *condition : removeConditions) {
1241 onCombatRemoveCondition(condition);
1242 }
1243}
1244
1245void Creature::removeCondition(Condition *condition, bool force /* = false*/) {
1246 auto it = std::find(conditions.begin(), conditions.end(), condition);
1247 if (it == conditions.end()) {
1248 return;
1249 }
1250
1251 if (!force && condition->getType() == CONDITION_PARALYZE) {
1252 int64_t walkDelay = getWalkDelay();
1253 if (walkDelay > 0) {
1254 g_scheduler.addEvent(createSchedulerTask(
1255 walkDelay, std::bind(&Game::forceRemoveCondition, &g_game, getID(),
1256 condition->getType())));
1257 return;
1258 }
1259 }
1260
1261 conditions.erase(it);
1262
1263 condition->endCondition(this);
1264 onEndCondition(condition->getType());
1265 delete condition;
1266}
1267
1268Condition *Creature::getCondition(ConditionType_t type) const {
1269 for (Condition *condition : conditions) {
1270 if (condition->getType() == type) {
1271 return condition;
1272 }
1273 }
1274 return nullptr;
1275}
1276
1277Condition *Creature::getCondition(ConditionType_t type,
1278 ConditionId_t conditionId,
1279 uint32_t subId /* = 0*/) const {
1280 for (Condition *condition : conditions) {
1281 if (condition->getType() == type && condition->getId() == conditionId &&
1282 condition->getSubId() == subId) {
1283 return condition;
1284 }
1285 }
1286 return nullptr;
1287}
1288
1289void Creature::executeConditions(uint32_t interval) {
1290 ConditionList tempConditions{conditions};
1291 for (Condition *condition : tempConditions) {
1292 auto it = std::find(conditions.begin(), conditions.end(), condition);
1293 if (it == conditions.end()) {
1294 continue;
1295 }
1296
1297 if (!condition->executeCondition(this, interval)) {
1298 it = std::find(conditions.begin(), conditions.end(), condition);
1299 if (it != conditions.end()) {
1300 conditions.erase(it);
1301 condition->endCondition(this);
1302 onEndCondition(condition->getType());
1303 delete condition;
1304 }
1305 }
1306 }
1307}
1308
1309bool Creature::hasCondition(ConditionType_t type,
1310 uint32_t subId /* = 0*/) const {
1311 if (isSuppress(type)) {
1312 return false;
1313 }
1314
1315 int64_t timeNow = OTSYS_TIME();
1316 for (Condition *condition : conditions) {
1317 if (condition->getType() != type || condition->getSubId() != subId) {
1318 continue;
1319 }
1320
1321 if (condition->getEndTime() >= timeNow || condition->getTicks() == -1) {
1322 return true;
1323 }
1324 }
1325 return false;
1326}
1327
1328bool Creature::isImmune(CombatType_t type) const {
1329 return hasBitSet(static_cast<uint32_t>(type), getDamageImmunities());
1330}
1331
1332bool Creature::isImmune(ConditionType_t type) const {
1333 return hasBitSet(static_cast<uint32_t>(type), getConditionImmunities());
1334}
1335
1336bool Creature::isSuppress(ConditionType_t type) const {
1337 return hasBitSet(static_cast<uint32_t>(type), getConditionSuppressions());
1338}
1339
1340int64_t Creature::getStepDuration(Direction dir) const {
1341 int64_t stepDuration = getStepDuration();
1342 if ((dir & DIRECTION_DIAGONAL_MASK) != 0) {
1343 stepDuration *= 3;
1344 }
1345 return stepDuration;
1346}
1347
1348int64_t Creature::getStepDuration(bool forAnimation) const {
1349 if (isRemoved()) {
1350 return 0;
1351 }
1352
1353 uint32_t calculatedStepSpeed;
1354 uint32_t groundSpeed;
1355
1356 int32_t stepSpeed = getStepSpeed();
1357 if (stepSpeed > -Creature::speedB) {
1358 calculatedStepSpeed =
1359 floor((Creature::speedA * log((stepSpeed / 2) + Creature::speedB) +
1360 Creature::speedC) +
1361 0.5);
1362 if (calculatedStepSpeed == 0) {
1363 calculatedStepSpeed = 1;
1364 }
1365 } else {
1366 calculatedStepSpeed = 1;
1367 }
1368
1369 Item *ground = tile->getGround();
1370 if (ground) {
1371 groundSpeed = Item::items[ground->getID()].speed;
1372 if (groundSpeed == 0) {
1373 groundSpeed = 150;
1374 }
1375 } else {
1376 groundSpeed = 150;
1377 }
1378
1379 double duration = std::floor(1000 * groundSpeed / calculatedStepSpeed);
1380 int64_t stepDuration = duration;
1381
1382 if (!forAnimation) {
1383 const Monster *monster = getMonster();
1384 if (monster && monster->isTargetNearby() && !monster->isFleeing() &&
1385 !monster->getMaster()) {
1386 stepDuration *= 2;
1387 }
1388 }
1389
1390 return stepDuration;
1391}
1392
1393int64_t Creature::getEventStepTicks() const {
1394 int64_t ret = getWalkDelay();
1395 if (ret <= 0) {
1396 ret = 1;
1397 }
1398 return ret;
1399}
1400
1401LightInfo Creature::getCreatureLight() const { return internalLight; }
1402
1403void Creature::setCreatureLight(LightInfo lightInfo) {
1404 internalLight = std::move(lightInfo);
1405}
1406
1407void Creature::setNormalCreatureLight() { internalLight = {}; }
1408
1409bool Creature::registerCreatureEvent(const std::string &name) {
1410 CreatureEvent *event = g_creatureEvents->getEventByName(name);
1411 if (!event) {
1412 return false;
1413 }
1414
1415 CreatureEventType_t type = event->getEventType();
1416 if (hasEventRegistered(type)) {
1417 for (CreatureEvent *creatureEvent : eventsList) {
1418 if (creatureEvent == event) {
1419 return false;
1420 }
1421 }
1422 } else {
1423 scriptEventsBitField |= static_cast<uint32_t>(1) << type;
1424 }
1425
1426 eventsList.push_back(event);
1427 return true;
1428}
1429
1430bool Creature::unregisterCreatureEvent(const std::string &name) {
1431 CreatureEvent *event = g_creatureEvents->getEventByName(name);
1432 if (!event) {
1433 return false;
1434 }
1435
1436 CreatureEventType_t type = event->getEventType();
1437 if (!hasEventRegistered(type)) {
1438 return false;
1439 }
1440
1441 bool resetTypeBit = true;
1442
1443 auto it = eventsList.begin(), end = eventsList.end();
1444 while (it != end) {
1445 CreatureEvent *curEvent = *it;
1446 if (curEvent == event) {
1447 it = eventsList.erase(it);
1448 continue;
1449 }
1450
1451 if (curEvent->getEventType() == type) {
1452 resetTypeBit = false;
1453 }
1454 ++it;
1455 }
1456
1457 if (resetTypeBit) {
1458 scriptEventsBitField &= ~(static_cast<uint32_t>(1) << type);
1459 }
1460 return true;
1461}
1462
1463CreatureEventList Creature::getCreatureEvents(CreatureEventType_t type) {
1464 CreatureEventList tmpEventList;
1465
1466 if (!hasEventRegistered(type)) {
1467 return tmpEventList;
1468 }
1469
1470 for (CreatureEvent *creatureEvent : eventsList) {
1471 if (!creatureEvent->isLoaded()) {
1472 continue;
1473 }
1474
1475 if (creatureEvent->getEventType() == type) {
1476 tmpEventList.push_back(creatureEvent);
1477 }
1478 }
1479
1480 return tmpEventList;
1481}
1482
1483bool FrozenPathingConditionCall::isInRange(const Position &startPos,
1484 const Position &testPos,
1485 const FindPathParams &fpp) const {
1486 if (fpp.fullPathSearch) {
1487 if (testPos.x > targetPos.x + fpp.maxTargetDist) {
1488 return false;
1489 }
1490
1491 if (testPos.x < targetPos.x - fpp.maxTargetDist) {
1492 return false;
1493 }
1494
1495 if (testPos.y > targetPos.y + fpp.maxTargetDist) {
1496 return false;
1497 }
1498
1499 if (testPos.y < targetPos.y - fpp.maxTargetDist) {
1500 return false;
1501 }
1502 } else {
1503 int_fast32_t dx = Position::getOffsetX(startPos, targetPos);
1504
1505 int32_t dxMax = (dx >= 0 ? fpp.maxTargetDist : 0);
1506 if (testPos.x > targetPos.x + dxMax) {
1507 return false;
1508 }
1509
1510 int32_t dxMin = (dx <= 0 ? fpp.maxTargetDist : 0);
1511 if (testPos.x < targetPos.x - dxMin) {
1512 return false;
1513 }
1514
1515 int_fast32_t dy = Position::getOffsetY(startPos, targetPos);
1516
1517 int32_t dyMax = (dy >= 0 ? fpp.maxTargetDist : 0);
1518 if (testPos.y > targetPos.y + dyMax) {
1519 return false;
1520 }
1521
1522 int32_t dyMin = (dy <= 0 ? fpp.maxTargetDist : 0);
1523 if (testPos.y < targetPos.y - dyMin) {
1524 return false;
1525 }
1526 }
1527 return true;
1528}
1529
1530bool FrozenPathingConditionCall::operator()(const Position &startPos,
1531 const Position &testPos,
1532 const FindPathParams &fpp,
1533 int32_t &bestMatchDist) const {
1534 if (!isInRange(startPos, testPos, fpp)) {
1535 return false;
1536 }
1537
1538 if (fpp.clearSight && !g_game.isSightClear(testPos, targetPos, true)) {
1539 return false;
1540 }
1541
1542 int32_t testDist =
1543 std::max<int32_t>(Position::getDistanceX(targetPos, testPos),
1544 Position::getDistanceY(targetPos, testPos));
1545 if (fpp.maxTargetDist == 1) {
1546 if (testDist < fpp.minTargetDist || testDist > fpp.maxTargetDist) {
1547 return false;
1548 }
1549
1550 return true;
1551 } else if (testDist <= fpp.maxTargetDist) {
1552 if (testDist < fpp.minTargetDist) {
1553 return false;
1554 }
1555
1556 if (testDist == fpp.maxTargetDist) {
1557 bestMatchDist = 0;
1558 return true;
1559 } else if (testDist > bestMatchDist) {
1560 // not quite what we want, but the best so far
1561 bestMatchDist = testDist;
1562 return true;
1563 }
1564 }
1565 return false;
1566}
1567
1568bool Creature::isInvisible() const {
1569 return std::find_if(conditions.begin(), conditions.end(),
1570 [](const Condition *condition) {
1571 return condition->getType() == CONDITION_INVISIBLE;
1572 }) != conditions.end();
1573}
1574
1575bool Creature::getPathTo(const Position &targetPos,
1576 std::list<Direction> &dirList,
1577 const FindPathParams &fpp) const {
1578 return g_game.map.getPathMatching(*this, dirList,
1579 FrozenPathingConditionCall(targetPos), fpp);
1580}
1581
1582bool Creature::getPathTo(const Position &targetPos,
1583 std::list<Direction> &dirList, int32_t minTargetDist,
1584 int32_t maxTargetDist, bool fullPathSearch /*= true*/,
1585 bool clearSight /*= true*/,
1586 int32_t maxSearchDist /*= 0*/) const {
1587 FindPathParams fpp;
1588 fpp.fullPathSearch = fullPathSearch;
1589 fpp.maxSearchDist = maxSearchDist;
1590 fpp.clearSight = clearSight;
1591 fpp.minTargetDist = minTargetDist;
1592 fpp.maxTargetDist = maxTargetDist;
1593 return getPathTo(targetPos, dirList, fpp);
1594}
1595
1596// WARNING: This function may use extra cpu but makes autowalk (map click) much
1597// better
1598bool Creature::tryToFixAutoWalk(Direction dir) {
1599 Position pos = getNextPosition(dir, getPosition());
1600 for (int x = 0; x < 3; ++x) {
1601 if (listWalkDir.empty()) {
1602 break;
1603 }
1604
1605 for (int i = 0; i < 2; ++i) {
1606 if (listWalkDir.empty())
1607 break;
1608 pos = getNextPosition(listWalkDir.front(), pos);
1609 listWalkDir.pop_front();
1610 }
1611
1612 std::list<Direction> newPath;
1613 if (getPathTo(pos, newPath, 0, 0, false, true)) {
1614 newPath.reverse();
1615 for (auto &it : newPath)
1616 listWalkDir.push_front(it);
1617 return true;
1618 }
1619 }
1620
1621 return false;
1622}
1623
1624void Creature::pulseAttackOutfit(const Outfit_t &outfit,
1625 uint32_t durationMs /* = 1000 */) {
1626 // Si no estamos pulsando, guardamos el original para poder revertir
1627 // fielmente.
1628 if (!isAttackPulsing) {
1629 originalOutfit = currentOutfit;
1630 isAttackPulsing = true;
1631 }
1632
1633 // Siempre enviamos el cambio de outfit para que el cliente pueda reiniciar
1634 // el ciclo de animación (swing) en cada "tick" de ataque, incluso si es
1635 // el mismo lookType.
1636 // Usamos sendCreatureAttackPulse para enviar también la duración.
1637 SpectatorVec spectators;
1638 g_game.map.getSpectators(spectators, getPosition(), true, true);
1639 for (Creature *spectator : spectators) {
1640 spectator->getPlayer()->sendCreatureAttackPulse(this, outfit, durationMs);
1641 }
1642
1643 g_game.internalCreatureTurn(this, getDirection());
1644
1645 // Renovar el timer de reversión.
1646 if (attackOutfitEventId != 0) {
1647 g_scheduler.stopEvent(attackOutfitEventId);
1648 attackOutfitEventId = 0;
1649 }
1650
1651 uint32_t creatureId = getID();
1652 attackOutfitEventId = g_scheduler.addEvent(createSchedulerTask(
1653 std::max<uint32_t>(SCHEDULER_MINTICKS, durationMs), [creatureId]() {
1654 Creature *c = g_game.getCreatureByID(creatureId);
1655 if (c) {
1656 c->revertAttackOutfit();
1657 }
1658 }));
1659}
1660
1661void Creature::revertAttackOutfit() {
1662 if (attackOutfitEventId != 0) {
1663 g_scheduler.stopEvent(attackOutfitEventId);
1664 attackOutfitEventId = 0;
1665 }
1666
1667 if (isAttackPulsing) {
1668 // Restauramos el original guardado al inicio del combate
1669 g_game.internalCreatureChangeOutfit(this, originalOutfit);
1670 isAttackPulsing = false;
1671 }
1672}
1673