mirror of
https://github.com/araxiaonline/AscEmu.git
synced 2026-06-13 03:02:22 -04:00
2801 lines
77 KiB
C++
2801 lines
77 KiB
C++
/*
|
|
Copyright (c) 2014-2023 AscEmu Team <http://www.ascemu.org>
|
|
This file is released under the MIT license. See README-MIT for more information.
|
|
*/
|
|
|
|
#include "Objects/DynamicObject.hpp"
|
|
#include "Management/AuctionMgr.h"
|
|
#include "Management/QuestMgr.h"
|
|
#include "Management/Quest.h"
|
|
#include "Management/GameEvent.h"
|
|
#include "Management/Skill.hpp"
|
|
#include "Management/Battleground/Battleground.hpp"
|
|
#include "Objects/Units/Stats.h"
|
|
#include "Storage/MySQLDataStore.hpp"
|
|
#include "Server/MainServerDefines.h"
|
|
#include "Map/Cells/MapCell.hpp"
|
|
#include "Map/Management/MapMgr.hpp"
|
|
#include "Spell/Definitions/PowerType.hpp"
|
|
#include "Pet.h"
|
|
#include "Spell/Definitions/SpellEffects.hpp"
|
|
#include "Storage/MySQLStructures.h"
|
|
#include "Management/ObjectMgr.h"
|
|
#include "Map/Maps/BattleGroundMap.hpp"
|
|
#include "Server/Script/CreatureAIScript.h"
|
|
#include "Objects/Units/Creatures/CreatureGroups.h"
|
|
#include "Server/Definitions.h"
|
|
#include "Storage/DBC/DBCStores.h"
|
|
|
|
using namespace AscEmu::Packets;
|
|
|
|
uint8_t CreatureProperties::generateRandomDisplayIdAndReturnGender(uint32_t* displayId) const
|
|
{
|
|
if (isTriggerNpc)
|
|
{
|
|
// Trigger npcs should load their invisible model
|
|
*displayId = getInvisibleModelForTriggerNpc();
|
|
return 0;
|
|
}
|
|
|
|
const uint32_t models[] = { Male_DisplayID, Male_DisplayID2, Female_DisplayID, Female_DisplayID2 };
|
|
if (!models[0] && !models[1] && !models[2] && !models[3])
|
|
{
|
|
// All models are invalid
|
|
sLogger.failure("CreatureProperties : All display IDs are invalid for creature entry %u", Id);
|
|
return 0;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
const auto res = Util::getRandomUInt(3);
|
|
if (models[res])
|
|
{
|
|
*displayId = models[res];
|
|
return res < 2 ? 0U : 1U;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t CreatureProperties::getRandomModelId() const
|
|
{
|
|
if (isTriggerNpc)
|
|
{
|
|
// Trigger npcs should load their invisible model
|
|
return getInvisibleModelForTriggerNpc();
|
|
}
|
|
|
|
std::vector<uint32_t> modelIds;
|
|
|
|
if (Male_DisplayID)
|
|
modelIds.push_back(Male_DisplayID);
|
|
if (Male_DisplayID2)
|
|
modelIds.push_back(Male_DisplayID2);
|
|
if (Female_DisplayID)
|
|
modelIds.push_back(Female_DisplayID);
|
|
if (Female_DisplayID2)
|
|
modelIds.push_back(Female_DisplayID2);
|
|
|
|
if (modelIds.empty())
|
|
{
|
|
sLogger.failure("CreatureProperties : All display IDs are invalid for creature entry %u", Id);
|
|
return 0;
|
|
}
|
|
|
|
Util::randomShuffleVector(&modelIds);
|
|
return modelIds.front();
|
|
}
|
|
|
|
uint32_t CreatureProperties::getInvisibleModelForTriggerNpc() const
|
|
{
|
|
const auto* displayInfo = sObjectMgr.getCreatureDisplayInfoData(Male_DisplayID);
|
|
if (displayInfo != nullptr && displayInfo->isModelInvisibleStalker)
|
|
return Male_DisplayID;
|
|
|
|
displayInfo = sObjectMgr.getCreatureDisplayInfoData(Male_DisplayID2);
|
|
if (displayInfo != nullptr && displayInfo->isModelInvisibleStalker)
|
|
return Male_DisplayID2;
|
|
|
|
displayInfo = sObjectMgr.getCreatureDisplayInfoData(Female_DisplayID);
|
|
if (displayInfo != nullptr && displayInfo->isModelInvisibleStalker)
|
|
return Female_DisplayID;
|
|
|
|
displayInfo = sObjectMgr.getCreatureDisplayInfoData(Female_DisplayID2);
|
|
if (displayInfo != nullptr && displayInfo->isModelInvisibleStalker)
|
|
return Female_DisplayID2;
|
|
|
|
// Incase invisible model not found, return default trigger invisible model
|
|
return 11686;
|
|
}
|
|
|
|
uint32_t CreatureProperties::getVisibleModelForTriggerNpc() const
|
|
{
|
|
const auto* displayInfo = sObjectMgr.getCreatureDisplayInfoData(Male_DisplayID);
|
|
if (displayInfo != nullptr && !displayInfo->isModelInvisibleStalker)
|
|
return Male_DisplayID;
|
|
|
|
displayInfo = sObjectMgr.getCreatureDisplayInfoData(Male_DisplayID2);
|
|
if (displayInfo != nullptr && !displayInfo->isModelInvisibleStalker)
|
|
return Male_DisplayID2;
|
|
|
|
displayInfo = sObjectMgr.getCreatureDisplayInfoData(Female_DisplayID);
|
|
if (displayInfo != nullptr && !displayInfo->isModelInvisibleStalker)
|
|
return Female_DisplayID;
|
|
|
|
displayInfo = sObjectMgr.getCreatureDisplayInfoData(Female_DisplayID2);
|
|
if (displayInfo != nullptr && !displayInfo->isModelInvisibleStalker)
|
|
return Female_DisplayID2;
|
|
|
|
// Incase visible model not found, return default trigger visible model
|
|
return 17519;
|
|
}
|
|
|
|
Creature::Creature(uint64_t guid)
|
|
{
|
|
//////////////////////////////////////////////////////////////////////////
|
|
m_objectTypeId = TYPEID_UNIT;
|
|
m_valuesCount = getSizeOfStructure(WoWUnit);
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//\todo Why is there a pointer to the same thing in a derived class? ToDo: sort this out..
|
|
m_uint32Values = _fields;
|
|
|
|
memset(m_uint32Values, 0, ((getSizeOfStructure(WoWUnit)) * sizeof(uint32_t)));
|
|
m_updateMask.SetCount(getSizeOfStructure(WoWUnit));
|
|
|
|
setOType(TYPE_UNIT | TYPE_OBJECT);
|
|
setGuid(guid);
|
|
|
|
setAttackPowerMultiplier(0.0f);
|
|
setRangedAttackPowerMultiplier(0.0f);
|
|
|
|
//override class "Unit" initialisation
|
|
setAItoUse(true);
|
|
|
|
// Override initialization from Unit class
|
|
getThreatManager().initialize();
|
|
}
|
|
|
|
Creature::~Creature()
|
|
{
|
|
sEventMgr.RemoveEvents(this);
|
|
|
|
if (_myScriptClass)
|
|
{
|
|
_myScriptClass->Destroy();
|
|
_myScriptClass = nullptr;
|
|
}
|
|
|
|
if (m_respawnCell)
|
|
m_respawnCell->_respawnObjects.erase(this);
|
|
|
|
if (m_escorter)
|
|
m_escorter = nullptr;
|
|
}
|
|
|
|
bool Creature::isVendor() const { return getNpcFlags() & UNIT_NPC_FLAG_VENDOR; }
|
|
bool Creature::isTrainer() const { return getNpcFlags() & UNIT_NPC_FLAG_TRAINER; }
|
|
bool Creature::isClassTrainer() const { return getNpcFlags() & UNIT_NPC_FLAG_TRAINER_CLASS; }
|
|
bool Creature::isProfessionTrainer() const { return getNpcFlags() & UNIT_NPC_FLAG_TRAINER_PROFESSION; }
|
|
bool Creature::isQuestGiver() const { return getNpcFlags() & UNIT_NPC_FLAG_QUESTGIVER; }
|
|
bool Creature::isGossip() const{ return getNpcFlags() & UNIT_NPC_FLAG_GOSSIP; }
|
|
bool Creature::isTaxi() const { return getNpcFlags() & UNIT_NPC_FLAG_FLIGHTMASTER; }
|
|
bool Creature::isCharterGiver() const { return getNpcFlags() & UNIT_NPC_FLAG_PETITIONER; }
|
|
bool Creature::isGuildBank() const { return getNpcFlags() & UNIT_NPC_FLAG_GUILD_BANKER; }
|
|
bool Creature::isBattleMaster() const { return getNpcFlags() & UNIT_NPC_FLAG_BATTLEMASTER; }
|
|
bool Creature::isBanker() const { return getNpcFlags() & UNIT_NPC_FLAG_BANKER; }
|
|
bool Creature::isInnkeeper() const { return getNpcFlags() & UNIT_NPC_FLAG_INNKEEPER; }
|
|
bool Creature::isSpiritHealer() const { return getNpcFlags() & UNIT_NPC_FLAG_SPIRITHEALER; }
|
|
bool Creature::isTabardDesigner() const { return getNpcFlags() & UNIT_NPC_FLAG_TABARDDESIGNER; }
|
|
bool Creature::isAuctioneer() const { return getNpcFlags() & UNIT_NPC_FLAG_AUCTIONEER; }
|
|
bool Creature::isStableMaster() const { return getNpcFlags() & UNIT_NPC_FLAG_STABLEMASTER; }
|
|
bool Creature::isArmorer() const { return getNpcFlags() & UNIT_NPC_FLAG_REPAIR; }
|
|
#if VERSION_STRING >= Cata
|
|
bool Creature::isTransmog() const { return getNpcFlags() & UNIT_NPC_FLAG_TRANSMOGRIFIER; }
|
|
bool Creature::isReforger() const { return getNpcFlags() & UNIT_NPC_FLAG_REFORGER; }
|
|
bool Creature::isVoidStorage() const { return getNpcFlags() & UNIT_NPC_FLAG_VAULTKEEPER; }
|
|
#endif
|
|
|
|
bool Creature::isVehicle() const
|
|
{
|
|
return creature_properties->vehicleid != 0;
|
|
}
|
|
|
|
bool Creature::isTrainingDummy()
|
|
{
|
|
return creature_properties->isTrainingDummy;
|
|
}
|
|
|
|
bool Creature::isPvpFlagSet()
|
|
{
|
|
return getPvpFlags() & U_FIELD_BYTES_FLAG_PVP;
|
|
}
|
|
|
|
void Creature::setPvpFlag()
|
|
{
|
|
addPvpFlags(U_FIELD_BYTES_FLAG_PVP);
|
|
getSummonInterface()->setPvPFlags(true);
|
|
}
|
|
|
|
void Creature::removePvpFlag()
|
|
{
|
|
removePvpFlags(U_FIELD_BYTES_FLAG_PVP);
|
|
getSummonInterface()->setPvPFlags(false);
|
|
}
|
|
|
|
bool Creature::isFfaPvpFlagSet()
|
|
{
|
|
return getPvpFlags() & U_FIELD_BYTES_FLAG_FFA_PVP;
|
|
}
|
|
|
|
void Creature::setFfaPvpFlag()
|
|
{
|
|
addPvpFlags(U_FIELD_BYTES_FLAG_FFA_PVP);
|
|
getSummonInterface()->setFFAPvPFlags(true);
|
|
}
|
|
|
|
void Creature::removeFfaPvpFlag()
|
|
{
|
|
removePvpFlags(U_FIELD_BYTES_FLAG_FFA_PVP);
|
|
getSummonInterface()->setFFAPvPFlags(false);
|
|
}
|
|
|
|
bool Creature::isSanctuaryFlagSet()
|
|
{
|
|
return getPvpFlags() & U_FIELD_BYTES_FLAG_SANCTUARY;
|
|
}
|
|
|
|
void Creature::setSanctuaryFlag()
|
|
{
|
|
addPvpFlags(U_FIELD_BYTES_FLAG_SANCTUARY);
|
|
getSummonInterface()->setSanctuaryFlags(true);
|
|
}
|
|
|
|
void Creature::removeSanctuaryFlag()
|
|
{
|
|
removePvpFlags(U_FIELD_BYTES_FLAG_SANCTUARY);
|
|
getSummonInterface()->setSanctuaryFlags(false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Owner
|
|
Unit* Creature::getUnitOwner()
|
|
{
|
|
if (getCharmedByGuid() != 0)
|
|
return getWorldMapUnit(getCharmedByGuid());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
Unit* Creature::getUnitOwnerOrSelf()
|
|
{
|
|
if (auto* const unitOwner = getUnitOwner())
|
|
return unitOwner;
|
|
|
|
return this;
|
|
}
|
|
|
|
Player* Creature::getPlayerOwner()
|
|
{
|
|
if (getCharmedByGuid() != 0)
|
|
return getWorldMapPlayer(getCharmedByGuid());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
// Misc
|
|
float_t Creature::getMaxWanderDistance() const
|
|
{
|
|
return m_wanderDistance;
|
|
}
|
|
|
|
void Creature::setMaxWanderDistance(float_t dist)
|
|
{
|
|
m_wanderDistance = dist;
|
|
}
|
|
|
|
#if VERSION_STRING < WotLK
|
|
uint32_t Creature::getVirtualItemEntry(uint8_t slot) const
|
|
{
|
|
if (slot >= TOTAL_WEAPON_DAMAGE_TYPES)
|
|
return 0;
|
|
|
|
return m_virtualItemEntry[slot];
|
|
}
|
|
|
|
void Creature::setVirtualItemEntry(uint8_t slot, uint32_t itemId)
|
|
{
|
|
if (slot >= TOTAL_WEAPON_DAMAGE_TYPES)
|
|
return;
|
|
|
|
m_virtualItemEntry[slot] = itemId;
|
|
}
|
|
#endif
|
|
|
|
void Creature::toggleDualwield(bool enable)
|
|
{
|
|
setDualWield(enable);
|
|
if (enable)
|
|
{
|
|
setBaseAttackTime(OFFHAND, getBaseAttackTime(MELEE));
|
|
// Creatures deal 50% damage on offhand hits
|
|
setMinOffhandDamage(getMinDamage() * 0.5f);
|
|
setMaxOffhandDamage(getMaxDamage() * 0.5f);
|
|
}
|
|
else
|
|
{
|
|
setBaseAttackTime(OFFHAND, 0);
|
|
setMinOffhandDamage(0.0f);
|
|
setMaxOffhandDamage(0.0f);
|
|
}
|
|
}
|
|
|
|
std::vector<CreatureItem>* Creature::getSellItems()
|
|
{
|
|
return m_SellItems;
|
|
}
|
|
|
|
void Creature::setDeathState(DeathState s)
|
|
{
|
|
Unit::setDeathState(s);
|
|
|
|
if (s == ALIVE)
|
|
this->removeUnitFlags(UNIT_FLAG_DEAD);
|
|
|
|
if (s == JUST_DIED)
|
|
{
|
|
// Respawn Handling
|
|
const auto now = Util::getTimeNow();
|
|
|
|
m_corpseRemoveTime = now + m_corpseDelay;
|
|
|
|
uint32_t respawnDelay = m_respawnDelay;
|
|
|
|
if (isDungeonBoss() && !m_respawnDelay)
|
|
m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance
|
|
else
|
|
m_respawnTime = now + respawnDelay + m_corpseDelay;
|
|
|
|
saveRespawnTime();
|
|
|
|
setTargetGuid(0);
|
|
|
|
setUnitFlags(UNIT_NPC_FLAG_NONE);
|
|
|
|
setMountDisplayId(0);
|
|
|
|
getAIInterface()->setNoSearchAssistance(false);
|
|
|
|
if (m_enslaveSpell)
|
|
RemoveEnslave();
|
|
|
|
//Dismiss group if is leader
|
|
if (m_formation && m_formation->getLeader() == this)
|
|
m_formation->formationReset(true);
|
|
|
|
for (uint8_t i = 0; i < CURRENT_SPELL_MAX; ++i)
|
|
interruptSpellWithSpellType(static_cast<CurrentSpellType>(i));
|
|
|
|
// if it's not a Pet, and not a summon and it has skinningloot then we will allow skinning
|
|
if (getCreatedByGuid() == 0 && getSummonedByGuid() == 0 && sLootMgr.isSkinnable(creature_properties->Id))
|
|
addUnitFlags(UNIT_FLAG_SKINNABLE);
|
|
|
|
bool needsFalling = (IsFlying() || isHovering()) && !isUnderWater();
|
|
setMoveHover(false);
|
|
setMoveDisableGravity(false);
|
|
|
|
if (needsFalling)
|
|
getMovementManager()->moveFall();
|
|
|
|
Unit::setDeathState(CORPSE);
|
|
}
|
|
else if (s == JUST_RESPAWNED)
|
|
{
|
|
if (isPet())
|
|
{
|
|
setFullHealth();
|
|
}
|
|
else
|
|
{
|
|
uint32_t curhealth = getMaxHealth();
|
|
setPower(POWER_TYPE_MANA, getMaxPower(POWER_TYPE_MANA));
|
|
setHealth((m_deathState == ALIVE || m_deathState == JUST_RESPAWNED) ? curhealth : 0);
|
|
}
|
|
|
|
setTaggerGuid(0);
|
|
|
|
getAIInterface()->setCannotReachTarget(false);
|
|
updateMovementFlags();
|
|
|
|
removeUnitStateFlag(UNIT_STATE_ALL_ERASABLE);
|
|
|
|
OnRespawn(getWorldMap());
|
|
|
|
motion_Initialize();
|
|
Unit::setDeathState(ALIVE);
|
|
}
|
|
}
|
|
|
|
void Creature::addToInRangeObjects(Object* pObj)
|
|
{
|
|
Unit::addToInRangeObjects(pObj);
|
|
}
|
|
|
|
void Creature::onRemoveInRangeObject(Object* pObj)
|
|
{
|
|
if (m_escorter == pObj)
|
|
{
|
|
stopMoving();
|
|
|
|
m_escorter = nullptr;
|
|
Despawn(1000, 1000);
|
|
}
|
|
|
|
Unit::onRemoveInRangeObject(pObj);
|
|
}
|
|
|
|
void Creature::clearInRangeSets()
|
|
{
|
|
Unit::clearInRangeSets();
|
|
}
|
|
|
|
//! brief: used to generate gossip menu based on db values from table gossip_menu, gossip_menu_item and gossip_menu_option. WIP.
|
|
class DatabaseGossip : public GossipScript
|
|
{
|
|
uint32_t m_gossipMenuId;
|
|
|
|
public:
|
|
|
|
DatabaseGossip(uint32_t gossipId) : m_gossipMenuId(gossipId) {}
|
|
|
|
void onHello(Object* object, Player* player) override
|
|
{
|
|
sObjectMgr.generateDatabaseGossipMenu(object, m_gossipMenuId, player);
|
|
}
|
|
|
|
void onSelectOption(Object* object, Player* player, uint32_t gossipItemId, const char* /*Code*/, uint32_t gossipMenuId) override
|
|
{
|
|
if (gossipItemId > 0)
|
|
{
|
|
if (gossipMenuId != 0)
|
|
sObjectMgr.generateDatabaseGossipOptionAndSubMenu(object, player, gossipItemId, gossipMenuId);
|
|
else
|
|
sObjectMgr.generateDatabaseGossipOptionAndSubMenu(object, player, gossipItemId, m_gossipMenuId);
|
|
}
|
|
}
|
|
};
|
|
|
|
void Creature::registerDatabaseGossip()
|
|
{
|
|
if (GetCreatureProperties()->gossipId)
|
|
sScriptMgr.register_creature_gossip(getEntry(), new DatabaseGossip(GetCreatureProperties()->gossipId));
|
|
}
|
|
|
|
bool Creature::isReturningHome() const
|
|
{
|
|
if (getMovementManager()->getCurrentMovementGeneratorType() == HOME_MOTION_TYPE)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void Creature::searchFormation()
|
|
{
|
|
if (isSummon())
|
|
return;
|
|
|
|
uint32_t lowguid = getSpawnId();
|
|
if (!lowguid)
|
|
return;
|
|
|
|
if (FormationInfo const* formationInfo = sFormationMgr->getFormationInfo(lowguid))
|
|
sFormationMgr->addCreatureToGroup(formationInfo->LeaderSpawnId, this);
|
|
}
|
|
|
|
bool Creature::isFormationLeader() const
|
|
{
|
|
if (!m_formation)
|
|
return false;
|
|
|
|
return m_formation->isLeader(this);
|
|
}
|
|
|
|
void Creature::signalFormationMovement()
|
|
{
|
|
if (!m_formation)
|
|
return;
|
|
|
|
if (!m_formation->isLeader(this))
|
|
return;
|
|
|
|
m_formation->leaderStartedMoving();
|
|
}
|
|
|
|
bool Creature::isFormationLeaderMoveAllowed() const
|
|
{
|
|
if (!m_formation)
|
|
return false;
|
|
|
|
return m_formation->canLeaderStartMoving();
|
|
}
|
|
|
|
void Creature::motion_Initialize()
|
|
{
|
|
if (m_formation)
|
|
{
|
|
if (m_formation->getLeader() == this)
|
|
m_formation->formationReset(false);
|
|
else if (m_formation->isFormed())
|
|
{
|
|
getMovementManager()->moveIdle(); // wait the order of leader
|
|
return;
|
|
}
|
|
}
|
|
|
|
getMovementManager()->initialize();
|
|
}
|
|
|
|
void Creature::immediateMovementFlagsUpdate()
|
|
{
|
|
updateMovementFlags();
|
|
// reset timer
|
|
m_movementFlagUpdateTimer = 1000;
|
|
}
|
|
|
|
void Creature::updateMovementFlags()
|
|
{
|
|
// Do not update movement flags if creature is controlled by a player (charm/vehicle)
|
|
if (getUnitOwner())
|
|
return;
|
|
|
|
// Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc)
|
|
const float ground = getFloorZ();
|
|
|
|
bool isInAir;
|
|
|
|
#if VERSION_STRING < WotLK
|
|
isInAir = (G3D::fuzzyGt(GetPositionZ(), ground + GROUND_HEIGHT_TOLERANCE) || G3D::fuzzyLt(GetPositionZ(), ground - GROUND_HEIGHT_TOLERANCE)); // Can be underground too, prevent the falling
|
|
#else
|
|
isInAir = (G3D::fuzzyGt(GetPositionZ(), ground + (canHover() ? getHoverHeight() : 0.0f) + GROUND_HEIGHT_TOLERANCE) || G3D::fuzzyLt(GetPositionZ(), ground - GROUND_HEIGHT_TOLERANCE)); // Can be underground too, prevent the falling
|
|
#endif
|
|
|
|
if (isInAir && !IsFalling())
|
|
{
|
|
auto needsFalling = false;
|
|
if (getMovementTemplate().isFlightAllowed())
|
|
{
|
|
if (isAlive())
|
|
{
|
|
if (getMovementTemplate().Flight == CreatureFlightMovementType::CanFly)
|
|
{
|
|
if (!hasUnitMovementFlag(MOVEFLAG_CAN_FLY))
|
|
setMoveCanFly(true);
|
|
}
|
|
else
|
|
{
|
|
if (!hasUnitMovementFlag(MOVEFLAG_DISABLEGRAVITY))
|
|
setMoveDisableGravity(true);
|
|
}
|
|
|
|
if (isHovering() && hasAuraWithAuraEffect(SPELL_AURA_HOVER))
|
|
setMoveHover(false);
|
|
}
|
|
else
|
|
{
|
|
if (hasUnitMovementFlag(MOVEFLAG_CAN_FLY))
|
|
setMoveCanFly(false);
|
|
|
|
if (hasUnitMovementFlag(MOVEFLAG_DISABLEGRAVITY))
|
|
setMoveDisableGravity(false);
|
|
|
|
// Unit is in air and is dead => needs to fall down
|
|
needsFalling = true;
|
|
}
|
|
}
|
|
|
|
if (isHovering() && (!hasAuraWithAuraEffect(SPELL_AURA_HOVER) || !isAlive()))
|
|
{
|
|
setMoveHover(false);
|
|
needsFalling = true;
|
|
}
|
|
|
|
// Make unit fall
|
|
if (needsFalling && !isUnderWater())
|
|
getMovementManager()->moveFall();
|
|
}
|
|
|
|
if (!isInAir)
|
|
{
|
|
removeUnitMovementFlag(MOVEFLAG_FALLING);
|
|
|
|
if (hasUnitMovementFlag(MOVEFLAG_CAN_FLY))
|
|
setMoveCanFly(false);
|
|
|
|
if (hasUnitMovementFlag(MOVEFLAG_DISABLEGRAVITY))
|
|
setMoveDisableGravity(false);
|
|
|
|
if (!isHovering() && isAlive() && (canHover() || hasAuraWithAuraEffect(SPELL_AURA_HOVER)))
|
|
setMoveHover(true);
|
|
}
|
|
|
|
// Swimming flag
|
|
if (isInWater() && getMovementTemplate().isSwimAllowed())
|
|
{
|
|
if (!hasUnitMovementFlag(MOVEFLAG_SWIMMING))
|
|
setMoveSwim(true);
|
|
}
|
|
else
|
|
{
|
|
if (hasUnitMovementFlag(MOVEFLAG_SWIMMING))
|
|
setMoveSwim(false);
|
|
}
|
|
}
|
|
|
|
//MIT end
|
|
|
|
void Creature::Update(unsigned long time_passed)
|
|
{
|
|
Unit::Update(time_passed);
|
|
|
|
// When we are a Summon handle its Update
|
|
if (isSummon())
|
|
ToSummon()->Update(time_passed);
|
|
|
|
// Update Movement
|
|
if (time_passed >= m_movementFlagUpdateTimer)
|
|
{
|
|
updateMovementFlags();
|
|
m_movementFlagUpdateTimer = 1000;
|
|
}
|
|
else
|
|
{
|
|
m_movementFlagUpdateTimer -= static_cast<uint16_t>(time_passed);
|
|
}
|
|
|
|
const auto now = Util::getTimeNow();
|
|
|
|
// Update DeathState
|
|
switch (m_deathState)
|
|
{
|
|
case DEAD:
|
|
{
|
|
if (m_respawnTime <= now)
|
|
respawn();
|
|
} break;
|
|
case CORPSE:
|
|
{
|
|
if (m_corpseRemoveTime <= now)
|
|
OnRemoveCorpse();
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Creature::SafeDelete()
|
|
{
|
|
sEventMgr.RemoveEvents(this);
|
|
|
|
delete this;
|
|
}
|
|
|
|
void Creature::DeleteMe()
|
|
{
|
|
if (IsInWorld())
|
|
RemoveFromWorld(false, true);
|
|
else
|
|
SafeDelete();
|
|
}
|
|
|
|
void Creature::OnRemoveCorpse()
|
|
{
|
|
// time to respawn!
|
|
if (IsInWorld() && (int32)m_WorldMap->getInstanceId() == m_instanceId)
|
|
{
|
|
sLogger.info("Removing corpse of " I64FMT "...", getGuid());
|
|
|
|
setDeathState(DEAD);
|
|
m_position = m_spawnLocation;
|
|
|
|
// Respawn Handling
|
|
const auto now = Util::getTimeNow();
|
|
m_corpseRemoveTime = now;
|
|
|
|
// if corpse was removed during falling, the falling will continue and override relocation to respawn position
|
|
if (IsFalling())
|
|
stopMoving();
|
|
|
|
setMoveCanFly(false);
|
|
|
|
getMovementManager()->clear();
|
|
|
|
if ((getWorldMap()->getBaseMap()->getMapInfo() && getWorldMap()->getBaseMap()->getMapInfo()->isRaid() && creature_properties->isBoss) || m_noRespawn)
|
|
{
|
|
RemoveFromWorld(false, true);
|
|
}
|
|
else
|
|
{
|
|
if (m_respawnTime)
|
|
RemoveFromWorld(true, false);
|
|
else
|
|
RemoveFromWorld(false, true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sLogger.failure("Creature::OnRemoveCorpse but Creature is not in World or in instance");
|
|
}
|
|
}
|
|
|
|
void Creature::OnRespawn(WorldMap* m)
|
|
{
|
|
if (m_noRespawn)
|
|
return;
|
|
|
|
// if corpse was removed during falling, the falling will continue and override relocation to respawn position
|
|
if (IsFalling())
|
|
stopMoving();
|
|
|
|
getMovementManager()->clear();
|
|
|
|
sLogger.info("Respawning " I64FMT "...", getGuid());
|
|
setHealth(getMaxHealth());
|
|
|
|
if (m_spawn)
|
|
{
|
|
setNpcFlags(creature_properties->NPCFLags);
|
|
setEmoteState(m_spawn->emote_state);
|
|
|
|
// creature's death state
|
|
if (m_spawn->death_state == CREATURE_STATE_APPEAR_DEAD)
|
|
{
|
|
m_limbostate = true;
|
|
setDeathState(ALIVE); // we are not actually dead, we just appear dead
|
|
setDynamicFlags(U_DYN_FLAG_DEAD);
|
|
}
|
|
else if (m_spawn->death_state == CREATURE_STATE_DEAD)
|
|
{
|
|
setHealth(0);
|
|
m_limbostate = true;
|
|
setDeathState(CORPSE);
|
|
}
|
|
else
|
|
setDeathState(ALIVE);
|
|
}
|
|
|
|
removeUnitFlags(UNIT_FLAG_SKINNABLE);
|
|
|
|
setTaggerGuid(0);
|
|
|
|
//empty loot
|
|
loot.items.clear();
|
|
|
|
// Init Movement Handlers
|
|
getMovementManager()->initializeDefault();
|
|
|
|
// Re-initialize reactstate that could be altered by movementgenerators
|
|
getAIInterface()->initializeReactState();
|
|
|
|
#ifdef FT_VEHICLES
|
|
// Init Vehicle
|
|
createVehicleKit(GetCreatureProperties()->vehicleid, getEntry());
|
|
#endif
|
|
|
|
m_PickPocketed = false;
|
|
PushToWorld(m);
|
|
}
|
|
|
|
void Creature::Create(uint32 mapid, float x, float y, float z, float ang)
|
|
{
|
|
Object::_Create(mapid, x, y, z, ang);
|
|
}
|
|
|
|
void Creature::CreateWayPoint(uint32 /*WayPointID*/, uint32 mapid, float x, float y, float z, float ang)
|
|
{
|
|
Object::_Create(mapid, x, y, z, ang);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
/// Looting
|
|
|
|
void Creature::SaveToDB()
|
|
{
|
|
if (m_spawn == NULL)
|
|
{
|
|
m_spawn = new MySQLStructure::CreatureSpawn;
|
|
m_spawn->entry = getEntry();
|
|
m_spawn->id = spawnid = sObjectMgr.GenerateCreatureSpawnID();
|
|
m_spawn->movetype = getDefaultMovementType();
|
|
m_spawn->displayid = getDisplayId();
|
|
m_spawn->x = m_position.x;
|
|
m_spawn->y = m_position.y;
|
|
m_spawn->z = m_position.z;
|
|
m_spawn->o = m_position.o;
|
|
m_spawn->emote_state = getEmoteState();
|
|
m_spawn->flags = getUnitFlags();
|
|
m_spawn->factionid = getFactionTemplate();
|
|
m_spawn->bytes0 = getBytes0();
|
|
m_spawn->bytes1 = getBytes1();
|
|
m_spawn->bytes2 = getBytes2();
|
|
m_spawn->stand_state = getStandState();
|
|
m_spawn->death_state = 0;
|
|
m_spawn->channel_target_creature = 0;
|
|
m_spawn->channel_target_go = 0;
|
|
m_spawn->channel_spell = 0;
|
|
m_spawn->MountedDisplayID = getMountDisplayId();
|
|
|
|
#if VERSION_STRING < WotLK
|
|
m_spawn->Item1SlotEntry = getVirtualItemEntry(MELEE);
|
|
m_spawn->Item2SlotEntry = getVirtualItemEntry(OFFHAND);
|
|
m_spawn->Item3SlotEntry = getVirtualItemEntry(RANGED);
|
|
#else
|
|
m_spawn->Item1SlotEntry = getVirtualItemSlotId(MELEE);
|
|
m_spawn->Item2SlotEntry = getVirtualItemSlotId(OFFHAND);
|
|
m_spawn->Item3SlotEntry = getVirtualItemSlotId(RANGED);
|
|
#endif
|
|
|
|
if (IsFlying())
|
|
m_spawn->CanFly = 1;
|
|
else
|
|
m_spawn->CanFly = 0;
|
|
|
|
m_spawn->phase = m_phase;
|
|
|
|
uint32 x = getWorldMap()->getPosX(GetPositionX());
|
|
uint32 y = getWorldMap()->getPosY(GetPositionY());
|
|
|
|
// Add spawn to map
|
|
getWorldMap()->getBaseMap()->getSpawnsListAndCreate(x, y)->CreatureSpawns.push_back(m_spawn);
|
|
}
|
|
|
|
std::stringstream ss;
|
|
|
|
ss << "DELETE FROM " << m_spawn->origine << " WHERE id = ";
|
|
ss << spawnid;
|
|
ss << " AND min_build <= ";
|
|
ss << VERSION_STRING;
|
|
ss << " AND max_build >= ";
|
|
ss << VERSION_STRING;
|
|
ss << ";";
|
|
|
|
WorldDatabase.Execute(ss.str().c_str());
|
|
|
|
ss.rdbuf()->str("");
|
|
|
|
ss << "INSERT INTO " << m_spawn->origine << " VALUES("
|
|
<< spawnid << ","
|
|
<< VERSION_STRING << ","
|
|
<< VERSION_STRING << ","
|
|
<< getEntry() << ","
|
|
<< GetMapId() << ","
|
|
<< m_position.x << ","
|
|
<< m_position.y << ","
|
|
<< m_position.z << ","
|
|
<< m_position.o << ","
|
|
<< static_cast<uint32_t>(getDefaultMovementType()) << ","
|
|
<< getDisplayId() << ","
|
|
<< getFactionTemplate() << ","
|
|
<< getUnitFlags() << ","
|
|
<< getBytes0() << ","
|
|
<< getBytes1() << ","
|
|
<< getBytes2() << ","
|
|
<< getEmoteState() << ",0,";
|
|
|
|
ss << m_spawn->channel_spell << ","
|
|
<< m_spawn->channel_target_go << ","
|
|
<< m_spawn->channel_target_creature << ",";
|
|
|
|
ss << uint32(getStandState()) << ",";
|
|
|
|
ss << m_spawn->death_state << ",";
|
|
|
|
ss << getMountDisplayId() << ","
|
|
<< m_spawn->Item1SlotEntry << ","
|
|
<< m_spawn->Item2SlotEntry << ","
|
|
<< m_spawn->Item3SlotEntry << ",";
|
|
|
|
if (IsFlying())
|
|
ss << 1 << ",";
|
|
else
|
|
ss << 0 << ",";
|
|
|
|
ss << m_phase << ","
|
|
<< "0" // event_entry
|
|
<< ",0" // wander distance
|
|
<< ",0" // waypoint_group
|
|
<< ")";
|
|
|
|
WorldDatabase.Execute(ss.str().c_str());
|
|
}
|
|
|
|
void Creature::LoadScript()
|
|
{
|
|
_myScriptClass = sScriptMgr.CreateAIScriptClassForEntry(this);
|
|
}
|
|
|
|
void Creature::DeleteFromDB()
|
|
{
|
|
if (!GetSQL_id())
|
|
return;
|
|
|
|
WorldDatabase.Execute("DELETE FROM %s WHERE id = %u AND min_build <= %u AND max_build >= %u", m_spawn->origine.c_str(), GetSQL_id(), VERSION_STRING, VERSION_STRING);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
/// Quests
|
|
|
|
void Creature::AddQuest(QuestRelation* Q)
|
|
{
|
|
m_quests->push_back(Q);
|
|
}
|
|
|
|
void Creature::DeleteQuest(QuestRelation* Q)
|
|
{
|
|
std::list<QuestRelation*>::iterator it;
|
|
for (it = m_quests->begin(); it != m_quests->end(); ++it)
|
|
{
|
|
if (((*it)->type == Q->type) && ((*it)->qst == Q->qst))
|
|
{
|
|
delete(*it);
|
|
m_quests->erase(it);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QuestProperties const* Creature::FindQuest(uint32 quest_id, uint8 quest_relation)
|
|
{
|
|
std::list<QuestRelation*>::iterator it;
|
|
for (it = m_quests->begin(); it != m_quests->end(); ++it)
|
|
{
|
|
QuestRelation* ptr = (*it);
|
|
|
|
if ((ptr->qst->id == quest_id) && (ptr->type & quest_relation))
|
|
{
|
|
return ptr->qst;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
uint16 Creature::GetQuestRelation(uint32 quest_id)
|
|
{
|
|
uint16 quest_relation = 0;
|
|
std::list<QuestRelation*>::iterator it;
|
|
|
|
for (it = m_quests->begin(); it != m_quests->end(); ++it)
|
|
{
|
|
if ((*it)->qst->id == quest_id)
|
|
{
|
|
quest_relation |= (*it)->type;
|
|
}
|
|
}
|
|
return quest_relation;
|
|
}
|
|
|
|
uint32 Creature::NumOfQuests()
|
|
{
|
|
return (uint32)m_quests->size();
|
|
}
|
|
|
|
std::list<QuestRelation*>::iterator Creature::QuestsBegin()
|
|
{
|
|
return m_quests->begin();
|
|
}
|
|
|
|
std::list<QuestRelation*>::iterator Creature::QuestsEnd()
|
|
{
|
|
return m_quests->end();
|
|
}
|
|
|
|
void Creature::SetQuestList(std::list<QuestRelation*>* qst_lst)
|
|
{
|
|
m_quests = qst_lst;
|
|
}
|
|
|
|
uint32 Creature::GetHealthFromSpell()
|
|
{
|
|
return m_healthfromspell;
|
|
}
|
|
|
|
void Creature::SetHealthFromSpell(uint32 value)
|
|
{
|
|
m_healthfromspell = value;
|
|
}
|
|
|
|
void Creature::_LoadQuests()
|
|
{
|
|
sQuestMgr.LoadNPCQuests(this);
|
|
}
|
|
|
|
bool Creature::HasQuests()
|
|
{
|
|
return m_quests != NULL;
|
|
}
|
|
|
|
bool Creature::HasQuest(uint32 id, uint32 type)
|
|
{
|
|
if (!m_quests) return false;
|
|
for (std::list<QuestRelation*>::iterator itr = m_quests->begin(); itr != m_quests->end(); ++itr)
|
|
{
|
|
if ((*itr)->qst->id == id && (*itr)->type & type)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Creature::AddToWorld()
|
|
{
|
|
// force set faction
|
|
if (m_factionTemplate == NULL || m_factionEntry == NULL)
|
|
setServersideFaction();
|
|
|
|
if (creature_properties == nullptr)
|
|
creature_properties = sMySQLStore.getCreatureProperties(getEntry());
|
|
|
|
if (creature_properties == nullptr)
|
|
return;
|
|
|
|
if (m_factionTemplate == NULL || m_factionEntry == NULL)
|
|
return;
|
|
|
|
Object::AddToWorld();
|
|
searchFormation();
|
|
motion_Initialize();
|
|
immediateMovementFlagsUpdate();
|
|
|
|
if (getMovementTemplate().isRooted())
|
|
setControlled(true, UNIT_STATE_ROOTED);
|
|
}
|
|
|
|
void Creature::AddToWorld(WorldMap* pMapMgr)
|
|
{
|
|
// force set faction
|
|
if (m_factionTemplate == NULL || m_factionEntry == NULL)
|
|
setServersideFaction();
|
|
|
|
if (creature_properties == nullptr)
|
|
creature_properties = sMySQLStore.getCreatureProperties(getEntry());
|
|
|
|
if (creature_properties == nullptr)
|
|
return;
|
|
|
|
if (m_factionTemplate == NULL || m_factionEntry == NULL)
|
|
return;
|
|
|
|
Object::AddToWorld(pMapMgr);
|
|
searchFormation();
|
|
motion_Initialize();
|
|
immediateMovementFlagsUpdate();
|
|
|
|
if (getMovementTemplate().isRooted())
|
|
setControlled(true, UNIT_STATE_ROOTED);
|
|
}
|
|
|
|
bool Creature::CanAddToWorld()
|
|
{
|
|
if (m_factionEntry == NULL || m_factionTemplate == NULL)
|
|
setServersideFaction();
|
|
|
|
if (m_factionTemplate == NULL || m_factionEntry == NULL || creature_properties == nullptr)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::RemoveFromWorld(bool addrespawnevent, bool /*free_guid*/)
|
|
{
|
|
if (addrespawnevent && m_respawnTime > 0)
|
|
despawn(0);
|
|
else
|
|
Despawn(0, 0);
|
|
}
|
|
|
|
void Creature::RemoveFromWorld(bool free_guid)
|
|
{
|
|
PrepareForRemove();
|
|
Unit::RemoveFromWorld(free_guid);
|
|
}
|
|
|
|
void Creature::EnslaveExpire()
|
|
{
|
|
++m_enslaveCount;
|
|
|
|
uint64 charmer = getCharmedByGuid();
|
|
|
|
Player* caster = sObjectMgr.GetPlayer(WoWGuid::getGuidLowPartFromUInt64(charmer));
|
|
if (caster)
|
|
{
|
|
caster->setCharmGuid(0);
|
|
caster->setSummonGuid(0);
|
|
|
|
WorldPacket data(SMSG_PET_SPELLS, 8);
|
|
|
|
data << uint64(0);
|
|
data << uint32(0);
|
|
|
|
caster->sendPacket(&data);
|
|
}
|
|
setCharmedByGuid(0);
|
|
setSummonedByGuid(0);
|
|
|
|
resetCurrentSpeeds();
|
|
|
|
switch (GetCreatureProperties()->Type)
|
|
{
|
|
case UNIT_TYPE_DEMON:
|
|
setFaction(90);
|
|
break;
|
|
default:
|
|
setFaction(954);
|
|
break;
|
|
};
|
|
|
|
getAIInterface()->Init(this);
|
|
|
|
updateInRangeOppositeFactionSet();
|
|
updateInRangeSameFactionSet();
|
|
}
|
|
|
|
uint32 Creature::GetEnslaveCount()
|
|
{
|
|
return m_enslaveCount;
|
|
}
|
|
|
|
void Creature::SetEnslaveCount(uint32 count)
|
|
{
|
|
m_enslaveCount = count;
|
|
}
|
|
|
|
uint32 Creature::GetEnslaveSpell()
|
|
{
|
|
return m_enslaveSpell;
|
|
}
|
|
|
|
void Creature::SetEnslaveSpell(uint32 spellId)
|
|
{
|
|
m_enslaveSpell = spellId;
|
|
}
|
|
|
|
bool Creature::RemoveEnslave()
|
|
{
|
|
return removeAllAurasByIdReturnCount(m_enslaveSpell) > 0;
|
|
}
|
|
|
|
void Creature::CalcResistance(uint8_t type)
|
|
{
|
|
int32 pos = 0;
|
|
int32 neg = 0;
|
|
|
|
if (BaseResistanceModPct[type] < 0)
|
|
neg = (m_baseResistance[type] * abs(BaseResistanceModPct[type]) / 100);
|
|
else
|
|
pos = (m_baseResistance[type] * BaseResistanceModPct[type]) / 100;
|
|
|
|
if (isPet() && isAlive() && IsInWorld())
|
|
{
|
|
Player* owner = static_cast<Pet*>(this)->getPlayerOwner();
|
|
if (type == 0 && owner)
|
|
pos += int32(0.35f * owner->getResistance(type));
|
|
else if (owner)
|
|
pos += int32(0.40f * owner->getResistance(type));
|
|
}
|
|
|
|
if (ResistanceModPct[type] < 0)
|
|
neg += (m_baseResistance[type] + pos - neg) * abs(ResistanceModPct[type]) / 100;
|
|
else
|
|
pos += (m_baseResistance[type] + pos - neg) * ResistanceModPct[type] / 100;
|
|
|
|
if (FlatResistanceMod[type] < 0)
|
|
neg += abs(FlatResistanceMod[type]);
|
|
else
|
|
pos += FlatResistanceMod[type];
|
|
|
|
#if VERSION_STRING > Classic
|
|
setResistanceBuffModPositive(type, pos);
|
|
setResistanceBuffModNegative(type, neg);
|
|
#endif
|
|
|
|
int32 tot = m_baseResistance[type] + pos - neg;
|
|
|
|
setResistance(type, tot > 0 ? tot : 0);
|
|
}
|
|
|
|
void Creature::CalcStat(uint8_t type)
|
|
{
|
|
int32 pos = 0;
|
|
int32 neg = 0;
|
|
|
|
if (StatModPct[type] < 0)
|
|
neg = (m_baseStats[type] * abs(StatModPct[type]) / 100);
|
|
else
|
|
pos = (m_baseStats[type] * StatModPct[type]) / 100;
|
|
|
|
if (isPet())
|
|
{
|
|
Player* owner = static_cast<Pet*>(this)->getPlayerOwner();
|
|
if (type == STAT_STAMINA && owner)
|
|
pos += int32(0.45f * owner->getStat(STAT_STAMINA));
|
|
else if (type == STAT_INTELLECT && owner && getCreatedBySpellId())
|
|
pos += int32(0.30f * owner->getStat(STAT_INTELLECT));
|
|
}
|
|
|
|
if (TotalStatModPct[type] < 0)
|
|
neg += (m_baseStats[type] + pos - neg) * abs(TotalStatModPct[type]) / 100;
|
|
else
|
|
pos += (m_baseStats[type] + pos - neg) * TotalStatModPct[type] / 100;
|
|
|
|
if (FlatStatMod[type] < 0)
|
|
neg += abs(FlatStatMod[type]);
|
|
else
|
|
pos += FlatStatMod[type];
|
|
|
|
#if VERSION_STRING != Classic
|
|
setPosStat(type, pos);
|
|
setNegStat(type, neg);
|
|
#endif
|
|
|
|
int32 tot = m_baseStats[type] + pos - neg;
|
|
setStat(type, tot > 0 ? tot : 0);
|
|
|
|
switch (type)
|
|
{
|
|
case STAT_STRENGTH:
|
|
{
|
|
//Attack Power
|
|
if (!isPet()) //We calculate pet's later
|
|
{
|
|
uint32 str = getStat(STAT_STRENGTH);
|
|
int32 AP = (str * 2 - 20);
|
|
if (AP < 0) AP = 0;
|
|
setAttackPower(AP);
|
|
}
|
|
calculateDamage();
|
|
}
|
|
break;
|
|
case STAT_AGILITY:
|
|
{
|
|
//Ranged Attack Power (Does any creature use this?)
|
|
int32 RAP = getLevel() + getStat(STAT_AGILITY) - 10;
|
|
if (RAP < 0)
|
|
RAP = 0;
|
|
|
|
setRangedAttackPower(RAP);
|
|
}
|
|
break;
|
|
case STAT_STAMINA:
|
|
{
|
|
#if VERSION_STRING != Classic
|
|
//Health
|
|
uint32 hp = getBaseHealth();
|
|
uint32 stat_bonus = getPosStat(STAT_STAMINA)- getNegStat(STAT_STAMINA);
|
|
if (static_cast<int32>(stat_bonus) < 0) stat_bonus = 0;
|
|
|
|
uint32 bonus = stat_bonus * 10 + m_healthfromspell;
|
|
uint32 res = hp + bonus;
|
|
|
|
if (res < hp)
|
|
res = hp;
|
|
setMaxHealth(res);
|
|
if (getHealth() > getMaxHealth())
|
|
setHealth(getMaxHealth());
|
|
#endif
|
|
}
|
|
break;
|
|
case STAT_INTELLECT:
|
|
{
|
|
#if VERSION_STRING != Classic
|
|
if (getPowerType() == POWER_TYPE_MANA)
|
|
{
|
|
uint32 mana = getBaseMana();
|
|
uint32 stat_bonus = getPosStat(STAT_INTELLECT) - getNegStat(STAT_INTELLECT);
|
|
if (static_cast<int32>(stat_bonus) < 0) stat_bonus = 0;
|
|
|
|
uint32 bonus = stat_bonus * 15;
|
|
uint32 res = mana + bonus;
|
|
|
|
if (res < mana) res = mana;
|
|
setMaxPower(POWER_TYPE_MANA, res);
|
|
}
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Creature::RegenerateHealth()
|
|
{
|
|
if (m_limbostate || !m_canRegenerateHP)
|
|
return;
|
|
|
|
uint32 cur = getHealth();
|
|
uint32 mh = getMaxHealth();
|
|
if (cur >= mh)return;
|
|
|
|
float amt = 0.0f;
|
|
|
|
// While polymorphed health is regenerated rapidly
|
|
// Exact value is yet unknown but it's roughly 10% of health per sec
|
|
if (hasUnitStateFlag(UNIT_STATE_POLYMORPHED))
|
|
{
|
|
amt = getMaxHealth() * 0.10f;
|
|
}
|
|
else if (!getCombatHandler().isInCombat())
|
|
{
|
|
// 25% of max health per tick
|
|
amt = getMaxHealth() * 0.25f;
|
|
|
|
if (m_pctRegenModifier)
|
|
amt += (amt * m_pctRegenModifier) / 100;
|
|
|
|
//Apply shit from conf file
|
|
amt *= worldConfig.getFloatRate(RATE_HEALTH);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (amt <= 1.0f) //this fixes regen like 0.98
|
|
cur++;
|
|
else
|
|
cur += (uint32)amt;
|
|
setHealth((cur >= mh) ? mh : cur);
|
|
}
|
|
|
|
void Creature::CallScriptUpdate(unsigned long time_passed)
|
|
{
|
|
if (_myScriptClass)
|
|
_myScriptClass->_internalAIUpdate(time_passed);
|
|
}
|
|
|
|
CreatureProperties const* Creature::GetCreatureProperties()
|
|
{
|
|
return creature_properties;
|
|
}
|
|
|
|
void Creature::SetCreatureProperties(CreatureProperties const* cp)
|
|
{
|
|
creature_properties = cp;
|
|
}
|
|
|
|
std::shared_ptr<Trainer> Creature::GetTrainer()
|
|
{
|
|
return mTrainer;
|
|
}
|
|
|
|
#if VERSION_STRING < Cata
|
|
void Creature::AddVendorItem(uint32 itemid, uint32 amount, DBC::Structures::ItemExtendedCostEntry const* ec)
|
|
#else
|
|
void Creature::AddVendorItem(uint32 itemid, uint32 amount, DB2::Structures::ItemExtendedCostEntry const* ec)
|
|
#endif
|
|
{
|
|
CreatureItem ci;
|
|
ci.amount = amount;
|
|
ci.itemid = itemid;
|
|
ci.available_amount = 0;
|
|
ci.max_amount = 0;
|
|
ci.incrtime = 0;
|
|
ci.extended_cost = ec;
|
|
if (!m_SellItems)
|
|
{
|
|
m_SellItems = new std::vector < CreatureItem > ;
|
|
sObjectMgr.setVendorList(getEntry(), m_SellItems);
|
|
}
|
|
m_SellItems->push_back(ci);
|
|
}
|
|
|
|
void Creature::ModAvItemAmount(uint32 itemid, uint32 value)
|
|
{
|
|
for (std::vector<CreatureItem>::iterator itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
{
|
|
if (itr->available_amount)
|
|
{
|
|
if (value > itr->available_amount) // shouldn't happen
|
|
{
|
|
itr->available_amount = 0;
|
|
return;
|
|
}
|
|
else
|
|
itr->available_amount -= value;
|
|
|
|
if (!event_HasEvent(EVENT_ITEM_UPDATE))
|
|
sEventMgr.AddEvent(this, &Creature::UpdateItemAmount, itr->itemid, EVENT_ITEM_UPDATE, itr->incrtime, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::UpdateItemAmount(uint32 itemid)
|
|
{
|
|
for (std::vector<CreatureItem>::iterator itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
{
|
|
if (itr->max_amount == 0) // shouldn't happen
|
|
itr->available_amount = 0;
|
|
else
|
|
{
|
|
itr->available_amount = itr->max_amount;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::ChannelLinkUpGO(uint32 SqlId)
|
|
{
|
|
if (!m_WorldMap) // shouldn't happen
|
|
return;
|
|
|
|
GameObject* go = m_WorldMap->getSqlIdGameObject(SqlId);
|
|
if (go != nullptr)
|
|
{
|
|
event_RemoveEvents(EVENT_CREATURE_CHANNEL_LINKUP);
|
|
setChannelObjectGuid(go->getGuid());
|
|
setChannelSpellId(m_spawn->channel_spell);
|
|
}
|
|
}
|
|
|
|
void Creature::ChannelLinkUpCreature(uint32 SqlId)
|
|
{
|
|
if (!m_WorldMap) // shouldn't happen
|
|
return;
|
|
|
|
Creature* creature = m_WorldMap->getSqlIdCreature(SqlId);
|
|
if (creature != nullptr)
|
|
{
|
|
event_RemoveEvents(EVENT_CREATURE_CHANNEL_LINKUP);
|
|
setChannelObjectGuid(creature->getGuid());
|
|
setChannelSpellId(m_spawn->channel_spell);
|
|
}
|
|
}
|
|
|
|
bool Creature::isattackable(MySQLStructure::CreatureSpawn* spawn)
|
|
{
|
|
if (spawn == nullptr)
|
|
return false;
|
|
|
|
if ((spawn->flags & 2) || (spawn->flags & 128) || (spawn->flags & 256) || (spawn->flags & 65536))
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
uint8 get_byte(uint32 buffer, uint32 index)
|
|
{
|
|
uint32 mask = uint32(~0ul);
|
|
if (index > sizeof(uint32) - 1)
|
|
return 0;
|
|
|
|
buffer = buffer >> index * 8;
|
|
mask = mask >> 3 * 8;
|
|
buffer = buffer & mask;
|
|
|
|
return (uint8)buffer;
|
|
}
|
|
|
|
bool Creature::teleport(const LocationVector& vec, WorldMap* map)
|
|
{
|
|
if (map == nullptr)
|
|
return false;
|
|
|
|
if (map->getCreature(this->getGuidLow()))
|
|
{
|
|
this->SetPosition(vec);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Creature::Load(MySQLStructure::CreatureSpawn* spawn, uint8 mode, MySQLStructure::MapInfo const* info)
|
|
{
|
|
m_spawn = spawn;
|
|
creature_properties = sMySQLStore.getCreatureProperties(spawn->entry);
|
|
if (creature_properties == nullptr)
|
|
return false;
|
|
|
|
spawnid = spawn->id;
|
|
m_phase = spawn->phase;
|
|
|
|
setSpeedRate(TYPE_WALK, creature_properties->walk_speed, false);
|
|
setSpeedRate(TYPE_RUN, creature_properties->run_speed, false);
|
|
setSpeedRate(TYPE_FLY, creature_properties->fly_speed, false);
|
|
resetCurrentSpeeds();
|
|
|
|
//Set fields
|
|
setEntry(creature_properties->Id);
|
|
setScale(creature_properties->Scale);
|
|
|
|
#if VERSION_STRING > TBC
|
|
setHoverHeight(creature_properties->Scale);
|
|
#endif
|
|
|
|
uint32 health;
|
|
if (creature_properties->MinHealth > creature_properties->MaxHealth)
|
|
{
|
|
sLogger.failure("MinHealth is bigger than MaxHealt! Using MaxHealth value. You should fix this in creature_proto table for entry: %u!", creature_properties->Id);
|
|
health = creature_properties->MaxHealth - Util::getRandomUInt(10);
|
|
}
|
|
else
|
|
{
|
|
health = creature_properties->MinHealth + Util::getRandomUInt(creature_properties->MaxHealth - creature_properties->MinHealth);
|
|
}
|
|
|
|
setMaxHealth(health);
|
|
setHealth(health);
|
|
setBaseHealth(health);
|
|
|
|
setMaxPower(POWER_TYPE_MANA, creature_properties->Mana);
|
|
setBaseMana(creature_properties->Mana);
|
|
setPower(POWER_TYPE_MANA, creature_properties->Mana);
|
|
|
|
|
|
setDisplayId(spawn->displayid);
|
|
setNativeDisplayId(spawn->displayid);
|
|
setMountDisplayId(spawn->MountedDisplayID);
|
|
|
|
eventModelChange();
|
|
|
|
setLevel(creature_properties->MinLevel + (Util::getRandomUInt(creature_properties->MaxLevel - creature_properties->MinLevel)));
|
|
|
|
if (mode && info)
|
|
setLevel(std::min(73 - getLevel(), info->lvl_mod_a));
|
|
|
|
for (uint8 i = 0; i < 7; ++i)
|
|
setResistance(i, creature_properties->Resistances[i]);
|
|
|
|
setBaseAttackTime(MELEE, creature_properties->AttackTime);
|
|
|
|
setMinDamage(creature_properties->MinDamage);
|
|
setMaxDamage(creature_properties->MaxDamage);
|
|
|
|
setBaseAttackTime(RANGED, creature_properties->RangedAttackTime);
|
|
setMinRangedDamage(creature_properties->RangedMinDamage);
|
|
setMaxRangedDamage(creature_properties->RangedMaxDamage);
|
|
|
|
setVirtualItemSlotId(MELEE, spawn->Item1SlotEntry);
|
|
setVirtualItemSlotId(OFFHAND, spawn->Item2SlotEntry);
|
|
setVirtualItemSlotId(RANGED, spawn->Item3SlotEntry);
|
|
|
|
setFaction(spawn->factionid);
|
|
setUnitFlags(spawn->flags);
|
|
setEmoteState(spawn->emote_state);
|
|
setBoundingRadius(creature_properties->BoundingRadius);
|
|
setCombatReach(creature_properties->CombatReach);
|
|
original_emotestate = spawn->emote_state;
|
|
|
|
// set position
|
|
m_position.ChangeCoords({ spawn->x, spawn->y, spawn->z, spawn->o });
|
|
m_spawnLocation.ChangeCoords({ spawn->x, spawn->y, spawn->z, spawn->o });
|
|
m_aiInterface->timed_emotes = sObjectMgr.getTimedEmoteList(spawn->id);
|
|
|
|
// not a neutral creature
|
|
if (!(m_factionEntry != nullptr && m_factionEntry->RepListId == -1 && m_factionTemplate->HostileMask == 0 && m_factionTemplate->FriendlyMask == 0))
|
|
{
|
|
getAIInterface()->setCanCallForHelp(true);
|
|
}
|
|
|
|
getAIInterface()->initializeReactState();
|
|
|
|
// set if creature can shoot or not.
|
|
if (creature_properties->CanRanged == 1)
|
|
getAIInterface()->m_canRangedAttack = true;
|
|
else
|
|
m_aiInterface->m_canRangedAttack = false;
|
|
|
|
// checked at loading
|
|
m_defaultMovementType = MovementGeneratorType(spawn->movetype);
|
|
|
|
setMaxWanderDistance(static_cast<float_t>(spawn->wander_distance));
|
|
if (getMaxWanderDistance() == 0.0f && m_defaultMovementType == RANDOM_MOTION_TYPE)
|
|
m_defaultMovementType = IDLE_MOTION_TYPE;
|
|
|
|
_waypointPathId = spawn->waypoint_id;
|
|
|
|
//SETUP NPC FLAGS
|
|
setNpcFlags(creature_properties->NPCFLags);
|
|
|
|
if (isVendor())
|
|
m_SellItems = sObjectMgr.getVendorList(getEntry());
|
|
|
|
if (isQuestGiver())
|
|
_LoadQuests();
|
|
|
|
if (isTrainer() || isProfessionTrainer())
|
|
mTrainer = sObjectMgr.getTrainer(getEntry());
|
|
|
|
if (isAuctioneer())
|
|
auctionHouse = sAuctionMgr.GetAuctionHouse(getEntry());
|
|
|
|
//load resistances
|
|
for (uint8 x = 0; x < TOTAL_SPELL_SCHOOLS; ++x)
|
|
m_baseResistance[x] = getResistance(x);
|
|
for (uint8 x = 0; x < STAT_COUNT; ++x)
|
|
m_baseStats[x] = getStat(x);
|
|
|
|
m_baseDamage[0] = getMinDamage();
|
|
m_baseDamage[1] = getMaxDamage();
|
|
m_baseOffhandDamage[0] = getMinOffhandDamage();
|
|
m_baseOffhandDamage[1] = getMaxOffhandDamage();
|
|
m_baseRangedDamage[0] = getMinRangedDamage();
|
|
m_baseRangedDamage[1] = getMaxRangedDamage();
|
|
BaseAttackType = creature_properties->attackSchool;
|
|
|
|
setModCastSpeed(1.0f); // better set this one
|
|
|
|
// Bytes 0
|
|
setBytes0(spawn->bytes0);
|
|
|
|
// Bytes 1
|
|
setStandState(static_cast<uint8_t>((spawn->bytes1) & 0xFF));
|
|
setPetTalentPoints(static_cast<uint8_t>((spawn->bytes1 >> 8) & 0xFF));
|
|
setStandStateFlags(static_cast<uint8_t>((spawn->bytes1 >> 16) & 0xFF));
|
|
setAnimationTier(static_cast<AnimationTier>((spawn->bytes1 >> 24) & 0xFF));
|
|
|
|
// Bytes 2
|
|
setBytes2(spawn->bytes2);
|
|
|
|
////////////AI
|
|
sEventMgr.AddEvent(this, &Creature::OnLoaded, 0, 100, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
|
|
|
|
if (isattackable(spawn))
|
|
getAIInterface()->setAllowedToEnterCombat(true);
|
|
|
|
//////////////AI
|
|
|
|
myFamily = sCreatureFamilyStore.LookupEntry(creature_properties->Family);
|
|
|
|
|
|
//HACK!
|
|
if (getDisplayId() == 17743 ||
|
|
getDisplayId() == 20242 ||
|
|
getDisplayId() == 15435 ||
|
|
(creature_properties->Family == UNIT_TYPE_MISC))
|
|
{
|
|
setAItoUse(false);
|
|
}
|
|
|
|
// more hacks!
|
|
if (creature_properties->Mana != 0)
|
|
setPowerType(POWER_TYPE_MANA);
|
|
else
|
|
setPowerType(0);
|
|
|
|
/* // Dont was Used in old AIInterface left the code here if needed at other Date
|
|
if (creature_properties->guardtype == GUARDTYPE_CITY)
|
|
getAIInterface()->setGuard(true);
|
|
else
|
|
getAIInterface()->setGuard(false);*/
|
|
|
|
if (creature_properties->guardtype == GUARDTYPE_NEUTRAL)
|
|
getAIInterface()->setGuard(true);
|
|
else
|
|
getAIInterface()->setGuard(false);
|
|
|
|
|
|
// creature death state
|
|
if (spawn->death_state == CREATURE_STATE_APPEAR_DEAD)
|
|
{
|
|
m_limbostate = true;
|
|
setDynamicFlags(U_DYN_FLAG_DEAD);
|
|
}
|
|
else if (spawn->death_state == CREATURE_STATE_DEAD)
|
|
{
|
|
setHealth(0);
|
|
m_limbostate = true;
|
|
setDeathState(CORPSE);
|
|
}
|
|
|
|
if (spawn->stand_state)
|
|
setStandState((uint8)spawn->stand_state);
|
|
|
|
m_aiInterface->eventAiInterfaceParamsetFinish();
|
|
|
|
this->m_position.x = spawn->x;
|
|
this->m_position.y = spawn->y;
|
|
this->m_position.z = spawn->z;
|
|
this->m_position.o = spawn->o;
|
|
|
|
// Set spell immunities
|
|
if (creature_properties->modImmunities != 0)
|
|
{
|
|
const auto immunityMask = static_cast<SpellImmunityMask>(creature_properties->modImmunities);
|
|
addSpellImmunity(immunityMask, true);
|
|
}
|
|
|
|
#ifdef FT_VEHICLES
|
|
if (isVehicle())
|
|
{
|
|
createVehicleKit(creature_properties->vehicleid, creature_properties->Id);
|
|
addNpcFlags(UNIT_NPC_FLAG_SPELLCLICK);
|
|
setAItoUse(false);
|
|
}
|
|
#endif
|
|
|
|
if (getMovementTemplate().isRooted())
|
|
setControlled(true, UNIT_STATE_ROOTED);
|
|
|
|
// Respawn
|
|
m_respawnDelay = creature_properties->RespawnTime / IN_MILLISECONDS;
|
|
|
|
if (isDungeonBoss())
|
|
m_respawnDelay = 0; // special value, prevents respawn for dungeon bosses unless overridden
|
|
|
|
switch (this->creature_properties->Rank)
|
|
{
|
|
case ELITE_ELITE:
|
|
m_corpseDelay = worldConfig.corpseDecay.eliteTimeInSeconds;
|
|
break;
|
|
case ELITE_RAREELITE:
|
|
m_corpseDelay = worldConfig.corpseDecay.rareEliteTimeInSeconds;
|
|
break;
|
|
case ELITE_WORLDBOSS:
|
|
m_corpseDelay = worldConfig.corpseDecay.worldbossTimeInSeconds;
|
|
break;
|
|
case ELITE_RARE:
|
|
m_corpseDelay = worldConfig.corpseDecay.rareTimeInSeconds;
|
|
break;
|
|
default:
|
|
m_corpseDelay = worldConfig.corpseDecay.normalTimeInSeconds;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::Load(CreatureProperties const* properties_, float x, float y, float z, float o)
|
|
{
|
|
creature_properties = properties_;
|
|
|
|
if (creature_properties->isTrainingDummy == 0 && !isVehicle())
|
|
{
|
|
getAIInterface()->setAllowedToEnterCombat(true);
|
|
}
|
|
|
|
setSpeedRate(TYPE_WALK, creature_properties->walk_speed, false);
|
|
setSpeedRate(TYPE_RUN, creature_properties->run_speed, false);
|
|
setSpeedRate(TYPE_FLY, creature_properties->fly_speed, false);
|
|
resetCurrentSpeeds();
|
|
|
|
//Set fields
|
|
setEntry(creature_properties->Id);
|
|
setScale(creature_properties->Scale);
|
|
|
|
#if VERSION_STRING > TBC
|
|
setHoverHeight(creature_properties->Scale);
|
|
#endif
|
|
|
|
uint32 health = creature_properties->MinHealth + Util::getRandomUInt(creature_properties->MaxHealth - creature_properties->MinHealth);
|
|
|
|
setMaxHealth(health);
|
|
setHealth(health);
|
|
setBaseHealth(health);
|
|
|
|
setMaxPower(POWER_TYPE_MANA, creature_properties->Mana);
|
|
setBaseMana(creature_properties->Mana);
|
|
setPower(POWER_TYPE_MANA, creature_properties->Mana);
|
|
|
|
uint32 model = 0;
|
|
uint8 gender = creature_properties->generateRandomDisplayIdAndReturnGender(&model);
|
|
setGender(gender);
|
|
|
|
setDisplayId(model);
|
|
setNativeDisplayId(model);
|
|
setMountDisplayId(0);
|
|
|
|
eventModelChange();
|
|
|
|
setLevel(creature_properties->MinLevel + (Util::getRandomUInt(creature_properties->MaxLevel - creature_properties->MinLevel)));
|
|
|
|
for (uint8 i = 0; i < 7; ++i)
|
|
setResistance(i, creature_properties->Resistances[i]);
|
|
|
|
setBaseAttackTime(MELEE, creature_properties->AttackTime);
|
|
setMinDamage(creature_properties->MinDamage);
|
|
setMaxDamage(creature_properties->MaxDamage);
|
|
|
|
|
|
setFaction(creature_properties->Faction);
|
|
setBoundingRadius(creature_properties->BoundingRadius);
|
|
setCombatReach(creature_properties->CombatReach);
|
|
|
|
original_emotestate = 0;
|
|
|
|
// set position
|
|
m_position.ChangeCoords({ x, y, z, o });
|
|
m_spawnLocation.ChangeCoords({ x, y, z, o });
|
|
|
|
// not a neutral creature
|
|
if (m_factionEntry && !(m_factionEntry->RepListId == -1 && m_factionTemplate->HostileMask == 0 && m_factionTemplate->FriendlyMask == 0))
|
|
{
|
|
getAIInterface()->setCanCallForHelp(true);
|
|
}
|
|
|
|
getAIInterface()->initializeReactState();
|
|
|
|
// set if creature can shoot or not.
|
|
if (creature_properties->CanRanged == 1)
|
|
getAIInterface()->m_canRangedAttack = true;
|
|
else
|
|
getAIInterface()->m_canRangedAttack = false;
|
|
|
|
// checked at loading
|
|
m_defaultMovementType = MovementGeneratorType(IDLE_MOTION_TYPE);
|
|
|
|
if (getMaxWanderDistance() == 0.0f && m_defaultMovementType == RANDOM_MOTION_TYPE)
|
|
m_defaultMovementType = IDLE_MOTION_TYPE;
|
|
|
|
_waypointPathId = 0;
|
|
|
|
//SETUP NPC FLAGS
|
|
setNpcFlags(creature_properties->NPCFLags);
|
|
|
|
if (isVendor())
|
|
m_SellItems = sObjectMgr.getVendorList(getEntry());
|
|
|
|
if (isQuestGiver())
|
|
_LoadQuests();
|
|
|
|
if (isTrainer() || isProfessionTrainer())
|
|
mTrainer = sObjectMgr.getTrainer(getEntry());
|
|
|
|
if (isAuctioneer())
|
|
auctionHouse = sAuctionMgr.GetAuctionHouse(getEntry());
|
|
|
|
//load resistances
|
|
for (uint8 j = 0; j < TOTAL_SPELL_SCHOOLS; ++j)
|
|
m_baseResistance[j] = getResistance(j);
|
|
for (uint8 j = 0; j < STAT_COUNT; ++j)
|
|
m_baseStats[j] = getStat(j);
|
|
|
|
m_baseDamage[0] = getMinDamage();
|
|
m_baseDamage[1] = getMaxDamage();
|
|
m_baseOffhandDamage[0] = getMinOffhandDamage();
|
|
m_baseOffhandDamage[1] = getMaxOffhandDamage();
|
|
m_baseRangedDamage[0] = getMinRangedDamage();
|
|
m_baseRangedDamage[1] = getMaxRangedDamage();
|
|
BaseAttackType = creature_properties->attackSchool;
|
|
|
|
setModCastSpeed(1.0f); // better set this one
|
|
|
|
////////////AI
|
|
getAIInterface()->initialiseScripts(getEntry());
|
|
getAIInterface()->eventOnLoad();
|
|
|
|
//////////////AI
|
|
|
|
myFamily = sCreatureFamilyStore.LookupEntry(creature_properties->Family);
|
|
|
|
|
|
// \todo remove this HACK! already included few lines above
|
|
if (getDisplayId() == 17743 ||
|
|
getDisplayId() == 20242 ||
|
|
getDisplayId() == 15435 ||
|
|
creature_properties->Type == UNIT_TYPE_MISC)
|
|
{
|
|
setAItoUse(false);
|
|
}
|
|
|
|
setPowerType(POWER_TYPE_MANA);
|
|
|
|
/* // Dont was Used in old AIInterface left the code here if needed at other Date
|
|
if (creature_properties->guardtype == GUARDTYPE_CITY)
|
|
getAIInterface()->setGuard(true);
|
|
else
|
|
getAIInterface()->setGuard(false);*/
|
|
|
|
if (creature_properties->guardtype == GUARDTYPE_NEUTRAL)
|
|
getAIInterface()->setGuard(true);
|
|
else
|
|
getAIInterface()->setGuard(false);
|
|
|
|
// Set spell immunities
|
|
if (creature_properties->modImmunities != 0)
|
|
{
|
|
const auto immunityMask = static_cast<SpellImmunityMask>(creature_properties->modImmunities);
|
|
addSpellImmunity(immunityMask, true);
|
|
}
|
|
|
|
#ifdef FT_VEHICLES
|
|
if (isVehicle())
|
|
{
|
|
createVehicleKit(creature_properties->vehicleid, creature_properties->Id);
|
|
addNpcFlags(UNIT_NPC_FLAG_SPELLCLICK);
|
|
setAItoUse(false);
|
|
}
|
|
#endif
|
|
|
|
if (getMovementTemplate().isRooted())
|
|
setControlled(true, UNIT_STATE_ROOTED);
|
|
|
|
// Respawn
|
|
m_respawnDelay = creature_properties->RespawnTime / IN_MILLISECONDS;
|
|
|
|
if (isDungeonBoss())
|
|
m_respawnDelay = 0; // special value, prevents respawn for dungeon bosses unless overridden
|
|
|
|
switch (this->creature_properties->Rank)
|
|
{
|
|
case ELITE_ELITE:
|
|
m_corpseDelay = worldConfig.corpseDecay.eliteTimeInSeconds;
|
|
break;
|
|
case ELITE_RAREELITE:
|
|
m_corpseDelay = worldConfig.corpseDecay.rareEliteTimeInSeconds;
|
|
break;
|
|
case ELITE_WORLDBOSS:
|
|
m_corpseDelay = worldConfig.corpseDecay.worldbossTimeInSeconds;
|
|
break;
|
|
case ELITE_RARE:
|
|
m_corpseDelay = worldConfig.corpseDecay.rareTimeInSeconds;
|
|
break;
|
|
default:
|
|
m_corpseDelay = worldConfig.corpseDecay.normalTimeInSeconds;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Creature::OnLoaded()
|
|
{
|
|
getAIInterface()->initialiseScripts(getEntry());
|
|
getAIInterface()->eventOnLoad();
|
|
|
|
immediateMovementFlagsUpdate();
|
|
}
|
|
|
|
void Creature::OnPushToWorld()
|
|
{
|
|
if (creature_properties == nullptr)
|
|
{
|
|
sLogger.failure("Something tried to push Creature with entry %u with invalid creature_properties!", getEntry());
|
|
return;
|
|
}
|
|
|
|
std::set<uint32>::iterator itr = creature_properties->start_auras.begin();
|
|
for (; itr != creature_properties->start_auras.end(); ++itr)
|
|
{
|
|
SpellInfo const* sp = sSpellMgr.getSpellInfo((*itr));
|
|
if (sp == nullptr)
|
|
continue;
|
|
|
|
castSpell(this, sp, 0);
|
|
}
|
|
|
|
if (!sScriptMgr.has_creature_gossip(getEntry()))
|
|
registerDatabaseGossip();
|
|
|
|
if (GetScript() == NULL)
|
|
{
|
|
LoadScript();
|
|
}
|
|
|
|
// Formations
|
|
searchFormation();
|
|
motion_Initialize();
|
|
|
|
Unit::OnPushToWorld();
|
|
|
|
if (_myScriptClass)
|
|
_myScriptClass->OnLoad();
|
|
|
|
if (m_spawn)
|
|
{
|
|
if (m_spawn->channel_target_creature)
|
|
sEventMgr.AddEvent(this, &Creature::ChannelLinkUpCreature, m_spawn->channel_target_creature, EVENT_CREATURE_CHANNEL_LINKUP, 1000, 5, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT); // only 5 attempts
|
|
|
|
if (m_spawn->channel_target_go)
|
|
sEventMgr.AddEvent(this, &Creature::ChannelLinkUpGO, m_spawn->channel_target_go, EVENT_CREATURE_CHANNEL_LINKUP, 1000, 5, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT); // only 5 attempts
|
|
}
|
|
|
|
if (m_WorldMap)
|
|
m_aiInterface->m_is_in_instance = (!m_WorldMap->getBaseMap()->getMapInfo()->isNonInstanceMap()) ? true : false;
|
|
else
|
|
m_aiInterface->m_is_in_instance = false;
|
|
|
|
if (this->HasItems())
|
|
{
|
|
for (std::vector<CreatureItem>::iterator itr2 = m_SellItems->begin(); itr2 != m_SellItems->end(); ++itr2)
|
|
{
|
|
if (itr2->max_amount == 0)
|
|
itr2->available_amount = 0;
|
|
else if (itr2->available_amount < itr2->max_amount)
|
|
sEventMgr.AddEvent(this, &Creature::UpdateItemAmount, itr2->itemid, EVENT_ITEM_UPDATE, vendorItemsUpdate, 1, 0);
|
|
}
|
|
|
|
}
|
|
|
|
getAIInterface()->setCreatureProtoDifficulty(creature_properties->Id);
|
|
|
|
if (mEvent != nullptr)
|
|
{
|
|
if (mEvent->mEventScript != nullptr)
|
|
mEvent->mEventScript->OnCreaturePushToWorld(mEvent, this);
|
|
}
|
|
|
|
if (m_WorldMap && m_WorldMap->getScript())
|
|
{
|
|
m_WorldMap->getScript()->OnCreaturePushToWorld(this);
|
|
m_WorldMap->getScript()->addObject(this);
|
|
}
|
|
}
|
|
|
|
void Creature::respawn(bool force)
|
|
{
|
|
if (force)
|
|
{
|
|
if (isAlive())
|
|
setDeathState(JUST_DIED);
|
|
else if (getDeathState() != CORPSE)
|
|
setDeathState(CORPSE);
|
|
}
|
|
|
|
// do this for now delete the part when we are only respawning with spawnid
|
|
if (true)
|
|
{
|
|
OnRemoveCorpse();
|
|
|
|
if (getDeathState() == DEAD)
|
|
{
|
|
sLogger.debug("Respawning creature %s (%s)", GetCreatureProperties()->Name.c_str(), getGuid());
|
|
m_respawnTime = 0;
|
|
loot.clear();
|
|
|
|
auto minlevel = std::min(GetCreatureProperties()->MaxLevel, GetCreatureProperties()->MinLevel);
|
|
auto maxlevel = std::max(GetCreatureProperties()->MaxLevel, GetCreatureProperties()->MinLevel);
|
|
auto level = minlevel == maxlevel ? minlevel : Util::getRandomUInt(minlevel, maxlevel);
|
|
setLevel(level);
|
|
|
|
setDeathState(JUST_RESPAWNED);
|
|
|
|
uint32_t displayID = getNativeDisplayId();
|
|
uint8_t gender = GetCreatureProperties()->generateRandomDisplayIdAndReturnGender(&displayID);
|
|
|
|
setGender(gender);
|
|
setDisplayId(displayID);
|
|
setNativeDisplayId(displayID);
|
|
|
|
getMovementManager()->initializeDefault();
|
|
|
|
// Re-initialize reactstate that could be altered by movementgenerators
|
|
getAIInterface()->initializeReactState();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (getSpawnId())
|
|
{
|
|
MapCell* pCell = getWorldMap()->getCellByCoords(GetSpawnX(), GetSpawnY());
|
|
if (pCell == nullptr)
|
|
pCell = GetMapCell();
|
|
|
|
getWorldMap()->doRespawn(SPAWN_TYPE_CREATURE, nullptr, getSpawnId(), pCell->getPositionX(), pCell->getPositionY());
|
|
}
|
|
}
|
|
|
|
sLogger.debug("Respawning creature %s (%s)", GetCreatureProperties()->Name.c_str(), getGuid());
|
|
}
|
|
|
|
void Creature::Despawn(uint32 delay, uint32 respawntime)
|
|
{
|
|
if (delay)
|
|
{
|
|
sEventMgr.AddEvent(this, &Creature::Despawn, (uint32)0, respawntime, EVENT_CREATURE_RESPAWN, delay, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
|
|
}
|
|
else
|
|
{
|
|
PrepareForRemove();
|
|
|
|
if (!IsInWorld())
|
|
return;
|
|
|
|
if (_myScriptClass != NULL)
|
|
_myScriptClass->OnDespawn();
|
|
|
|
if (respawntime && !m_noRespawn)
|
|
{
|
|
// get the cell with our SPAWN location. if we've moved cell this might break :P
|
|
MapCell* pCell = m_WorldMap->getCellByCoords(m_spawnLocation.x, m_spawnLocation.y);
|
|
if (pCell == nullptr)
|
|
pCell = GetMapCell();
|
|
|
|
if (pCell != nullptr)
|
|
{
|
|
pCell->_respawnObjects.insert(this);
|
|
|
|
sEventMgr.RemoveEvents(this);
|
|
|
|
m_position = m_spawnLocation;
|
|
m_respawnCell = pCell;
|
|
|
|
saveRespawnTime(respawntime);
|
|
Unit::RemoveFromWorld(false);
|
|
}
|
|
else
|
|
{
|
|
sLogger.failure("Creature::Despawn not able to get a valid MapCell (nullptr)");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Unit::RemoveFromWorld(true);
|
|
SafeDelete();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::despawn(uint32_t delay)
|
|
{
|
|
if (delay)
|
|
{
|
|
sEventMgr.AddEvent(this, &Creature::despawn, (uint32_t)0, EVENT_CREATURE_RESPAWN, delay, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
|
|
}
|
|
else
|
|
{
|
|
PrepareForRemove();
|
|
|
|
if (!IsInWorld())
|
|
return;
|
|
|
|
if (_myScriptClass != nullptr)
|
|
_myScriptClass->OnDespawn();
|
|
|
|
if (!m_noRespawn)
|
|
{
|
|
// get the cell with our SPAWN location. if we've moved cell this might break :P
|
|
MapCell* pCell = m_WorldMap->getCellByCoords(m_spawnLocation.x, m_spawnLocation.y);
|
|
if (pCell == nullptr)
|
|
pCell = GetMapCell();
|
|
|
|
if (pCell != nullptr)
|
|
{
|
|
pCell->_respawnObjects.insert(this);
|
|
|
|
sEventMgr.RemoveEvents(this);
|
|
|
|
m_position = m_spawnLocation;
|
|
m_respawnCell = pCell;
|
|
|
|
saveRespawnTime();
|
|
Unit::RemoveFromWorld(false);
|
|
}
|
|
else
|
|
{
|
|
sLogger.failure("Creature::Despawn not able to get a valid MapCell (nullptr)");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Unit::RemoveFromWorld(true);
|
|
SafeDelete();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::saveRespawnTime(uint32_t forceDelay)
|
|
{
|
|
if (isSummon() || !getSpawnId() || !getWorldMap())
|
|
return;
|
|
|
|
const auto now = Util::getTimeNow();
|
|
|
|
// do this for now delete the part when we are only respawning with spawnid
|
|
if (true)
|
|
{
|
|
RespawnInfo ri;
|
|
ri.type = SPAWN_TYPE_CREATURE;
|
|
ri.spawnId = getSpawnId();
|
|
ri.entry = getEntry();
|
|
ri.time = forceDelay ? now + forceDelay / IN_MILLISECONDS : m_respawnTime;
|
|
ri.cellX = m_spawnLocation.x;
|
|
ri.cellY = m_spawnLocation.y;
|
|
ri.obj = this;
|
|
|
|
bool success = getWorldMap()->addRespawn(ri);
|
|
if (success)
|
|
getWorldMap()->saveRespawnDB(ri);
|
|
return;
|
|
}
|
|
|
|
time_t thisRespawnTime = forceDelay ? now + forceDelay / IN_MILLISECONDS : m_respawnTime;
|
|
getWorldMap()->saveRespawnTime(SPAWN_TYPE_CREATURE, getSpawnId(), getEntry(), thisRespawnTime, m_respawnCell->getPositionX(), m_respawnCell->getPositionY());
|
|
}
|
|
|
|
void Creature::TriggerScriptEvent(int fRef)
|
|
{
|
|
if (_myScriptClass)
|
|
_myScriptClass->StringFunctionCall(fRef);
|
|
}
|
|
|
|
bool Creature::IsInLimboState()
|
|
{
|
|
return m_limbostate;
|
|
}
|
|
|
|
void Creature::SetLimboState(bool set)
|
|
{
|
|
m_limbostate = set;
|
|
}
|
|
|
|
uint32 Creature::GetLineByFamily(DBC::Structures::CreatureFamilyEntry const* family)
|
|
{
|
|
return family->skilline ? family->skilline : 0;
|
|
}
|
|
|
|
void Creature::RemoveLimboState(Unit* /*healer*/)
|
|
{
|
|
if (!m_limbostate != true)
|
|
return;
|
|
|
|
m_limbostate = false;
|
|
setEmoteState(m_spawn ? m_spawn->emote_state : EMOTE_ONESHOT_NONE);
|
|
setHealth(getMaxHealth());
|
|
m_isInvincible = false;
|
|
}
|
|
|
|
uint32 Creature::GetNpcTextId()
|
|
{
|
|
return sMySQLStore.getGossipTextIdForNpc(this->getEntry());
|
|
}
|
|
|
|
float Creature::GetBaseParry()
|
|
{
|
|
//\todo what are the parry rates for mobs?
|
|
// FACT: bosses have varying parry rates (used to tune the difficulty of boss fights)
|
|
|
|
// for now return a base of 5%, later get from dbase?
|
|
return 5.0f;
|
|
}
|
|
|
|
int32 Creature::GetDamageDoneMod(uint16_t school)
|
|
{
|
|
if (school >= TOTAL_SPELL_SCHOOLS)
|
|
return 0;
|
|
|
|
return ModDamageDone[ school ];
|
|
}
|
|
|
|
float Creature::GetDamageDonePctMod(uint16_t school)
|
|
{
|
|
if (school >= TOTAL_SPELL_SCHOOLS)
|
|
return 0;
|
|
|
|
return ModDamageDonePct[ school ];
|
|
}
|
|
|
|
bool Creature::IsPickPocketed()
|
|
{
|
|
return m_PickPocketed;
|
|
}
|
|
|
|
void Creature::SetPickPocketed(bool val)
|
|
{
|
|
m_PickPocketed = val;
|
|
}
|
|
|
|
CreatureAIScript* Creature::GetScript()
|
|
{
|
|
return _myScriptClass;
|
|
}
|
|
|
|
bool Creature::HasLootForPlayer(Player* plr)
|
|
{
|
|
if (loot.isLooted()) // nothing to loot or everything looted.
|
|
return false;
|
|
|
|
Group* thisGroup = plr->getGroup();
|
|
if (!thisGroup)
|
|
return (getTaggerGuid() == plr->getGuid());
|
|
|
|
switch (thisGroup->GetMethod())
|
|
{
|
|
case PARTY_LOOT_FREE_FOR_ALL:
|
|
return true;
|
|
case PARTY_LOOT_ROUND_ROBIN:
|
|
case PARTY_LOOT_MASTER_LOOTER:
|
|
// only loot if the player is Plunder Master or Round Robbin Player
|
|
if (loot.roundRobinPlayer == 0 || loot.roundRobinPlayer == plr->getGuid())
|
|
return true;
|
|
// or when it has Personal loot
|
|
return loot.hasItemFor(plr);
|
|
case PARTY_LOOT_GROUP:
|
|
case PARTY_LOOT_NEED_BEFORE_GREED:
|
|
// only loot when no Round Robbin or is Round Robber
|
|
if (loot.roundRobinPlayer == 0 || loot.roundRobinPlayer == plr->getGuid())
|
|
return true;
|
|
// or if Items is under Group Threshold research this also grey items are under threshold which means free loot ?
|
|
if (loot.hasOverThresholdItem())
|
|
return true;
|
|
// or when it has Personal loot
|
|
return loot.hasItemFor(plr);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
uint16_t Creature::GetRequiredLootSkill()
|
|
{
|
|
if (GetCreatureProperties()->typeFlags & CREATURE_FLAG1_HERBLOOT)
|
|
return SKILL_HERBALISM; // herbalism
|
|
else if (GetCreatureProperties()->typeFlags & CREATURE_FLAG1_MININGLOOT)
|
|
return SKILL_MINING; // mining
|
|
else if (GetCreatureProperties()->typeFlags & CREATURE_FLAG1_ENGINEERLOOT)
|
|
return SKILL_ENGINEERING;
|
|
else
|
|
return SKILL_SKINNING; // skinning
|
|
}
|
|
|
|
uint32 Creature::GetSQL_id()
|
|
{
|
|
return spawnid;
|
|
};
|
|
|
|
bool Creature::HasItems()
|
|
{
|
|
return ((m_SellItems != NULL) ? true : false);
|
|
}
|
|
|
|
int32 Creature::GetSlotByItemId(uint32 itemid)
|
|
{
|
|
uint32 slot = 0;
|
|
for (std::vector<CreatureItem>::iterator itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
return slot;
|
|
else
|
|
++slot;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uint32 Creature::GetItemAmountByItemId(uint32 itemid)
|
|
{
|
|
for (std::vector<CreatureItem>::iterator itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
return ((itr->amount < 1) ? 1 : itr->amount);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Creature::GetSellItemBySlot(uint32 slot, CreatureItem& ci)
|
|
{
|
|
ci = m_SellItems->at(slot);
|
|
}
|
|
|
|
void Creature::GetSellItemByItemId(uint32 itemid, CreatureItem& ci)
|
|
{
|
|
for (std::vector<CreatureItem>::iterator itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
{
|
|
ci = (*itr);
|
|
return;
|
|
}
|
|
}
|
|
ci.amount = 0;
|
|
ci.max_amount = 0;
|
|
ci.available_amount = 0;
|
|
ci.incrtime = 0;
|
|
ci.itemid = 0;
|
|
}
|
|
|
|
#if VERSION_STRING < Cata
|
|
DBC::Structures::ItemExtendedCostEntry const* Creature::GetItemExtendedCostByItemId(uint32 itemid)
|
|
#else
|
|
DB2::Structures::ItemExtendedCostEntry const* Creature::GetItemExtendedCostByItemId(uint32 itemid)
|
|
#endif
|
|
{
|
|
for (auto itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
return itr->extended_cost;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<CreatureItem>::iterator Creature::GetSellItemBegin()
|
|
{
|
|
return m_SellItems->begin();
|
|
}
|
|
|
|
std::vector<CreatureItem>::iterator Creature::GetSellItemEnd()
|
|
{
|
|
return m_SellItems->end();
|
|
}
|
|
|
|
|
|
size_t Creature::GetSellItemCount()
|
|
{
|
|
return m_SellItems->size();
|
|
}
|
|
|
|
void Creature::RemoveVendorItem(uint32 itemid)
|
|
{
|
|
for (auto itr = m_SellItems->begin(); itr != m_SellItems->end(); ++itr)
|
|
{
|
|
if (itr->itemid == itemid)
|
|
{
|
|
m_SellItems->erase(itr);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::PrepareForRemove()
|
|
{
|
|
removeAllAuras();
|
|
|
|
getSummonInterface()->removeAllSummons();
|
|
|
|
if (!IsInWorld())
|
|
return;
|
|
|
|
if (getCreatedByGuid() != 0)
|
|
{
|
|
|
|
Unit* summoner = getWorldMapUnit(getCreatedByGuid());
|
|
if (summoner != NULL)
|
|
{
|
|
#if VERSION_STRING > TBC
|
|
if (summoner->getCritterGuid() == getGuid())
|
|
summoner->setCritterGuid(0);
|
|
#endif
|
|
|
|
if (getCreatedBySpellId() != 0)
|
|
summoner->removeAllAurasById(getCreatedBySpellId());
|
|
}
|
|
}
|
|
|
|
if (getWorldMap()->getBaseMap()->getMapInfo() && getWorldMap()->getBaseMap()->getMapInfo()->isRaid())
|
|
{
|
|
if (GetCreatureProperties()->Rank == 3)
|
|
{
|
|
getWorldMap()->removeCombatInProgress(getGuid());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Creature::IsExotic()
|
|
{
|
|
if ((GetCreatureProperties()->typeFlags & CREATURE_FLAG1_EXOTIC) != 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Creature::isCritter()
|
|
{
|
|
if (creature_properties->Type == UNIT_TYPE_CRITTER)
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
void Creature::die(Unit* pAttacker, uint32 /*damage*/, uint32 spellid)
|
|
{
|
|
#ifdef FT_VEHICLES
|
|
// Exit Vehicle
|
|
removeVehicleKit();
|
|
callExitVehicle();
|
|
#endif
|
|
|
|
//general hook for die
|
|
if (!sHookInterface.OnPreUnitDie(pAttacker, this))
|
|
return;
|
|
|
|
// on die and an target die proc
|
|
{
|
|
SpellInfo const* killerspell;
|
|
if (spellid)
|
|
killerspell = sSpellMgr.getSpellInfo(spellid);
|
|
else killerspell = NULL;
|
|
}
|
|
|
|
setDeathState(JUST_DIED);
|
|
getAIInterface()->enterEvadeMode();
|
|
|
|
if (getChannelObjectGuid() != 0)
|
|
{
|
|
Spell* spl = getCurrentSpell(CURRENT_CHANNELED_SPELL);
|
|
|
|
if (spl != NULL)
|
|
{
|
|
|
|
for (uint8 i = 0; i < 3; i++)
|
|
{
|
|
if (spl->getSpellInfo()->getEffect(i) == SPELL_EFFECT_PERSISTENT_AREA_AURA)
|
|
{
|
|
uint64 guid = getChannelObjectGuid();
|
|
DynamicObject* dObj = getWorldMap()->getDynamicObject(WoWGuid::getGuidLowPartFromUInt64(guid));
|
|
if (!dObj)
|
|
return;
|
|
|
|
dObj->remove();
|
|
}
|
|
}
|
|
|
|
if (spl->getSpellInfo()->getChannelInterruptFlags() == 48140)
|
|
interruptSpell(spl->getSpellInfo()->getId());
|
|
}
|
|
}
|
|
|
|
// Stop players from casting
|
|
for (const auto& itr : getInRangePlayersSet())
|
|
{
|
|
Unit* attacker = static_cast<Unit*>(itr);
|
|
if (attacker && attacker->isCastingSpell())
|
|
{
|
|
for (uint8_t i = 0; i < CURRENT_SPELL_MAX; ++i)
|
|
{
|
|
Spell* curSpell = attacker->getCurrentSpell(CurrentSpellType(i));
|
|
if (curSpell != nullptr && curSpell->m_targets.getUnitTarget() == getGuid())
|
|
attacker->interruptSpellWithSpellType(CurrentSpellType(i));
|
|
}
|
|
}
|
|
}
|
|
|
|
smsg_AttackStop(this);
|
|
setHealth(0);
|
|
|
|
removeAllNonPersistentAuras();
|
|
|
|
if (pAttacker->IsInWorld() && pAttacker->isCreature() && dynamic_cast<Creature*>(pAttacker)->GetScript())
|
|
{
|
|
dynamic_cast<Creature*>(pAttacker)->GetScript()->_internalOnTargetDied(this);
|
|
dynamic_cast<Creature*>(pAttacker)->GetScript()->OnTargetDied(this);
|
|
}
|
|
pAttacker->getAIInterface()->eventOnTargetDied(this);
|
|
|
|
pAttacker->smsg_AttackStop(this);
|
|
|
|
getSummonInterface()->removeAllSummons();
|
|
|
|
// Clear Threat
|
|
getThreatManager().clearAllThreat();
|
|
getThreatManager().removeMeFromThreatLists();
|
|
|
|
getCombatHandler().clearCombat();
|
|
|
|
// Add Kills if Player is in Vehicle
|
|
if (pAttacker->isVehicle())
|
|
{
|
|
Unit* vehicle_owner = getWorldMap()->getUnit(pAttacker->getCharmedByGuid());
|
|
|
|
if (vehicle_owner != nullptr && vehicle_owner->isPlayer())
|
|
{
|
|
sQuestMgr.OnPlayerKill(static_cast<Player*>(vehicle_owner), this, true);
|
|
}
|
|
}
|
|
|
|
addUnitFlags(UNIT_FLAG_DEAD);
|
|
|
|
Player* looter = nullptr;
|
|
if (getTaggerGuid())
|
|
{
|
|
if (Unit* tagger = m_WorldMap->getUnit(getTaggerGuid()))
|
|
looter = tagger->ToPlayer();
|
|
}
|
|
else if (pAttacker->isPlayer())
|
|
{
|
|
looter = pAttacker->ToPlayer();
|
|
}
|
|
else if (pAttacker->isCreature())
|
|
{
|
|
looter = pAttacker->getPlayerOwner();
|
|
}
|
|
|
|
// Setup Loot and Round Robin Player in group case
|
|
if (looter)
|
|
{
|
|
if (Group* group = looter->getGroup())
|
|
{
|
|
if (group->GetLooter())
|
|
{
|
|
// just incase he is not online
|
|
looter = sObjectMgr.GetPlayer(group->GetLooter()->guid);
|
|
if (looter)
|
|
{
|
|
setTaggerGuid(looter->getGuid()); // set Tagger to the allowed looter.
|
|
group->sendLooter(this, looter);
|
|
}
|
|
else
|
|
{
|
|
group->sendLooter(this, nullptr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
group->sendLooter(this, nullptr);
|
|
}
|
|
|
|
group->updateLooterGuid(this);
|
|
}
|
|
else
|
|
{
|
|
looter->sendLooter(this);
|
|
}
|
|
|
|
// Generate Loot
|
|
sLootMgr.fillCreatureLoot(looter, &loot, getEntry(), m_WorldMap->getDifficulty());
|
|
|
|
// Generate Gold
|
|
loot.generateGold(sMySQLStore.getCreatureProperties(getEntry()), getAIInterface()->getDifficultyType());
|
|
|
|
if (loot.items.empty())
|
|
return;
|
|
|
|
// Master Looting Ninja Checker
|
|
if (worldConfig.player.deactivateMasterLootNinja)
|
|
{
|
|
looter = sObjectMgr.GetPlayer(static_cast<uint32_t>(this->getTaggerGuid()));
|
|
if (looter && looter->getGroup() && looter->getGroup()->GetMethod() == PARTY_LOOT_MASTER_LOOTER)
|
|
{
|
|
uint16_t lootThreshold = looter->getGroup()->GetThreshold();
|
|
|
|
for (auto itr = loot.items.begin(); itr != loot.items.end(); ++itr)
|
|
{
|
|
if (itr->itemproto->Quality < lootThreshold)
|
|
continue;
|
|
|
|
// Master Loot Stuff - Let the rest of the raid know what dropped..
|
|
///\todo Shouldn't we move this array to a global position? Or maybe it already exists^^ (VirtualAngel) --- I can see (dead) talking pigs...^^
|
|
const char* itemColours[8] = { "9d9d9d", "ffffff", "1eff00", "0070dd", "a335ee", "ff8000", "e6cc80", "e6cc80" };
|
|
char buffer[256];
|
|
sprintf(buffer, "\174cff%s\174Hitem:%u:0:0:0:0:0:0:0\174h[%s]\174h\174r", itemColours[itr->itemproto->Quality], itr->itemproto->ItemId, itr->itemproto->Name.c_str());
|
|
this->sendChatMessage(CHAT_MSG_MONSTER_SAY, LANG_UNIVERSAL, buffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sLogger.debug("no loot owner found loot will not be filled for creature %u", getEntry());
|
|
}
|
|
|
|
if (getCharmedByGuid())
|
|
{
|
|
//remove owner warlock soul link from caster
|
|
Unit* owner = getWorldMap()->getUnit(getCharmedByGuid());
|
|
|
|
if (owner != NULL && owner->isPlayer())
|
|
static_cast<Player*>(owner)->eventDismissPet();
|
|
}
|
|
|
|
if (getCharmedByGuid() != 0)
|
|
{
|
|
Unit* charmer = m_WorldMap->getUnit(getCharmedByGuid());
|
|
if (charmer != NULL)
|
|
charmer->unPossess();
|
|
}
|
|
|
|
// Clear health batch on death
|
|
clearHealthBatch();
|
|
|
|
// Update movement flags on death
|
|
immediateMovementFlagsUpdate();
|
|
|
|
if (m_WorldMap->getBaseMap()->isBattlegroundOrArena() && reinterpret_cast<BattlegroundMap*>(m_WorldMap)->getBattleground())
|
|
reinterpret_cast<BattlegroundMap*>(m_WorldMap)->getBattleground()->HookOnUnitDied(this);
|
|
}
|
|
|
|
/// \todo implement localization support
|
|
// 1. Chat Areas (Area, Map, World)
|
|
// 2. WorldPacket... support for MONSTER_SAY
|
|
// 3. data resize, map with players (PlayerSession)
|
|
// 4. Sending localizations if available... puh
|
|
void Creature::SendScriptTextChatMessage(uint32 textid, Unit* target/* = target*/)
|
|
{
|
|
SendCreatureChatMessageInRange(this, textid, target);
|
|
}
|
|
|
|
void Creature::SendTimedScriptTextChatMessage(uint32 textid, uint32 delay, Unit* target/* = nullptr*/)
|
|
{
|
|
if (delay > 0)
|
|
{
|
|
sEventMgr.AddEvent(this, &Creature::SendTimedScriptTextChatMessage, textid, uint32_t(0), target, EVENT_UNIT_CHAT_MSG, delay, 1, EVENT_FLAG_DO_NOT_EXECUTE_IN_WORLD_CONTEXT);
|
|
return;
|
|
}
|
|
|
|
SendCreatureChatMessageInRange(this, textid, target);
|
|
}
|
|
|
|
uint32 Creature::GetType()
|
|
{
|
|
return m_Creature_type;
|
|
}
|
|
|
|
void Creature::SetType(uint32 t)
|
|
{
|
|
m_Creature_type = t;
|
|
}
|
|
|
|
void Creature::buildPetSpellList(WorldPacket& data)
|
|
{
|
|
data << uint64_t(getGuid());
|
|
data << uint16_t(creature_properties->Family);
|
|
data << uint32_t(0);
|
|
|
|
if (!isVehicle())
|
|
data << uint32_t(0);
|
|
else
|
|
data << uint32_t(0x8000101);
|
|
|
|
std::vector<uint32_t>::const_iterator itr = creature_properties->castable_spells.begin();
|
|
|
|
// Send the actionbar
|
|
for (uint8_t i = 0; i < 10; ++i)
|
|
{
|
|
if (itr != creature_properties->castable_spells.end())
|
|
{
|
|
const auto spell = *itr;
|
|
const uint32_t actionButton = uint32_t(spell) | uint32_t(i + 8) << 24;
|
|
data << uint32_t(actionButton);
|
|
++itr;
|
|
}
|
|
else
|
|
{
|
|
data << uint16_t(0);
|
|
data << uint8_t(0);
|
|
data << uint8_t(i + 8);
|
|
}
|
|
}
|
|
|
|
data << uint8_t(0);
|
|
// cooldowns
|
|
data << uint8_t(0);
|
|
}
|
|
|
|
CreatureMovementData const& Creature::getMovementTemplate()
|
|
{
|
|
if (CreatureMovementData const* movementOverride = sObjectMgr.getCreatureMovementOverride(spawnid))
|
|
return *movementOverride;
|
|
|
|
return GetCreatureProperties()->Movement;
|
|
}
|
|
|
|
void Creature::InitSummon(Object* summoner)
|
|
{
|
|
if (summoner)
|
|
{
|
|
if (summoner->IsInWorld() && summoner->isCreature() && static_cast<Creature*>(summoner)->GetScript())
|
|
static_cast<Creature*>(summoner)->GetScript()->onSummonedCreature(this);
|
|
|
|
if (GetScript() != nullptr)
|
|
GetScript()->OnSummon(static_cast<Unit*>(summoner));
|
|
}
|
|
}
|
|
|
|
bool Creature::updateEntry(uint32 entry)
|
|
{
|
|
CreatureProperties const* cInfo = sMySQLStore.getCreatureProperties(entry);
|
|
if (!cInfo)
|
|
return false;
|
|
|
|
if (_myScriptClass)
|
|
{
|
|
_myScriptClass->Destroy();
|
|
_myScriptClass = nullptr;
|
|
}
|
|
|
|
Load(cInfo, GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation());
|
|
LoadScript();
|
|
|
|
motion_Initialize();
|
|
|
|
// When we are Still Alive call OnLoad for new CreatureAIScript
|
|
if (isAlive())
|
|
{
|
|
if (_myScriptClass)
|
|
_myScriptClass->OnLoad();
|
|
}
|
|
|
|
return true;
|
|
}
|