monster.cpp

main 2049 lines · 57.9 KB Raw
Alex Alex Commit Server upload 18/10/2026 23:38
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 "events.h"
23#include "game.h"
24#include "monster.h"
25#include "spells.h"
26
27extern Game g_game;
28extern Monsters g_monsters;
29extern Events *g_events;
30
31int32_t Monster::despawnRange;
32int32_t Monster::despawnRadius;
33
34uint32_t Monster::monsterAutoID = 0x40000000;
35
36Monster *Monster::createMonster(const std::string &name) {
37 MonsterType *mType = g_monsters.getMonsterType(name);
38 if (!mType) {
39 return nullptr;
40 }
41 return new Monster(mType);
42}
43
44Monster::Monster(MonsterType *mType)
45 : Creature(), strDescription(mType->nameDescription), mType(mType) {
46 defaultOutfit = mType->info.outfit;
47 currentOutfit = mType->info.outfit;
48 attackOutfit = mType->info.lookAttack;
49 skull = mType->info.skull;
50 health = mType->info.health;
51 healthMax = mType->info.healthMax;
52 baseSpeed = mType->info.baseSpeed;
53 internalLight = mType->info.light;
54 hiddenHealth = mType->info.hiddenHealth;
55
56 // register creature events
57 for (const std::string &scriptName : mType->info.scripts) {
58 if (!registerCreatureEvent(scriptName)) {
59 std::cout << "[Warning - Monster::Monster] Unknown event name: "
60 << scriptName << std::endl;
61 }
62 }
63}
64
65Monster::~Monster() {
66 clearTargetList();
67 clearFriendList();
68}
69
70void Monster::addList() { g_game.addMonster(this); }
71
72void Monster::removeList() { g_game.removeMonster(this); }
73
74bool Monster::canSee(const Position &pos) const {
75 return Creature::canSee(getPosition(), pos, 14, 10);
76}
77
78bool Monster::canWalkOnFieldType(CombatType_t combatType) const {
79 switch (combatType) {
80 case COMBAT_ENERGYDAMAGE:
81 return mType->info.canWalkOnEnergy;
82 case COMBAT_FIREDAMAGE:
83 return mType->info.canWalkOnFire;
84 case COMBAT_EARTHDAMAGE:
85 return mType->info.canWalkOnPoison;
86 default:
87 return true;
88 }
89}
90
91void Monster::onAttackedCreatureDisappear(bool) {
92 attackTicks = 0;
93 extraMeleeAttack = true;
94}
95
96void Monster::onCreatureAppear(Creature *creature, bool isLogin) {
97 Creature::onCreatureAppear(creature, isLogin);
98
99 if (mType->info.creatureAppearEvent != -1) {
100 // onCreatureAppear(self, creature)
101 LuaScriptInterface *scriptInterface = mType->info.scriptInterface;
102 if (!scriptInterface->reserveScriptEnv()) {
103 std::cout << "[Error - Monster::onCreatureAppear] Call stack overflow"
104 << std::endl;
105 return;
106 }
107
108 ScriptEnvironment *env = scriptInterface->getScriptEnv();
109 env->setScriptId(mType->info.creatureAppearEvent, scriptInterface);
110
111 lua_State *L = scriptInterface->getLuaState();
112 scriptInterface->pushFunction(mType->info.creatureAppearEvent);
113
114 LuaScriptInterface::pushUserdata<Monster>(L, this);
115 LuaScriptInterface::setMetatable(L, -1, "Monster");
116
117 LuaScriptInterface::pushUserdata<Creature>(L, creature);
118 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
119
120 if (scriptInterface->callFunction(2)) {
121 return;
122 }
123 }
124
125 if (creature == this) {
126 // We just spawned lets look around to see who is there.
127 if (isSummon()) {
128 isMasterInRange = canSee(getMaster()->getPosition());
129 }
130
131 updateTargetList();
132 updateIdleStatus();
133 } else {
134 onCreatureEnter(creature);
135 }
136}
137
138void Monster::onRemoveCreature(Creature *creature, bool isLogout) {
139 Creature::onRemoveCreature(creature, isLogout);
140
141 if (mType->info.creatureDisappearEvent != -1) {
142 // onCreatureDisappear(self, creature)
143 LuaScriptInterface *scriptInterface = mType->info.scriptInterface;
144 if (!scriptInterface->reserveScriptEnv()) {
145 std::cout << "[Error - Monster::onCreatureDisappear] Call stack overflow"
146 << std::endl;
147 return;
148 }
149
150 ScriptEnvironment *env = scriptInterface->getScriptEnv();
151 env->setScriptId(mType->info.creatureDisappearEvent, scriptInterface);
152
153 lua_State *L = scriptInterface->getLuaState();
154 scriptInterface->pushFunction(mType->info.creatureDisappearEvent);
155
156 LuaScriptInterface::pushUserdata<Monster>(L, this);
157 LuaScriptInterface::setMetatable(L, -1, "Monster");
158
159 LuaScriptInterface::pushUserdata<Creature>(L, creature);
160 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
161
162 if (scriptInterface->callFunction(2)) {
163 return;
164 }
165 }
166
167 if (creature == this) {
168 if (spawn) {
169 spawn->startSpawnCheck();
170 }
171
172 setIdle(true);
173 } else {
174 onCreatureLeave(creature);
175 }
176}
177
178void Monster::onCreatureMove(Creature *creature, const Tile *newTile,
179 const Position &newPos, const Tile *oldTile,
180 const Position &oldPos, bool teleport) {
181 Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos,
182 teleport);
183
184 if (mType->info.creatureMoveEvent != -1) {
185 // onCreatureMove(self, creature, oldPosition, newPosition)
186 LuaScriptInterface *scriptInterface = mType->info.scriptInterface;
187 if (!scriptInterface->reserveScriptEnv()) {
188 std::cout << "[Error - Monster::onCreatureMove] Call stack overflow"
189 << std::endl;
190 return;
191 }
192
193 ScriptEnvironment *env = scriptInterface->getScriptEnv();
194 env->setScriptId(mType->info.creatureMoveEvent, scriptInterface);
195
196 lua_State *L = scriptInterface->getLuaState();
197 scriptInterface->pushFunction(mType->info.creatureMoveEvent);
198
199 LuaScriptInterface::pushUserdata<Monster>(L, this);
200 LuaScriptInterface::setMetatable(L, -1, "Monster");
201
202 LuaScriptInterface::pushUserdata<Creature>(L, creature);
203 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
204
205 LuaScriptInterface::pushPosition(L, oldPos);
206 LuaScriptInterface::pushPosition(L, newPos);
207
208 if (scriptInterface->callFunction(4)) {
209 return;
210 }
211 }
212
213 if (creature == this) {
214 if (isSummon()) {
215 isMasterInRange = canSee(getMaster()->getPosition());
216 }
217
218 updateTargetList();
219 updateIdleStatus();
220 } else {
221 bool canSeeNewPos = canSee(newPos);
222 bool canSeeOldPos = canSee(oldPos);
223
224 if (canSeeNewPos && !canSeeOldPos) {
225 onCreatureEnter(creature);
226 } else if (!canSeeNewPos && canSeeOldPos) {
227 onCreatureLeave(creature);
228 }
229
230 if (canSeeNewPos && isSummon() && getMaster() == creature) {
231 isMasterInRange = true; // Follow master again
232 }
233
234 updateIdleStatus();
235
236 if (!isSummon()) {
237 if (followCreature) {
238 const Position &followPosition = followCreature->getPosition();
239 const Position &position = getPosition();
240
241 int32_t offset_x = Position::getDistanceX(followPosition, position);
242 int32_t offset_y = Position::getDistanceY(followPosition, position);
243 if ((offset_x > 1 || offset_y > 1) &&
244 mType->info.changeTargetChance > 0) {
245 Direction dir = getDirectionTo(position, followPosition);
246 const Position &checkPosition = getNextPosition(dir, position);
247
248 Tile *tile = g_game.map.getTile(checkPosition);
249 if (tile) {
250 Creature *topCreature = tile->getTopCreature();
251 if (topCreature && followCreature != topCreature &&
252 isOpponent(topCreature)) {
253 selectTarget(topCreature);
254 }
255 }
256 }
257 } else if (isOpponent(creature)) {
258 // we have no target lets try pick this one
259 selectTarget(creature);
260 }
261 }
262 }
263}
264
265void Monster::onCreatureSay(Creature *creature, SpeakClasses type,
266 const std::string &text) {
267 Creature::onCreatureSay(creature, type, text);
268
269 if (mType->info.creatureSayEvent != -1) {
270 // onCreatureSay(self, creature, type, message)
271 LuaScriptInterface *scriptInterface = mType->info.scriptInterface;
272 if (!scriptInterface->reserveScriptEnv()) {
273 std::cout << "[Error - Monster::onCreatureSay] Call stack overflow"
274 << std::endl;
275 return;
276 }
277
278 ScriptEnvironment *env = scriptInterface->getScriptEnv();
279 env->setScriptId(mType->info.creatureSayEvent, scriptInterface);
280
281 lua_State *L = scriptInterface->getLuaState();
282 scriptInterface->pushFunction(mType->info.creatureSayEvent);
283
284 LuaScriptInterface::pushUserdata<Monster>(L, this);
285 LuaScriptInterface::setMetatable(L, -1, "Monster");
286
287 LuaScriptInterface::pushUserdata<Creature>(L, creature);
288 LuaScriptInterface::setCreatureMetatable(L, -1, creature);
289
290 lua_pushnumber(L, type);
291 LuaScriptInterface::pushString(L, text);
292
293 scriptInterface->callVoidFunction(4);
294 }
295}
296
297void Monster::addFriend(Creature *creature) {
298 assert(creature != this);
299 auto result = friendList.insert(creature);
300 if (result.second) {
301 creature->incrementReferenceCounter();
302 }
303}
304
305void Monster::removeFriend(Creature *creature) {
306 auto it = friendList.find(creature);
307 if (it != friendList.end()) {
308 creature->decrementReferenceCounter();
309 friendList.erase(it);
310 }
311}
312
313void Monster::addTarget(Creature *creature, bool pushFront /* = false*/) {
314 assert(creature != this);
315 if (std::find(targetList.begin(), targetList.end(), creature) ==
316 targetList.end()) {
317 creature->incrementReferenceCounter();
318 if (pushFront) {
319 targetList.push_front(creature);
320 } else {
321 targetList.push_back(creature);
322 }
323 }
324}
325
326void Monster::removeTarget(Creature *creature) {
327 auto it = std::find(targetList.begin(), targetList.end(), creature);
328 if (it != targetList.end()) {
329 creature->decrementReferenceCounter();
330 targetList.erase(it);
331 }
332}
333
334void Monster::updateTargetList() {
335 auto friendIterator = friendList.begin();
336 while (friendIterator != friendList.end()) {
337 Creature *creature = *friendIterator;
338 if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) {
339 creature->decrementReferenceCounter();
340 friendIterator = friendList.erase(friendIterator);
341 } else {
342 ++friendIterator;
343 }
344 }
345
346 auto targetIterator = targetList.begin();
347 while (targetIterator != targetList.end()) {
348 Creature *creature = *targetIterator;
349 if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) {
350 creature->decrementReferenceCounter();
351 targetIterator = targetList.erase(targetIterator);
352 } else {
353 ++targetIterator;
354 }
355 }
356
357 SpectatorVec spectators;
358 g_game.map.getSpectators(spectators, position, true);
359 spectators.erase(this);
360 for (Creature *spectator : spectators) {
361 if (canSee(spectator->getPosition())) {
362 onCreatureFound(spectator);
363 }
364 }
365}
366
367void Monster::clearTargetList() {
368 for (Creature *creature : targetList) {
369 creature->decrementReferenceCounter();
370 }
371 targetList.clear();
372}
373
374void Monster::clearFriendList() {
375 for (Creature *creature : friendList) {
376 creature->decrementReferenceCounter();
377 }
378 friendList.clear();
379}
380
381void Monster::onCreatureFound(Creature *creature, bool pushFront /* = false*/) {
382 if (isFriend(creature)) {
383 addFriend(creature);
384 }
385
386 if (isOpponent(creature)) {
387 addTarget(creature, pushFront);
388 }
389
390 updateIdleStatus();
391}
392
393void Monster::onCreatureEnter(Creature *creature) {
394 // std::cout << "onCreatureEnter - " << creature->getName() << std::endl;
395
396 if (getMaster() == creature) {
397 // Follow master again
398 isMasterInRange = true;
399 }
400
401 onCreatureFound(creature, true);
402}
403
404bool Monster::isFriend(const Creature *creature) const {
405 if (isSummon() && getMaster()->getPlayer()) {
406 const Player *masterPlayer = getMaster()->getPlayer();
407 const Player *tmpPlayer = nullptr;
408
409 if (creature->getPlayer()) {
410 tmpPlayer = creature->getPlayer();
411 } else {
412 const Creature *creatureMaster = creature->getMaster();
413
414 if (creatureMaster && creatureMaster->getPlayer()) {
415 tmpPlayer = creatureMaster->getPlayer();
416 }
417 }
418
419 if (tmpPlayer &&
420 (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer))) {
421 return true;
422 }
423 } else if (creature->getMonster() && !creature->isSummon()) {
424 return true;
425 }
426
427 return false;
428}
429
430bool Monster::isOpponent(const Creature *creature) const {
431 if (isSummon() && getMaster()->getPlayer()) {
432 if (creature != getMaster()) {
433 return true;
434 }
435 } else {
436 if ((creature->getPlayer() &&
437 !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) ||
438 (creature->getMaster() && creature->getMaster()->getPlayer())) {
439 return true;
440 }
441 }
442
443 return false;
444}
445
446void Monster::onCreatureLeave(Creature *creature) {
447 // std::cout << "onCreatureLeave - " << creature->getName() << std::endl;
448
449 if (getMaster() == creature) {
450 // Take random steps and only use defense abilities (e.g. heal) until its
451 // master comes back
452 isMasterInRange = false;
453 }
454
455 // update friendList
456 if (isFriend(creature)) {
457 removeFriend(creature);
458 }
459
460 // update targetList
461 if (isOpponent(creature)) {
462 removeTarget(creature);
463 if (targetList.empty()) {
464 updateIdleStatus();
465 }
466 }
467}
468
469bool Monster::searchTarget(
470 TargetSearchType_t searchType /*= TARGETSEARCH_DEFAULT*/) {
471 std::list<Creature *> resultList;
472 const Position &myPos = getPosition();
473
474 for (Creature *creature : targetList) {
475 if (followCreature != creature && isTarget(creature)) {
476 if (searchType == TARGETSEARCH_RANDOM || canUseAttack(myPos, creature)) {
477 resultList.push_back(creature);
478 }
479 }
480 }
481
482 switch (searchType) {
483 case TARGETSEARCH_NEAREST: {
484 Creature *target = nullptr;
485 if (!resultList.empty()) {
486 auto it = resultList.begin();
487 target = *it;
488
489 if (++it != resultList.end()) {
490 const Position &targetPosition = target->getPosition();
491 int32_t minRange = Position::getDistanceX(myPos, targetPosition) +
492 Position::getDistanceY(myPos, targetPosition);
493 do {
494 const Position &pos = (*it)->getPosition();
495
496 int32_t distance = Position::getDistanceX(myPos, pos) +
497 Position::getDistanceY(myPos, pos);
498 if (distance < minRange) {
499 target = *it;
500 minRange = distance;
501 }
502 } while (++it != resultList.end());
503 }
504 } else {
505 int32_t minRange = std::numeric_limits<int32_t>::max();
506 for (Creature *creature : targetList) {
507 if (!isTarget(creature)) {
508 continue;
509 }
510
511 const Position &pos = creature->getPosition();
512 int32_t distance = Position::getDistanceX(myPos, pos) +
513 Position::getDistanceY(myPos, pos);
514 if (distance < minRange) {
515 target = creature;
516 minRange = distance;
517 }
518 }
519 }
520
521 if (target && selectTarget(target)) {
522 return true;
523 }
524 break;
525 }
526
527 case TARGETSEARCH_DEFAULT:
528 case TARGETSEARCH_ATTACKRANGE:
529 case TARGETSEARCH_RANDOM:
530 default: {
531 if (!resultList.empty()) {
532 auto it = resultList.begin();
533 std::advance(it, uniform_random(0, resultList.size() - 1));
534 return selectTarget(*it);
535 }
536
537 if (searchType == TARGETSEARCH_ATTACKRANGE) {
538 return false;
539 }
540
541 break;
542 }
543 }
544
545 // lets just pick the first target in the list
546 for (Creature *target : targetList) {
547 if (followCreature != target && selectTarget(target)) {
548 return true;
549 }
550 }
551 return false;
552}
553
554void Monster::onFollowCreatureComplete(const Creature *creature) {
555 if (creature) {
556 auto it = std::find(targetList.begin(), targetList.end(), creature);
557 if (it != targetList.end()) {
558 Creature *target = (*it);
559 targetList.erase(it);
560
561 if (hasFollowPath) {
562 targetList.push_front(target);
563 } else if (!isSummon()) {
564 targetList.push_back(target);
565 } else {
566 target->decrementReferenceCounter();
567 }
568 }
569 }
570}
571
572BlockType_t Monster::blockHit(Creature *attacker, CombatType_t combatType,
573 int32_t &damage, bool checkDefense /* = false*/,
574 bool checkArmor /* = false*/,
575 bool /* field = false */) {
576 BlockType_t blockType = Creature::blockHit(attacker, combatType, damage,
577 checkDefense, checkArmor);
578
579 if (damage != 0) {
580 int32_t elementMod = 0;
581 auto it = mType->info.elementMap.find(combatType);
582 if (it != mType->info.elementMap.end()) {
583 elementMod = it->second;
584 }
585
586 if (elementMod != 0) {
587 damage = static_cast<int32_t>(
588 std::round(damage * ((100 - elementMod) / 100.)));
589 if (damage <= 0) {
590 damage = 0;
591 blockType = BLOCK_ARMOR;
592 }
593 }
594 }
595
596 return blockType;
597}
598
599bool Monster::isTarget(const Creature *creature) const {
600 if (creature->isRemoved() || !creature->isAttackable() ||
601 creature->getZone() == ZONE_PROTECTION || !canSeeCreature(creature)) {
602 return false;
603 }
604
605 if (creature->getPosition().z != getPosition().z) {
606 return false;
607 }
608 return true;
609}
610
611bool Monster::selectTarget(Creature *creature) {
612 if (!isTarget(creature)) {
613 return false;
614 }
615
616 auto it = std::find(targetList.begin(), targetList.end(), creature);
617 if (it == targetList.end()) {
618 // Target not found in our target list.
619 return false;
620 }
621
622 if (isHostile() || isSummon()) {
623 if (setAttackedCreature(creature) && !isSummon()) {
624 g_dispatcher.addTask(
625 createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID())));
626 }
627 }
628 return setFollowCreature(creature);
629}
630
631void Monster::setIdle(bool idle) {
632 if (isRemoved() || getHealth() <= 0) {
633 return;
634 }
635
636 isIdle = idle;
637
638 if (!isIdle) {
639 g_game.addCreatureCheck(this);
640 } else {
641 onIdleStatus();
642 clearTargetList();
643 clearFriendList();
644 Game::removeCreatureCheck(this);
645 }
646}
647
648void Monster::updateIdleStatus() {
649 bool idle = false;
650 if (!isSummon() && targetList.empty()) {
651 // check if there are aggressive conditions
652 idle = std::find_if(conditions.begin(), conditions.end(),
653 [](Condition *condition) {
654 return condition->isAggressive();
655 }) == conditions.end();
656 }
657
658 setIdle(idle);
659}
660
661void Monster::onAddCondition(ConditionType_t type) {
662 if (type == CONDITION_FIRE || type == CONDITION_ENERGY ||
663 type == CONDITION_POISON) {
664 updateMapCache();
665 }
666
667 updateIdleStatus();
668}
669
670void Monster::onEndCondition(ConditionType_t type) {
671 if (type == CONDITION_FIRE || type == CONDITION_ENERGY ||
672 type == CONDITION_POISON) {
673 ignoreFieldDamage = false;
674 updateMapCache();
675 }
676
677 updateIdleStatus();
678}
679
680void Monster::onThink(uint32_t interval) {
681 Creature::onThink(interval);
682
683 if (mType->info.thinkEvent != -1) {
684 // onThink(self, interval)
685 LuaScriptInterface *scriptInterface = mType->info.scriptInterface;
686 if (!scriptInterface->reserveScriptEnv()) {
687 std::cout << "[Error - Monster::onThink] Call stack overflow"
688 << std::endl;
689 return;
690 }
691
692 ScriptEnvironment *env = scriptInterface->getScriptEnv();
693 env->setScriptId(mType->info.thinkEvent, scriptInterface);
694
695 lua_State *L = scriptInterface->getLuaState();
696 scriptInterface->pushFunction(mType->info.thinkEvent);
697
698 LuaScriptInterface::pushUserdata<Monster>(L, this);
699 LuaScriptInterface::setMetatable(L, -1, "Monster");
700
701 lua_pushnumber(L, interval);
702
703 if (scriptInterface->callFunction(2)) {
704 return;
705 }
706 }
707
708 if (!isInSpawnRange(position)) {
709 g_game.internalTeleport(this, masterPos);
710 setIdle(true);
711 } else {
712 updateIdleStatus();
713
714 if (!isIdle) {
715 addEventWalk();
716
717 if (isSummon()) {
718 if (!attackedCreature) {
719 if (getMaster() && getMaster()->getAttackedCreature()) {
720 // This happens if the monster is summoned during combat
721 selectTarget(getMaster()->getAttackedCreature());
722 } else if (getMaster() != followCreature) {
723 // Our master has not ordered us to attack anything, lets follow him
724 // around instead.
725 setFollowCreature(getMaster());
726 }
727 } else if (attackedCreature == this) {
728 setFollowCreature(nullptr);
729 } else if (followCreature != attackedCreature) {
730 // This happens just after a master orders an attack, so lets follow
731 // it aswell.
732 setFollowCreature(attackedCreature);
733 }
734 } else if (!targetList.empty()) {
735 if (!followCreature || !hasFollowPath) {
736 searchTarget();
737 } else if (isFleeing()) {
738 if (attackedCreature &&
739 !canUseAttack(getPosition(), attackedCreature)) {
740 searchTarget(TARGETSEARCH_ATTACKRANGE);
741 }
742 }
743 }
744
745 onThinkTarget(interval);
746 onThinkYell(interval);
747 onThinkDefense(interval);
748 }
749 }
750}
751
752void Monster::doAttacking(uint32_t interval) {
753 if (!attackedCreature || (isSummon() && attackedCreature == this)) {
754 return;
755 }
756
757 bool updateLook = true;
758 bool resetTicks = interval != 0;
759 attackTicks += interval;
760
761 const Position &myPos = getPosition();
762 const Position &targetPos = attackedCreature->getPosition();
763
764 for (const spellBlock_t &spellBlock : mType->info.attackSpells) {
765 bool inRange = false;
766
767 if (attackedCreature == nullptr) {
768 break;
769 }
770
771 if (canUseSpell(myPos, targetPos, spellBlock, interval, inRange,
772 resetTicks)) {
773 if (spellBlock.chance >= static_cast<uint32_t>(uniform_random(1, 100))) {
774 if (updateLook) {
775 updateLookDirection();
776 updateLook = false;
777 }
778
779 minCombatValue = spellBlock.minCombatValue;
780 maxCombatValue = spellBlock.maxCombatValue;
781 spellBlock.spell->castSpell(this, attackedCreature);
782
783 if (spellBlock.isMelee) {
784 extraMeleeAttack = false;
785 }
786
787 uint16_t attackLookType = getAttackOutfitLookType();
788 if (attackLookType != 0) {
789 // Limitamos la duración de la animación a 1000ms máximo para que
790 // los monstruos no se vean lentos en cooldowns largos, manteniendo
791 // el enfoque dinámico y fluido del personaje.
792 uint32_t revertDelay = std::min<uint32_t>(1000, spellBlock.speed);
793 Outfit_t combatOutfit = currentOutfit;
794 combatOutfit.lookType = attackLookType;
795 pulseAttackOutfit(combatOutfit, revertDelay);
796 }
797 }
798 }
799
800 if (!inRange && spellBlock.isMelee) {
801 // melee swing out of reach
802 extraMeleeAttack = true;
803 }
804 }
805
806 if (updateLook) {
807 updateLookDirection();
808 }
809
810 if (resetTicks) {
811 attackTicks = 0;
812 }
813}
814
815bool Monster::canUseAttack(const Position &pos, const Creature *target) const {
816 if (isHostile()) {
817 const Position &targetPos = target->getPosition();
818 uint32_t distance =
819 std::max<uint32_t>(Position::getDistanceX(pos, targetPos),
820 Position::getDistanceY(pos, targetPos));
821 for (const spellBlock_t &spellBlock : mType->info.attackSpells) {
822 if (spellBlock.range != 0 && distance <= spellBlock.range) {
823 return g_game.isSightClear(pos, targetPos, true);
824 }
825 }
826 return false;
827 }
828 return true;
829}
830
831bool Monster::canUseSpell(const Position &pos, const Position &targetPos,
832 const spellBlock_t &sb, uint32_t interval,
833 bool &inRange, bool &resetTicks) {
834 inRange = true;
835
836 if (sb.isMelee && isFleeing()) {
837 return false;
838 }
839
840 if (extraMeleeAttack) {
841 lastMeleeAttack = OTSYS_TIME();
842 } else if (sb.isMelee && (OTSYS_TIME() - lastMeleeAttack) < 1500) {
843 return false;
844 }
845
846 if (!sb.isMelee || !extraMeleeAttack) {
847 if (sb.speed > attackTicks) {
848 resetTicks = false;
849 return false;
850 }
851
852 if (attackTicks % sb.speed >= interval) {
853 // already used this spell for this round
854 return false;
855 }
856 }
857
858 if (sb.range != 0 &&
859 std::max<uint32_t>(Position::getDistanceX(pos, targetPos),
860 Position::getDistanceY(pos, targetPos)) > sb.range) {
861 inRange = false;
862 return false;
863 }
864 return true;
865}
866
867void Monster::onThinkTarget(uint32_t interval) {
868 if (!isSummon()) {
869 if (mType->info.changeTargetSpeed != 0) {
870 bool canChangeTarget = true;
871
872 if (challengeFocusDuration > 0) {
873 challengeFocusDuration -= interval;
874
875 if (challengeFocusDuration <= 0) {
876 challengeFocusDuration = 0;
877 }
878 }
879
880 if (targetChangeCooldown > 0) {
881 targetChangeCooldown -= interval;
882
883 if (targetChangeCooldown <= 0) {
884 targetChangeCooldown = 0;
885 targetChangeTicks = mType->info.changeTargetSpeed;
886 } else {
887 canChangeTarget = false;
888 }
889 }
890
891 if (canChangeTarget) {
892 targetChangeTicks += interval;
893
894 if (targetChangeTicks >= mType->info.changeTargetSpeed) {
895 targetChangeTicks = 0;
896 targetChangeCooldown = mType->info.changeTargetSpeed;
897
898 if (challengeFocusDuration > 0) {
899 challengeFocusDuration = 0;
900 }
901
902 if (mType->info.changeTargetChance >= uniform_random(1, 100)) {
903 if (mType->info.targetDistance <= 1) {
904 searchTarget(TARGETSEARCH_RANDOM);
905 } else {
906 searchTarget(TARGETSEARCH_NEAREST);
907 }
908 }
909 }
910 }
911 }
912 }
913}
914
915void Monster::onThinkDefense(uint32_t interval) {
916 bool resetTicks = true;
917 defenseTicks += interval;
918
919 for (const spellBlock_t &spellBlock : mType->info.defenseSpells) {
920 if (spellBlock.speed > defenseTicks) {
921 resetTicks = false;
922 continue;
923 }
924
925 if (defenseTicks % spellBlock.speed >= interval) {
926 // already used this spell for this round
927 continue;
928 }
929
930 if ((spellBlock.chance >= static_cast<uint32_t>(uniform_random(1, 100)))) {
931 minCombatValue = spellBlock.minCombatValue;
932 maxCombatValue = spellBlock.maxCombatValue;
933 spellBlock.spell->castSpell(this, this);
934 }
935 }
936
937 if (!isSummon() && summons.size() < mType->info.maxSummons && hasFollowPath) {
938 for (const summonBlock_t &summonBlock : mType->info.summons) {
939 if (summonBlock.speed > defenseTicks) {
940 resetTicks = false;
941 continue;
942 }
943
944 if (summons.size() >= mType->info.maxSummons) {
945 continue;
946 }
947
948 if (defenseTicks % summonBlock.speed >= interval) {
949 // already used this spell for this round
950 continue;
951 }
952
953 uint32_t summonCount = 0;
954 for (Creature *summon : summons) {
955 if (summon->getName() == summonBlock.name) {
956 ++summonCount;
957 }
958 }
959
960 if (summonCount >= summonBlock.max) {
961 continue;
962 }
963
964 if (summonBlock.chance < static_cast<uint32_t>(uniform_random(1, 100))) {
965 continue;
966 }
967
968 Monster *summon = Monster::createMonster(summonBlock.name);
969 if (summon) {
970 if (g_game.placeCreature(summon, getPosition(), false,
971 summonBlock.force)) {
972 summon->setDropLoot(false);
973 summon->setSkillLoss(false);
974 summon->setMaster(this);
975 g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE);
976 g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT);
977 } else {
978 delete summon;
979 }
980 }
981 }
982 }
983
984 if (resetTicks) {
985 defenseTicks = 0;
986 }
987}
988
989void Monster::onThinkYell(uint32_t interval) {
990 if (mType->info.yellSpeedTicks == 0) {
991 return;
992 }
993
994 yellTicks += interval;
995 if (yellTicks >= mType->info.yellSpeedTicks) {
996 yellTicks = 0;
997
998 if (!mType->info.voiceVector.empty() &&
999 (mType->info.yellChance >=
1000 static_cast<uint32_t>(uniform_random(1, 100)))) {
1001 uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1);
1002 const voiceBlock_t &vb = mType->info.voiceVector[index];
1003
1004 if (vb.yellText) {
1005 g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, vb.text, false);
1006 } else {
1007 g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, vb.text, false);
1008 }
1009 }
1010 }
1011}
1012
1013void Monster::onWalk() { Creature::onWalk(); }
1014
1015bool Monster::pushItem(Item *item) {
1016 const Position &centerPos = item->getPosition();
1017
1018 static std::vector<std::pair<int32_t, int32_t>> relList{
1019 {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1}};
1020
1021 std::shuffle(relList.begin(), relList.end(), getRandomGenerator());
1022
1023 for (const auto &it : relList) {
1024 Position tryPos(centerPos.x + it.first, centerPos.y + it.second,
1025 centerPos.z);
1026 Tile *tile = g_game.map.getTile(tryPos);
1027 if (tile && g_game.canThrowObjectTo(centerPos, tryPos)) {
1028 if (g_game.internalMoveItem(item->getParent(), tile, INDEX_WHEREEVER,
1029 item, item->getItemCount(),
1030 nullptr) == RETURNVALUE_NOERROR) {
1031 return true;
1032 }
1033 }
1034 }
1035 return false;
1036}
1037
1038void Monster::pushItems(Tile *tile) {
1039 // We can not use iterators here since we can push the item to another tile
1040 // which will invalidate the iterator.
1041 // start from the end to minimize the amount of traffic
1042 if (TileItemVector *items = tile->getItemList()) {
1043 uint32_t moveCount = 0;
1044 uint32_t removeCount = 0;
1045
1046 int32_t downItemSize = tile->getDownItemCount();
1047 for (int32_t i = downItemSize; --i >= 0;) {
1048 Item *item = items->at(i);
1049 if (item && item->hasProperty(CONST_PROP_MOVEABLE) &&
1050 (item->hasProperty(CONST_PROP_BLOCKPATH) ||
1051 item->hasProperty(CONST_PROP_BLOCKSOLID))) {
1052 if (moveCount < 20 && Monster::pushItem(item)) {
1053 ++moveCount;
1054 } else if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) {
1055 ++removeCount;
1056 }
1057 }
1058 }
1059
1060 if (removeCount > 0) {
1061 g_game.addMagicEffect(tile->getPosition(), CONST_ME_POFF);
1062 }
1063 }
1064}
1065
1066bool Monster::pushCreature(Creature *creature) {
1067 static std::vector<Direction> dirList{DIRECTION_NORTH, DIRECTION_WEST,
1068 DIRECTION_EAST, DIRECTION_SOUTH};
1069 std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator());
1070
1071 for (Direction dir : dirList) {
1072 const Position &tryPos = Spells::getCasterPosition(creature, dir);
1073 Tile *toTile = g_game.map.getTile(tryPos);
1074 if (toTile && !toTile->hasFlag(TILESTATE_BLOCKPATH)) {
1075 if (g_game.internalMoveCreature(creature, dir) == RETURNVALUE_NOERROR) {
1076 return true;
1077 }
1078 }
1079 }
1080 return false;
1081}
1082
1083void Monster::pushCreatures(Tile *tile) {
1084 // We can not use iterators here since we can push a creature to another tile
1085 // which will invalidate the iterator.
1086 if (CreatureVector *creatures = tile->getCreatures()) {
1087 uint32_t removeCount = 0;
1088 Monster *lastPushedMonster = nullptr;
1089
1090 for (size_t i = 0; i < creatures->size();) {
1091 Monster *monster = creatures->at(i)->getMonster();
1092 if (monster && monster->isPushable()) {
1093 if (monster != lastPushedMonster && Monster::pushCreature(monster)) {
1094 lastPushedMonster = monster;
1095 continue;
1096 }
1097
1098 monster->changeHealth(-monster->getHealth());
1099 monster->setDropLoot(false);
1100 removeCount++;
1101 }
1102
1103 ++i;
1104 }
1105
1106 if (removeCount > 0) {
1107 g_game.addMagicEffect(tile->getPosition(), CONST_ME_BLOCKHIT);
1108 }
1109 }
1110}
1111
1112bool Monster::getNextStep(Direction &direction, uint32_t &flags) {
1113 if (isIdle || getHealth() <= 0) {
1114 // we dont have anyone watching might aswell stop walking
1115 eventWalk = 0;
1116 return false;
1117 }
1118
1119 bool result = false;
1120 if ((!followCreature || !hasFollowPath) &&
1121 (!isSummon() || !isMasterInRange)) {
1122 if (getTimeSinceLastMove() >= 1000) {
1123 randomStepping = true;
1124 // choose a random direction
1125 result = getRandomStep(getPosition(), direction);
1126 }
1127 } else if ((isSummon() && isMasterInRange) || followCreature) {
1128 randomStepping = false;
1129 result = Creature::getNextStep(direction, flags);
1130 if (result) {
1131 flags |= FLAG_PATHFINDING;
1132 } else {
1133 if (ignoreFieldDamage) {
1134 ignoreFieldDamage = false;
1135 updateMapCache();
1136 }
1137 // target dancing
1138 if (attackedCreature && attackedCreature == followCreature) {
1139 if (isFleeing()) {
1140 result = getDanceStep(getPosition(), direction, false, false);
1141 } else if (mType->info.staticAttackChance <
1142 static_cast<uint32_t>(uniform_random(1, 100))) {
1143 result = getDanceStep(getPosition(), direction);
1144 }
1145 }
1146 }
1147 }
1148
1149 if (result && (canPushItems() || canPushCreatures())) {
1150 const Position &pos = Spells::getCasterPosition(this, direction);
1151 Tile *tile = g_game.map.getTile(pos);
1152 if (tile) {
1153 if (canPushItems()) {
1154 Monster::pushItems(tile);
1155 }
1156
1157 if (canPushCreatures()) {
1158 Monster::pushCreatures(tile);
1159 }
1160 }
1161 }
1162
1163 return result;
1164}
1165
1166bool Monster::getRandomStep(const Position &creaturePos,
1167 Direction &direction) const {
1168 static std::vector<Direction> dirList{DIRECTION_NORTH, DIRECTION_WEST,
1169 DIRECTION_EAST, DIRECTION_SOUTH};
1170 std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator());
1171
1172 for (Direction dir : dirList) {
1173 if (canWalkTo(creaturePos, dir)) {
1174 direction = dir;
1175 return true;
1176 }
1177 }
1178 return false;
1179}
1180
1181bool Monster::getDanceStep(const Position &creaturePos, Direction &direction,
1182 bool keepAttack /*= true*/,
1183 bool keepDistance /*= true*/) {
1184 bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature);
1185
1186 assert(attackedCreature != nullptr);
1187 const Position &centerPos = attackedCreature->getPosition();
1188
1189 int_fast32_t offset_x = Position::getOffsetX(creaturePos, centerPos);
1190 int_fast32_t offset_y = Position::getOffsetY(creaturePos, centerPos);
1191
1192 int_fast32_t distance_x = std::abs(offset_x);
1193 int_fast32_t distance_y = std::abs(offset_y);
1194
1195 uint32_t centerToDist = std::max<uint32_t>(distance_x, distance_y);
1196
1197 std::vector<Direction> dirList;
1198
1199 if (!keepDistance || offset_y >= 0) {
1200 uint32_t tmpDist = std::max<uint32_t>(
1201 distance_x, std::abs((creaturePos.getY() - 1) - centerPos.getY()));
1202 if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_NORTH)) {
1203 bool result = true;
1204
1205 if (keepAttack) {
1206 result = (!canDoAttackNow ||
1207 canUseAttack(
1208 Position(creaturePos.x, creaturePos.y - 1, creaturePos.z),
1209 attackedCreature));
1210 }
1211
1212 if (result) {
1213 dirList.push_back(DIRECTION_NORTH);
1214 }
1215 }
1216 }
1217
1218 if (!keepDistance || offset_y <= 0) {
1219 uint32_t tmpDist = std::max<uint32_t>(
1220 distance_x, std::abs((creaturePos.getY() + 1) - centerPos.getY()));
1221 if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_SOUTH)) {
1222 bool result = true;
1223
1224 if (keepAttack) {
1225 result = (!canDoAttackNow ||
1226 canUseAttack(
1227 Position(creaturePos.x, creaturePos.y + 1, creaturePos.z),
1228 attackedCreature));
1229 }
1230
1231 if (result) {
1232 dirList.push_back(DIRECTION_SOUTH);
1233 }
1234 }
1235 }
1236
1237 if (!keepDistance || offset_x <= 0) {
1238 uint32_t tmpDist = std::max<uint32_t>(
1239 std::abs((creaturePos.getX() + 1) - centerPos.getX()), distance_y);
1240 if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_EAST)) {
1241 bool result = true;
1242
1243 if (keepAttack) {
1244 result = (!canDoAttackNow ||
1245 canUseAttack(
1246 Position(creaturePos.x + 1, creaturePos.y, creaturePos.z),
1247 attackedCreature));
1248 }
1249
1250 if (result) {
1251 dirList.push_back(DIRECTION_EAST);
1252 }
1253 }
1254 }
1255
1256 if (!keepDistance || offset_x >= 0) {
1257 uint32_t tmpDist = std::max<uint32_t>(
1258 std::abs((creaturePos.getX() - 1) - centerPos.getX()), distance_y);
1259 if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_WEST)) {
1260 bool result = true;
1261
1262 if (keepAttack) {
1263 result = (!canDoAttackNow ||
1264 canUseAttack(
1265 Position(creaturePos.x - 1, creaturePos.y, creaturePos.z),
1266 attackedCreature));
1267 }
1268
1269 if (result) {
1270 dirList.push_back(DIRECTION_WEST);
1271 }
1272 }
1273 }
1274
1275 if (!dirList.empty()) {
1276 std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator());
1277 direction = dirList[uniform_random(0, dirList.size() - 1)];
1278 return true;
1279 }
1280 return false;
1281}
1282
1283bool Monster::getDistanceStep(const Position &targetPos, Direction &direction,
1284 bool flee /* = false */) {
1285 const Position &creaturePos = getPosition();
1286
1287 int_fast32_t dx = Position::getDistanceX(creaturePos, targetPos);
1288 int_fast32_t dy = Position::getDistanceY(creaturePos, targetPos);
1289
1290 int32_t distance = std::max<int32_t>(dx, dy);
1291
1292 if (!flee && (distance > mType->info.targetDistance ||
1293 !g_game.isSightClear(creaturePos, targetPos, true))) {
1294 return false; // let the A* calculate it
1295 } else if (!flee && distance == mType->info.targetDistance) {
1296 return true; // we don't really care here, since it's what we wanted to
1297 // reach (a dancestep will take of dancing in that position)
1298 }
1299
1300 int_fast32_t offsetx = Position::getOffsetX(creaturePos, targetPos);
1301 int_fast32_t offsety = Position::getOffsetY(creaturePos, targetPos);
1302
1303 if (dx <= 1 && dy <= 1) {
1304 // seems like a target is near, it this case we need to slow down our
1305 // movements (as a monster)
1306 if (stepDuration < 2) {
1307 stepDuration++;
1308 }
1309 } else if (stepDuration > 0) {
1310 stepDuration--;
1311 }
1312
1313 if (offsetx == 0 && offsety == 0) {
1314 return getRandomStep(
1315 creaturePos,
1316 direction); // player is "on" the monster so let's get some random step
1317 // and rest will be taken care later.
1318 }
1319
1320 if (dx == dy) {
1321 // player is diagonal to the monster
1322 if (offsetx >= 1 && offsety >= 1) {
1323 // player is NW
1324 // escape to SE, S or E [and some extra]
1325 bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
1326 bool e = canWalkTo(creaturePos, DIRECTION_EAST);
1327
1328 if (s && e) {
1329 direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST;
1330 return true;
1331 } else if (s) {
1332 direction = DIRECTION_SOUTH;
1333 return true;
1334 } else if (e) {
1335 direction = DIRECTION_EAST;
1336 return true;
1337 } else if (canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) {
1338 direction = DIRECTION_SOUTHEAST;
1339 return true;
1340 }
1341
1342 /* fleeing */
1343 bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
1344 bool w = canWalkTo(creaturePos, DIRECTION_WEST);
1345
1346 if (flee) {
1347 if (n && w) {
1348 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_WEST;
1349 return true;
1350 } else if (n) {
1351 direction = DIRECTION_NORTH;
1352 return true;
1353 } else if (w) {
1354 direction = DIRECTION_WEST;
1355 return true;
1356 }
1357 }
1358
1359 /* end of fleeing */
1360
1361 if (w && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) {
1362 direction = DIRECTION_WEST;
1363 } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) {
1364 direction = DIRECTION_NORTH;
1365 }
1366
1367 return true;
1368 } else if (offsetx <= -1 && offsety <= -1) {
1369 // player is SE
1370 // escape to NW , W or N [and some extra]
1371 bool w = canWalkTo(creaturePos, DIRECTION_WEST);
1372 bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
1373
1374 if (w && n) {
1375 direction = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH;
1376 return true;
1377 } else if (w) {
1378 direction = DIRECTION_WEST;
1379 return true;
1380 } else if (n) {
1381 direction = DIRECTION_NORTH;
1382 return true;
1383 }
1384
1385 if (canWalkTo(creaturePos, DIRECTION_NORTHWEST)) {
1386 direction = DIRECTION_NORTHWEST;
1387 return true;
1388 }
1389
1390 /* fleeing */
1391 bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
1392 bool e = canWalkTo(creaturePos, DIRECTION_EAST);
1393
1394 if (flee) {
1395 if (s && e) {
1396 direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST;
1397 return true;
1398 } else if (s) {
1399 direction = DIRECTION_SOUTH;
1400 return true;
1401 } else if (e) {
1402 direction = DIRECTION_EAST;
1403 return true;
1404 }
1405 }
1406
1407 /* end of fleeing */
1408
1409 if (s && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) {
1410 direction = DIRECTION_SOUTH;
1411 } else if (e && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) {
1412 direction = DIRECTION_EAST;
1413 }
1414
1415 return true;
1416 } else if (offsetx >= 1 && offsety <= -1) {
1417 // player is SW
1418 // escape to NE, N, E [and some extra]
1419 bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
1420 bool e = canWalkTo(creaturePos, DIRECTION_EAST);
1421 if (n && e) {
1422 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST;
1423 return true;
1424 } else if (n) {
1425 direction = DIRECTION_NORTH;
1426 return true;
1427 } else if (e) {
1428 direction = DIRECTION_EAST;
1429 return true;
1430 }
1431
1432 if (canWalkTo(creaturePos, DIRECTION_NORTHEAST)) {
1433 direction = DIRECTION_NORTHEAST;
1434 return true;
1435 }
1436
1437 /* fleeing */
1438 bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
1439 bool w = canWalkTo(creaturePos, DIRECTION_WEST);
1440
1441 if (flee) {
1442 if (s && w) {
1443 direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_WEST;
1444 return true;
1445 } else if (s) {
1446 direction = DIRECTION_SOUTH;
1447 return true;
1448 } else if (w) {
1449 direction = DIRECTION_WEST;
1450 return true;
1451 }
1452 }
1453
1454 /* end of fleeing */
1455
1456 if (w && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) {
1457 direction = DIRECTION_WEST;
1458 } else if (s && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) {
1459 direction = DIRECTION_SOUTH;
1460 }
1461
1462 return true;
1463 } else if (offsetx <= -1 && offsety >= 1) {
1464 // player is NE
1465 // escape to SW, S, W [and some extra]
1466 bool w = canWalkTo(creaturePos, DIRECTION_WEST);
1467 bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
1468 if (w && s) {
1469 direction = boolean_random() ? DIRECTION_WEST : DIRECTION_SOUTH;
1470 return true;
1471 } else if (w) {
1472 direction = DIRECTION_WEST;
1473 return true;
1474 } else if (s) {
1475 direction = DIRECTION_SOUTH;
1476 return true;
1477 } else if (canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) {
1478 direction = DIRECTION_SOUTHWEST;
1479 return true;
1480 }
1481
1482 /* fleeing */
1483 bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
1484 bool e = canWalkTo(creaturePos, DIRECTION_EAST);
1485
1486 if (flee) {
1487 if (n && e) {
1488 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST;
1489 return true;
1490 } else if (n) {
1491 direction = DIRECTION_NORTH;
1492 return true;
1493 } else if (e) {
1494 direction = DIRECTION_EAST;
1495 return true;
1496 }
1497 }
1498
1499 /* end of fleeing */
1500
1501 if (e && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) {
1502 direction = DIRECTION_EAST;
1503 } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) {
1504 direction = DIRECTION_NORTH;
1505 }
1506
1507 return true;
1508 }
1509 }
1510
1511 // Now let's decide where the player is located to the monster (what
1512 // direction) so we can decide where to escape.
1513 if (dy > dx) {
1514 Direction playerDir = offsety < 0 ? DIRECTION_SOUTH : DIRECTION_NORTH;
1515 switch (playerDir) {
1516 case DIRECTION_NORTH: {
1517 // Player is to the NORTH, so obviously we need to check if we can go
1518 // SOUTH, if not then let's choose WEST or EAST and again if we can't we
1519 // need to decide about some diagonal movements.
1520 if (canWalkTo(creaturePos, DIRECTION_SOUTH)) {
1521 direction = DIRECTION_SOUTH;
1522 return true;
1523 }
1524
1525 bool w = canWalkTo(creaturePos, DIRECTION_WEST);
1526 bool e = canWalkTo(creaturePos, DIRECTION_EAST);
1527 if (w && e && offsetx == 0) {
1528 direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
1529 return true;
1530 } else if (w && offsetx <= 0) {
1531 direction = DIRECTION_WEST;
1532 return true;
1533 } else if (e && offsetx >= 0) {
1534 direction = DIRECTION_EAST;
1535 return true;
1536 }
1537
1538 /* fleeing */
1539 if (flee) {
1540 if (w && e) {
1541 direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
1542 return true;
1543 } else if (w) {
1544 direction = DIRECTION_WEST;
1545 return true;
1546 } else if (e) {
1547 direction = DIRECTION_EAST;
1548 return true;
1549 }
1550 }
1551
1552 /* end of fleeing */
1553
1554 bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST);
1555 bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST);
1556 if (sw || se) {
1557 // we can move both dirs
1558 if (sw && se) {
1559 direction =
1560 boolean_random() ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST;
1561 } else if (w) {
1562 direction = DIRECTION_WEST;
1563 } else if (sw) {
1564 direction = DIRECTION_SOUTHWEST;
1565 } else if (e) {
1566 direction = DIRECTION_EAST;
1567 } else if (se) {
1568 direction = DIRECTION_SOUTHEAST;
1569 }
1570 return true;
1571 }
1572
1573 /* fleeing */
1574 if (flee && canWalkTo(creaturePos, DIRECTION_NORTH)) {
1575 // towards player, yea
1576 direction = DIRECTION_NORTH;
1577 return true;
1578 }
1579
1580 /* end of fleeing */
1581 break;
1582 }
1583
1584 case DIRECTION_SOUTH: {
1585 if (canWalkTo(creaturePos, DIRECTION_NORTH)) {
1586 direction = DIRECTION_NORTH;
1587 return true;
1588 }
1589
1590 bool w = canWalkTo(creaturePos, DIRECTION_WEST);
1591 bool e = canWalkTo(creaturePos, DIRECTION_EAST);
1592 if (w && e && offsetx == 0) {
1593 direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
1594 return true;
1595 } else if (w && offsetx <= 0) {
1596 direction = DIRECTION_WEST;
1597 return true;
1598 } else if (e && offsetx >= 0) {
1599 direction = DIRECTION_EAST;
1600 return true;
1601 }
1602
1603 /* fleeing */
1604 if (flee) {
1605 if (w && e) {
1606 direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
1607 return true;
1608 } else if (w) {
1609 direction = DIRECTION_WEST;
1610 return true;
1611 } else if (e) {
1612 direction = DIRECTION_EAST;
1613 return true;
1614 }
1615 }
1616
1617 /* end of fleeing */
1618
1619 bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST);
1620 bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST);
1621 if (nw || ne) {
1622 // we can move both dirs
1623 if (nw && ne) {
1624 direction =
1625 boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST;
1626 } else if (w) {
1627 direction = DIRECTION_WEST;
1628 } else if (nw) {
1629 direction = DIRECTION_NORTHWEST;
1630 } else if (e) {
1631 direction = DIRECTION_EAST;
1632 } else if (ne) {
1633 direction = DIRECTION_NORTHEAST;
1634 }
1635 return true;
1636 }
1637
1638 /* fleeing */
1639 if (flee && canWalkTo(creaturePos, DIRECTION_SOUTH)) {
1640 // towards player, yea
1641 direction = DIRECTION_SOUTH;
1642 return true;
1643 }
1644
1645 /* end of fleeing */
1646 break;
1647 }
1648
1649 default:
1650 break;
1651 }
1652 } else {
1653 Direction playerDir = offsetx < 0 ? DIRECTION_EAST : DIRECTION_WEST;
1654 switch (playerDir) {
1655 case DIRECTION_WEST: {
1656 if (canWalkTo(creaturePos, DIRECTION_EAST)) {
1657 direction = DIRECTION_EAST;
1658 return true;
1659 }
1660
1661 bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
1662 bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
1663 if (n && s && offsety == 0) {
1664 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
1665 return true;
1666 } else if (n && offsety <= 0) {
1667 direction = DIRECTION_NORTH;
1668 return true;
1669 } else if (s && offsety >= 0) {
1670 direction = DIRECTION_SOUTH;
1671 return true;
1672 }
1673
1674 /* fleeing */
1675 if (flee) {
1676 if (n && s) {
1677 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
1678 return true;
1679 } else if (n) {
1680 direction = DIRECTION_NORTH;
1681 return true;
1682 } else if (s) {
1683 direction = DIRECTION_SOUTH;
1684 return true;
1685 }
1686 }
1687
1688 /* end of fleeing */
1689
1690 bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST);
1691 bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST);
1692 if (se || ne) {
1693 if (se && ne) {
1694 direction =
1695 boolean_random() ? DIRECTION_SOUTHEAST : DIRECTION_NORTHEAST;
1696 } else if (s) {
1697 direction = DIRECTION_SOUTH;
1698 } else if (se) {
1699 direction = DIRECTION_SOUTHEAST;
1700 } else if (n) {
1701 direction = DIRECTION_NORTH;
1702 } else if (ne) {
1703 direction = DIRECTION_NORTHEAST;
1704 }
1705 return true;
1706 }
1707
1708 /* fleeing */
1709 if (flee && canWalkTo(creaturePos, DIRECTION_WEST)) {
1710 // towards player, yea
1711 direction = DIRECTION_WEST;
1712 return true;
1713 }
1714
1715 /* end of fleeing */
1716 break;
1717 }
1718
1719 case DIRECTION_EAST: {
1720 if (canWalkTo(creaturePos, DIRECTION_WEST)) {
1721 direction = DIRECTION_WEST;
1722 return true;
1723 }
1724
1725 bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
1726 bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
1727 if (n && s && offsety == 0) {
1728 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
1729 return true;
1730 } else if (n && offsety <= 0) {
1731 direction = DIRECTION_NORTH;
1732 return true;
1733 } else if (s && offsety >= 0) {
1734 direction = DIRECTION_SOUTH;
1735 return true;
1736 }
1737
1738 /* fleeing */
1739 if (flee) {
1740 if (n && s) {
1741 direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
1742 return true;
1743 } else if (n) {
1744 direction = DIRECTION_NORTH;
1745 return true;
1746 } else if (s) {
1747 direction = DIRECTION_SOUTH;
1748 return true;
1749 }
1750 }
1751
1752 /* end of fleeing */
1753
1754 bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST);
1755 bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST);
1756 if (nw || sw) {
1757 if (nw && sw) {
1758 direction =
1759 boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_SOUTHWEST;
1760 } else if (n) {
1761 direction = DIRECTION_NORTH;
1762 } else if (nw) {
1763 direction = DIRECTION_NORTHWEST;
1764 } else if (s) {
1765 direction = DIRECTION_SOUTH;
1766 } else if (sw) {
1767 direction = DIRECTION_SOUTHWEST;
1768 }
1769 return true;
1770 }
1771
1772 /* fleeing */
1773 if (flee && canWalkTo(creaturePos, DIRECTION_EAST)) {
1774 // towards player, yea
1775 direction = DIRECTION_EAST;
1776 return true;
1777 }
1778
1779 /* end of fleeing */
1780 break;
1781 }
1782
1783 default:
1784 break;
1785 }
1786 }
1787
1788 return true;
1789}
1790
1791bool Monster::canWalkTo(Position pos, Direction direction) const {
1792 pos = getNextPosition(direction, pos);
1793 if (isInSpawnRange(pos)) {
1794 if (getWalkCache(pos) == 0) {
1795 return false;
1796 }
1797
1798 Tile *tile = g_game.map.getTile(pos);
1799 if (tile && tile->getTopVisibleCreature(this) == nullptr &&
1800 tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR) {
1801 return true;
1802 }
1803 }
1804 return false;
1805}
1806
1807void Monster::death(Creature *) {
1808 setAttackedCreature(nullptr);
1809
1810 for (Creature *summon : summons) {
1811 summon->changeHealth(-summon->getHealth());
1812 summon->removeMaster();
1813 }
1814 summons.clear();
1815
1816 clearTargetList();
1817 clearFriendList();
1818 onIdleStatus();
1819}
1820
1821Item *Monster::getCorpse(Creature *lastHitCreature,
1822 Creature *mostDamageCreature) {
1823 Item *corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature);
1824 if (corpse) {
1825 if (mostDamageCreature) {
1826 if (mostDamageCreature->getPlayer()) {
1827 corpse->setCorpseOwner(mostDamageCreature->getID());
1828 } else {
1829 const Creature *mostDamageCreatureMaster =
1830 mostDamageCreature->getMaster();
1831 if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) {
1832 corpse->setCorpseOwner(mostDamageCreatureMaster->getID());
1833 }
1834 }
1835 }
1836 }
1837 return corpse;
1838}
1839
1840bool Monster::isInSpawnRange(const Position &pos) const {
1841 if (!spawn) {
1842 return true;
1843 }
1844
1845 if (Monster::despawnRadius == 0) {
1846 return true;
1847 }
1848
1849 if (!Spawns::isInZone(masterPos, Monster::despawnRadius, pos)) {
1850 return false;
1851 }
1852
1853 if (Monster::despawnRange == 0) {
1854 return true;
1855 }
1856
1857 if (Position::getDistanceZ(pos, masterPos) > Monster::despawnRange) {
1858 return false;
1859 }
1860
1861 return true;
1862}
1863
1864bool Monster::getCombatValues(int32_t &min, int32_t &max) {
1865 if (minCombatValue == 0 && maxCombatValue == 0) {
1866 return false;
1867 }
1868
1869 min = minCombatValue;
1870 max = maxCombatValue;
1871 return true;
1872}
1873
1874void Monster::updateLookDirection() {
1875 if (attackedCreature) {
1876 const Position &myPos = getPosition();
1877 const Position &targetPos = attackedCreature->getPosition();
1878
1879 int32_t diffX = targetPos.x - myPos.x;
1880 int32_t diffY = targetPos.y - myPos.y;
1881
1882 Direction dir = DIRECTION_NONE;
1883 int32_t absX = std::abs(diffX);
1884 int32_t absY = std::abs(diffY);
1885
1886 if (absX > 0 || absY > 0) {
1887 if (absX > absY * 2) {
1888 dir = (diffX > 0) ? DIRECTION_EAST : DIRECTION_WEST;
1889 } else if (absY > absX * 2) {
1890 dir = (diffY > 0) ? DIRECTION_SOUTH : DIRECTION_NORTH;
1891 } else {
1892 if (diffX > 0 && diffY < 0)
1893 dir = DIRECTION_NORTHEAST;
1894 else if (diffX > 0 && diffY > 0)
1895 dir = DIRECTION_SOUTHEAST;
1896 else if (diffX < 0 && diffY < 0)
1897 dir = DIRECTION_NORTHWEST;
1898 else if (diffX < 0 && diffY > 0)
1899 dir = DIRECTION_SOUTHWEST;
1900 }
1901 }
1902
1903 if (dir != DIRECTION_NONE) {
1904 g_game.internalCreatureTurn(this, dir);
1905 }
1906 }
1907}
1908
1909void Monster::dropLoot(Container *corpse, Creature *lastHit) {
1910 if (corpse && lootDrop) {
1911 g_events->eventMonsterOnDropLoot(this, corpse);
1912
1913 if (lastHit) {
1914 Player *player = lastHit->getPlayer();
1915 if (!player) {
1916 Creature* master = lastHit->getMaster();
1917 if (master) player = master->getPlayer();
1918 }
1919
1920 if (player) {
1921 RuneEffect* activeEffect = g_game.runeEffects.getRuneEffectByID(player->getCurrentRuneEffect());
1922 if (activeEffect && activeEffect->bonusLoot > 0) {
1923 const ItemDeque& items = corpse->getItemList();
1924 size_t itemCount = items.size();
1925 for (size_t i = 0; i < itemCount; ++i) {
1926 Item* item = items[i];
1927 if (item && uniform_random(1, 100) <= activeEffect->bonusLoot) {
1928 Item* bonusItem = Item::CreateItem(item->getID(), item->getSubType());
1929 if (bonusItem) {
1930 bonusItem->setItemCount(item->getItemCount());
1931 g_game.internalAddItem(corpse, bonusItem, INDEX_WHEREEVER, FLAG_NOLIMIT);
1932 }
1933 }
1934 }
1935 }
1936 }
1937 }
1938 }
1939}
1940
1941void Monster::setNormalCreatureLight() { internalLight = mType->info.light; }
1942
1943void Monster::drainHealth(Creature *attacker, int32_t damage) {
1944 Creature::drainHealth(attacker, damage);
1945
1946 if (damage > 0) {
1947 if (randomStepping) {
1948 ignoreFieldDamage = true;
1949 updateMapCache();
1950 }
1951 }
1952
1953 if (isInvisible()) {
1954 removeCondition(CONDITION_INVISIBLE);
1955 }
1956}
1957
1958void Monster::changeHealth(int32_t healthChange,
1959 bool sendHealthChange /* = true*/) {
1960 // In case a player with ignore flag set attacks the monster
1961 setIdle(false);
1962 Creature::changeHealth(healthChange, sendHealthChange);
1963}
1964
1965bool Monster::challengeCreature(Creature *creature) {
1966 if (isSummon()) {
1967 return false;
1968 }
1969
1970 bool result = selectTarget(creature);
1971 if (result) {
1972 targetChangeCooldown = 8000;
1973 challengeFocusDuration = targetChangeCooldown;
1974 targetChangeTicks = 0;
1975 }
1976 return result;
1977}
1978
1979void Monster::getPathSearchParams(const Creature *creature,
1980 FindPathParams &fpp) const {
1981 Creature::getPathSearchParams(creature, fpp);
1982
1983 fpp.minTargetDist = 1;
1984 fpp.maxTargetDist = mType->info.targetDistance;
1985
1986 if (mType->info.lookAttack.attackRange > 1) {
1987 fpp.maxTargetDist = mType->info.lookAttack.attackRange;
1988 }
1989
1990 if (isSummon()) {
1991 if (getMaster() == creature) {
1992 fpp.maxTargetDist = 2;
1993 fpp.fullPathSearch = true;
1994 } else if (mType->info.targetDistance <= 1) {
1995 fpp.fullPathSearch = true;
1996 } else {
1997 fpp.fullPathSearch = !canUseAttack(getPosition(), creature);
1998 }
1999 } else if (isFleeing()) {
2000 // Distance should be higher than the client view range
2001 // (Map::maxClientViewportX/Map::maxClientViewportY)
2002 fpp.maxTargetDist = Map::maxViewportX;
2003 fpp.clearSight = false;
2004 fpp.keepDistance = true;
2005 fpp.fullPathSearch = false;
2006 } else if (mType->info.targetDistance <= 1) {
2007 fpp.fullPathSearch = true;
2008 } else {
2009 fpp.fullPathSearch = !canUseAttack(getPosition(), creature);
2010 }
2011}
2012
2013uint16_t Monster::getAttackOutfitLookType() const {
2014 if (attackOutfit.lookType != 0) {
2015 return attackOutfit.lookType;
2016 }
2017
2018 if (attackedCreature) {
2019 const Position &myPos = getPosition();
2020 const Position &targetPos = attackedCreature->getPosition();
2021 int32_t dx = std::abs(static_cast<int32_t>(myPos.x) -
2022 static_cast<int32_t>(targetPos.x));
2023 int32_t dy = std::abs(static_cast<int32_t>(myPos.y) -
2024 static_cast<int32_t>(targetPos.y));
2025 uint32_t dist =
2026 std::max<uint32_t>(static_cast<uint32_t>(dx), static_cast<uint32_t>(dy));
2027
2028 if (dist <= 1) {
2029 if (mType->info.lookAttackMelee.lookType != 0) {
2030 return mType->info.lookAttackMelee.lookType;
2031 }
2032 return mType->info.lookAttack.lookType;
2033 }
2034
2035 if (mType->info.lookAttackRange.lookType != 0 &&
2036 dist <= mType->info.lookAttackRange.attackRange) {
2037 return mType->info.lookAttackRange.lookType;
2038 }
2039
2040 // Fallback to melee or generic attack outfit if within its range
2041 if (mType->info.lookAttackMelee.lookType != 0 &&
2042 dist <= mType->info.lookAttackMelee.attackRange) {
2043 return mType->info.lookAttackMelee.lookType;
2044 }
2045 }
2046
2047 return mType->info.lookAttack.lookType;
2048}
2049
Top