JustPaste.it

/**
 * @file monster.cpp
 *
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2019 Mark Samman <mark.samman@gmail.com>
 *
 * 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 "config/configmanager.h"
#include "creatures/monsters/monster.h"
#include "game/game.h"
#include "creatures/combat/spells.h"
#include "lua/creature/events.h"
#include "lua/callbacks/creaturecallback.h"

extern Game g_game;
extern Monsters g_monsters;
extern Events* g_events;
extern ConfigManager g_config;

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;
    float multiplier = g_config.getFloat(ConfigManager::RATE_MONSTER_HEALTH);
    health = mType->info.health*multiplier;
    healthMax = mType->info.healthMax*multiplier;
    baseSpeed = mType->info.baseSpeed;
    internalLight = mType->info.light;
    hiddenHealth = mType->info.hiddenHealth;
    targetDistance = mType->info.targetDistance;

    // Register creature events
    for (const std::string& scriptName : mType->info.scripts) {
        if (!registerCreatureEvent(scriptName)) {
            SPDLOG_WARN("[Monster::Monster] - "
                        "Unknown event name: {}", scriptName);
        }
    }
}

Monster::~Monster()
{
    clearTargetList();
    clearFriendList();
}

void Monster::addList()
{
    g_game.addMonster(this);
}

void Monster::removeList()
{
    g_game.removeMonster(this);
}

bool Monster::canSee(const Position& pos) const
{
    return Creature::canSee(getPosition(), pos, 10, 10); //jlcvp FIX - range 10 Avoids killing monster without reaction
}

bool Monster::canWalkOnFieldType(CombatType_t combatType) const
{
    switch (combatType) {
        case COMBAT_ENERGYDAMAGE:
            return mType->info.canWalkOnEnergy;
        case COMBAT_FIREDAMAGE:
            return mType->info.canWalkOnFire;
        case COMBAT_EARTHDAMAGE:
                return mType->info.canWalkOnPoison;
            default:
        return true;
    }
}

uint32_t Monster::getReflectValue(CombatType_t reflectType) const {
    auto it = mType->info.reflectMap.find(reflectType);
    if (it != mType->info.reflectMap.end()) {
        return it->second;
    }
    return 0;
}

uint32_t Monster::getHealingCombatValue(CombatType_t healingType) const {
    auto it = mType->info.healingMap.find(healingType);
    if (it != mType->info.healingMap.end()) {
        return it->second;
    }
    return 0;
}

void Monster::onAttackedCreatureDisappear(bool)
{
    attackTicks = 0;
    extraMeleeAttack = true;
}

void Monster::onCreatureAppear(Creature* creature, bool isLogin)
{
    Creature::onCreatureAppear(creature, isLogin);

    // onCreatureAppear(self, creature)
    CreatureCallback callback = CreatureCallback(mType->info.scriptInterface, this);
    if (callback.startScriptInterface(mType->info.creatureAppearEvent)) {
        callback.pushSpecificCreature(this);
        callback.pushCreature(creature);
    }

    if (callback.persistLuaState()) {
        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);

    // onCreatureDisappear(self, creature)
    CreatureCallback callback = CreatureCallback(mType->info.scriptInterface, this);
    if (callback.startScriptInterface(mType->info.creatureDisappearEvent)) {
        callback.pushSpecificCreature(this);
        callback.pushCreature(creature);
    }

    if (callback.persistLuaState()) {
        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()) {
            SPDLOG_ERROR("[Monster::onCreatureMove - Monster {} creature {}] "
                         "Call stack overflow. Too many lua script calls being nested.",
                         getName(), creature->getName());
            return;
        }

        ScriptEnvironment* env = scriptInterface->getScriptEnv();
        env->setScriptId(mType->info.creatureMoveEvent, scriptInterface);

        lua_State* L = scriptInterface->getLuaState();
        scriptInterface->pushFunction(mType->info.creatureMoveEvent);

        LuaScriptInterface::pushUserdata<Monster>(L, this);
        LuaScriptInterface::setMetatable(L, -1, "Monster");

        LuaScriptInterface::pushUserdata<Creature>(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& pos = getPosition();

                int32_t offset_x = Position::getDistanceX(followPosition, pos);
                int32_t offset_y = Position::getDistanceY(followPosition, pos);
                if ((offset_x > 1 || offset_y > 1) && mType->info.changeTargetChance > 0) {
                    Direction dir = getDirectionTo(pos, followPosition);
                    const Position& checkPosition = getNextPosition(dir, pos);

                    Tile* nextTile = g_game.map.getTile(checkPosition);
                    if (nextTile) {
                        Creature* topCreature = nextTile->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 (!creature->getPlayer()) {
        return;
    }

    // onCreatureSay(self, creature, type, message)
    CreatureCallback callback = CreatureCallback(mType->info.scriptInterface, this);
    if (callback.startScriptInterface(mType->info.creatureSayEvent)) {
        callback.pushSpecificCreature(this);
        callback.pushCreature(creature);
        callback.pushNumber(type);
        callback.pushString(text);
    }

    if (callback.persistLuaState()) {
        return;
    }
}

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);
        }
        if(!master && getFaction() != FACTION_DEFAULT && creature->getPlayer())
            totalPlayersOnScreen++;
    }
}

void Monster::removeTarget(Creature* creature)
{
    auto it = std::find(targetList.begin(), targetList.end(), creature);
    if (it != targetList.end()) {
        creature->decrementReferenceCounter();
        targetList.erase(it);
        if(!master && getFaction() != FACTION_DEFAULT && creature->getPlayer())
            totalPlayersOnScreen--;
    }
}

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;
        }
    }

    SpectatorHashSet spectators;
    g_game.map.getSpectators(spectators, position, true);
    spectators.erase(this);
    for (Creature* spectator : spectators) {
        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)
{
    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() && creature->getPlayer()->hasFlag(PlayerFlag_IgnoredByMonsters)) {
        return false;
    } else {
        if (getFaction() != FACTION_DEFAULT) {
            return isEnemyFaction(creature->getFaction()) || creature->getFaction() == FACTION_PLAYER;
        }
        if ((creature->getPlayer()) || (creature->getMaster() && creature->getMaster()->getPlayer())) {
            return true;
        }
    }

    return false;
}

void Monster::onCreatureLeave(Creature* creature)
{
    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 /*= TARGETSEARCH_DEFAULT*/)
{
    if (searchType == TARGETSEARCH_DEFAULT) {
        int32_t rnd = uniform_random(1, 100);

        searchType = TARGETSEARCH_NEAREST;

        int32_t sum = this->mType->info.targetStrategiesNearestPercent;
        if (rnd > sum) {
            searchType = TARGETSEARCH_HP;
            sum += this->mType->info.targetStrategiesLowerHPPercent;

            if (rnd > sum) {
                searchType = TARGETSEARCH_DAMAGE;
                sum += this->mType->info.targetStrategiesMostDamagePercent;
                if (rnd > sum) {
                    searchType = TARGETSEARCH_RANDOM;
                }
            }
        }
    }

    std::list<Creature*> resultList;
    const Position& myPos = getPosition();

    for (Creature* creature : targetList) {
        if (isTarget(creature)) {
            if ((this->targetDistance == 1) || canUseAttack(myPos, creature)) {
                resultList.push_back(creature);
            }
        }
    }

    if (resultList.empty()) {
        return false;
    }

    Creature* getTarget = nullptr;

    switch (searchType) {
        case TARGETSEARCH_NEAREST: {
            getTarget = nullptr;
            if (!resultList.empty()) {
                auto it = resultList.begin();
                getTarget = *it;

                if (++it != resultList.end()) {
                    const Position& targetPosition = getTarget->getPosition();
                    int32_t minRange = std::max<int32_t>(Position::getDistanceX(myPos, targetPosition), Position::getDistanceY(myPos, targetPosition));
                    do {
                        const Position& pos = (*it)->getPosition();

                        int32_t distance = std::max<int32_t>(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos));
                        if (distance < minRange) {
                            getTarget = *it;
                            minRange = distance;
                        }
                    } while (++it != resultList.end());
                }
            } else {
                int32_t minRange = std::numeric_limits<int32_t>::max();
                for (Creature* creature : targetList) {
                    if (!isTarget(creature)) {
                        continue;
                    }

                    const Position& pos = creature->getPosition();
                    int32_t distance = std::max<int32_t>(Position::getDistanceX(myPos, pos), Position::getDistanceY(myPos, pos));
                    if (distance < minRange) {
                        getTarget = creature;
                        minRange = distance;
                    }
                }
            }

            if (getTarget && selectTarget(getTarget)) {
                return true;
            }
            break;
        }
        case TARGETSEARCH_HP: {
            getTarget = nullptr;
            if (!resultList.empty()) {
                auto it = resultList.begin();
                getTarget = *it;
                if (++it != resultList.end()) {
                    int32_t minHp = getTarget->getHealth();
                    do {
                        if ((*it)->getHealth() < minHp) {
                            getTarget = *it;

                            minHp = getTarget->getHealth();
                        }
                    } while (++it != resultList.end());
                }
            }
            if (getTarget && selectTarget(getTarget)) {
                return true;
            }
            break;
        }
        case TARGETSEARCH_DAMAGE: {
            getTarget = nullptr;
            if (!resultList.empty()) {
                auto it = resultList.begin();
                getTarget = *it;
                if (++it != resultList.end()) {
                    int32_t mostDamage = 0;
                    do {
                        const auto& dmg = damageMap.find((*it)->getID());
                        if (dmg != damageMap.end()) {
                            if (dmg->second.total > mostDamage) {
                                mostDamage = dmg->second.total;
                                getTarget = *it;
                            }
                        }
                    } while (++it != resultList.end());
                }
            }
            if (getTarget && selectTarget(getTarget)) {
                return true;
            }
            break;
        }
        case TARGETSEARCH_RANDOM:
        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
    for (Creature* target : targetList) {
        if (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<int32_t>(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;
    }
    Faction_t targetFaction = creature->getFaction();
    if (getFaction() != FACTION_DEFAULT) {
        return isEnemyFaction(targetFaction);
    }
    return true;
}

bool Monster::selectTarget(Creature* creature)
{
    if (!isTarget(creature) || returnToMasterInterval > 0) {
        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())));
        }
    }
    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;
        }
        else if ((!master || master->getMonster()) && getFaction() != FACTION_DEFAULT && (totalPlayersOnScreen == 0 && (!master || master->getMonster()->totalPlayersOnScreen == 0))) {
            idle = true;
        }
    }

    setIdle(idle);
}

