/** * Tibia GIMUD Server - a free and open-source MMORPG server emulator * Copyright (C) 2019 Sabrehaven and Mark Samman * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "otpch.h" #include "monster.h" #include "game.h" #include "spells.h" #include "configmanager.h" extern ConfigManager g_config; extern Game g_game; extern Monsters g_monsters; int32_t Monster::despawnRange; int32_t Monster::despawnRadius; uint32_t Monster::monsterAutoID = 0x40000000; Monster* Monster::createMonster(const std::string& name) { MonsterType* mType = g_monsters.getMonsterType(name); if (!mType) { return nullptr; } return new Monster(mType); } Monster::Monster(MonsterType* mtype) : Creature(), strDescription(asLowerCaseString(mtype->nameDescription)), mType(mtype) { defaultOutfit = mType->info.outfit; currentOutfit = mType->info.outfit; skull = mType->info.skull; health = mType->info.health; healthMax = mType->info.healthMax; baseSpeed = mType->info.baseSpeed; internalLight = mType->info.light; hiddenHealth = mType->info.hiddenHealth; // register creature events for (const std::string& scriptName : mType->info.scripts) { if (!registerCreatureEvent(scriptName)) { std::cout << "[Warning - Monster::Monster] Unknown event name: " << scriptName << std::endl; } } } Monster::~Monster() { clearTargetList(); clearFriendList(); } void Monster::addList() { g_game.addMonster(this); } void Monster::removeList() { g_game.removeMonster(this); } int32_t Monster::getDefense() { int32_t totalDefense = mType->info.defense + 1; int32_t defenseSkill = mType->info.skill; fightMode_t attackMode = FIGHTMODE_BALANCED; if ((followCreature || !attackedCreature) && earliestAttackTime <= OTSYS_TIME()) { attackMode = FIGHTMODE_DEFENSE; } if (attackMode == FIGHTMODE_ATTACK) { totalDefense -= 4 * totalDefense / 10; } else if (attackMode == FIGHTMODE_DEFENSE) { totalDefense += 8 * totalDefense / 10; } if (totalDefense) { int32_t formula = (5 * (defenseSkill) + 50) * totalDefense; int32_t randresult = rand() % 100; totalDefense = formula * ((rand() % 100 + randresult) / 2) / 10000.; } return totalDefense; } bool Monster::canSee(const Position& pos) const { return Creature::canSee(getPosition(), pos, 9, 9); } void Monster::onAttackedCreature(Creature* creature) { if (isSummon() && getMaster()) { master->onAttackedCreature(creature); } } void Monster::onAttackedCreatureDisappear(bool) { extraMeleeAttack = true; } void Monster::onCreatureAppear(Creature* creature, bool isLogin) { Creature::onCreatureAppear(creature, isLogin); if (mType->info.creatureAppearEvent != -1) { // onCreatureAppear(self, creature) LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Monster::onCreatureAppear] Call stack overflow" << std::endl; return; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(mType->info.creatureAppearEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(mType->info.creatureAppearEvent); LuaScriptInterface::pushUserdata(L, this); LuaScriptInterface::setMetatable(L, -1, "Monster"); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); if (scriptInterface->callFunction(2)) { return; } } if (creature == this) { //We just spawned lets look around to see who is there. if (isSummon()) { isMasterInRange = canSee(getMaster()->getPosition()); } updateTargetList(); updateIdleStatus(); } else { onCreatureEnter(creature); } } void Monster::onRemoveCreature(Creature* creature, bool isLogout) { Creature::onRemoveCreature(creature, isLogout); if (mType->info.creatureDisappearEvent != -1) { // onCreatureDisappear(self, creature) LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Monster::onCreatureDisappear] Call stack overflow" << std::endl; return; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(mType->info.creatureDisappearEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(mType->info.creatureDisappearEvent); LuaScriptInterface::pushUserdata(L, this); LuaScriptInterface::setMetatable(L, -1, "Monster"); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); if (scriptInterface->callFunction(2)) { return; } } if (creature == this) { if (spawn) { spawn->startSpawnCheck(); } setIdle(true); } else { onCreatureLeave(creature); } } void Monster::onCreatureMove(Creature* creature, const Tile* newTile, const Position& newPos, const Tile* oldTile, const Position& oldPos, bool teleport) { Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, teleport); if (mType->info.creatureMoveEvent != -1) { // onCreatureMove(self, creature, oldPosition, newPosition) LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Monster::onCreatureMove] Call stack overflow" << std::endl; return; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(mType->info.creatureMoveEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(mType->info.creatureMoveEvent); LuaScriptInterface::pushUserdata(L, this); LuaScriptInterface::setMetatable(L, -1, "Monster"); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); LuaScriptInterface::pushPosition(L, oldPos); LuaScriptInterface::pushPosition(L, newPos); if (scriptInterface->callFunction(4)) { return; } } if (creature == this) { if (isSummon()) { isMasterInRange = canSee(getMaster()->getPosition()); } updateTargetList(); updateIdleStatus(); } else { bool canSeeNewPos = canSee(newPos); bool canSeeOldPos = canSee(oldPos); if (canSeeNewPos && !canSeeOldPos) { onCreatureEnter(creature); } else if (!canSeeNewPos && canSeeOldPos) { onCreatureLeave(creature); } if (canSeeNewPos && isSummon() && getMaster() == creature) { isMasterInRange = true; //Follow master again } updateIdleStatus(); if (!isSummon()) { if (followCreature) { const Position& followPosition = followCreature->getPosition(); const Position& position = getPosition(); int32_t offset_x = Position::getDistanceX(followPosition, position); int32_t offset_y = Position::getDistanceY(followPosition, position); if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0 && targetChangeCooldown <= 0) { Direction dir = getDirectionTo(position, followPosition); const Position& checkPosition = getNextPosition(dir, position); Tile* tile = g_game.map.getTile(checkPosition); if (tile) { Creature* topCreature = tile->getTopCreature(); if (topCreature && followCreature != topCreature && isOpponent(topCreature)) { selectTarget(topCreature); } } } } else if (isOpponent(creature)) { //we have no target lets try pick this one selectTarget(creature); } } } } void Monster::onCreatureSay(Creature* creature, SpeakClasses type, const std::string& text) { Creature::onCreatureSay(creature, type, text); if (mType->info.creatureSayEvent != -1) { // onCreatureSay(self, creature, type, message) LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Monster::onCreatureSay] Call stack overflow" << std::endl; return; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(mType->info.creatureSayEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(mType->info.creatureSayEvent); LuaScriptInterface::pushUserdata(L, this); LuaScriptInterface::setMetatable(L, -1, "Monster"); LuaScriptInterface::pushUserdata(L, creature); LuaScriptInterface::setCreatureMetatable(L, -1, creature); lua_pushnumber(L, type); LuaScriptInterface::pushString(L, text); scriptInterface->callVoidFunction(4); } } void Monster::addFriend(Creature* creature) { assert(creature != this); auto result = friendList.insert(creature); if (result.second) { creature->incrementReferenceCounter(); } } void Monster::removeFriend(Creature* creature) { auto it = friendList.find(creature); if (it != friendList.end()) { creature->decrementReferenceCounter(); friendList.erase(it); } } void Monster::addTarget(Creature* creature, bool pushFront/* = false*/) { assert(creature != this); if (std::find(targetList.begin(), targetList.end(), creature) == targetList.end()) { creature->incrementReferenceCounter(); if (pushFront) { targetList.push_front(creature); } else { targetList.push_back(creature); } } } void Monster::removeTarget(Creature* creature) { auto it = std::find(targetList.begin(), targetList.end(), creature); if (it != targetList.end()) { creature->decrementReferenceCounter(); targetList.erase(it); } } void Monster::updateTargetList() { auto friendIterator = friendList.begin(); while (friendIterator != friendList.end()) { Creature* creature = *friendIterator; if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { creature->decrementReferenceCounter(); friendIterator = friendList.erase(friendIterator); } else { ++friendIterator; } } auto targetIterator = targetList.begin(); while (targetIterator != targetList.end()) { Creature* creature = *targetIterator; if (creature->getHealth() <= 0 || !canSee(creature->getPosition())) { creature->decrementReferenceCounter(); targetIterator = targetList.erase(targetIterator); } else { ++targetIterator; } } SpectatorVec list; g_game.map.getSpectators(list, position, true); list.erase(this); for (Creature* spectator : list) { if (canSee(spectator->getPosition())) { onCreatureFound(spectator); } } } void Monster::clearTargetList() { for (Creature* creature : targetList) { creature->decrementReferenceCounter(); } targetList.clear(); } void Monster::clearFriendList() { for (Creature* creature : friendList) { creature->decrementReferenceCounter(); } friendList.clear(); } void Monster::onCreatureFound(Creature* creature, bool pushFront/* = false*/) { if (isFriend(creature)) { addFriend(creature); } if (isOpponent(creature)) { addTarget(creature, pushFront); } updateIdleStatus(); } void Monster::onCreatureEnter(Creature* creature) { // std::cout << "onCreatureEnter - " << creature->getName() << std::endl; if (getMaster() == creature) { //Follow master again isMasterInRange = true; } onCreatureFound(creature, true); } bool Monster::isFriend(const Creature* creature) const { if (isSummon() && getMaster()->getPlayer()) { const Player* masterPlayer = getMaster()->getPlayer(); const Player* tmpPlayer = nullptr; if (creature->getPlayer()) { tmpPlayer = creature->getPlayer(); } else { const Creature* creatureMaster = creature->getMaster(); if (creatureMaster && creatureMaster->getPlayer()) { tmpPlayer = creatureMaster->getPlayer(); } } if (tmpPlayer && (tmpPlayer == getMaster() || masterPlayer->isPartner(tmpPlayer))) { return true; } } else if (creature->getMonster() && !creature->isSummon()) { return true; } return false; } bool Monster::isOpponent(const Creature* creature) const { if (isSummon() && getMaster()->getPlayer()) { if (creature != getMaster()) { return true; } } else { if ((creature->getPlayer() && !creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) || (creature->getMaster() && creature->getMaster()->getPlayer())) { return true; } } return false; } void Monster::onCreatureLeave(Creature* creature) { // std::cout << "onCreatureLeave - " << creature->getName() << std::endl; if (getMaster() == creature) { //Take random steps and only use defense abilities (e.g. heal) until its master comes back isMasterInRange = false; } //update friendList if (isFriend(creature)) { removeFriend(creature); } //update targetList if (isOpponent(creature)) { removeTarget(creature); if (targetList.empty()) { updateIdleStatus(); } } } bool Monster::searchTarget(TargetSearchType_t searchType) { std::list resultList; const Position& myPos = getPosition(); for (Creature* creature : targetList) { if (followCreature != creature && isTarget(creature)) { if (searchType == TARGETSEARCH_ANY || canUseAttack(myPos, creature)) { resultList.push_back(creature); } } } switch (searchType) { case TARGETSEARCH_NEAREST: { Creature* target = nullptr; int32_t minRange = 0; if (attackedCreature) { const Position& targetPosition = attackedCreature->getPosition(); minRange = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); } if (!resultList.empty()) { for (Creature* creature : resultList) { const Position& targetPosition = creature->getPosition(); int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); if (distance < minRange) { target = creature; minRange = distance; } } } else { for (Creature* creature : targetList) { if (!isTarget(creature)) { continue; } const Position& targetPosition = creature->getPosition(); int32_t distance = Position::getDistanceX(myPos, targetPosition) + Position::getDistanceY(myPos, targetPosition); if (distance < minRange) { target = creature; minRange = distance; } } } if (target && selectTarget(target)) { return true; } break; } case TARGETSEARCH_WEAKEST: { Creature* target = nullptr; int32_t health = 0; if (attackedCreature) { health = attackedCreature->getMaxHealth(); } if (!resultList.empty()) { for (Creature* creature : resultList) { if (creature->getMaxHealth() < health) { target = creature; health = creature->getMaxHealth(); } } } else { for (Creature* creature : targetList) { if (creature->getMaxHealth() < health) { target = creature; health = creature->getMaxHealth(); } } } if (target && selectTarget(target)) { return true; } break; } case TARGETSEARCH_MOSTDAMAGE: { Creature* target = nullptr; int32_t maxDamage = 0; if (!resultList.empty()) { for (Creature* creature : resultList) { auto it = damageMap.find(creature->getID()); if (it == damageMap.end()) { continue; } int32_t damage = it->second.total; if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { if (damage > maxDamage) { target = creature; maxDamage = damage; } } } } else { for (Creature* creature : targetList) { auto it = damageMap.find(creature->getID()); if (it == damageMap.end()) { continue; } int32_t damage = it->second.total; if (OTSYS_TIME() - it->second.ticks <= g_config.getNumber(ConfigManager::PZ_LOCKED)) { if (damage > maxDamage) { target = creature; maxDamage = damage; } } } } if (target && selectTarget(target)) { return true; } break; } default: { if (!resultList.empty()) { auto it = resultList.begin(); std::advance(it, uniform_random(0, resultList.size() - 1)); return selectTarget(*it); } break; } } //lets just pick the first target in the list if we do not have a target if (!attackedCreature) { for (Creature* target : targetList) { if (followCreature != target && selectTarget(target)) { return true; } } } return false; } void Monster::onFollowCreatureComplete(const Creature* creature) { if (creature) { auto it = std::find(targetList.begin(), targetList.end(), creature); if (it != targetList.end()) { Creature* target = (*it); targetList.erase(it); if (hasFollowPath) { targetList.push_front(target); } else if (!isSummon()) { targetList.push_back(target); } else { target->decrementReferenceCounter(); } } } } BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage, bool checkDefense /* = false*/, bool checkArmor /* = false*/, bool /* field = false */) { BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor); if (damage != 0) { int32_t elementMod = 0; auto it = mType->info.elementMap.find(combatType); if (it != mType->info.elementMap.end()) { elementMod = it->second; } if (elementMod != 0) { damage = static_cast(std::round(damage * ((100 - elementMod) / 100.))); if (damage <= 0) { damage = 0; blockType = BLOCK_ARMOR; } } } return blockType; } bool Monster::isTarget(const Creature* creature) const { if (creature->isRemoved() || !creature->isAttackable() || creature->getZone() == ZONE_PROTECTION || !canSeeCreature(creature)) { return false; } if (creature->getPosition().z != getPosition().z) { return false; } return true; } bool Monster::selectTarget(Creature* creature) { if (!isTarget(creature)) { return false; } auto it = std::find(targetList.begin(), targetList.end(), creature); if (it == targetList.end()) { //Target not found in our target list. return false; } if (isHostile() || isSummon()) { if (setAttackedCreature(creature) && !isSummon()) { g_dispatcher.addTask(createTask(std::bind(&Game::checkCreatureAttack, &g_game, getID()))); } } // without this task, monster would randomly start dancing until next game round g_dispatcher.addTask(createTask(std::bind(&Game::updateCreatureWalk, &g_game, getID()))); targetChangeCooldown += 3000; return setFollowCreature(creature); } void Monster::setIdle(bool idle) { if (isRemoved() || getHealth() <= 0) { return; } isIdle = idle; if (!isIdle) { g_game.addCreatureCheck(this); } else { onIdleStatus(); clearTargetList(); clearFriendList(); Game::removeCreatureCheck(this); } } void Monster::updateIdleStatus() { bool idle = false; if (conditions.empty()) { if (!isSummon() && targetList.empty()) { idle = true; } } setIdle(idle); } void Monster::onAddCondition(ConditionType_t type) { if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { updateMapCache(); } updateIdleStatus(); } void Monster::onEndCondition(ConditionType_t type) { if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON || type == CONDITION_AGGRESSIVE) { updateMapCache(); } updateIdleStatus(); } void Monster::onThink(uint32_t interval) { if (OTSYS_TIME() < earliestWakeUpTime) { return; } Creature::onThink(interval); if (mType->info.thinkEvent != -1) { // onThink(self, interval) LuaScriptInterface* scriptInterface = mType->info.scriptInterface; if (!scriptInterface->reserveScriptEnv()) { std::cout << "[Error - Monster::onThink] Call stack overflow" << std::endl; return; } ScriptEnvironment* env = scriptInterface->getScriptEnv(); env->setScriptId(mType->info.thinkEvent, scriptInterface); lua_State* L = scriptInterface->getLuaState(); scriptInterface->pushFunction(mType->info.thinkEvent); LuaScriptInterface::pushUserdata(L, this); LuaScriptInterface::setMetatable(L, -1, "Monster"); lua_pushnumber(L, interval); if (scriptInterface->callFunction(2)) { return; } } if (!isInSpawnRange(position) || (lifetime > 0 && (OTSYS_TIME() >= lifetime))) { // Despawn creatures if they are out of their spawn zone g_game.removeCreature(this); g_game.addMagicEffect(getPosition(), CONST_ME_POFF); } else { updateIdleStatus(); if (!isIdle) { addEventWalk(); if (isSummon()) { if (!attackedCreature) { if (getMaster() && getMaster()->getAttackedCreature()) { selectTarget(getMaster()->getAttackedCreature()); } else if (getMaster() != followCreature) { //Our master has not ordered us to attack anything, lets follow him around instead. setFollowCreature(getMaster()); } } else if (attackedCreature == this) { setFollowCreature(nullptr); } else if (followCreature != attackedCreature) { setFollowCreature(attackedCreature); } if (master) { if (Monster* monster = master->getMonster()) { if (monster->mType->info.targetDistance <= 1 && !monster->hasFollowPath) { setFollowCreature(master); setAttackedCreature(nullptr); } } } } else if (!targetList.empty()) { if (!followCreature || !hasFollowPath) { searchTarget(TARGETSEARCH_ANY); } else if (isFleeing()) { if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) { searchTarget(TARGETSEARCH_NEAREST); } } } onThinkTarget(interval); onThinkYell(interval); onThinkDefense(interval); } } } void Monster::doExtraMeleeAttack() { if (!attackedCreature || (isSummon() && attackedCreature == this)) { return; } bool updateLook = false; bool isCloseAttack = false; if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { updateLook = true; isCloseAttack = Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED); if (isCloseAttack) { egibleToDance = true; earliestAttackTime = OTSYS_TIME() + 2000; removeCondition(CONDITION_AGGRESSIVE, true); extraMeleeAttack = false; } if (!isCloseAttack) { //melee swing out of reach extraMeleeAttack = true; } } if (updateLook) { updateLookDirection(); } } void Monster::doAttacking(uint32_t) { if (!attackedCreature || (isSummon() && attackedCreature == this)) { return; } const Position& myPos = getPosition(); const Position& targetPos = attackedCreature->getPosition(); bool updateLook = false; bool isCloseAttack = false; if (OTSYS_TIME() >= earliestAttackTime && !isFleeing()) { updateLook = true; isCloseAttack = Combat::closeAttack(this, attackedCreature, FIGHTMODE_BALANCED); if (isCloseAttack) { egibleToDance = true; earliestAttackTime = OTSYS_TIME() + 2000; removeCondition(CONDITION_AGGRESSIVE, true); extraMeleeAttack = false; } if (!isCloseAttack) { //melee swing out of reach extraMeleeAttack = true; } } for (spellBlock_t& spellBlock : mType->info.attackSpells) { if (spellBlock.range != 0 && std::max(Position::getDistanceX(myPos, targetPos), Position::getDistanceY(myPos, targetPos)) <= spellBlock.range) { if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { updateLookDirection(); minCombatValue = spellBlock.minCombatValue; maxCombatValue = spellBlock.maxCombatValue; spellBlock.spell->castSpell(this, attackedCreature); egibleToDance = true; } } } if (updateLook) { updateLookDirection(); } } bool Monster::canUseAttack(const Position& pos, const Creature* target) const { if (isHostile()) { const Position& targetPos = target->getPosition(); uint32_t distance = std::max(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)); for (const spellBlock_t& spellBlock : mType->info.attackSpells) { if (spellBlock.range != 0 && distance <= spellBlock.range) { return g_game.isSightClear(pos, targetPos, true); } } return false; } return true; } void Monster::onThinkTarget(uint32_t interval) { if (!isSummon()) { if (mType->info.changeTargetSpeed != 0) { bool canChangeTarget = true; if (targetChangeCooldown > 0) { targetChangeCooldown -= interval; if (targetChangeCooldown <= 0) { targetChangeCooldown = 0; targetChangeTicks = mType->info.changeTargetSpeed; } else { canChangeTarget = false; } } if (canChangeTarget) { targetChangeTicks += interval; if (targetChangeTicks >= mType->info.changeTargetSpeed) { targetChangeTicks = 0; if (mType->info.changeTargetChance > uniform_random(0, 99)) { // search target strategies, if no strategy succeeds, target is not switched int32_t random = uniform_random(0, 99); int32_t current_strategy = 0; TargetSearchType_t searchType = TARGETSEARCH_ANY; do { int32_t strategy = 0; if (current_strategy == 0) { strategy = mType->info.strategyNearestEnemy; searchType = TARGETSEARCH_NEAREST; } else if (current_strategy == 1) { strategy = mType->info.strategyWeakestEnemy; searchType = TARGETSEARCH_WEAKEST; } else if (current_strategy == 2) { strategy = mType->info.strategyMostDamageEnemy; searchType = TARGETSEARCH_MOSTDAMAGE; } else if (current_strategy == 3) { strategy = mType->info.strategyRandomEnemy; searchType = TARGETSEARCH_RANDOM; } if (random < strategy) { break; } current_strategy++; random -= strategy; } while (current_strategy <= 3); if (searchType != TARGETSEARCH_ANY) { searchTarget(searchType); } } } } } } } void Monster::onThinkDefense(uint32_t) { for (const spellBlock_t& spellBlock : mType->info.defenseSpells) { if (uniform_random(0, spellBlock.chance) == 0 && (master || health > mType->info.runAwayHealth || uniform_random(1, 3) == 1)) { minCombatValue = spellBlock.minCombatValue; maxCombatValue = spellBlock.maxCombatValue; spellBlock.spell->castSpell(this, this); } } if (!isSummon() && summons.size() < mType->info.maxSummons && hasFollowPath) { for (const summonBlock_t& summonBlock : mType->info.summons) { if (summons.size() >= mType->info.maxSummons) { continue; } uint32_t summonCount = 0; for (Creature* summon : summons) { if (summon->getName() == summonBlock.name) { ++summonCount; } } if (summonCount >= summonBlock.max) { continue; } if (normal_random(0, summonBlock.chance) == 0 && (health > mType->info.runAwayHealth || normal_random(1, 3) == 1)) { Monster* summon = Monster::createMonster(summonBlock.name); if (summon) { const Position& summonPos = getPosition(); addSummon(summon); if (!g_game.placeCreature(summon, summonPos, false, summonBlock.force)) { removeSummon(summon); } else { g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE); g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT); } } } } } } void Monster::onThinkYell(uint32_t) { if (mType->info.voiceVector.empty()) { return; } int32_t randomResult = rand(); if (rand() == 50 * (randomResult / 50)) { uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1); const voiceBlock_t& voice = mType->info.voiceVector[index]; if (voice.yellText) { g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, voice.text, false); } else { g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, voice.text, false); } } } void Monster::onWalk() { Creature::onWalk(); } bool Monster::pushItem(Item* item) { const Position& centerPos = item->getPosition(); static std::vector> relList { {-1, -1}, {0, -1}, {1, -1}, {-1, 0}, {1, 0}, {-1, 1}, {0, 1}, {1, 1} }; std::shuffle(relList.begin(), relList.end(), getRandomGenerator()); for (const auto& it : relList) { Position tryPos(centerPos.x + it.first, centerPos.y + it.second, centerPos.z); Tile* tile = g_game.map.getTile(tryPos); if (tile && g_game.canThrowObjectTo(centerPos, tryPos)) { if (g_game.internalMoveItem(item->getParent(), tile, INDEX_WHEREEVER, item, item->getItemCount(), nullptr) == RETURNVALUE_NOERROR) { return true; } } } return false; } void Monster::pushItems(Tile* tile) { //We can not use iterators here since we can push the item to another tile //which will invalidate the iterator. //start from the end to minimize the amount of traffic if (TileItemVector* items = tile->getItemList()) { uint32_t moveCount = 0; uint32_t removeCount = 0; int32_t downItemSize = tile->getDownItemCount(); for (int32_t i = downItemSize; --i >= 0;) { Item* item = items->at(i); if (item && item->hasProperty(CONST_PROP_MOVEABLE) && (item->hasProperty(CONST_PROP_BLOCKPATH) || item->hasProperty(CONST_PROP_BLOCKSOLID))) { if (moveCount < 20 && Monster::pushItem(item)) { ++moveCount; } else if (g_game.internalRemoveItem(item) == RETURNVALUE_NOERROR) { ++removeCount; } } } if (removeCount > 0) { g_game.addMagicEffect(tile->getPosition(), CONST_ME_POFF); } } } bool Monster::pushCreature(Creature* creature) { static std::vector dirList { DIRECTION_NORTH, DIRECTION_WEST, DIRECTION_EAST, DIRECTION_SOUTH }; std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); for (Direction dir : dirList) { const Position& tryPos = Spells::getCasterPosition(creature, dir); Tile* toTile = g_game.map.getTile(tryPos); if (toTile && !toTile->hasFlag(TILESTATE_BLOCKPATH)) { if (g_game.internalMoveCreature(creature, dir) == RETURNVALUE_NOERROR) { return true; } } } return false; } void Monster::pushCreatures(Tile* tile) { //We can not use iterators here since we can push a creature to another tile //which will invalidate the iterator. if (CreatureVector* creatures = tile->getCreatures()) { uint32_t removeCount = 0; Monster* lastPushedMonster = nullptr; for (size_t i = 0; i < creatures->size();) { Monster* monster = creatures->at(i)->getMonster(); if (monster && monster->isPushable()) { if (monster != lastPushedMonster && Monster::pushCreature(monster)) { lastPushedMonster = monster; continue; } monster->changeHealth(-monster->getHealth()); monster->setDropLoot(false); removeCount++; } ++i; } if (removeCount > 0) { g_game.addMagicEffect(tile->getPosition(), CONST_ME_BLOCKHIT); } } } bool Monster::getNextStep(Direction& direction, uint32_t& flags) { if (isIdle || getHealth() <= 0) { //we dont have anyone watching might aswell stop walking eventWalk = 0; return false; } bool result = false; if ((!followCreature || !hasFollowPath) && (!isSummon() || !isMasterInRange)) { if (OTSYS_TIME() >= nextDanceStepRound) { updateLookDirection(); nextDanceStepRound = OTSYS_TIME() + getStepDuration(); //choose a random direction result = getRandomStep(getPosition(), direction); } } else if ((isSummon() && isMasterInRange) || followCreature) { result = Creature::getNextStep(direction, flags); if (result) { flags |= FLAG_PATHFINDING; } else { //target dancing if (attackedCreature && attackedCreature == followCreature) { if (isFleeing()) { result = getDanceStep(getPosition(), direction, false, false); } else if (egibleToDance && OTSYS_TIME() >= earliestDanceTime) { if (mType->info.targetDistance >= 4) { const Position& myPos = getPosition(); const Position targetPos = attackedCreature->getPosition(); if (Position::getDistanceX(myPos, targetPos) == 4 || Position::getDistanceY(myPos, targetPos) == 4) { int32_t currentX = myPos.x; int32_t currentY = myPos.y; int32_t danceRandom = rand(); int32_t danceRandomResult = danceRandom % 5; if (danceRandom % 5 == 1) { direction = DIRECTION_EAST; currentX++; } else if (danceRandomResult <= 1) { if (danceRandom == 5 * (danceRandom / 5)) { direction = DIRECTION_WEST; currentX--; } } else if (danceRandomResult == 2) { direction = DIRECTION_NORTH; currentY--; } else if (danceRandomResult == 3) { direction = DIRECTION_SOUTH; currentY++; } if (danceRandomResult <= 3 && canWalkTo(myPos, direction)) { int32_t xTest = targetPos.x - currentX; if (currentX - targetPos.x > -1) { xTest = currentX - targetPos.x; } int32_t yTest = targetPos.y - currentY; if (currentY - targetPos.y > -1) { yTest = currentY - targetPos.y; } int32_t realTest = yTest; if (xTest >= yTest) { realTest = xTest; } if (realTest == 4) { result = true; egibleToDance = false; earliestWakeUpTime = OTSYS_TIME() + 1000; earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); earliestAttackTime += 200; } } } } else { const Position& myPos = getPosition(); const Position targetPos = attackedCreature->getPosition(); if (Position::areInRange<1, 1>(myPos, targetPos)) { int32_t danceRandom = rand(); int32_t danceRandomResult = danceRandom % 5; int32_t currentX = myPos.x; int32_t currentY = myPos.y; if (danceRandom % 5 == 1) { direction = DIRECTION_EAST; currentX++; } else if (danceRandomResult <= 1) { if (danceRandom == 5 * (danceRandom / 5)) { direction = DIRECTION_WEST; currentX--; } } else if (danceRandomResult == 2) { direction = DIRECTION_NORTH; currentY--; } else if (danceRandomResult == 3) { direction = DIRECTION_SOUTH; currentY++; } Position position = myPos; position.x = currentX; position.y = currentY; if (danceRandomResult <= 3 && canWalkTo(myPos, direction) && Position::areInRange<1, 1>(position, targetPos)) { result = true; egibleToDance = false; earliestWakeUpTime = OTSYS_TIME() + 1000; earliestDanceTime = OTSYS_TIME() + 1000 + getStepDuration(); earliestAttackTime += 200; } } } } } } } if (result && (canPushItems() || canPushCreatures())) { const Position& pos = Spells::getCasterPosition(this, direction); Tile* tile = g_game.map.getTile(pos); if (tile) { if (canPushItems()) { Monster::pushItems(tile); } if (canPushCreatures()) { Monster::pushCreatures(tile); } } } return result; } bool Monster::getRandomStep(const Position& creaturePos, Direction& direction) const { static std::vector dirList{ DIRECTION_NORTH, DIRECTION_WEST, DIRECTION_EAST, DIRECTION_SOUTH }; std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); for (Direction dir : dirList) { if (canWalkTo(creaturePos, dir)) { direction = dir; return true; } } return false; } bool Monster::getDanceStep(const Position& creaturePos, Direction& direction, bool keepAttack /*= true*/, bool keepDistance /*= true*/) { bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature); assert(attackedCreature != nullptr); const Position& centerPos = attackedCreature->getPosition(); int_fast32_t offset_x = Position::getOffsetX(creaturePos, centerPos); int_fast32_t offset_y = Position::getOffsetY(creaturePos, centerPos); int_fast32_t distance_x = std::abs(offset_x); int_fast32_t distance_y = std::abs(offset_y); uint32_t centerToDist = std::max(distance_x, distance_y); std::vector dirList; if (!keepDistance || offset_y >= 0) { uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() - 1) - centerPos.getY())); if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_NORTH)) { bool result = true; if (keepAttack) { result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature)); } if (result) { dirList.push_back(DIRECTION_NORTH); } } } if (!keepDistance || offset_y <= 0) { uint32_t tmpDist = std::max(distance_x, std::abs((creaturePos.getY() + 1) - centerPos.getY())); if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_SOUTH)) { bool result = true; if (keepAttack) { result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature)); } if (result) { dirList.push_back(DIRECTION_SOUTH); } } } if (!keepDistance || offset_x <= 0) { uint32_t tmpDist = std::max(std::abs((creaturePos.getX() + 1) - centerPos.getX()), distance_y); if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_EAST)) { bool result = true; if (keepAttack) { result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature)); } if (result) { dirList.push_back(DIRECTION_EAST); } } } if (!keepDistance || offset_x >= 0) { uint32_t tmpDist = std::max(std::abs((creaturePos.getX() - 1) - centerPos.getX()), distance_y); if (tmpDist == centerToDist && canWalkTo(creaturePos, DIRECTION_WEST)) { bool result = true; if (keepAttack) { result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature)); } if (result) { dirList.push_back(DIRECTION_WEST); } } } if (!dirList.empty()) { std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator()); direction = dirList[uniform_random(0, dirList.size() - 1)]; return true; } return false; } bool Monster::getDistanceStep(const Position& targetPos, Direction& direction, bool flee /* = false */) { const Position& creaturePos = getPosition(); int_fast32_t dx = Position::getDistanceX(creaturePos, targetPos); int_fast32_t dy = Position::getDistanceY(creaturePos, targetPos); int32_t distance = std::max(dx, dy); if (!flee && (distance > mType->info.targetDistance || !g_game.isSightClear(creaturePos, targetPos, true))) { return false; // let the A* calculate it } else if (!flee && distance == mType->info.targetDistance) { return true; // we don't really care here, since it's what we wanted to reach (a dancestep will take of dancing in that position) } int_fast32_t offsetx = Position::getOffsetX(creaturePos, targetPos); int_fast32_t offsety = Position::getOffsetY(creaturePos, targetPos); if (dx <= 1 && dy <= 1) { //seems like a target is near, it this case we need to slow down our movements (as a monster) if (stepDuration < 2) { stepDuration++; } } else if (stepDuration > 0) { stepDuration--; } if (offsetx == 0 && offsety == 0) { return getRandomStep(creaturePos, direction); // player is "on" the monster so let's get some random step and rest will be taken care later. } if (dx == dy) { //player is diagonal to the monster if (offsetx >= 1 && offsety >= 1) { // player is NW //escape to SE, S or E [and some extra] bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (s && e) { direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST; return true; } else if (s) { direction = DIRECTION_SOUTH; return true; } else if (e) { direction = DIRECTION_EAST; return true; } else if (canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { direction = DIRECTION_SOUTHEAST; return true; } /* fleeing */ bool n = canWalkTo(creaturePos, DIRECTION_NORTH); bool w = canWalkTo(creaturePos, DIRECTION_WEST); if (flee) { if (n && w) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_WEST; return true; } else if (n) { direction = DIRECTION_NORTH; return true; } else if (w) { direction = DIRECTION_WEST; return true; } } /* end of fleeing */ if (w && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { direction = DIRECTION_WEST; } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { direction = DIRECTION_NORTH; } return true; } else if (offsetx <= -1 && offsety <= -1) { //player is SE //escape to NW , W or N [and some extra] bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool n = canWalkTo(creaturePos, DIRECTION_NORTH); if (w && n) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH; return true; } else if (w) { direction = DIRECTION_WEST; return true; } else if (n) { direction = DIRECTION_NORTH; return true; } if (canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { direction = DIRECTION_NORTHWEST; return true; } /* fleeing */ bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (flee) { if (s && e) { direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST; return true; } else if (s) { direction = DIRECTION_SOUTH; return true; } else if (e) { direction = DIRECTION_EAST; return true; } } /* end of fleeing */ if (s && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { direction = DIRECTION_SOUTH; } else if (e && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { direction = DIRECTION_EAST; } return true; } else if (offsetx >= 1 && offsety <= -1) { //player is SW //escape to NE, N, E [and some extra] bool n = canWalkTo(creaturePos, DIRECTION_NORTH); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (n && e) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST; return true; } else if (n) { direction = DIRECTION_NORTH; return true; } else if (e) { direction = DIRECTION_EAST; return true; } if (canWalkTo(creaturePos, DIRECTION_NORTHEAST)) { direction = DIRECTION_NORTHEAST; return true; } /* fleeing */ bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); bool w = canWalkTo(creaturePos, DIRECTION_WEST); if (flee) { if (s && w) { direction = boolean_random() ? DIRECTION_SOUTH : DIRECTION_WEST; return true; } else if (s) { direction = DIRECTION_SOUTH; return true; } else if (w) { direction = DIRECTION_WEST; return true; } } /* end of fleeing */ if (w && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { direction = DIRECTION_WEST; } else if (s && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { direction = DIRECTION_SOUTH; } return true; } else if (offsetx <= -1 && offsety >= 1) { // player is NE //escape to SW, S, W [and some extra] bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); if (w && s) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_SOUTH; return true; } else if (w) { direction = DIRECTION_WEST; return true; } else if (s) { direction = DIRECTION_SOUTH; return true; } else if (canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) { direction = DIRECTION_SOUTHWEST; return true; } /* fleeing */ bool n = canWalkTo(creaturePos, DIRECTION_NORTH); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (flee) { if (n && e) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST; return true; } else if (n) { direction = DIRECTION_NORTH; return true; } else if (e) { direction = DIRECTION_EAST; return true; } } /* end of fleeing */ if (e && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) { direction = DIRECTION_EAST; } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) { direction = DIRECTION_NORTH; } return true; } } //Now let's decide where the player is located to the monster (what direction) so we can decide where to escape. if (dy > dx) { Direction playerDir = offsety < 0 ? DIRECTION_SOUTH : DIRECTION_NORTH; switch (playerDir) { case DIRECTION_NORTH: { // Player is to the NORTH, so obviously we need to check if we can go SOUTH, if not then let's choose WEST or EAST and again if we can't we need to decide about some diagonal movements. if (canWalkTo(creaturePos, DIRECTION_SOUTH)) { direction = DIRECTION_SOUTH; return true; } bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (w && e && offsetx == 0) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; return true; } else if (w && offsetx <= 0) { direction = DIRECTION_WEST; return true; } else if (e && offsetx >= 0) { direction = DIRECTION_EAST; return true; } /* fleeing */ if (flee) { if (w && e) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; return true; } else if (w) { direction = DIRECTION_WEST; return true; } else if (e) { direction = DIRECTION_EAST; return true; } } /* end of fleeing */ bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST); bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST); if (sw || se) { // we can move both dirs if (sw && se) { direction = boolean_random() ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST; } else if (w) { direction = DIRECTION_WEST; } else if (sw) { direction = DIRECTION_SOUTHWEST; } else if (e) { direction = DIRECTION_EAST; } else if (se) { direction = DIRECTION_SOUTHEAST; } return true; } /* fleeing */ if (flee && canWalkTo(creaturePos, DIRECTION_NORTH)) { // towards player, yea direction = DIRECTION_NORTH; return true; } /* end of fleeing */ break; } case DIRECTION_SOUTH: { if (canWalkTo(creaturePos, DIRECTION_NORTH)) { direction = DIRECTION_NORTH; return true; } bool w = canWalkTo(creaturePos, DIRECTION_WEST); bool e = canWalkTo(creaturePos, DIRECTION_EAST); if (w && e && offsetx == 0) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; return true; } else if (w && offsetx <= 0) { direction = DIRECTION_WEST; return true; } else if (e && offsetx >= 0) { direction = DIRECTION_EAST; return true; } /* fleeing */ if (flee) { if (w && e) { direction = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST; return true; } else if (w) { direction = DIRECTION_WEST; return true; } else if (e) { direction = DIRECTION_EAST; return true; } } /* end of fleeing */ bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST); bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST); if (nw || ne) { // we can move both dirs if (nw && ne) { direction = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST; } else if (w) { direction = DIRECTION_WEST; } else if (nw) { direction = DIRECTION_NORTHWEST; } else if (e) { direction = DIRECTION_EAST; } else if (ne) { direction = DIRECTION_NORTHEAST; } return true; } /* fleeing */ if (flee && canWalkTo(creaturePos, DIRECTION_SOUTH)) { // towards player, yea direction = DIRECTION_SOUTH; return true; } /* end of fleeing */ break; } default: break; } } else { Direction playerDir = offsetx < 0 ? DIRECTION_EAST : DIRECTION_WEST; switch (playerDir) { case DIRECTION_WEST: { if (canWalkTo(creaturePos, DIRECTION_EAST)) { direction = DIRECTION_EAST; return true; } bool n = canWalkTo(creaturePos, DIRECTION_NORTH); bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); if (n && s && offsety == 0) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; return true; } else if (n && offsety <= 0) { direction = DIRECTION_NORTH; return true; } else if (s && offsety >= 0) { direction = DIRECTION_SOUTH; return true; } /* fleeing */ if (flee) { if (n && s) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; return true; } else if (n) { direction = DIRECTION_NORTH; return true; } else if (s) { direction = DIRECTION_SOUTH; return true; } } /* end of fleeing */ bool se = canWalkTo(creaturePos, DIRECTION_SOUTHEAST); bool ne = canWalkTo(creaturePos, DIRECTION_NORTHEAST); if (se || ne) { if (se && ne) { direction = boolean_random() ? DIRECTION_SOUTHEAST : DIRECTION_NORTHEAST; } else if (s) { direction = DIRECTION_SOUTH; } else if (se) { direction = DIRECTION_SOUTHEAST; } else if (n) { direction = DIRECTION_NORTH; } else if (ne) { direction = DIRECTION_NORTHEAST; } return true; } /* fleeing */ if (flee && canWalkTo(creaturePos, DIRECTION_WEST)) { // towards player, yea direction = DIRECTION_WEST; return true; } /* end of fleeing */ break; } case DIRECTION_EAST: { if (canWalkTo(creaturePos, DIRECTION_WEST)) { direction = DIRECTION_WEST; return true; } bool n = canWalkTo(creaturePos, DIRECTION_NORTH); bool s = canWalkTo(creaturePos, DIRECTION_SOUTH); if (n && s && offsety == 0) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; return true; } else if (n && offsety <= 0) { direction = DIRECTION_NORTH; return true; } else if (s && offsety >= 0) { direction = DIRECTION_SOUTH; return true; } /* fleeing */ if (flee) { if (n && s) { direction = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH; return true; } else if (n) { direction = DIRECTION_NORTH; return true; } else if (s) { direction = DIRECTION_SOUTH; return true; } } /* end of fleeing */ bool nw = canWalkTo(creaturePos, DIRECTION_NORTHWEST); bool sw = canWalkTo(creaturePos, DIRECTION_SOUTHWEST); if (nw || sw) { if (nw && sw) { direction = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_SOUTHWEST; } else if (n) { direction = DIRECTION_NORTH; } else if (nw) { direction = DIRECTION_NORTHWEST; } else if (s) { direction = DIRECTION_SOUTH; } else if (sw) { direction = DIRECTION_SOUTHWEST; } return true; } /* fleeing */ if (flee && canWalkTo(creaturePos, DIRECTION_EAST)) { // towards player, yea direction = DIRECTION_EAST; return true; } /* end of fleeing */ break; } default: break; } } return true; } bool Monster::canWalkTo(Position pos, Direction direction) const { pos = getNextPosition(direction, pos); if (isInSpawnRange(pos)) { if (getWalkCache(pos) == 0) { return false; } Tile* tile = g_game.map.getTile(pos); if (tile && tile->getTopVisibleCreature(this) == nullptr && tile->queryAdd(0, *this, 1, FLAG_PATHFINDING) == RETURNVALUE_NOERROR) { return true; } } return false; } void Monster::death(Creature*) { setAttackedCreature(nullptr); for (Creature* summon : summons) { summon->changeHealth(-summon->getHealth()); summon->setMaster(nullptr); summon->decrementReferenceCounter(); } summons.clear(); clearTargetList(); clearFriendList(); onIdleStatus(); } Item* Monster::getCorpse(Creature* lastHitCreature, Creature* mostDamageCreature) { Item* corpse = Creature::getCorpse(lastHitCreature, mostDamageCreature); if (corpse) { if (mostDamageCreature) { if (mostDamageCreature->getPlayer()) { corpse->setCorpseOwner(mostDamageCreature->getID()); } else { const Creature* mostDamageCreatureMaster = mostDamageCreature->getMaster(); if (mostDamageCreatureMaster && mostDamageCreatureMaster->getPlayer()) { corpse->setCorpseOwner(mostDamageCreatureMaster->getID()); } } } } return corpse; } bool Monster::isInSpawnRange(const Position& pos) const { if (!spawn) { return true; } if (Monster::despawnRadius == 0) { return true; } if (!Spawns::isInZone(masterPos, Monster::despawnRadius, pos)) { return false; } if (Monster::despawnRange == 0) { return true; } if (Position::getDistanceZ(pos, masterPos) > Monster::despawnRange) { return false; } return true; } bool Monster::getCombatValues(int32_t& min, int32_t& max) { if (minCombatValue == 0 && maxCombatValue == 0) { return false; } min = minCombatValue; max = maxCombatValue; return true; } void Monster::updateLookDirection() { Direction newDir = getDirection(); if (attackedCreature) { const Position& pos = getPosition(); const Position& attackedCreaturePos = attackedCreature->getPosition(); int_fast32_t offsetx = Position::getOffsetX(attackedCreaturePos, pos); int_fast32_t offsety = Position::getOffsetY(attackedCreaturePos, pos); int32_t dx = std::abs(offsetx); int32_t dy = std::abs(offsety); if (dx > dy) { //look EAST/WEST if (offsetx < 0) { newDir = DIRECTION_WEST; } else { newDir = DIRECTION_EAST; } } else if (dx < dy) { //look NORTH/SOUTH if (offsety < 0) { newDir = DIRECTION_NORTH; } else { newDir = DIRECTION_SOUTH; } } else { Direction dir = getDirection(); if (offsetx < 0 && offsety < 0) { if (offsetx == -1 && offsety == -1) { if (dir == DIRECTION_NORTH) { newDir = DIRECTION_WEST; } } if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_WEST; } else if (dir == DIRECTION_EAST) { newDir = DIRECTION_NORTH; } } else if (offsetx < 0 && offsety > 0) { if (offsetx == -1 && offsety == 1) { if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_WEST; } } if (dir == DIRECTION_NORTH) { newDir = DIRECTION_WEST; } else if (dir == DIRECTION_EAST) { newDir = DIRECTION_SOUTH; } } else if (offsetx > 0 && offsety < 0) { if (offsetx == 1 && offsety == -1) { if (dir == DIRECTION_NORTH) { newDir = DIRECTION_EAST; } } if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_EAST; } else if (dir == DIRECTION_WEST) { newDir = DIRECTION_NORTH; } } else { if (offsetx == 1 && offsety == 1) { if (dir == DIRECTION_SOUTH) { newDir = DIRECTION_EAST; } } if (dir == DIRECTION_NORTH) { newDir = DIRECTION_EAST; } else if (dir == DIRECTION_WEST) { newDir = DIRECTION_SOUTH; } } } } if (direction != newDir) { g_game.internalCreatureTurn(this, newDir); } } void Monster::dropLoot(Container* corpse, Creature*) { if (corpse && lootDrop) { mType->createLoot(corpse); } } void Monster::setNormalCreatureLight() { internalLight = mType->info.light; } void Monster::drainHealth(Creature* attacker, int32_t damage) { Creature::drainHealth(attacker, damage); if (isInvisible()) { removeCondition(CONDITION_INVISIBLE); } } void Monster::changeHealth(int32_t healthChange, bool sendHealthChange/* = true*/) { //In case a player with ignore flag set attacks the monster setIdle(false); Creature::changeHealth(healthChange, sendHealthChange); } bool Monster::challengeCreature(Creature* creature) { if (isSummon()) { return false; } bool result = selectTarget(creature); if (result) { targetChangeCooldown = 1000; targetChangeTicks = 0; } return result; } bool Monster::convinceCreature(Creature* creature) { Player* player = creature->getPlayer(); if (player && !player->hasFlag(PlayerFlag_CanConvinceAll)) { if (!mType->info.isConvinceable) { return false; } } if (isSummon()) { if (getMaster() == creature) { return false; } Creature* oldMaster = getMaster(); oldMaster->removeSummon(this); } creature->addSummon(this); setFollowCreature(nullptr); setAttackedCreature(nullptr); //destroy summons for (Creature* summon : summons) { summon->changeHealth(-summon->getHealth()); summon->setMaster(nullptr); summon->decrementReferenceCounter(); } summons.clear(); isMasterInRange = true; updateTargetList(); updateIdleStatus(); //Notify surrounding about the change SpectatorVec list; g_game.map.getSpectators(list, getPosition(), true); g_game.map.getSpectators(list, creature->getPosition(), true); for (Creature* spectator : list) { spectator->onCreatureConvinced(creature, this); } if (spawn) { spawn->removeMonster(this); spawn = nullptr; } return true; } void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature) { if (convincer != this && (isFriend(creature) || isOpponent(creature))) { updateTargetList(); updateIdleStatus(); } } void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const { Creature::getPathSearchParams(creature, fpp); fpp.minTargetDist = 1; fpp.maxTargetDist = mType->info.targetDistance; if (isSummon()) { if (getMaster() == creature) { fpp.maxTargetDist = 2; fpp.fullPathSearch = true; } else if (mType->info.targetDistance <= 1) { fpp.fullPathSearch = true; } else { fpp.fullPathSearch = !canUseAttack(getPosition(), creature); } } else if (isFleeing()) { //Distance should be higher than the client view range (Map::maxClientViewportX/Map::maxClientViewportY) fpp.maxTargetDist = Map::maxViewportX; fpp.clearSight = false; fpp.keepDistance = true; fpp.fullPathSearch = false; } else if (mType->info.targetDistance <= 1) { fpp.fullPathSearch = true; } else { fpp.fullPathSearch = !canUseAttack(getPosition(), creature); } }