////////////////////////////////////////////////////////////////////////
// OpenTibia - an opensource roleplaying game
////////////////////////////////////////////////////////////////////////
// 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 3 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, see .
////////////////////////////////////////////////////////////////////////
#include "otpch.h"
#include "monster.h"
#include "spawn.h"
#include "monsters.h"
#include "spells.h"
#include "combat.h"
#include "configmanager.h"
#include "game.h"
extern Game g_game;
extern ConfigManager g_config;
extern Monsters g_monsters;
AutoListMonster::listMonster;
#ifdef __ENABLE_SERVER_DIAGNOSTIC__
uint32_t Monster::monsterCount = 0;
#endif
int32_t Monster::despawnRange;
int32_t Monster::despawnRadius;
Monster* Monster::createMonster(MonsterType* mType)
{
return new Monster(mType);
}
Monster* Monster::createMonster(const std::string& name)
{
MonsterType* mType = g_monsters.getMonsterType(name);
if(!mType)
return NULL;
return createMonster(mType);
}
Monster::Monster(MonsterType* _mtype) :
Creature()
{
isActivated = false;
isMasterInRange = false;
teleportToMaster = false;
mType = _mtype;
spawn = NULL;
defaultOutfit = mType->outfit;
currentOutfit = mType->outfit;
health = mType->health;
healthMax = mType->healthMax;
baseSpeed = mType->baseSpeed;
internalLight.level = mType->lightLevel;
internalLight.color = mType->lightColor;
setSkull(mType->skull);
setShield(mType->partyShield);
hideName = mType->hideName, hideHealth = mType->hideHealth;
minCombatValue = 0;
maxCombatValue = 0;
targetTicks = 0;
targetChangeTicks = 0;
targetChangeCooldown = 0;
attackTicks = 0;
defenseTicks = 0;
yellTicks = 0;
extraMeleeAttack = false;
// register creature events
MonsterScriptList::iterator it;
for(it = mType->scriptList.begin(); it != mType->scriptList.end(); ++it)
{
if(!registerCreatureEvent(*it))
std::cout << "Warning: [Monster::Monster]. Unknown event name - " << *it << std::endl;
}
#ifdef __ENABLE_SERVER_DIAGNOSTIC__
monsterCount++;
#endif
}
Monster::~Monster()
{
clearTargetList();
clearFriendList();
#ifdef __ENABLE_SERVER_DIAGNOSTIC__
monsterCount--;
#endif
}
void Monster::onAttackedCreatureDisappear(bool isLogout)
{
#ifdef __DEBUG__
std::cout << "Attacked creature disappeared." << std::endl;
#endif
attackTicks = 0;
extraMeleeAttack = true;
}
void Monster::onFollowCreatureDisappear(bool isLogout)
{
#ifdef __DEBUG__
std::cout << "Follow creature disappeared." << std::endl;
#endif
}
void Monster::onCreatureAppear(const Creature* creature, bool isLogin)
{
Creature::onCreatureAppear(creature, isLogin);
if(creature == this)
{
//We just spawned lets look around to see who is there.
if(isSummon())
isMasterInRange = canSee(getMaster()->getPosition());
updateTargetList();
activate();
}
else
onCreatureEnter(const_cast(creature));
}
void Monster::onCreatureDisappear(const Creature* creature, uint32_t stackpos, bool isLogout)
{
Creature::onCreatureDisappear(creature, stackpos, isLogout);
if(creature == this)
{
if(spawn)
spawn->startSpawnCheck();
deactivate(true);
}
else
onCreatureLeave(const_cast(creature));
}
void Monster::onCreatureMove(const Creature* creature, const Tile* newTile, const Position& newPos,
const Tile* oldTile, const Position& oldPos, uint32_t oldStackPos, bool teleport)
{
Creature::onCreatureMove(creature, newTile, newPos, oldTile, oldPos, oldStackPos, teleport);
if(creature == this)
{
if(isSummon())
isMasterInRange = canSee(getMaster()->getPosition());
updateTargetList();
activate();
/*
TODO: Optimizations here
if(teleport)
//do a full update of the friend/target list
else
//partial update of the friend/target list
*/
}
else
{
bool canSeeNewPos = canSee(newPos), canSeeOldPos = canSee(oldPos);
if(canSeeNewPos && !canSeeOldPos)
onCreatureEnter(const_cast(creature));
else if(!canSeeNewPos && canSeeOldPos)
onCreatureLeave(const_cast(creature));
if(isSummon() && getMaster() == creature)
{
if(canSeeNewPos)
{
//Turn the summon on again
isMasterInRange = true;
activate();
}
}
if(!followCreature && !isSummon())
{
//we have no target lets try pick this one
if(isOpponent(creature))
selectTarget(const_cast(creature));
}
}
}
void Monster::updateTargetList()
{
CreatureList::iterator it;
for(it = friendList.begin(); it != friendList.end();)
{
if((*it)->getHealth() <= 0 || !canSee((*it)->getPosition()))
{
(*it)->releaseThing2();
it = friendList.erase(it);
}
else
++it;
}
for(it = targetList.begin(); it != targetList.end();)
{
if((*it)->getHealth() <= 0 || !canSee((*it)->getPosition()))
{
(*it)->releaseThing2();
it = targetList.erase(it);
}
else
++it;
}
const SpectatorVec& list = g_game.getSpectators(getPosition());
for(SpectatorVec::const_iterator it = list.begin(); it != list.end(); ++it)
{
if((*it) != this && canSee((*it)->getPosition()))
onCreatureFound(*it);
}
}
void Monster::clearTargetList()
{
for(CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it)
(*it)->releaseThing2();
targetList.clear();
}
void Monster::clearFriendList()
{
for(CreatureList::iterator it = friendList.begin(); it != friendList.end(); ++it)
(*it)->releaseThing2();
friendList.clear();
}
void Monster::onCreatureFound(Creature* creature, bool pushFront /*= false*/)
{
if(isFriend(creature))
{
assert(creature != this);
if(std::find(friendList.begin(), friendList.end(), creature) == friendList.end())
{
creature->useThing2();
friendList.push_back(creature);
}
}
if(isOpponent(creature))
{
assert(creature != this);
if(std::find(targetList.begin(), targetList.end(), creature) == targetList.end())
{
creature->useThing2();
if(pushFront)
targetList.push_front(creature);
else
targetList.push_back(creature);
activate();
}
}
}
void Monster::onCreatureEnter(Creature* creature)
{
//std::cout << "onCreatureEnter - " << creature->getName() << std::endl;
if(getMaster() == creature)
{
//Turn the summon on again
isMasterInRange = true;
activate();
}
onCreatureFound(creature, true);
}
bool Monster::isFriend(const Creature* creature)
{
if(isSummon() && getMaster()->getPlayer())
{
const Player* masterPlayer = getMaster()->getPlayer();
const Player* tmpPlayer = NULL;
if(creature->getPlayer())
tmpPlayer = creature->getPlayer();
else if(creature->getMaster() && creature->getMaster()->getPlayer())
tmpPlayer = creature->getMaster()->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)
{
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;
}
bool Monster::doTeleportToMaster()
{
const Position& tmp = getPosition();
if(g_game.internalTeleport(this, g_game.getClosestFreeTile(this, getMaster()->getPosition(), true), false) == RET_NOERROR)
{
g_game.addMagicEffect(tmp, NM_ME_POFF);
g_game.addMagicEffect(getPosition(), NM_ME_TELEPORT);
return true;
}
return false;
}
void Monster::onCreatureLeave(Creature* creature)
{
//std::cout << "onCreatureLeave - " << creature->getName() << std::endl;
if(isSummon() && getMaster() == creature)
{
if(!g_config.getBool(ConfigManager::TELEPORT_SUMMONS) && (!getMaster()->getPlayer() || !g_config.getBool(ConfigManager::TELEPORT_PLAYER_SUMMONS)))
{
//Turn the monster off until its master comes back
isMasterInRange = false;
deactivate();
}
else
{
if(!doTeleportToMaster())
teleportToMaster = true;
}
}
//update friendList
if(isFriend(creature))
{
CreatureList::iterator it = std::find(friendList.begin(), friendList.end(), creature);
if(it != friendList.end())
{
(*it)->releaseThing2();
friendList.erase(it);
}
#ifdef __DEBUG__
else
std::cout << "Monster: " << creature->getName() << " not found in the friendList." << std::endl;
#endif
}
//update targetList
if(isOpponent(creature))
{
CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature);
if(it != targetList.end())
{
(*it)->releaseThing2();
targetList.erase(it);
if(targetList.empty())
deactivate();
}
#ifdef __DEBUG__
else
std::cout << "Player: " << creature->getName() << " not found in the targetList." << std::endl;
#endif
}
}
bool Monster::searchTarget(TargetSearchType_t searchType /*= TARGETSEARCH_DEFAULT*/)
{
#ifdef __DEBUG__
std::cout << "Searching target... " << std::endl;
#endif
std::list resultList;
const Position& myPos = getPosition();
for(CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it)
{
if(followCreature != (*it) && isTarget(*it))
{
if(searchType == TARGETSEARCH_RANDOM || canUseAttack(myPos, *it))
resultList.push_back(*it);
}
}
if(!resultList.empty())
{
uint32_t index = random_range(0, resultList.size() - 1);
CreatureList::iterator it = resultList.begin();
std::advance(it, index);
#ifdef __DEBUG__
std::cout << "Selecting target " << (*it)->getName() << std::endl;
#endif
return selectTarget(*it);
}
if(searchType == TARGETSEARCH_ATTACKRANGE)
return false;
//lets just pick the first target in the list
for(CreatureList::iterator it = targetList.begin(); it != targetList.end(); ++it)
{
if(followCreature != (*it) && selectTarget(*it))
{
#ifdef __DEBUG__
std::cout << "Selecting target " << (*it)->getName() << std::endl;
#endif
return true;
}
}
return false;
}
void Monster::onFollowCreatureComplete(const Creature* creature)
{
if(!creature)
return;
CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature);
if(it != targetList.end())
{
Creature* target = (*it);
targetList.erase(it);
if(hasFollowPath) //push target we have found a path to the front
targetList.push_front(target);
else if(!isSummon()) //push target we have not found a path to the back
targetList.push_back(target);
else //Since we removed the creature from the targetList (and not put it back) we have to release it too
target->releaseThing2();
}
}
BlockType_t Monster::blockHit(Creature* attacker, CombatType_t combatType, int32_t& damage,
bool checkDefense/* = false*/, bool checkArmor/* = false*/)
{
BlockType_t blockType = Creature::blockHit(attacker, combatType, damage, checkDefense, checkArmor);
if(damage != 0)
{
int32_t elementMod = 0;
ElementMap::iterator it = mType->elementMap.find(combatType);
if(it != mType->elementMap.end())
elementMod = it->second;
if(elementMod != 0)
{
damage = (int32_t)std::ceil(damage * ((float)(100 - elementMod) / 100));
if(damage <= 0)
{
damage = 0;
blockType = BLOCK_DEFENSE;
}
}
}
return blockType;
}
bool Monster::isTarget(Creature* creature)
{
return (!creature->isRemoved() && creature->isAttackable() && creature->getZone() != ZONE_PROTECTION
&& canSeeCreature(creature) && creature->getPosition().z == getPosition().z);
}
bool Monster::selectTarget(Creature* creature)
{
#ifdef __DEBUG__
std::cout << "Selecting target... " << std::endl;
#endif
if(!isTarget(creature))
return false;
CreatureList::iterator it = std::find(targetList.begin(), targetList.end(), creature);
if(it == targetList.end())
{
//Target not found in our target list.
#ifdef __DEBUG__
std::cout << "Target not found in targetList." << std::endl;
#endif
return false;
}
if(isHostile() || isSummon())
{
if(setAttackedCreature(creature) && !isSummon())
{
Dispatcher::getDispatcher().addTask(createTask(
boost::bind(&Game::checkCreatureAttack, &g_game, getID())));
}
}
return setFollowCreature(creature, true);
}
bool Monster::activate(bool forced /*= false*/)
{
if(isSummon())
{
if(isMasterInRange || forced)
isActivated = true;
}
else
{
if(!targetList.empty() || forced)
isActivated = true;
}
if(isActivated || !conditions.empty())
g_game.addCreatureCheck(this);
return isActivated;
}
bool Monster::deactivate(bool forced /*= false*/)
{
if(isSummon())
{
if(!isMasterInRange || getMaster()->isIdle() || forced)
isActivated = false;
}
else
{
if(targetList.empty() || forced)
isActivated = false;
}
if((!isActivated && conditions.empty()) || forced)
{
onIdleStatus();
g_game.removeCreatureCheck(this);
}
return !isActivated;
}
void Monster::onAddCondition(ConditionType_t type)
{
//the walkCache need to be updated if the monster becomes "resistent" to the damage, see Tile::__queryAdd()
if(type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON)
updateMapCache();
activate();
}
void Monster::onEndCondition(ConditionType_t type)
{
//the walkCache need to be updated if the monster loose the "resistent" to the damage, see Tile::__queryAdd()
if(type == CONDITION_FIRE || type == CONDITION_ENERGY || type == CONDITION_POISON)
updateMapCache();
deactivate();
}
void Monster::onThink(uint32_t interval)
{
Creature::onThink(interval);
if(despawn())
{
g_game.removeCreature(this, true);
deactivate(true);
}
else if(!deactivate())
{
if(teleportToMaster && doTeleportToMaster())
teleportToMaster = false;
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(NULL);
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();
else if(isFleeing())
{
if(attackedCreature && !canUseAttack(getPosition(), attackedCreature))
searchTarget(TARGETSEARCH_ATTACKRANGE);
}
}
onThinkTarget(interval);
onThinkYell(interval);
onThinkDefense(interval);
}
}
void Monster::doAttacking(uint32_t interval)
{
if(!attackedCreature || (isSummon() && attackedCreature == this))
return;
bool updateLook = true, outOfRange = true;
resetTicks = interval != 0;
attackTicks += interval;
const Position& myPos = getPosition();
const Position& targetPos = attackedCreature->getPosition();
for(SpellList::iterator it = mType->spellAttackList.begin(); it != mType->spellAttackList.end(); ++it)
{
if(it->isMelee && isFleeing())
continue;
bool inRange = false;
if(canUseSpell(myPos, targetPos, *it, interval, inRange))
{
if(it->chance >= (uint32_t)random_range(1, 100))
{
if(updateLook)
{
updateLookDirection();
updateLook = false;
}
minCombatValue = it->minCombatValue;
maxCombatValue = it->maxCombatValue;
it->spell->castSpell(this, attackedCreature);
if(it->isMelee)
extraMeleeAttack = false;
#ifdef __DEBUG__
static uint64_t prevTicks = OTSYS_TIME();
std::cout << "doAttacking ticks: " << OTSYS_TIME() - prevTicks << std::endl;
prevTicks = OTSYS_TIME();
#endif
}
}
if(inRange)
outOfRange = false;
else if(it->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())
return true;
const Position& targetPos = target->getPosition();
for(SpellList::iterator it = mType->spellAttackList.begin(); it != mType->spellAttackList.end(); ++it)
{
if((*it).range != 0 && std::max(std::abs(pos.x - targetPos.x), std::abs(pos.y - targetPos.y)) <= (int32_t)(*it).range)
return g_game.isSightClear(pos, targetPos, true);
}
return false;
}
bool Monster::canUseSpell(const Position& pos, const Position& targetPos,
const spellBlock_t& sb, uint32_t interval, bool& inRange)
{
inRange = true;
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(std::abs(pos.x - targetPos.x), std::abs(pos.y - targetPos.y)) > (int32_t)sb.range)
{
inRange = false;
return false;
}
return true;
}
void Monster::onThinkTarget(uint32_t interval)
{
if(!isSummon())
{
if(mType->changeTargetSpeed > 0)
{
bool canChangeTarget = true;
if(targetChangeCooldown > 0)
{
targetChangeCooldown -= interval;
if(targetChangeCooldown <= 0)
{
targetChangeCooldown = 0;
targetChangeTicks = (uint32_t)mType->changeTargetSpeed;
}
else
canChangeTarget = false;
}
if(canChangeTarget)
{
targetChangeTicks += interval;
if(targetChangeTicks >= (uint32_t)mType->changeTargetSpeed)
{
targetChangeTicks = 0;
targetChangeCooldown = (uint32_t)mType->changeTargetSpeed;
if(mType->changeTargetChance >= random_range(1, 100))
searchTarget(TARGETSEARCH_RANDOM);
}
}
}
}
}
void Monster::onThinkDefense(uint32_t interval)
{
resetTicks = true;
defenseTicks += interval;
for(SpellList::iterator it = mType->spellDefenseList.begin(); it != mType->spellDefenseList.end(); ++it)
{
if(it->speed > defenseTicks)
{
resetTicks = false;
continue;
}
if(defenseTicks % it->speed >= interval)
{
//already used this spell for this round
continue;
}
if((it->chance >= (uint32_t)random_range(1, 100)))
{
minCombatValue = it->minCombatValue;
maxCombatValue = it->maxCombatValue;
it->spell->castSpell(this, this);
}
}
if(!isSummon() && (int32_t)summons.size() < mType->maxSummons)
{
for(SummonList::iterator it = mType->summonList.begin(); it != mType->summonList.end(); ++it)
{
if(it->interval > defenseTicks)
{
resetTicks = false;
continue;
}
if((int32_t)summons.size() >= mType->maxSummons)
continue;
if(defenseTicks % it->interval >= interval)
continue;
uint32_t typeCount = 0;
for(CreatureList::iterator cit = summons.begin(); cit != summons.end(); ++cit)
{
if((*cit)->getMonster() && (*cit)->getMonster()->getName() == it->name)
typeCount++;
}
if(typeCount >= it->amount)
continue;
if((it->chance >= (uint32_t)random_range(1, 100)))
{
Monster* summon = Monster::createMonster(it->name);
if(summon)
{
addSummon(summon);
if(!g_game.placeCreature(summon, getPosition()))
removeSummon(summon);
else
g_game.addMagicEffect(getPosition(), NM_ME_MAGIC_ENERGY);
}
}
}
}
if(resetTicks)
defenseTicks = 0;
}
void Monster::onThinkYell(uint32_t interval)
{
if(mType->yellSpeedTicks > 0)
{
yellTicks += interval;
if(yellTicks >= mType->yellSpeedTicks)
{
yellTicks = 0;
if(!mType->voiceVector.empty() && (mType->yellChance >= (uint32_t)random_range(1, 100)))
{
uint32_t index = random_range(0, mType->voiceVector.size() - 1);
const voiceBlock_t& vb = mType->voiceVector[index];
if(vb.yellText)
g_game.internalCreatureSay(this, SPEAK_MONSTER_YELL, vb.text);
else
g_game.internalCreatureSay(this, SPEAK_MONSTER_SAY, vb.text);
}
}
}
}
void Monster::onWalk()
{
Creature::onWalk();
}
bool Monster::pushItem(Item* item, int32_t radius)
{
const Position& centerPos = item->getPosition();
PairVector pairVector;
pairVector.push_back(PositionPair(-1, -1));
pairVector.push_back(PositionPair(-1, 0));
pairVector.push_back(PositionPair(-1, 1));
pairVector.push_back(PositionPair(0, -1));
pairVector.push_back(PositionPair(0, 1));
pairVector.push_back(PositionPair(1, -1));
pairVector.push_back(PositionPair(1, 0));
pairVector.push_back(PositionPair(1, 1));
std::random_shuffle(pairVector.begin(), pairVector.end());
Position tryPos;
for(int32_t n = 1; n <= radius; ++n)
{
for(PairVector::iterator it = pairVector.begin(); it != pairVector.end(); ++it)
{
int32_t dx = it->first * n;
int32_t dy = it->second * n;
tryPos = centerPos;
tryPos.x = tryPos.x + dx;
tryPos.y = tryPos.y + dy;
Tile* tile = g_game.getTile(tryPos.x, tryPos.y, tryPos.z);
if(tile && g_game.canThrowObjectTo(centerPos, tryPos) && g_game.internalMoveItem(this, item->getParent(),
tile, INDEX_WHEREEVER, item, item->getItemCount(), NULL) == RET_NOERROR)
return true;
}
}
return false;
}
void Monster::pushItems(Tile* tile)
{
if(!tile->downItems)
return;
//We cannot 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
int32_t moveCount = 0, removeCount = 0, downItemSize = tile->downItems->size();
for(int32_t i = downItemSize - 1; i >= 0; --i)
{
assert(i >= 0 && i < (int32_t)tile->downItems->size());
Item* item = tile->downItems->at(i);
if(item && item->hasProperty(MOVEABLE) && (item->hasProperty(BLOCKPATH)
|| item->hasProperty(BLOCKSOLID)))
{
if(moveCount < 20 && pushItem(item, 1))
moveCount++;
else if(g_game.internalRemoveItem(this, item) == RET_NOERROR)
++removeCount;
}
}
if(removeCount > 0)
g_game.addMagicEffect(tile->getPosition(), NM_ME_POFF);
}
bool Monster::pushCreature(Creature* creature)
{
Position monsterPos = creature->getPosition();
DirVector dirVector;
dirVector.push_back(NORTH);
dirVector.push_back(SOUTH);
dirVector.push_back(WEST);
dirVector.push_back(EAST);
std::random_shuffle(dirVector.begin(), dirVector.end());
for(DirVector::iterator it = dirVector.begin(); it != dirVector.end(); ++it)
{
Tile* toTile = g_game.getTile(Spells::getCasterPosition(creature, *it));
if(toTile && !toTile->hasProperty(BLOCKPATH) && g_game.internalMoveCreature(creature, *it) == RET_NOERROR)
return true; //TODO: internalMoveCreature is always returning RET_NOERROR, but creature is really not moved = CRASH
}
return false;
}
void Monster::pushCreatures(Tile* tile)
{
if(!tile || !tile->creatures || tile->creatures->empty())
return;
bool effect = false;
Monster* monster = NULL;
for(uint32_t i = 0; (tile->creatures && i < tile->creatures->size());)
{
if(tile->creatures->at(i) && (monster = tile->creatures->at(
i)->getMonster()) && monster->isPushable())
{
if(pushCreature(monster))
continue;
monster->setDropLoot(LOOT_DROP_NONE);
monster->changeHealth(-monster->getHealth());
if(!effect)
effect = true;
}
++i;
}
if(effect)
g_game.addMagicEffect(tile->getPosition(), NM_ME_BLOCKHIT);
}
bool Monster::getNextStep(Direction& dir)
{
if(!isActivated || getHealth() <= 0)
{
//we dont have anyone watching might aswell stop walking
eventWalk = 0;
return false;
}
bool result = false;
if((!followCreature || !hasFollowPath) && !isSummon())
{
if(followCreature || getTimeSinceLastMove() > 1000)
{
//choose a random direction
result = getRandomStep(getPosition(), dir);
}
}
else if(isSummon() || followCreature)
{
result = Creature::getNextStep(dir);
if(!result)
{
//target dancing
if(attackedCreature && attackedCreature == followCreature)
{
if(isFleeing())
result = getDanceStep(getPosition(), dir, false, false);
else if(mType->staticAttackChance < (uint32_t)random_range(1, 100))
result = getDanceStep(getPosition(), dir);
}
}
}
if(result && (canPushItems() || canPushCreatures()))
{
if(Tile* tile = g_game.getTile(Spells::getCasterPosition(this, dir)))
{
if(canPushItems())
pushItems(tile);
if(canPushCreatures())
pushCreatures(tile);
}
#ifdef __DEBUG__
else
std::cout << "[Warning - Monster::getNextStep] no tile found." << std::endl;
#endif
}
return result;
}
bool Monster::getRandomStep(const Position& creaturePos, Direction& dir)
{
DirVector dirVector;
dirVector.push_back(NORTH);
dirVector.push_back(SOUTH);
dirVector.push_back(WEST);
dirVector.push_back(EAST);
std::random_shuffle(dirVector.begin(), dirVector.end());
for(DirVector::iterator it = dirVector.begin(); it != dirVector.end(); ++it)
{
if(canWalkTo(creaturePos, *it))
{
dir = *it;
return true;
}
}
return false;
}
bool Monster::getDanceStep(const Position& creaturePos, Direction& dir, bool keepAttack /*= true*/, bool keepDistance /*= true*/)
{
assert(attackedCreature != NULL);
bool canDoAttackNow = canUseAttack(creaturePos, attackedCreature);
const Position& centerPos = attackedCreature->getPosition();
uint32_t tmpDist, centerToDist = std::max(std::abs(creaturePos.x - centerPos.x), std::abs(creaturePos.y - centerPos.y));
DirVector dirVector;
if(!keepDistance || creaturePos.y - centerPos.y >= 0)
{
tmpDist = std::max(std::abs((creaturePos.x) - centerPos.x), std::abs((creaturePos.y - 1) - centerPos.y));
if(tmpDist == centerToDist && canWalkTo(creaturePos, NORTH))
{
bool result = true;
if(keepAttack)
result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y - 1, creaturePos.z), attackedCreature));
if(result)
dirVector.push_back(NORTH);
}
}
if(!keepDistance || creaturePos.y - centerPos.y <= 0)
{
tmpDist = std::max(std::abs((creaturePos.x) - centerPos.x), std::abs((creaturePos.y + 1) - centerPos.y));
if(tmpDist == centerToDist && canWalkTo(creaturePos, SOUTH))
{
bool result = true;
if(keepAttack)
result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x, creaturePos.y + 1, creaturePos.z), attackedCreature));
if(result)
dirVector.push_back(SOUTH);
}
}
if(!keepDistance || creaturePos.x - centerPos.x >= 0)
{
tmpDist = std::max(std::abs((creaturePos.x + 1) - centerPos.x), std::abs((creaturePos.y) - centerPos.y));
if(tmpDist == centerToDist && canWalkTo(creaturePos, EAST))
{
bool result = true;
if(keepAttack)
result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x + 1, creaturePos.y, creaturePos.z), attackedCreature));
if(result)
dirVector.push_back(EAST);
}
}
if(!keepDistance || creaturePos.x - centerPos.x <= 0)
{
tmpDist = std::max(std::abs((creaturePos.x - 1) - centerPos.x), std::abs((creaturePos.y) - centerPos.y));
if(tmpDist == centerToDist && canWalkTo(creaturePos, WEST))
{
bool result = true;
if(keepAttack)
result = (!canDoAttackNow || canUseAttack(Position(creaturePos.x - 1, creaturePos.y, creaturePos.z), attackedCreature));
if(result)
dirVector.push_back(WEST);
}
}
if(!dirVector.empty())
{
std::random_shuffle(dirVector.begin(), dirVector.end());
dir = dirVector[random_range(0, dirVector.size() - 1)];
return true;
}
return false;
}
bool Monster::isInSpawnRange(const Position& toPos)
{
if(masterRadius == -1)
return true;
return !inDespawnRange(toPos);
}
bool Monster::canWalkTo(Position pos, Direction dir)
{
if(getNoMove())
return false;
switch(dir)
{
case NORTH:
pos.y += -1;
break;
case WEST:
pos.x += -1;
break;
case EAST:
pos.x += 1;
break;
case SOUTH:
pos.y += 1;
break;
default:
break;
}
if(!isInSpawnRange(pos) || !getWalkCache(pos))
return false;
Tile* tile = g_game.getTile(pos);
return tile && tile->__queryAdd(0, this, 1, FLAG_PATHFINDING) == RET_NOERROR;
}
bool Monster::onDeath()
{
if(!Creature::onDeath())
return false;
setAttackedCreature(NULL);
deactivate(true);
for(CreatureList::iterator cit = summons.begin(); cit != summons.end(); ++cit)
{
(*cit)->changeHealth(-(*cit)->getHealth());
(*cit)->setMaster(NULL);
(*cit)->releaseThing2();
}
summons.clear();
clearTargetList();
clearFriendList();
g_game.removeCreature(this, false);
return true;
}
Item* Monster::getCorpse()
{
Item* corpse = Creature::getCorpse();
if(corpse && mostDamageCreature)
{
uint32_t owner = 0;
if(mostDamageCreature->getPlayer())
owner = mostDamageCreature->getID();
else if(mostDamageCreature->getMaster() && mostDamageCreature->getMaster()->getPlayer())
owner = mostDamageCreature->getMaster()->getID();
if(owner != 0)
corpse->setCorpseOwner(owner);
}
return corpse;
}
bool Monster::inDespawnRange(const Position& pos)
{
if(spawn && !mType->isLureable)
{
if(Monster::despawnRadius == 0)
return false;
if(!Spawns::getInstance()->isInZone(masterPos, Monster::despawnRadius, pos))
return true;
if(Monster::despawnRange == 0)
return false;
if(std::abs(pos.z - masterPos.z) > Monster::despawnRange)
return true;
}
return false;
}
bool Monster::despawn()
{
return inDespawnRange(getPosition());
}
bool Monster::getCombatValues(int32_t& min, int32_t& max)
{
if(!minCombatValue && !maxCombatValue)
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();
int32_t dx = attackedCreaturePos.x - pos.x;
int32_t dy = attackedCreaturePos.y - pos.y;
if(std::abs(dx) > std::abs(dy))
{
//look EAST/WEST
if(dx < 0)
newDir = WEST;
else
newDir = EAST;
}
else if(std::abs(dx) < std::abs(dy))
{
//look NORTH/SOUTH
if(dy < 0)
newDir = NORTH;
else
newDir = SOUTH;
}
else
{
if(dx < 0 && dy < 0)
{
if(getDirection() == SOUTH)
newDir = WEST;
else if(getDirection() == EAST)
newDir = NORTH;
}
else if(dx < 0 && dy > 0)
{
if(getDirection() == NORTH)
newDir = WEST;
else if(getDirection() == EAST)
newDir = SOUTH;
}
else if(dx > 0 && dy < 0)
{
if(getDirection() == SOUTH)
newDir = EAST;
else if(getDirection() == WEST)
newDir = NORTH;
}
else
{
if(getDirection() == NORTH)
newDir = EAST;
else if(getDirection() == WEST)
newDir = SOUTH;
}
}
}
g_game.internalCreatureTurn(this, newDir);
}
void Monster::dropLoot(Container* corpse)
{
if(corpse && lootDrop == LOOT_DROP_FULL)
mType->createLoot(corpse);
}
bool Monster::isImmune(CombatType_t type) const
{
ElementMap::const_iterator it = mType->elementMap.find(type);
if(it == mType->elementMap.end())
return Creature::isImmune(type);
return it->second >= 100;
}
void Monster::setNormalCreatureLight()
{
internalLight.level = mType->lightLevel;
internalLight.color = mType->lightColor;
}
void Monster::drainHealth(Creature* attacker, CombatType_t combatType, int32_t damage)
{
Creature::drainHealth(attacker, combatType, damage);
if(isInvisible())
removeCondition(CONDITION_INVISIBLE);
}
void Monster::changeHealth(int32_t healthChange)
{
Creature::changeHealth(healthChange);
//In case a player with ignore flag set attacks the monster
activate(true);
}
bool Monster::challengeCreature(Creature* creature)
{
if(isSummon())
return false;
bool result = selectTarget(creature);
if(result)
{
targetChangeCooldown = 8000;
targetChangeTicks = 0;
}
return result;
}
bool Monster::convinceCreature(Creature* creature)
{
Player* player = creature->getPlayer();
if(player && !player->hasFlag(PlayerFlag_CanConvinceAll))
{
if(!mType->isConvinceable)
return false;
}
if(isSummon())
{
if(getMaster()->getPlayer())
return false;
else if(getMaster() != creature)
{
Creature* oldMaster = getMaster();
oldMaster->removeSummon(this);
creature->addSummon(this);
setFollowCreature(NULL);
setAttackedCreature(NULL);
//destroy summons
for(CreatureList::iterator cit = summons.begin(); cit != summons.end(); ++cit)
{
(*cit)->changeHealth(-(*cit)->getHealth());
(*cit)->setMaster(NULL);
(*cit)->releaseThing2();
}
summons.clear();
isMasterInRange = true;
updateTargetList();
activate();
//Notify surrounding about the change
SpectatorVec list;
g_game.getSpectators(list, getPosition(), false, true);
g_game.getSpectators(list, creature->getPosition(), true, true);
for(SpectatorVec::iterator it = list.begin(); it != list.end(); ++it)
(*it)->onCreatureConvinced(creature, this);
if(spawn)
{
spawn->removeMonster(this);
spawn = NULL;
masterRadius = -1;
}
return true;
}
}
else
{
creature->addSummon(this);
setFollowCreature(NULL);
setAttackedCreature(NULL);
for(CreatureList::iterator cit = summons.begin(); cit != summons.end(); ++cit)
{
(*cit)->changeHealth(-(*cit)->getHealth());
(*cit)->setMaster(NULL);
(*cit)->releaseThing2();
}
summons.clear();
isMasterInRange = true;
updateTargetList();
activate();
//Notify surrounding about the change
SpectatorVec list;
g_game.getSpectators(list, getPosition(), false, true);
g_game.getSpectators(list, creature->getPosition(), true, true);
for(SpectatorVec::iterator it = list.begin(); it != list.end(); ++it)
(*it)->onCreatureConvinced(creature, this);
if(spawn)
{
spawn->removeMonster(this);
spawn = NULL;
masterRadius = -1;
}
return true;
}
return false;
}
void Monster::onCreatureConvinced(const Creature* convincer, const Creature* creature)
{
if(convincer != this && (isFriend(creature) || isOpponent(creature)))
{
updateTargetList();
activate();
}
}
void Monster::getPathSearchParams(const Creature* creature, FindPathParams& fpp) const
{
Creature::getPathSearchParams(creature, fpp);
fpp.minTargetDist = 1;
fpp.maxTargetDist = mType->targetDistance;
if(isSummon())
{
if(getMaster() == creature)
{
fpp.maxTargetDist = 2;
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
fpp.fullPathSearch = !canUseAttack(getPosition(), creature);
}
}