void Monster::onAddCondition(ConditionType_t type)
{
    if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) {
    ignoreFieldDamage = true;
        updateMapCache();
    }

    updateIdleStatus();
}

void Monster::onEndCondition(ConditionType_t type)
{
    if (type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON) {
        ignoreFieldDamage = false;
        updateMapCache();
    }

    updateIdleStatus();
}

void Monster::onThink(uint32_t interval)
{
    Creature::onThink(interval);

    // onThink(self, interval)
    CreatureCallback callback = CreatureCallback(mType->info.scriptInterface, this);
    if (callback.startScriptInterface(mType->info.thinkEvent)) {
        callback.pushSpecificCreature(this);
        callback.pushNumber(interval);
    }

    if (challengeMeleeDuration != 0) {
        challengeMeleeDuration -= interval;
        if (challengeMeleeDuration <= 0) {
            challengeMeleeDuration = 0;
            targetDistance = mType->info.targetDistance;
            g_game.updateCreatureIcon(this);
        }
    }

    if (callback.persistLuaState()) {
        return;
    }

    if (!mType->canSpawn(position)) {
        g_game.removeCreature(this);
    }

    if (!isInSpawnRange(position)) {
        g_game.internalTeleport(this, masterPos);
        setIdle(true);
    } else {
        updateIdleStatus();

        if (!isIdle) {
            addEventWalk();

            if (isSummon()) {
                if (!attackedCreature) {
                    if (getMaster() && getMaster()->getAttackedCreature()) {
                        //This happens if the monster is summoned during combat
                        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) {
                    //This happens just after a master orders an attack, so lets follow it aswell.
                    setFollowCreature(attackedCreature);
                }
            } else if (!targetList.empty()) {
                if (!followCreature || !hasFollowPath) {
                    searchTarget(TARGETSEARCH_NEAREST);
                } else if (isFleeing()) {
                    if (attackedCreature && !canUseAttack(getPosition(), attackedCreature)) {
                        searchTarget(TARGETSEARCH_DEFAULT);
                    }
                }
            }

            onThinkTarget(interval);
            onThinkYell(interval);
            onThinkDefense(interval);
        }
    }
}

void Monster::doAttacking(uint32_t interval)
{
    if (!attackedCreature || (isSummon() && attackedCreature == this)) {
        return;
    }

    bool updateLook = true;
    bool resetTicks = interval != 0;
    attackTicks += interval;

    const Position& myPos = getPosition();
    const Position& targetPos = attackedCreature->getPosition();

    for (const spellBlock_t& spellBlock : mType->info.attackSpells) {
        bool inRange = false;

        if (attackedCreature == nullptr) {
            break;
        }

        if(spellBlock.isMelee && isFleeing()){
            continue;
        }

        if (canUseSpell(myPos, targetPos, spellBlock, interval, inRange, resetTicks)) {
            if (spellBlock.chance >= static_cast<uint32_t>(uniform_random(1, 100))) {
                if (updateLook) {
                    updateLookDirection();
                    updateLook = false;
                }

                float multiplier;
                if (maxCombatValue > 0) { //defense
                    multiplier = g_config.getFloat(ConfigManager::RATE_MONSTER_DEFENSE);
                } else { //attack
                    multiplier = g_config.getFloat(ConfigManager::RATE_MONSTER_ATTACK);
                }

                minCombatValue = spellBlock.minCombatValue * multiplier;
                maxCombatValue = spellBlock.maxCombatValue * multiplier;
                spellBlock.spell->castSpell(this, attackedCreature);

                if (spellBlock.isMelee) {
                    extraMeleeAttack = false;
                }
            }
        }

        if (!inRange && spellBlock.isMelee) {
            //melee swing out of reach
            extraMeleeAttack = true;
        }
    }

    if (updateLook) {
        updateLookDirection();
    }

    if (resetTicks) {
        attackTicks = 0;
    }
}

bool Monster::canUseAttack(const Position& pos, const Creature* target) const
{
    if (isHostile()) {
        const Position& targetPos = target->getPosition();
        uint32_t distance = std::max<uint32_t>(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;
}

bool Monster::canUseSpell(const Position& pos, const Position& targetPos,
                          const spellBlock_t& sb, uint32_t interval, bool& inRange, bool& resetTicks)
{
    inRange = true;

    if (sb.isMelee && isFleeing()) {
        return false;
    }

    if (extraMeleeAttack) {
        lastMeleeAttack = OTSYS_TIME();
    } else if (sb.isMelee && (OTSYS_TIME() - lastMeleeAttack) < 1500) {
        return false;
    }

    if (!sb.isMelee || !extraMeleeAttack) {
        if (sb.speed > attackTicks) {
            resetTicks = false;
            return false;
        }

        if (attackTicks % sb.speed >= interval) {
            //already used this spell for this round
            return false;
        }
    }

    if (sb.range != 0 && std::max<uint32_t>(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos)) > sb.range) {
        inRange = false;
        return false;
    }
    return true;
}

void Monster::onThinkTarget(uint32_t interval)
{
    if (!isSummon()) {
        if (mType->info.changeTargetSpeed != 0) {
            bool canChangeTarget = true;

            if (challengeFocusDuration > 0) {
                challengeFocusDuration -= interval;

                if (challengeFocusDuration <= 0) {
                    challengeFocusDuration = 0;
                }
            }

            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;
                    targetChangeCooldown = mType->info.changeTargetSpeed;

                    if (challengeFocusDuration > 0) {
                        challengeFocusDuration = 0;
                    }

                    if (mType->info.changeTargetChance >= uniform_random(1, 100)) {
                        searchTarget(TARGETSEARCH_DEFAULT);
                    }
                }
            }
        }
    }
}

void Monster::onThinkDefense(uint32_t interval)
{
    bool resetTicks = true;
    defenseTicks += interval;

    for (const spellBlock_t& spellBlock : mType->info.defenseSpells) {
        if (spellBlock.speed > defenseTicks) {
            resetTicks = false;
            continue;
        }

        if (defenseTicks % spellBlock.speed >= interval) {
            //already used this spell for this round
            continue;
        }

        if ((spellBlock.chance >= static_cast<uint32_t>(uniform_random(1, 100)))) {
            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 (summonBlock.speed > defenseTicks) {
                resetTicks = false;
                continue;
            }

            if (summons.size() >= mType->info.maxSummons) {
                continue;
            }

            if (defenseTicks % summonBlock.speed >= interval) {
                //already used this spell for this round
                continue;
            }

            uint32_t summonCount = 0;
            for (Creature* summon : summons) {
                if (summon->getName() == summonBlock.name) {
                    ++summonCount;
                }
            }

            if (summonCount >= summonBlock.max) {
                continue;
            }

            if (summonBlock.chance < static_cast<uint32_t>(uniform_random(1, 100))) {
                continue;
            }

            Monster* summon = Monster::createMonster(summonBlock.name);
            if (summon) {
                if (g_game.placeCreature(summon, getPosition(), false, summonBlock.force)) {
                    summon->setDropLoot(false);
                    summon->setSkillLoss(false);
                    summon->setMaster(this);
                    g_game.addMagicEffect(getPosition(), CONST_ME_MAGIC_BLUE);
                    g_game.addMagicEffect(summon->getPosition(), CONST_ME_TELEPORT);
                } else {
                    delete summon;
                }
            }
        }
    }

    if (resetTicks) {
        defenseTicks = 0;
    }
}

void Monster::onThinkYell(uint32_t interval)
{
    if (mType->info.yellSpeedTicks == 0) {
        return;
    }

    yellTicks += interval;
    if (yellTicks >= mType->info.yellSpeedTicks) {
        yellTicks = 0;

        if (!mType->info.voiceVector.empty() && (mType->info.yellChance >= static_cast<uint32_t>(uniform_random(1, 100)))) {
            uint32_t index = uniform_random(0, mType->info.voiceVector.size() - 1);
            const voiceBlock_t& vb = mType->info.voiceVector[index];

            if (vb.yellText) {
                g_game.internalCreatureSay(this, TALKTYPE_MONSTER_YELL, vb.text, false);
            } else {
                g_game.internalCreatureSay(this, TALKTYPE_MONSTER_SAY, vb.text, false);
            }
        }
    }
}

void Monster::onCreatureWalk()
{
    Creature::onCreatureWalk();
}

bool Monster::pushItem(Item* item)
{
    const Position& centerPos = item->getPosition();

    static std::vector<std::pair<int32_t, int32_t>> 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)) && item->getActionId() != 100 /* non-moveable action*/) {
                if (moveCount < 20 && Monster::pushItem(item)) {
                    ++moveCount;
                } else if (!item->isCorpse() && 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<Direction> 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(true);
                removeCount++;
            }

            ++i;
        }

        if (removeCount > 0) {
            g_game.addMagicEffect(tile->getPosition(), CONST_ME_BLOCKHIT);
        }
    }
}

bool Monster::getNextStep(Direction& nextDirection, 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 (getTimeSinceLastMove() >= 1000) {
            randomStepping = true;
            //choose a random direction
            result = getRandomStep(getPosition(), nextDirection);
        }
    } else if ((isSummon() && isMasterInRange) || followCreature) {
        randomStepping = false;
        result = Creature::getNextStep(nextDirection, flags);
        if (result) {
            flags |= FLAG_PATHFINDING;
        } else {
            if (ignoreFieldDamage) {
                updateMapCache();
            }
            //target dancing
            if (attackedCreature && attackedCreature == followCreature) {
                if (isFleeing()) {
                    result = getDanceStep(getPosition(), nextDirection, false, false);
                } else if (mType->info.staticAttackChance < static_cast<uint32_t>(uniform_random(1, 100))) {
                    result = getDanceStep(getPosition(), nextDirection);
                }
            }
        }
    }

    if (result && (canPushItems() || canPushCreatures())) {
        const Position& pos = Spells::getCasterPosition(this, direction);
        Tile* posTile = g_game.map.getTile(pos);
        if (posTile) {
            if (canPushItems()) {
                Monster::pushItems(posTile);
            }

            if (canPushCreatures()) {
                Monster::pushCreatures(posTile);
            }
        }
    }

    return result;
}

bool Monster::getRandomStep(const Position& creaturePos, Direction& moveDirection) const
{
    static std::vector<Direction> dirList{
            DIRECTION_NORTH,
        DIRECTION_WEST, DIRECTION_EAST,
            DIRECTION_SOUTH
    };
    std::shuffle(dirList.begin(), dirList.end(), getRandomGenerator());

    for (Direction dir : dirList) {
        if (canWalkTo(creaturePos, dir)) {
            moveDirection = dir;
            return true;
        }
    }
    return false;
}

bool Monster::getDanceStep(const Position& creaturePos, Direction& moveDirection,
                           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<uint32_t>(distance_x, distance_y);

    //monsters not at targetDistance shouldn't dancestep
    if (centerToDist < (uint32_t) targetDistance) {
        return false;
    }

    std::vector<Direction> dirList;

    if (!keepDistance || offset_y >= 0) {
        uint32_t tmpDist = std::max<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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());
        moveDirection = dirList[uniform_random(0, dirList.size() - 1)];
        return true;
    }
    return false;
}

bool Monster::getDistanceStep(const Position& targetPos, Direction& moveDirection, 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<int32_t>(dx, dy);

    if (!flee && (distance > targetDistance || !g_game.isSightClear(creaturePos, targetPos, true))) {
        return false; // let the A* calculate it
    } else if (!flee && distance == 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, moveDirection); // 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) {
                moveDirection = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST;
                return true;
            } else if (s) {
                moveDirection = DIRECTION_SOUTH;
                return true;
            } else if (e) {
                moveDirection = DIRECTION_EAST;
                return true;
            } else if (canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) {
                moveDirection = DIRECTION_SOUTHEAST;
                return true;
            }

            /* fleeing */
            bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
            bool w = canWalkTo(creaturePos, DIRECTION_WEST);

            if (flee) {
                if (n && w) {
                    moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_WEST;
                    return true;
                } else if (n) {
                    moveDirection = DIRECTION_NORTH;
                    return true;
                } else if (w) {
                    moveDirection = DIRECTION_WEST;
                    return true;
                }
            }

            /* end of fleeing */

            if (w && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) {
                moveDirection = DIRECTION_WEST;
            } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) {
                moveDirection = 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) {
                moveDirection = boolean_random() ? DIRECTION_WEST : DIRECTION_NORTH;
                return true;
            } else if (w) {
                moveDirection = DIRECTION_WEST;
                return true;
            } else if (n) {
                moveDirection = DIRECTION_NORTH;
                return true;
            }

            if (canWalkTo(creaturePos, DIRECTION_NORTHWEST)) {
                moveDirection = DIRECTION_NORTHWEST;
                return true;
            }

            /* fleeing */
            bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
            bool e = canWalkTo(creaturePos, DIRECTION_EAST);

            if (flee) {
                if (s && e) {
                    moveDirection = boolean_random() ? DIRECTION_SOUTH : DIRECTION_EAST;
                    return true;
                } else if (s) {
                    moveDirection = DIRECTION_SOUTH;
                    return true;
                } else if (e) {
                    moveDirection = DIRECTION_EAST;
                    return true;
                }
            }

            /* end of fleeing */

            if (s && canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) {
                moveDirection = DIRECTION_SOUTH;
            } else if (e && canWalkTo(creaturePos, DIRECTION_NORTHEAST)) {
                moveDirection = 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) {
                moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST;
                return true;
            } else if (n) {
                moveDirection = DIRECTION_NORTH;
                return true;
            } else if (e) {
                moveDirection = DIRECTION_EAST;
                return true;
            }

            if (canWalkTo(creaturePos, DIRECTION_NORTHEAST)) {
                moveDirection = DIRECTION_NORTHEAST;
                return true;
            }

            /* fleeing */
            bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
            bool w = canWalkTo(creaturePos, DIRECTION_WEST);

            if (flee) {
                if (s && w) {
                    moveDirection = boolean_random() ? DIRECTION_SOUTH : DIRECTION_WEST;
                    return true;
                } else if (s) {
                    moveDirection = DIRECTION_SOUTH;
                    return true;
                } else if (w) {
                    moveDirection = DIRECTION_WEST;
                    return true;
                }
            }

            /* end of fleeing */

            if (w && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) {
                moveDirection = DIRECTION_WEST;
            } else if (s && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) {
                moveDirection = 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) {
                moveDirection = boolean_random() ? DIRECTION_WEST : DIRECTION_SOUTH;
                return true;
            } else if (w) {
                moveDirection = DIRECTION_WEST;
                return true;
            } else if (s) {
                moveDirection = DIRECTION_SOUTH;
                return true;
            } else if (canWalkTo(creaturePos, DIRECTION_SOUTHWEST)) {
                moveDirection = DIRECTION_SOUTHWEST;
                return true;
            }

            /* fleeing */
            bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
            bool e = canWalkTo(creaturePos, DIRECTION_EAST);

            if (flee) {
                if (n && e) {
                    moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_EAST;
                    return true;
                } else if (n) {
                    moveDirection = DIRECTION_NORTH;
                    return true;
                } else if (e) {
                    moveDirection = DIRECTION_EAST;
                    return true;
                }
            }

            /* end of fleeing */

            if (e && canWalkTo(creaturePos, DIRECTION_SOUTHEAST)) {
                moveDirection = DIRECTION_EAST;
            } else if (n && canWalkTo(creaturePos, DIRECTION_NORTHWEST)) {
                moveDirection = 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)) {
                    moveDirection = DIRECTION_SOUTH;
                    return true;
                }

                bool w = canWalkTo(creaturePos, DIRECTION_WEST);
                bool e = canWalkTo(creaturePos, DIRECTION_EAST);
                if (w && e && offsetx == 0) {
                    moveDirection = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
                    return true;
                } else if (w && offsetx <= 0) {
                    moveDirection = DIRECTION_WEST;
                    return true;
                } else if (e && offsetx >= 0) {
                    moveDirection = DIRECTION_EAST;
                    return true;
                }

                /* fleeing */
                if (flee) {
                    if (w && e) {
                        moveDirection = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
                        return true;
                    } else if (w) {
                        moveDirection = DIRECTION_WEST;
                        return true;
                    } else if (e) {
                        moveDirection = 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) {
                        moveDirection = boolean_random() ? DIRECTION_SOUTHWEST : DIRECTION_SOUTHEAST;
                    } else if (w) {
                        moveDirection = DIRECTION_WEST;
                    } else if (sw) {
                        moveDirection = DIRECTION_SOUTHWEST;
                    } else if (e) {
                        moveDirection = DIRECTION_EAST;
                    } else if (se) {
                        moveDirection = DIRECTION_SOUTHEAST;
                    }
                    return true;
                }

                /* fleeing */
                if (flee && canWalkTo(creaturePos, DIRECTION_NORTH)) {
                    // towards player, yea
                    moveDirection = DIRECTION_NORTH;
                    return true;
                }

                /* end of fleeing */
                break;
            }

            case DIRECTION_SOUTH: {
                if (canWalkTo(creaturePos, DIRECTION_NORTH)) {
                    moveDirection = DIRECTION_NORTH;
                    return true;
                }

                bool w = canWalkTo(creaturePos, DIRECTION_WEST);
                bool e = canWalkTo(creaturePos, DIRECTION_EAST);
                if (w && e && offsetx == 0) {
                    moveDirection = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
                    return true;
                } else if (w && offsetx <= 0) {
                    moveDirection = DIRECTION_WEST;
                    return true;
                } else if (e && offsetx >= 0) {
                    moveDirection = DIRECTION_EAST;
                    return true;
                }

                /* fleeing */
                if (flee) {
                    if (w && e) {
                        moveDirection = boolean_random() ? DIRECTION_WEST : DIRECTION_EAST;
                        return true;
                    } else if (w) {
                        moveDirection = DIRECTION_WEST;
                        return true;
                    } else if (e) {
                        moveDirection = 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) {
                        moveDirection = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_NORTHEAST;
                    } else if (w) {
                        moveDirection = DIRECTION_WEST;
                    } else if (nw) {
                        moveDirection = DIRECTION_NORTHWEST;
                    } else if (e) {
                        moveDirection = DIRECTION_EAST;
                    } else if (ne) {
                        moveDirection = DIRECTION_NORTHEAST;
                    }
                    return true;
                }

                /* fleeing */
                if (flee && canWalkTo(creaturePos, DIRECTION_SOUTH)) {
                    // towards player, yea
                    moveDirection = 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)) {
                    moveDirection = DIRECTION_EAST;
                    return true;
                }

                bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
                bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
                if (n && s && offsety == 0) {
                    moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
                    return true;
                } else if (n && offsety <= 0) {
                    moveDirection = DIRECTION_NORTH;
                    return true;
                } else if (s && offsety >= 0) {
                    moveDirection = DIRECTION_SOUTH;
                    return true;
                }

                /* fleeing */
                if (flee) {
                    if (n && s) {
                        moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
                        return true;
                    } else if (n) {
                        moveDirection = DIRECTION_NORTH;
                        return true;
                    } else if (s) {
                        moveDirection = 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) {
                        moveDirection = boolean_random() ? DIRECTION_SOUTHEAST : DIRECTION_NORTHEAST;
                    } else if (s) {
                        moveDirection = DIRECTION_SOUTH;
                    } else if (se) {
                        moveDirection = DIRECTION_SOUTHEAST;
                    } else if (n) {
                        moveDirection = DIRECTION_NORTH;
                    } else if (ne) {
                        moveDirection = DIRECTION_NORTHEAST;
                    }
                    return true;
                }

                /* fleeing */
                if (flee && canWalkTo(creaturePos, DIRECTION_WEST)) {
                    // towards player, yea
                    moveDirection = DIRECTION_WEST;
                    return true;
                }

                /* end of fleeing */
                break;
            }

            case DIRECTION_EAST: {
                if (canWalkTo(creaturePos, DIRECTION_WEST)) {
                    moveDirection = DIRECTION_WEST;
                    return true;
                }

                bool n = canWalkTo(creaturePos, DIRECTION_NORTH);
                bool s = canWalkTo(creaturePos, DIRECTION_SOUTH);
                if (n && s && offsety == 0) {
                    moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
                    return true;
                } else if (n && offsety <= 0) {
                    moveDirection = DIRECTION_NORTH;
                    return true;
                } else if (s && offsety >= 0) {
                    moveDirection = DIRECTION_SOUTH;
                    return true;
                }

                /* fleeing */
                if (flee) {
                    if (n && s) {
                        moveDirection = boolean_random() ? DIRECTION_NORTH : DIRECTION_SOUTH;
                        return true;
                    } else if (n) {
                        moveDirection = DIRECTION_NORTH;
                        return true;
                    } else if (s) {
                        moveDirection = 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) {
                        moveDirection = boolean_random() ? DIRECTION_NORTHWEST : DIRECTION_SOUTHWEST;
                    } else if (n) {
                        moveDirection = DIRECTION_NORTH;
                    } else if (nw) {
                        moveDirection = DIRECTION_NORTHWEST;
                    } else if (s) {
                        moveDirection = DIRECTION_SOUTH;
                    } else if (sw) {
                        moveDirection = DIRECTION_SOUTHWEST;
                    }
                    return true;
                }

                /* fleeing */
                if (flee && canWalkTo(creaturePos, DIRECTION_EAST)) {
                    // towards player, yea
                    moveDirection = DIRECTION_EAST;
                    return true;
                }

                /* end of fleeing */
                break;
            }

            default:
                break;
        }
    }

    return true;
}

bool Monster::canWalkTo(Position pos, Direction moveDirection) const
{
    pos = getNextPosition(moveDirection, 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 | FLAG_IGNOREFIELDDAMAGE) == RETURNVALUE_NOERROR) {
            return true;
        }
    }
    return false;
}

void Monster::death(Creature*)
{
    setAttackedCreature(nullptr);

    for (Creature* summon : summons) {
        summon->changeHealth(-summon->getHealth());
        summon->removeMaster();
    }
    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;
    }

    float multiplier;
    if (maxCombatValue > 0) { //defense
        multiplier = g_config.getFloat(ConfigManager::RATE_MONSTER_DEFENSE);
    } else { //attack
        multiplier = g_config.getFloat(ConfigManager::RATE_MONSTER_ATTACK);
    }

    min = minCombatValue * multiplier;
    max = maxCombatValue * multiplier;
    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 (dir == DIRECTION_SOUTH) {
                    newDir = DIRECTION_WEST;
                } else if (dir == DIRECTION_EAST) {
                    newDir = DIRECTION_NORTH;
                }
            } else if (offsetx < 0 && offsety > 0) {
                if (dir == DIRECTION_NORTH) {
                    newDir = DIRECTION_WEST;
                } else if (dir == DIRECTION_EAST) {
                    newDir = DIRECTION_SOUTH;
                }
            } else if (offsetx > 0 && offsety < 0) {
                if (dir == DIRECTION_SOUTH) {
                    newDir = DIRECTION_EAST;
                } else if (dir == DIRECTION_WEST) {
                    newDir = DIRECTION_NORTH;
                }
            } else {
                if (dir == DIRECTION_NORTH) {
                    newDir = DIRECTION_EAST;
                } else if (dir == DIRECTION_WEST) {
                    newDir = DIRECTION_SOUTH;
                }
            }
        }
    }

    g_game.internalCreatureTurn(this, newDir);
}

void Monster::dropLoot(Container* corpse, Creature*)
{
    if (corpse && lootDrop) {
        g_events->eventMonsterOnDropLoot(this, corpse);
    }
}

void Monster::setNormalCreatureLight()
{
    internalLight = mType->info.light;
}

void Monster::drainHealth(Creature* attacker, int32_t damage)
{
    Creature::drainHealth(attacker, damage);

    if (damage > 0 && randomStepping) {
        ignoreFieldDamage = true;
        updateMapCache();
    }

    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 = 8000;
        challengeFocusDuration = targetChangeCooldown;
        targetChangeTicks = 0;
    }
    return result;
}

bool Monster::changeTargetDistance(int32_t distance)
{
    if (isSummon()) {
        return false;
    }

    if (mType->info.isRewardBoss) {
        return false;
    }

    bool shouldUpdate = mType->info.targetDistance > distance ? true : false;
    challengeMeleeDuration = 12000;
    targetDistance = distance;

    if (shouldUpdate) {
        g_game.updateCreatureIcon(this);
    }
    return true;
}

void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const
{
    Creature::getPathSearchParams(creature, fpp);

    fpp.minTargetDist = 1;
    fpp.maxTargetDist = targetDistance;

    if (isSummon()) {
        if (getMaster() == creature) {
            int32_t distX = Position::getDistanceX(getPosition(), creature->getPosition());
            int32_t distY = Position::getDistanceY(getPosition(), creature->getPosition());
            fpp.absoluteDist = true;
            fpp.preferDiagonal = !(distX >= 2 && distY == 0 || distY >= 2 && distX == 0);
            fpp.maxTargetDist = 2;
            fpp.fullPathSearch = true;
        } else if (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 (targetDistance <= 1) {
        fpp.fullPathSearch = true;
    } else {
        fpp.fullPathSearch = !canUseAttack(getPosition(), creature);
    }
}