mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-01-13 17:19:07 +00:00
3772 lines
122 KiB
C++
3772 lines
122 KiB
C++
/*
|
|
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "Creature.h"
|
|
#include "BattlegroundMgr.h"
|
|
#include "CellImpl.h"
|
|
#include "Common.h"
|
|
#include "CreatureAI.h"
|
|
#include "CreatureAISelector.h"
|
|
#include "CreatureGroups.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "Formulas.h"
|
|
#include "GameEventMgr.h"
|
|
#include "GameTime.h"
|
|
#include "GridNotifiers.h"
|
|
#include "Group.h"
|
|
#include "GroupMgr.h"
|
|
#include "Log.h"
|
|
#include "LootMgr.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Opcodes.h"
|
|
#include "Pet.h"
|
|
#include "Player.h"
|
|
#include "PoolMgr.h"
|
|
#include "ScriptMgr.h"
|
|
#include "ScriptedGossip.h"
|
|
#include "SpellAuraDefines.h"
|
|
#include "SpellAuraEffects.h"
|
|
#include "SpellMgr.h"
|
|
#include "TemporarySummon.h"
|
|
#include "Transport.h"
|
|
#include "Util.h"
|
|
#include "Vehicle.h"
|
|
#include "WaypointMovementGenerator.h"
|
|
#include "World.h"
|
|
#include "WorldPacket.h"
|
|
#include "WorldSessionMgr.h"
|
|
|
|
/// @todo: this import is not necessary for compilation and marked as unused by the IDE
|
|
// however, for some reasons removing it would cause a damn linking issue
|
|
// there is probably some underlying problem with imports which should properly addressed
|
|
// see: https://github.com/azerothcore/azerothcore-wotlk/issues/9766
|
|
#include "GridNotifiersImpl.h"
|
|
|
|
CreatureMovementData::CreatureMovementData() : Ground(CreatureGroundMovementType::Run), Flight(CreatureFlightMovementType::None),
|
|
Swim(true), Rooted(false), Chase(CreatureChaseMovementType::Run),
|
|
Random(CreatureRandomMovementType::Walk), InteractionPauseTimer(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)) {}
|
|
|
|
std::string CreatureMovementData::ToString() const
|
|
{
|
|
constexpr std::array<char const*, 3> GroundStates = {"None", "Run", "Hover"};
|
|
constexpr std::array<char const*, 3> FlightStates = {"None", "DisableGravity", "CanFly"};
|
|
constexpr std::array<char const*, 3> ChaseStates = {"Run", "CanWalk", "AlwaysWalk"};
|
|
constexpr std::array<char const*, 3> RandomStates = {"Walk", "CanRun", "AlwaysRun"};
|
|
|
|
std::ostringstream str;
|
|
str << std::boolalpha
|
|
<< "Ground: " << GroundStates[AsUnderlyingType(Ground)]
|
|
<< ", Swim: " << Swim
|
|
<< ", Flight: " << FlightStates[AsUnderlyingType(Flight)]
|
|
<< ", Chase: " << ChaseStates[AsUnderlyingType(Chase)]
|
|
<< ", Random: " << RandomStates[AsUnderlyingType(Random)];
|
|
if (Rooted)
|
|
str << ", Rooted";
|
|
str << ", InteractionPauseTimer: " << InteractionPauseTimer;
|
|
|
|
return str.str();
|
|
}
|
|
|
|
bool VendorItemData::RemoveItem(uint32 item_id)
|
|
{
|
|
bool found = false;
|
|
for (VendorItemList::iterator i = m_items.begin(); i != m_items.end();)
|
|
{
|
|
if ((*i)->item == item_id)
|
|
{
|
|
i = m_items.erase(i++);
|
|
found = true;
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
return found;
|
|
}
|
|
|
|
VendorItemCount::VendorItemCount(uint32 _item, uint32 _count)
|
|
: itemId(_item), count(_count), lastIncrementTime(GameTime::GetGameTime().count()) { }
|
|
|
|
VendorItem const* VendorItemData::FindItemCostPair(uint32 item_id, uint32 extendedCost) const
|
|
{
|
|
for (VendorItemList::const_iterator i = m_items.begin(); i != m_items.end(); ++i)
|
|
if ((*i)->item == item_id && (*i)->ExtendedCost == extendedCost)
|
|
return *i;
|
|
return nullptr;
|
|
}
|
|
|
|
CreatureModel const CreatureModel::DefaultInvisibleModel(11686, 1.0f, 1.0f);
|
|
CreatureModel const CreatureModel::DefaultVisibleModel(17519, 1.0f, 1.0f);
|
|
|
|
CreatureModel const* CreatureTemplate::GetModelByIdx(uint32 idx) const
|
|
{
|
|
return idx < Models.size() ? &Models[idx] : nullptr;
|
|
}
|
|
|
|
CreatureModel const* CreatureTemplate::GetRandomValidModel() const
|
|
{
|
|
if (!Models.size())
|
|
return nullptr;
|
|
|
|
// If only one element, ignore the Probability (even if 0)
|
|
if (Models.size() == 1)
|
|
return &Models[0];
|
|
|
|
auto selectedItr = Acore::Containers::SelectRandomWeightedContainerElement(Models, [](CreatureModel const& model)
|
|
{
|
|
return model.Probability;
|
|
});
|
|
|
|
return &(*selectedItr);
|
|
}
|
|
|
|
CreatureModel const* CreatureTemplate::GetFirstValidModel() const
|
|
{
|
|
for (CreatureModel const& model : Models)
|
|
if (model.CreatureDisplayID)
|
|
return &model;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CreatureModel const* CreatureTemplate::GetModelWithDisplayId(uint32 displayId) const
|
|
{
|
|
for (CreatureModel const& model : Models)
|
|
if (displayId == model.CreatureDisplayID)
|
|
return &model;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
CreatureModel const* CreatureTemplate::GetFirstInvisibleModel() const
|
|
{
|
|
for (CreatureModel const& model : Models)
|
|
if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(model.CreatureDisplayID))
|
|
if (modelInfo && modelInfo->is_trigger)
|
|
return &model;
|
|
|
|
return &CreatureModel::DefaultInvisibleModel;
|
|
}
|
|
|
|
CreatureModel const* CreatureTemplate::GetFirstVisibleModel() const
|
|
{
|
|
for (CreatureModel const& model : Models)
|
|
if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(model.CreatureDisplayID))
|
|
if (modelInfo && !modelInfo->is_trigger)
|
|
return &model;
|
|
|
|
return &CreatureModel::DefaultVisibleModel;
|
|
}
|
|
|
|
void CreatureTemplate::InitializeQueryData()
|
|
{
|
|
queryData.Initialize(SMSG_CREATURE_QUERY_RESPONSE, 1);
|
|
|
|
queryData << uint32(Entry); // creature entry
|
|
queryData << Name;
|
|
queryData << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty
|
|
queryData << SubName;
|
|
queryData << IconName; // "Directions" for guard, string for Icons 2.3.0
|
|
queryData << uint32(type_flags); // flags
|
|
queryData << uint32(type); // CreatureType.dbc
|
|
queryData << uint32(family); // CreatureFamily.dbc
|
|
queryData << uint32(rank); // Creature Rank (elite, boss, etc)
|
|
queryData << uint32(KillCredit[0]); // new in 3.1, kill credit
|
|
queryData << uint32(KillCredit[1]); // new in 3.1, kill credit
|
|
if (GetModelByIdx(0))
|
|
queryData << uint32(GetModelByIdx(0)->CreatureDisplayID); // Modelid1
|
|
else
|
|
queryData << uint32(0); // Modelid1
|
|
if (GetModelByIdx(1))
|
|
queryData << uint32(GetModelByIdx(1)->CreatureDisplayID); // Modelid2
|
|
else
|
|
queryData << uint32(0); // Modelid2
|
|
if (GetModelByIdx(2))
|
|
queryData << uint32(GetModelByIdx(2)->CreatureDisplayID); // Modelid3
|
|
else
|
|
queryData << uint32(0); // Modelid3
|
|
if (GetModelByIdx(3))
|
|
queryData << uint32(GetModelByIdx(3)->CreatureDisplayID); // Modelid4
|
|
else
|
|
queryData << uint32(0); // Modelid4
|
|
queryData << float(ModHealth); // dmg/hp modifier
|
|
queryData << float(ModMana); // dmg/mana modifier
|
|
queryData << uint8(RacialLeader);
|
|
queryData << uint32(movementId); // CreatureMovementInfo.dbc
|
|
}
|
|
|
|
bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
|
|
{
|
|
if (Unit* victim = ObjectAccessor::GetUnit(*m_owner, m_victim))
|
|
{
|
|
while (!m_assistants.empty())
|
|
{
|
|
Creature* assistant = ObjectAccessor::GetCreature(*m_owner, *m_assistants.begin());
|
|
m_assistants.pop_front();
|
|
|
|
if (assistant && assistant->CanAssistTo(m_owner, victim))
|
|
{
|
|
assistant->SetNoCallAssistance(true);
|
|
assistant->CombatStart(victim);
|
|
if (assistant->IsAIEnabled)
|
|
{
|
|
assistant->AI()->AttackStart(victim);
|
|
|
|
// When nearby mobs aggro from another mob's initial call for assistance
|
|
// their leash timers become linked and attacking one will keep the rest from evading.
|
|
if (assistant->GetVictim())
|
|
assistant->SetLastLeashExtensionTimePtr(m_owner->GetLastLeashExtensionTimePtr());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
CreatureBaseStats const* CreatureBaseStats::GetBaseStats(uint8 level, uint8 unitClass)
|
|
{
|
|
return sObjectMgr->GetCreatureBaseStats(level, unitClass);
|
|
}
|
|
|
|
bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
|
|
{
|
|
m_owner.DespawnOrUnsummon(0ms, m_respawnTimer); // since we are here, we are not TempSummon as object type cannot change during runtime
|
|
return true;
|
|
}
|
|
|
|
bool TemporaryThreatModifierEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
|
|
{
|
|
if (Unit* victim = ObjectAccessor::GetUnit(m_owner, m_threatVictimGUID))
|
|
{
|
|
if (m_owner.IsInCombatWith(victim))
|
|
{
|
|
m_owner.GetThreatMgr().ModifyThreatByPercent(victim, -100); // Reset threat to zero.
|
|
m_owner.GetThreatMgr().AddThreat(victim, m_threatValue); // Set to the previous value it had, first before modification.
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Creature::Creature(): Unit(), MovableMapObject(), m_groupLootTimer(0), lootingGroupLowGUID(0), m_lootRecipientGroup(0),
|
|
m_corpseRemoveTime(0), m_respawnTime(0), m_respawnDelay(300), m_corpseDelay(60), m_wanderDistance(0.0f), m_boundaryCheckTime(2500),
|
|
m_transportCheckTimer(1000), lootPickPocketRestoreTime(0), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE), m_defaultMovementType(IDLE_MOTION_TYPE),
|
|
m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_alreadyCallForHelp(false), m_AlreadyCallAssistance(false),
|
|
m_AlreadySearchedAssistance(false), m_regenHealth(true), m_regenPower(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_moveInLineOfSightDisabled(false), m_moveInLineOfSightStrictlyDisabled(false),
|
|
m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_detectionDistance(20.0f),_sparringPct(0.0f), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_lastLeashExtensionTime(nullptr), m_cannotReachTimer(0),
|
|
_isMissingSwimmingFlagOutOfCombat(false), m_assistanceTimer(0), _playerDamageReq(0), _damagedByPlayer(false), _isCombatMovementAllowed(true)
|
|
{
|
|
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
|
m_valuesCount = UNIT_END;
|
|
|
|
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
|
m_spells[i] = 0;
|
|
|
|
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
|
|
m_ProhibitSchoolTime[i] = 0;
|
|
|
|
m_CreatureSpellCooldowns.clear();
|
|
|
|
DisableReputationReward = false;
|
|
DisableLootReward = false;
|
|
|
|
m_SightDistance = sWorld->getFloatConfig(CONFIG_SIGHT_MONSTER);
|
|
m_CombatDistance = 0.0f;
|
|
|
|
ResetLootMode(); // restore default loot mode
|
|
TriggerJustRespawned = false;
|
|
_focusSpell = nullptr;
|
|
|
|
m_respawnedTime = time_t(0);
|
|
}
|
|
|
|
Creature::~Creature()
|
|
{
|
|
m_vendorItemCounts.clear();
|
|
|
|
delete i_AI;
|
|
i_AI = nullptr;
|
|
}
|
|
|
|
void Creature::AddToWorld()
|
|
{
|
|
///- Register the creature for guid lookup
|
|
if (!IsInWorld())
|
|
{
|
|
// pussywizard: motion master needs to be initialized before OnCreatureCreate, which may set death state to JUST_DIED, to prevent crash
|
|
// it's also initialized in AIM_Initialize(), few lines below, but it's not a problem
|
|
Motion_Initialize();
|
|
|
|
GetMap()->GetObjectsStore().Insert<Creature>(GetGUID(), this);
|
|
if (m_spawnId)
|
|
{
|
|
GetMap()->GetCreatureBySpawnIdStore().insert(std::make_pair(m_spawnId, this));
|
|
}
|
|
Unit::AddToWorld();
|
|
|
|
SearchFormation();
|
|
|
|
AIM_Initialize();
|
|
|
|
if (IsVehicle())
|
|
{
|
|
GetVehicleKit()->Install();
|
|
}
|
|
|
|
if (GetZoneScript())
|
|
{
|
|
GetZoneScript()->OnCreatureCreate(this);
|
|
}
|
|
|
|
loot.sourceWorldObjectGUID = GetGUID();
|
|
|
|
sScriptMgr->OnCreatureAddWorld(this);
|
|
}
|
|
}
|
|
|
|
void Creature::RemoveFromWorld()
|
|
{
|
|
if (IsInWorld())
|
|
{
|
|
sScriptMgr->OnCreatureRemoveWorld(this);
|
|
|
|
if (GetZoneScript())
|
|
GetZoneScript()->OnCreatureRemove(this);
|
|
|
|
if (m_formation)
|
|
sFormationMgr->RemoveCreatureFromGroup(m_formation, this);
|
|
|
|
if (Transport* transport = GetTransport())
|
|
transport->RemovePassenger(this, true);
|
|
|
|
Unit::RemoveFromWorld();
|
|
|
|
if (m_spawnId)
|
|
Acore::Containers::MultimapErasePair(GetMap()->GetCreatureBySpawnIdStore(), m_spawnId, this);
|
|
|
|
GetMap()->GetObjectsStore().Remove<Creature>(GetGUID());
|
|
}
|
|
}
|
|
|
|
void Creature::DisappearAndDie()
|
|
{
|
|
DestroyForVisiblePlayers();
|
|
//SetVisibility(VISIBILITY_OFF);
|
|
//ObjectAccessor::UpdateObjectVisibility(this);
|
|
if (IsAlive())
|
|
setDeathState(DeathState::JustDied, true);
|
|
RemoveCorpse(false, true);
|
|
}
|
|
|
|
void Creature::SearchFormation()
|
|
{
|
|
if (IsSummon())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ObjectGuid::LowType spawnId = GetSpawnId();
|
|
if (!spawnId)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CreatureGroupInfoType::const_iterator frmdata = sFormationMgr->CreatureGroupMap.find(spawnId);
|
|
if (frmdata != sFormationMgr->CreatureGroupMap.end())
|
|
{
|
|
sFormationMgr->AddCreatureToGroup(frmdata->second.leaderGUID, this);
|
|
}
|
|
}
|
|
|
|
void Creature::RemoveCorpse(bool setSpawnTime, bool skipVisibility)
|
|
{
|
|
if (getDeathState() != DeathState::Corpse)
|
|
return;
|
|
|
|
m_corpseRemoveTime = GameTime::GetGameTime().count();
|
|
setDeathState(DeathState::Dead);
|
|
RemoveAllAuras();
|
|
if (!skipVisibility) // pussywizard
|
|
DestroyForVisiblePlayers(); // pussywizard: previous UpdateObjectVisibility()
|
|
loot.clear();
|
|
uint32 respawnDelay = m_respawnDelay;
|
|
if (IsAIEnabled)
|
|
AI()->CorpseRemoved(respawnDelay);
|
|
|
|
// Should get removed later, just keep "compatibility" with scripts
|
|
if (setSpawnTime)
|
|
{
|
|
m_respawnTime = GameTime::GetGameTime().count() + respawnDelay;
|
|
//SaveRespawnTime();
|
|
}
|
|
|
|
float x, y, z, o;
|
|
GetRespawnPosition(x, y, z, &o);
|
|
SetHomePosition(x, y, z, o);
|
|
SetPosition(x, y, z, o);
|
|
|
|
// xinef: relocate notifier
|
|
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
|
|
|
|
// pussywizard: if corpse was removed during falling then the falling will continue after respawn, so stop falling is such case
|
|
if (IsFalling())
|
|
StopMoving();
|
|
}
|
|
|
|
/**
|
|
* change the entry of creature until respawn
|
|
*/
|
|
bool Creature::InitEntry(uint32 Entry, const CreatureData* data)
|
|
{
|
|
CreatureTemplate const* normalInfo = sObjectMgr->GetCreatureTemplate(Entry);
|
|
if (!normalInfo)
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature::InitEntry creature entry {} does not exist.", Entry);
|
|
return false;
|
|
}
|
|
|
|
// get difficulty 1 mode entry
|
|
// Xinef: Skip for pets!
|
|
CreatureTemplate const* cinfo = normalInfo;
|
|
for (uint8 diff = uint8(GetMap()->GetSpawnMode()); diff > 0 && !IsPet();)
|
|
{
|
|
// we already have valid Map pointer for current creature!
|
|
if (normalInfo->DifficultyEntry[diff - 1])
|
|
{
|
|
cinfo = sObjectMgr->GetCreatureTemplate(normalInfo->DifficultyEntry[diff - 1]);
|
|
if (cinfo)
|
|
break; // template found
|
|
|
|
// check and reported at startup, so just ignore (restore normalInfo)
|
|
cinfo = normalInfo;
|
|
}
|
|
|
|
// for instances heroic to normal, other cases attempt to retrieve previous difficulty
|
|
if (diff >= RAID_DIFFICULTY_10MAN_HEROIC && GetMap()->IsRaid())
|
|
diff -= 2; // to normal raid difficulty cases
|
|
else
|
|
--diff;
|
|
}
|
|
|
|
SetEntry(Entry); // normal entry always
|
|
m_creatureInfo = cinfo; // map mode related always
|
|
|
|
// equal to player Race field, but creature does not have race
|
|
SetByteValue(UNIT_FIELD_BYTES_0, 0, 0);
|
|
|
|
// known valid are: CLASS_WARRIOR, CLASS_PALADIN, CLASS_ROGUE, CLASS_MAGE
|
|
SetByteValue(UNIT_FIELD_BYTES_0, 1, uint8(cinfo->unit_class));
|
|
|
|
// Cancel load if no model defined
|
|
if (!(cinfo->GetFirstValidModel()))
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template_model`, can't load. ", Entry);
|
|
return false;
|
|
}
|
|
|
|
CreatureModel model = *ObjectMgr::ChooseDisplayId(cinfo, data);
|
|
CreatureModelInfo const* mInfo = sObjectMgr->GetCreatureModelRandomGender(&model, cinfo);
|
|
if (!mInfo) // Cancel load if no model defined
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model {} defined in table `creature_template_model`, can't load. ", Entry, model.CreatureDisplayID);
|
|
return false;
|
|
}
|
|
|
|
SetDisplayId(model.CreatureDisplayID, model.DisplayScale);
|
|
SetNativeDisplayId(model.CreatureDisplayID);
|
|
|
|
// Load creature equipment
|
|
if (!data)
|
|
{
|
|
LoadEquipment(); // use default from the template
|
|
}
|
|
else if (data->equipmentId == 0)
|
|
{
|
|
LoadEquipment(0); // 0 means no equipment for creature table
|
|
}
|
|
else
|
|
{
|
|
m_originalEquipmentId = data->equipmentId;
|
|
LoadEquipment(data->equipmentId);
|
|
}
|
|
|
|
SetName(normalInfo->Name); // at normal entry always
|
|
|
|
SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
|
|
|
|
SetSpeed(MOVE_WALK, cinfo->speed_walk);
|
|
SetSpeed(MOVE_RUN, cinfo->speed_run);
|
|
SetSpeed(MOVE_SWIM, cinfo->speed_swim);
|
|
SetSpeed(MOVE_FLIGHT, cinfo->speed_flight);
|
|
|
|
// Will set UNIT_FIELD_BOUNDINGRADIUS and UNIT_FIELD_COMBATREACH
|
|
SetObjectScale(GetNativeObjectScale());
|
|
|
|
SetFloatValue(UNIT_FIELD_HOVERHEIGHT, cinfo->HoverHeight);
|
|
|
|
if (cinfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK))
|
|
SetDualWieldMode(DualWieldMode::ENABLED);
|
|
|
|
// checked at loading
|
|
m_defaultMovementType = MovementGeneratorType(cinfo->MovementType);
|
|
if (!m_wanderDistance && m_defaultMovementType == RANDOM_MOTION_TYPE)
|
|
m_defaultMovementType = IDLE_MOTION_TYPE;
|
|
|
|
for (uint8 i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
|
m_spells[i] = GetCreatureTemplate()->spells[i];
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changelevel, bool updateAI)
|
|
{
|
|
if (!InitEntry(Entry, data))
|
|
return false;
|
|
|
|
CreatureTemplate const* cInfo = GetCreatureTemplate();
|
|
|
|
m_regenHealth = cInfo->RegenHealth;
|
|
|
|
// creatures always have melee weapon ready if any unless specified otherwise
|
|
if (!GetCreatureAddon())
|
|
SetSheath(SHEATH_STATE_MELEE);
|
|
|
|
SetFaction(cInfo->faction);
|
|
|
|
uint32 npcflag, unit_flags, dynamicflags;
|
|
ObjectMgr::ChooseCreatureFlags(cInfo, npcflag, unit_flags, dynamicflags, data);
|
|
|
|
if (cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_WORLDEVENT))
|
|
ReplaceAllNpcFlags(NPCFlags(npcflag | sGameEventMgr->GetNPCFlag(this)));
|
|
else
|
|
ReplaceAllNpcFlags(NPCFlags(npcflag));
|
|
|
|
// Xinef: NPC is in combat, keep this flag!
|
|
unit_flags &= ~UNIT_FLAG_IN_COMBAT;
|
|
if (IsInCombat())
|
|
unit_flags |= UNIT_FLAG_IN_COMBAT;
|
|
|
|
ReplaceAllUnitFlags(UnitFlags(unit_flags));
|
|
ReplaceAllUnitFlags2(UnitFlags2(cInfo->unit_flags2));
|
|
|
|
ReplaceAllDynamicFlags(dynamicflags);
|
|
|
|
if (cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_USE_OFFHAND_ATTACK))
|
|
SetDualWieldMode(DualWieldMode::ENABLED);
|
|
|
|
SetAttackTime(BASE_ATTACK, cInfo->BaseAttackTime);
|
|
SetAttackTime(OFF_ATTACK, cInfo->BaseAttackTime);
|
|
SetAttackTime(RANGED_ATTACK, cInfo->RangeAttackTime);
|
|
|
|
uint32 previousHealth = GetHealth();
|
|
uint32 previousMaxHealth = GetMaxHealth();
|
|
uint32 previousPlayerDamageReq = _playerDamageReq;
|
|
|
|
SelectLevel(changelevel);
|
|
if (previousHealth > 0)
|
|
{
|
|
SetHealth(previousHealth);
|
|
|
|
if (previousMaxHealth && previousMaxHealth > GetMaxHealth())
|
|
{
|
|
_playerDamageReq = (uint32)(previousPlayerDamageReq * GetMaxHealth() / previousMaxHealth);
|
|
}
|
|
else
|
|
{
|
|
_playerDamageReq = previousPlayerDamageReq;
|
|
}
|
|
}
|
|
|
|
SetMeleeDamageSchool(SpellSchools(cInfo->dmgschool));
|
|
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(GetLevel(), cInfo->unit_class);
|
|
float armor = stats->GenerateArmor(cInfo);
|
|
SetStatFlatModifier(UNIT_MOD_ARMOR, BASE_VALUE, armor);
|
|
SetStatFlatModifier(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_HOLY]));
|
|
SetStatFlatModifier(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FIRE]));
|
|
SetStatFlatModifier(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_NATURE]));
|
|
SetStatFlatModifier(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FROST]));
|
|
SetStatFlatModifier(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_SHADOW]));
|
|
SetStatFlatModifier(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_ARCANE]));
|
|
|
|
SetCanModifyStats(true);
|
|
UpdateAllStats();
|
|
|
|
LoadSparringPct();
|
|
|
|
// checked and error show at loading templates
|
|
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction))
|
|
{
|
|
if (factionTemplate->factionFlags & FACTION_TEMPLATE_FLAG_ASSIST_PLAYERS)
|
|
SetPvP(true);
|
|
else
|
|
SetPvP(false);
|
|
}
|
|
|
|
// updates spell bars for vehicles and set player's faction - should be called here, to overwrite faction that is set from the new template
|
|
if (IsVehicle())
|
|
{
|
|
if (Player* owner = Creature::GetCharmerOrOwnerPlayerOrPlayerItself()) // this check comes in case we don't have a player
|
|
{
|
|
SetFaction(owner->GetFaction()); // vehicles should have same as owner faction
|
|
owner->VehicleSpellInitialize();
|
|
}
|
|
}
|
|
|
|
// trigger creature is always not selectable and can not be attacked
|
|
if (IsTrigger())
|
|
SetUnitFlag(UNIT_FLAG_NOT_SELECTABLE);
|
|
|
|
InitializeReactState();
|
|
|
|
if (!IsPet() && cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_TAUNT))
|
|
{
|
|
ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true);
|
|
ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true);
|
|
}
|
|
|
|
SetDetectionDistance(cInfo->detection_range);
|
|
|
|
// Update movement
|
|
if (IsRooted())
|
|
SetControlled(true, UNIT_STATE_ROOT);
|
|
UpdateMovementFlags();
|
|
|
|
LoadSpellTemplateImmunity();
|
|
|
|
if (updateAI)
|
|
{
|
|
AIM_Initialize();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::Update(uint32 diff)
|
|
{
|
|
if (IsAIEnabled && TriggerJustRespawned)
|
|
{
|
|
TriggerJustRespawned = false;
|
|
AI()->JustRespawned();
|
|
if (m_vehicleKit)
|
|
m_vehicleKit->Reset();
|
|
}
|
|
|
|
switch (m_deathState)
|
|
{
|
|
case DeathState::JustRespawned:
|
|
// Must not be called, see Creature::setDeathState JUST_RESPAWNED -> ALIVE promoting.
|
|
LOG_ERROR("entities.unit", "Creature ({}) in wrong state: DeathState::JustRespawned (4)", GetGUID().ToString());
|
|
break;
|
|
case DeathState::JustDied:
|
|
// Must not be called, see Creature::setDeathState JUST_DIED -> CORPSE promoting.
|
|
LOG_ERROR("entities.unit", "Creature ({}) in wrong state: DeathState::JustDead (1)", GetGUID().ToString());
|
|
break;
|
|
case DeathState::Dead:
|
|
{
|
|
time_t now = GameTime::GetGameTime().count();
|
|
if (m_respawnTime <= now)
|
|
{
|
|
Respawn();
|
|
}
|
|
break;
|
|
}
|
|
case DeathState::Corpse:
|
|
{
|
|
Unit::Update(diff);
|
|
// deathstate changed on spells update, prevent problems
|
|
if (m_deathState != DeathState::Corpse)
|
|
break;
|
|
|
|
if (m_groupLootTimer && lootingGroupLowGUID)
|
|
{
|
|
if (m_groupLootTimer <= diff)
|
|
{
|
|
Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID);
|
|
if (group)
|
|
group->EndRoll(&loot, GetMap());
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
}
|
|
else
|
|
{
|
|
m_groupLootTimer -= diff;
|
|
}
|
|
}
|
|
else if (m_corpseRemoveTime <= GameTime::GetGameTime().count())
|
|
{
|
|
RemoveCorpse(false);
|
|
LOG_DEBUG("entities.unit", "Removing corpse... {} ", GetUInt32Value(OBJECT_FIELD_ENTRY));
|
|
}
|
|
break;
|
|
}
|
|
case DeathState::Alive:
|
|
{
|
|
Unit::Update(diff);
|
|
|
|
// creature can be dead after Unit::Update call
|
|
// CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly)
|
|
if (!IsAlive())
|
|
break;
|
|
|
|
// if creature is charmed, switch to charmed AI
|
|
if (NeedChangeAI)
|
|
{
|
|
UpdateCharmAI();
|
|
NeedChangeAI = false;
|
|
IsAIEnabled = true;
|
|
|
|
// xinef: update combat state, if npc is not in combat - return to spawn correctly by calling EnterEvadeMode
|
|
SelectVictim();
|
|
}
|
|
|
|
// if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now
|
|
if (m_combatPulseDelay > 0 && IsInCombat() && GetMap()->IsDungeon())
|
|
{
|
|
if (diff > m_combatPulseTime)
|
|
m_combatPulseTime = 0;
|
|
else
|
|
m_combatPulseTime -= diff;
|
|
|
|
if (m_combatPulseTime == 0)
|
|
{
|
|
if (AI())
|
|
AI()->DoZoneInCombat();
|
|
else
|
|
SetInCombatWithZone();
|
|
m_combatPulseTime = m_combatPulseDelay * IN_MILLISECONDS;
|
|
}
|
|
}
|
|
|
|
// periodic check to see if the creature has passed an evade boundary
|
|
if (IsAIEnabled && !IsInEvadeMode() && IsEngaged())
|
|
{
|
|
if (diff >= m_boundaryCheckTime)
|
|
{
|
|
AI()->CheckInRoom();
|
|
m_boundaryCheckTime = 2500;
|
|
}
|
|
else
|
|
m_boundaryCheckTime -= diff;
|
|
}
|
|
|
|
Unit* owner = GetCharmerOrOwner();
|
|
if (IsCharmed() && !IsWithinDistInMap(owner, GetMap()->GetVisibilityRange(), true, false, false))
|
|
{
|
|
RemoveCharmAuras();
|
|
}
|
|
|
|
if (Unit* victim = GetVictim())
|
|
{
|
|
// If we are closer than 50% of the combat reach we are going to reposition the victim
|
|
if (diff >= m_moveBackwardsMovementTime)
|
|
{
|
|
float MaxRange = GetCollisionRadius() + GetVictim()->GetCollisionRadius();
|
|
|
|
if (IsInDist(victim, MaxRange))
|
|
AI()->MoveBackwardsChecks();
|
|
|
|
m_moveBackwardsMovementTime = urand(MOVE_BACKWARDS_CHECK_INTERVAL, MOVE_BACKWARDS_CHECK_INTERVAL * 3);
|
|
}
|
|
else
|
|
m_moveBackwardsMovementTime -= diff;
|
|
|
|
// Circling the target
|
|
if (diff >= m_moveCircleMovementTime)
|
|
{
|
|
AI()->MoveCircleChecks();
|
|
m_moveCircleMovementTime = urand(MOVE_CIRCLE_CHECK_INTERVAL, MOVE_CIRCLE_CHECK_INTERVAL * 2);
|
|
}
|
|
else
|
|
m_moveCircleMovementTime -= diff;
|
|
|
|
// Periodically check if able to move, if not, extend leash timer
|
|
if (diff >= m_extendLeashTime)
|
|
{
|
|
if (HasUnitState(UNIT_STATE_LOST_CONTROL))
|
|
UpdateLeashExtensionTime();
|
|
m_extendLeashTime = EXTEND_LEASH_CHECK_INTERVAL;
|
|
}
|
|
else
|
|
m_extendLeashTime -= diff;
|
|
}
|
|
|
|
// Call for assistance if not disabled
|
|
if (m_assistanceTimer)
|
|
{
|
|
if (m_assistanceTimer <= diff)
|
|
{
|
|
if (CanPeriodicallyCallForAssistance())
|
|
{
|
|
SetNoCallAssistance(false);
|
|
CallAssistance();
|
|
}
|
|
m_assistanceTimer = sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD);
|
|
}
|
|
else
|
|
{
|
|
m_assistanceTimer -= diff;
|
|
}
|
|
}
|
|
|
|
if (!IsInEvadeMode() && IsAIEnabled)
|
|
{
|
|
// do not allow the AI to be changed during update
|
|
m_AI_locked = true;
|
|
i_AI->UpdateAI(diff);
|
|
m_AI_locked = false;
|
|
}
|
|
|
|
// creature can be dead after UpdateAI call
|
|
// CORPSE/DEAD state will processed at next tick (in other case death timer will be updated unexpectedly)
|
|
if (!IsAlive())
|
|
break;
|
|
|
|
m_regenTimer -= diff;
|
|
if (m_regenTimer <= 0)
|
|
{
|
|
if (!IsInEvadeMode())
|
|
{
|
|
// regenerate health if not in combat or if polymorphed)
|
|
if (!IsInCombat() || IsPolymorphed())
|
|
RegenerateHealth();
|
|
else if (IsNotReachableAndNeedRegen())
|
|
{
|
|
// regenerate health if cannot reach the target and the setting is set to do so.
|
|
// this allows to disable the health regen of raid bosses if pathfinding has issues for whatever reason
|
|
if (sWorld->getBoolConfig(CONFIG_REGEN_HP_CANNOT_REACH_TARGET_IN_RAID) || !GetMap()->IsRaid())
|
|
{
|
|
RegenerateHealth();
|
|
LOG_DEBUG("entities.unit", "RegenerateHealth() enabled because Creature cannot reach the target. Detail: {}", GetDebugInfo());
|
|
}
|
|
else
|
|
LOG_DEBUG("entities.unit", "RegenerateHealth() disabled even if the Creature cannot reach the target. Detail: {}", GetDebugInfo());
|
|
}
|
|
}
|
|
|
|
if (getPowerType() == POWER_ENERGY)
|
|
Regenerate(POWER_ENERGY);
|
|
else
|
|
Regenerate(POWER_MANA);
|
|
|
|
m_regenTimer += CREATURE_REGEN_INTERVAL;
|
|
}
|
|
|
|
if (CanNotReachTarget() && !IsInEvadeMode())
|
|
{
|
|
m_cannotReachTimer += diff;
|
|
if (m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_EVADE_IF_NOT_REACHABLE) * IN_MILLISECONDS))
|
|
{
|
|
Player* cannotReachPlayer = ObjectAccessor::GetPlayer(*this, m_cannotReachTarget);
|
|
if (cannotReachPlayer && IsEngagedBy(cannotReachPlayer) && IsAIEnabled && AI()->OnTeleportUnreacheablePlayer(cannotReachPlayer))
|
|
{
|
|
SetCannotReachTarget();
|
|
}
|
|
else if (!GetMap()->IsRaid())
|
|
{
|
|
auto EnterEvade = [&]()
|
|
{
|
|
if (CreatureAI* ai = AI())
|
|
{
|
|
ai->EnterEvadeMode(CreatureAI::EvadeReason::EVADE_REASON_NO_PATH);
|
|
}
|
|
};
|
|
|
|
if (GetThreatMgr().GetThreatListSize() <= 1)
|
|
{
|
|
EnterEvade();
|
|
}
|
|
else
|
|
{
|
|
if (HostileReference* ref = GetThreatMgr().GetOnlineContainer().getReferenceByTarget(m_cannotReachTarget))
|
|
{
|
|
ref->removeReference();
|
|
SetCannotReachTarget();
|
|
}
|
|
else
|
|
{
|
|
EnterEvade();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (IsInWorld() && !IsDuringRemoveFromWorld())
|
|
{
|
|
// pussywizard:
|
|
if (GetOwnerGUID().IsPlayer())
|
|
{
|
|
if (m_transportCheckTimer <= diff)
|
|
{
|
|
m_transportCheckTimer = 1000;
|
|
Transport* newTransport = GetMap()->GetTransportForPos(GetPhaseMask(), GetPositionX(), GetPositionY(), GetPositionZ(), this);
|
|
if (newTransport != GetTransport())
|
|
{
|
|
if (GetTransport())
|
|
GetTransport()->RemovePassenger(this, true);
|
|
if (newTransport)
|
|
newTransport->AddPassenger(this, true);
|
|
this->StopMovingOnCurrentPos();
|
|
//SendMovementFlagUpdate();
|
|
}
|
|
}
|
|
else
|
|
m_transportCheckTimer -= diff;
|
|
}
|
|
|
|
sScriptMgr->OnCreatureUpdate(this, diff);
|
|
}
|
|
}
|
|
|
|
bool Creature::IsFreeToMove()
|
|
{
|
|
uint32 moveFlags = m_movementInfo.GetMovementFlags();
|
|
// Do not reposition ourself when we are not allowed to move
|
|
if ((IsMovementPreventedByCasting() || isMoving() || !CanFreeMove() || !IsCombatMovementAllowed()) &&
|
|
(GetMotionMaster()->GetCurrentMovementGeneratorType() != CHASE_MOTION_TYPE ||
|
|
moveFlags & MOVEMENTFLAG_SPLINE_ENABLED))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::Regenerate(Powers power)
|
|
{
|
|
uint32 curValue = GetPower(power);
|
|
uint32 maxValue = GetMaxPower(power);
|
|
|
|
// Xinef: implement power regeneration flag
|
|
if (!HasUnitFlag2(UNIT_FLAG2_REGENERATE_POWER) && !GetOwnerGUID().IsPlayer())
|
|
return;
|
|
|
|
if (!m_regenPower)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (curValue >= maxValue)
|
|
return;
|
|
|
|
float addvalue = 0.0f;
|
|
|
|
switch (power)
|
|
{
|
|
case POWER_FOCUS:
|
|
{
|
|
// For hunter pets.
|
|
addvalue = 24 * sWorld->getRate(RATE_POWER_FOCUS);
|
|
break;
|
|
}
|
|
case POWER_ENERGY:
|
|
{
|
|
// For deathknight's ghoul.
|
|
addvalue = 20;
|
|
break;
|
|
}
|
|
case POWER_MANA:
|
|
{
|
|
// Combat and any controlled creature
|
|
if (IsInCombat() || GetCharmerOrOwnerGUID())
|
|
{
|
|
if (GetEntry() == NPC_IMP || GetEntry() == NPC_WATER_ELEMENTAL_TEMP || GetEntry() == NPC_WATER_ELEMENTAL_PERM)
|
|
{
|
|
addvalue = uint32((GetStat(STAT_SPIRIT) / (IsUnderLastManaUseEffect() ? 8.0f : 5.0f) + 17.0f));
|
|
}
|
|
else if (!IsUnderLastManaUseEffect())
|
|
{
|
|
float ManaIncreaseRate = sWorld->getRate(RATE_POWER_MANA);
|
|
float Spirit = GetStat(STAT_SPIRIT);
|
|
|
|
addvalue = uint32((Spirit / 5.0f + 17.0f) * ManaIncreaseRate);
|
|
}
|
|
}
|
|
else
|
|
addvalue = maxValue / 3;
|
|
break;
|
|
}
|
|
default:
|
|
return;
|
|
}
|
|
|
|
// Apply modifiers (if any).
|
|
addvalue *= GetTotalAuraMultiplierByMiscValue(SPELL_AURA_MOD_POWER_REGEN_PERCENT, power);
|
|
|
|
addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, power) * (power == POWER_FOCUS ? PET_FOCUS_REGEN_INTERVAL.count() : CREATURE_REGEN_INTERVAL) / (5 * IN_MILLISECONDS);
|
|
|
|
ModifyPower(power, int32(addvalue));
|
|
}
|
|
|
|
void Creature::RegenerateHealth()
|
|
{
|
|
if (!isRegeneratingHealth())
|
|
return;
|
|
|
|
uint32 curValue = GetHealth();
|
|
uint32 maxValue = GetMaxHealth();
|
|
|
|
if (curValue >= maxValue)
|
|
return;
|
|
|
|
uint32 addvalue = 0;
|
|
|
|
// Not only pet, but any controlled creature
|
|
// Xinef: fix polymorph rapid regen
|
|
if (!GetCharmerOrOwnerGUID() || IsPolymorphed())
|
|
addvalue = maxValue / 3;
|
|
else //if (GetCharmerOrOwnerGUID())
|
|
{
|
|
float HealthIncreaseRate = sWorld->getRate(RATE_HEALTH);
|
|
float Spirit = GetStat(STAT_SPIRIT);
|
|
|
|
if (GetPower(POWER_MANA) > 0)
|
|
addvalue = uint32(Spirit * 0.25 * HealthIncreaseRate);
|
|
else
|
|
addvalue = uint32(Spirit * 0.80 * HealthIncreaseRate);
|
|
}
|
|
|
|
// Apply modifiers (if any).
|
|
addvalue *= GetTotalAuraMultiplier(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
|
|
|
|
addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * CREATURE_REGEN_INTERVAL / (5 * IN_MILLISECONDS);
|
|
|
|
ModifyHealth(addvalue);
|
|
}
|
|
|
|
void Creature::DoFleeToGetAssistance()
|
|
{
|
|
if (!GetVictim())
|
|
return;
|
|
|
|
if (HasPreventsFleeingAura())
|
|
return;
|
|
|
|
float radius = sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_FLEE_ASSISTANCE_RADIUS);
|
|
if (radius > 0)
|
|
{
|
|
Creature* creature = nullptr;
|
|
|
|
Acore::NearestAssistCreatureInCreatureRangeCheck u_check(this, GetVictim(), radius);
|
|
Acore::CreatureLastSearcher<Acore::NearestAssistCreatureInCreatureRangeCheck> searcher(this, creature, u_check);
|
|
|
|
Cell::VisitObjects(this, searcher, radius);
|
|
|
|
SetNoSearchAssistance(true);
|
|
UpdateSpeed(MOVE_RUN, false);
|
|
|
|
if (!creature)
|
|
//SetFeared(true, GetVictim()->GetGUID(), 0, sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_FLEE_DELAY));
|
|
//TODO: use 31365
|
|
SetControlled(true, UNIT_STATE_FLEEING, GetVictim());
|
|
else
|
|
GetMotionMaster()->MoveSeekAssistance(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ());
|
|
}
|
|
}
|
|
|
|
bool Creature::AIM_Initialize(CreatureAI* ai)
|
|
{
|
|
// make sure nothing can change the AI during AI update
|
|
if (m_AI_locked)
|
|
{
|
|
LOG_DEBUG("scripts.ai", "AIM_Initialize: failed to init, locked.");
|
|
return false;
|
|
}
|
|
|
|
UnitAI* oldAI = i_AI;
|
|
|
|
// Xinef: called in add to world
|
|
//Motion_Initialize();
|
|
|
|
i_AI = ai ? ai : FactorySelector::SelectAI(this);
|
|
delete oldAI;
|
|
IsAIEnabled = true;
|
|
i_AI->InitializeAI();
|
|
|
|
// Xinef: Initialize vehicle if it is not summoned!
|
|
if (GetVehicleKit() && m_spawnId)
|
|
GetVehicleKit()->Reset();
|
|
return true;
|
|
}
|
|
|
|
void Creature::Motion_Initialize()
|
|
{
|
|
if (!m_formation)
|
|
GetMotionMaster()->Initialize();
|
|
else if (m_formation->GetLeader() == this)
|
|
{
|
|
m_formation->FormationReset(false, true);
|
|
GetMotionMaster()->Initialize();
|
|
}
|
|
else if (m_formation->IsFormed())
|
|
GetMotionMaster()->MoveIdle(); //wait the order of leader
|
|
else
|
|
GetMotionMaster()->Initialize();
|
|
}
|
|
|
|
bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 Entry, uint32 vehId, float x, float y, float z, float ang, const CreatureData* data)
|
|
{
|
|
ASSERT(map);
|
|
SetMap(map);
|
|
SetPhaseMask(phaseMask, false);
|
|
|
|
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(Entry);
|
|
if (!cinfo)
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature::Create(): creature template (guidlow: {}, entry: {}) does not exist.", guidlow, Entry);
|
|
return false;
|
|
}
|
|
|
|
//! Relocate before CreateFromProto, to initialize coords and allow
|
|
//! returning correct zone id for selecting OutdoorPvP/Battlefield script
|
|
Relocate(x, y, z, ang);
|
|
|
|
if (!IsPositionValid())
|
|
{
|
|
LOG_ERROR("entities.unit", "Creature::Create(): given coordinates for creature (guidlow {}, entry {}) are not valid (X: {}, Y: {}, Z: {}, O: {})", guidlow, Entry, x, y, z, ang);
|
|
return false;
|
|
}
|
|
|
|
// area/zone id is needed immediately for ZoneScript::GetCreatureEntry hook before it is known which creature template to load (no model/scale available yet)
|
|
PositionFullTerrainStatus terrainData;
|
|
GetMap()->GetFullTerrainStatusForPosition(GetPhaseMask(), GetPositionX(), GetPositionY(), GetPositionZ(), DEFAULT_COLLISION_HEIGHT, terrainData);
|
|
ProcessPositionDataChanged(terrainData);
|
|
|
|
//oX = x; oY = y; dX = x; dY = y; m_moveTime = 0; m_startMove = 0;
|
|
if (!CreateFromProto(guidlow, Entry, vehId, data))
|
|
return false;
|
|
|
|
UpdateMovementFlags();
|
|
|
|
switch (GetCreatureTemplate()->rank)
|
|
{
|
|
case CREATURE_ELITE_RARE:
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_RARE);
|
|
break;
|
|
case CREATURE_ELITE_ELITE:
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_ELITE);
|
|
break;
|
|
case CREATURE_ELITE_RAREELITE:
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_RAREELITE);
|
|
break;
|
|
case CREATURE_ELITE_WORLDBOSS:
|
|
// Xinef: Reduce corpse delay for bossess outside of instance
|
|
if (!GetInstanceId())
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_ELITE) * 2;
|
|
else
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_WORLDBOSS);
|
|
break;
|
|
default:
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_NORMAL);
|
|
break;
|
|
}
|
|
|
|
CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f);
|
|
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, cinfo);
|
|
if (minfo && !IsTotem()) // Cancel load if no model defined or if totem
|
|
{
|
|
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
|
|
SetNativeDisplayId(display.CreatureDisplayID);
|
|
}
|
|
|
|
LoadCreaturesAddon();
|
|
LoadSparringPct();
|
|
|
|
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
|
|
m_positionZ += GetHoverHeight();
|
|
|
|
LastUsedScriptID = GetScriptId();
|
|
|
|
if (IsSpiritHealer() || IsSpiritGuide() || HasFlagsExtra(CREATURE_FLAG_EXTRA_GHOST_VISIBILITY))
|
|
{
|
|
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_GHOST);
|
|
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_GHOST);
|
|
}
|
|
else if (cinfo->type_flags & CREATURE_TYPE_FLAG_VISIBLE_TO_GHOSTS) // Xinef: Add ghost visibility for ghost units
|
|
{
|
|
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST);
|
|
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST);
|
|
}
|
|
if (Entry == VISUAL_WAYPOINT)
|
|
SetVisible(false);
|
|
|
|
if (HasFlagsExtra(CREATURE_FLAG_EXTRA_IGNORE_PATHFINDING))
|
|
AddUnitState(UNIT_STATE_IGNORE_PATHFINDING);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::isCanInteractWithBattleMaster(Player* player, bool msg) const
|
|
{
|
|
if (!IsBattleMaster())
|
|
return false;
|
|
|
|
BattlegroundTypeId bgTypeId = sBattlegroundMgr->GetBattleMasterBG(GetEntry());
|
|
if (!msg)
|
|
return player->GetBGAccessByLevel(bgTypeId);
|
|
|
|
if (!player->GetBGAccessByLevel(bgTypeId))
|
|
{
|
|
ClearGossipMenuFor(player);
|
|
switch (bgTypeId)
|
|
{
|
|
case BATTLEGROUND_AV:
|
|
SendGossipMenuFor(player, 7616, this);
|
|
break;
|
|
case BATTLEGROUND_WS:
|
|
SendGossipMenuFor(player, 7599, this);
|
|
break;
|
|
case BATTLEGROUND_AB:
|
|
SendGossipMenuFor(player, 7642, this);
|
|
break;
|
|
case BATTLEGROUND_EY:
|
|
case BATTLEGROUND_NA:
|
|
case BATTLEGROUND_BE:
|
|
case BATTLEGROUND_AA:
|
|
case BATTLEGROUND_RL:
|
|
case BATTLEGROUND_SA:
|
|
case BATTLEGROUND_DS:
|
|
case BATTLEGROUND_RV:
|
|
SendGossipMenuFor(player, 10024, this);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Creature::CanResetTalents(Player* player) const
|
|
{
|
|
Trainer::Trainer const* trainer = sObjectMgr->GetTrainer(GetEntry());
|
|
if (!trainer)
|
|
return false;
|
|
|
|
return player->GetLevel() >= 10 && trainer->IsTrainerValidForPlayer(player);
|
|
}
|
|
|
|
Player* Creature::GetLootRecipient() const
|
|
{
|
|
if (!m_lootRecipient)
|
|
return nullptr;
|
|
return ObjectAccessor::FindConnectedPlayer(m_lootRecipient);
|
|
}
|
|
|
|
Group* Creature::GetLootRecipientGroup() const
|
|
{
|
|
if (!m_lootRecipientGroup)
|
|
return nullptr;
|
|
return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup);
|
|
}
|
|
|
|
void Creature::SetLootRecipient(Unit* unit, bool withGroup)
|
|
{
|
|
// set the player whose group should receive the right
|
|
// to loot the creature after it dies
|
|
// should be set to nullptr after the loot disappears
|
|
|
|
if (!unit)
|
|
{
|
|
m_lootRecipient.Clear();
|
|
m_lootRecipientGroup = 0;
|
|
RemoveDynamicFlag(UNIT_DYNFLAG_LOOTABLE | UNIT_DYNFLAG_TAPPED);
|
|
ResetAllowedLooters();
|
|
return;
|
|
}
|
|
|
|
Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
if (!player) // normal creature, no player involved
|
|
return;
|
|
|
|
m_lootRecipient = player->GetGUID();
|
|
|
|
Map* map = GetMap();
|
|
if (map && map->IsDungeon() && (isWorldBoss() || IsDungeonBoss()))
|
|
{
|
|
AddAllowedLooter(m_lootRecipient);
|
|
}
|
|
|
|
if (withGroup)
|
|
{
|
|
if (Group* group = player->GetGroup())
|
|
{
|
|
m_lootRecipientGroup = group->GetGUID().GetCounter();
|
|
|
|
if (map && map->IsDungeon() && (isWorldBoss() || IsDungeonBoss()))
|
|
{
|
|
Map::PlayerList const& PlayerList = map->GetPlayers();
|
|
for (Map::PlayerList::const_iterator i = PlayerList.begin(); i != PlayerList.end(); ++i)
|
|
{
|
|
if (Player* groupMember = i->GetSource())
|
|
{
|
|
if (groupMember->IsGameMaster() || groupMember->IsSpectator())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (groupMember->GetGroup() == group)
|
|
{
|
|
AddAllowedLooter(groupMember->GetGUID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
m_lootRecipientGroup = 0;
|
|
|
|
SetDynamicFlag(UNIT_DYNFLAG_TAPPED);
|
|
}
|
|
|
|
// return true if this creature is tapped by the player or by a member of his group.
|
|
bool Creature::isTappedBy(Player const* player) const
|
|
{
|
|
if (player->GetGUID() == m_lootRecipient)
|
|
return true;
|
|
|
|
Group const* playerGroup = player->GetGroup();
|
|
if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient
|
|
return false; // if creature doesnt have group bound it means it was solo killed by someone else
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::SaveToDB()
|
|
{
|
|
// this should only be used when the creature has already been loaded
|
|
// preferably after adding to map, because mapid may not be valid otherwise
|
|
CreatureData const* data = sObjectMgr->GetCreatureData(m_spawnId);
|
|
if (!data)
|
|
{
|
|
LOG_ERROR("entities.unit", "Creature::SaveToDB failed, cannot get creature data!");
|
|
return;
|
|
}
|
|
|
|
uint32 mapId = GetTransport() && GetTransport()->ToMotionTransport() ? GetTransport()->GetGOInfo()->moTransport.mapID : GetMapId();
|
|
SaveToDB(mapId, data->spawnMask, GetPhaseMask());
|
|
}
|
|
|
|
void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask)
|
|
{
|
|
// update in loaded data
|
|
if (!m_spawnId)
|
|
m_spawnId = sObjectMgr->GenerateCreatureSpawnId();
|
|
|
|
CreatureData& data = sObjectMgr->NewOrExistCreatureData(m_spawnId);
|
|
data.spawnId = m_spawnId; // mod_playerbots
|
|
uint32 displayId = GetNativeDisplayId();
|
|
uint32 npcflag = GetNpcFlags();
|
|
uint32 unit_flags = GetUnitFlags();
|
|
uint32 dynamicflags = GetDynamicFlags();
|
|
|
|
// check if it's a custom model and if not, use 0 for displayId
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
if (cinfo)
|
|
{
|
|
for (CreatureModel model : cinfo->Models)
|
|
if (displayId && displayId == model.CreatureDisplayID)
|
|
displayId = 0;
|
|
|
|
if (npcflag == cinfo->npcflag)
|
|
npcflag = 0;
|
|
|
|
if (unit_flags == cinfo->unit_flags)
|
|
unit_flags = 0;
|
|
|
|
if (dynamicflags == cinfo->dynamicflags)
|
|
dynamicflags = 0;
|
|
}
|
|
|
|
data.id1 = GetEntry();
|
|
data.mapid = mapid;
|
|
data.phaseMask = phaseMask;
|
|
data.displayid = displayId;
|
|
data.equipmentId = GetCurrentEquipmentId();
|
|
if (!GetTransport())
|
|
{
|
|
data.posX = GetPositionX();
|
|
data.posY = GetPositionY();
|
|
data.posZ = GetPositionZ();
|
|
data.orientation = GetOrientation();
|
|
}
|
|
else
|
|
{
|
|
data.posX = GetTransOffsetX();
|
|
data.posY = GetTransOffsetY();
|
|
data.posZ = GetTransOffsetZ();
|
|
data.orientation = GetTransOffsetO();
|
|
}
|
|
|
|
data.spawntimesecs = m_respawnDelay;
|
|
// prevent add data integrity problems
|
|
data.wander_distance = GetDefaultMovementType() == IDLE_MOTION_TYPE ? 0.0f : m_wanderDistance;
|
|
data.currentwaypoint = 0;
|
|
data.curhealth = GetHealth();
|
|
data.curmana = GetPower(POWER_MANA);
|
|
// prevent add data integrity problems
|
|
data.movementType = !m_wanderDistance && GetDefaultMovementType() == RANDOM_MOTION_TYPE
|
|
? IDLE_MOTION_TYPE : GetDefaultMovementType();
|
|
data.spawnMask = spawnMask;
|
|
data.npcflag = npcflag;
|
|
data.unit_flags = unit_flags;
|
|
data.dynamicflags = dynamicflags;
|
|
|
|
// update in DB
|
|
WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
|
|
|
|
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE);
|
|
stmt->SetData(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
uint8 index = 0;
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_CREATURE);
|
|
stmt->SetData(index++, m_spawnId);
|
|
stmt->SetData(index++, GetEntry());
|
|
stmt->SetData(index++, 0);
|
|
stmt->SetData(index++, 0);
|
|
stmt->SetData(index++, uint16(mapid));
|
|
stmt->SetData(index++, spawnMask);
|
|
stmt->SetData(index++, GetPhaseMask());
|
|
stmt->SetData(index++, int32(GetCurrentEquipmentId()));
|
|
stmt->SetData(index++, GetPositionX());
|
|
stmt->SetData(index++, GetPositionY());
|
|
stmt->SetData(index++, GetPositionZ());
|
|
stmt->SetData(index++, GetOrientation());
|
|
stmt->SetData(index++, m_respawnDelay);
|
|
stmt->SetData(index++, m_wanderDistance);
|
|
stmt->SetData(index++, 0);
|
|
stmt->SetData(index++, GetHealth());
|
|
stmt->SetData(index++, GetPower(POWER_MANA));
|
|
stmt->SetData(index++, uint8(GetDefaultMovementType()));
|
|
stmt->SetData(index++, npcflag);
|
|
stmt->SetData(index++, unit_flags);
|
|
stmt->SetData(index++, dynamicflags);
|
|
trans->Append(stmt);
|
|
|
|
WorldDatabase.CommitTransaction(trans);
|
|
sScriptMgr->OnCreatureSaveToDB(this);
|
|
}
|
|
|
|
void Creature::SelectLevel(bool changelevel)
|
|
{
|
|
CreatureTemplate const* cInfo = GetCreatureTemplate();
|
|
|
|
uint32 rank = IsPet() ? 0 : cInfo->rank;
|
|
|
|
// level
|
|
uint8 minlevel = std::min(cInfo->maxlevel, cInfo->minlevel);
|
|
uint8 maxlevel = std::max(cInfo->maxlevel, cInfo->minlevel);
|
|
uint8 level = minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel);
|
|
|
|
sScriptMgr->OnBeforeCreatureSelectLevel(cInfo, this, level);
|
|
|
|
if (changelevel)
|
|
SetLevel(level);
|
|
|
|
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(level, cInfo->unit_class);
|
|
|
|
// health
|
|
float healthmod = _GetHealthMod(rank);
|
|
|
|
uint32 basehp = std::max<uint32>(1, stats->GenerateHealth(cInfo));
|
|
uint32 health = uint32(basehp * healthmod);
|
|
|
|
SetCreateHealth(health);
|
|
SetMaxHealth(health);
|
|
SetHealth(health);
|
|
ResetPlayerDamageReq();
|
|
|
|
// mana
|
|
uint32 mana = stats->GenerateMana(cInfo);
|
|
|
|
SetCreateMana(mana);
|
|
SetMaxPower(POWER_MANA, mana); //MAX Mana
|
|
SetPower(POWER_MANA, mana);
|
|
|
|
/// @todo: set UNIT_FIELD_POWER*, for some creature class case (energy, etc)
|
|
|
|
SetStatFlatModifier(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
|
|
SetStatFlatModifier(UNIT_MOD_MANA, BASE_VALUE, (float)mana);
|
|
|
|
// damage
|
|
|
|
float basedamage = stats->GenerateBaseDamage(cInfo);
|
|
|
|
float weaponBaseMinDamage = basedamage;
|
|
float weaponBaseMaxDamage = basedamage * 1.5;
|
|
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, weaponBaseMinDamage);
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, weaponBaseMaxDamage);
|
|
|
|
SetBaseWeaponDamage(OFF_ATTACK, MINDAMAGE, weaponBaseMinDamage);
|
|
SetBaseWeaponDamage(OFF_ATTACK, MAXDAMAGE, weaponBaseMaxDamage);
|
|
|
|
SetBaseWeaponDamage(RANGED_ATTACK, MINDAMAGE, weaponBaseMinDamage);
|
|
SetBaseWeaponDamage(RANGED_ATTACK, MAXDAMAGE, weaponBaseMaxDamage);
|
|
|
|
SetStatFlatModifier(UNIT_MOD_ATTACK_POWER, BASE_VALUE, stats->AttackPower);
|
|
SetStatFlatModifier(UNIT_MOD_ATTACK_POWER_RANGED, BASE_VALUE, stats->RangedAttackPower);
|
|
|
|
sScriptMgr->OnCreatureSelectLevel(cInfo, this);
|
|
}
|
|
|
|
float Creature::_GetHealthMod(int32 Rank)
|
|
{
|
|
switch (Rank) // define rates for each elite rank
|
|
{
|
|
case CREATURE_ELITE_NORMAL:
|
|
return sWorld->getRate(RATE_CREATURE_NORMAL_HP);
|
|
case CREATURE_ELITE_ELITE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_HP);
|
|
case CREATURE_ELITE_RAREELITE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_HP);
|
|
case CREATURE_ELITE_WORLDBOSS:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_HP);
|
|
case CREATURE_ELITE_RARE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_RARE_HP);
|
|
default:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_HP);
|
|
}
|
|
}
|
|
|
|
float Creature::_GetDamageMod(int32 Rank)
|
|
{
|
|
switch (Rank) // define rates for each elite rank
|
|
{
|
|
case CREATURE_ELITE_NORMAL:
|
|
return sWorld->getRate(RATE_CREATURE_NORMAL_DAMAGE);
|
|
case CREATURE_ELITE_ELITE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE);
|
|
case CREATURE_ELITE_RAREELITE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_DAMAGE);
|
|
case CREATURE_ELITE_WORLDBOSS:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE);
|
|
case CREATURE_ELITE_RARE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_RARE_DAMAGE);
|
|
default:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_DAMAGE);
|
|
}
|
|
}
|
|
|
|
float Creature::GetSpellDamageMod(int32 Rank)
|
|
{
|
|
switch (Rank) // define rates for each elite rank
|
|
{
|
|
case CREATURE_ELITE_NORMAL:
|
|
return sWorld->getRate(RATE_CREATURE_NORMAL_SPELLDAMAGE);
|
|
case CREATURE_ELITE_ELITE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE);
|
|
case CREATURE_ELITE_RAREELITE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_RAREELITE_SPELLDAMAGE);
|
|
case CREATURE_ELITE_WORLDBOSS:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_WORLDBOSS_SPELLDAMAGE);
|
|
case CREATURE_ELITE_RARE:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_RARE_SPELLDAMAGE);
|
|
default:
|
|
return sWorld->getRate(RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE);
|
|
}
|
|
}
|
|
|
|
bool Creature::CreateFromProto(ObjectGuid::LowType guidlow, uint32 Entry, uint32 vehId, const CreatureData* data)
|
|
{
|
|
SetZoneScript();
|
|
if (GetZoneScript() && data)
|
|
{
|
|
uint32 FirstEntry = GetZoneScript()->GetCreatureEntry(guidlow, data);
|
|
if (!FirstEntry)
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate const* normalInfo = sObjectMgr->GetCreatureTemplate(Entry);
|
|
if (!normalInfo)
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature::CreateFromProto(): creature template (guidlow: {}, entry: {}) does not exist.", guidlow, Entry);
|
|
return false;
|
|
}
|
|
|
|
SetOriginalEntry(Entry);
|
|
|
|
Object::_Create(guidlow, Entry, (vehId || normalInfo->VehicleId) ? HighGuid::Vehicle : HighGuid::Unit);
|
|
|
|
// Xinef: select proper vehicle id
|
|
if (!vehId)
|
|
{
|
|
CreatureTemplate const* cinfo = normalInfo;
|
|
for (uint8 diff = uint8(GetMap()->GetSpawnMode()); diff > 0 && !IsPet();)
|
|
{
|
|
// we already have valid Map pointer for current creature!
|
|
if (cinfo->DifficultyEntry[diff - 1])
|
|
{
|
|
cinfo = sObjectMgr->GetCreatureTemplate(normalInfo->DifficultyEntry[diff - 1]);
|
|
if (cinfo && cinfo->VehicleId)
|
|
break; // template found
|
|
|
|
// check and reported at startup, so just ignore (restore normalInfo)
|
|
cinfo = normalInfo;
|
|
}
|
|
|
|
// for instances heroic to normal, other cases attempt to retrieve previous difficulty
|
|
if (diff >= RAID_DIFFICULTY_10MAN_HEROIC && GetMap()->IsRaid())
|
|
diff -= 2; // to normal raid difficulty cases
|
|
else
|
|
--diff;
|
|
}
|
|
|
|
if (cinfo->VehicleId)
|
|
CreateVehicleKit(cinfo->VehicleId, (cinfo->VehicleId != normalInfo->VehicleId ? cinfo->Entry : normalInfo->Entry));
|
|
}
|
|
else
|
|
CreateVehicleKit(vehId, Entry);
|
|
|
|
if (!UpdateEntry(Entry, data))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::isVendorWithIconSpeak() const
|
|
{
|
|
return m_creatureInfo->IconName == "Speak" && m_creatureData->npcflag & UNIT_NPC_FLAG_VENDOR;
|
|
}
|
|
|
|
bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate /*= false*/)
|
|
{
|
|
if (!allowDuplicate)
|
|
{
|
|
// If an alive instance of this spawnId is already found, skip creation
|
|
// If only dead instance(s) exist, despawn them and spawn a new (maybe also dead) version
|
|
const auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(spawnId);
|
|
std::vector <Creature*> despawnList;
|
|
|
|
if (creatureBounds.first != creatureBounds.second)
|
|
{
|
|
for (auto itr = creatureBounds.first; itr != creatureBounds.second; ++itr)
|
|
{
|
|
if (itr->second->IsAlive())
|
|
{
|
|
LOG_DEBUG("maps", "Would have spawned {} but {} already exists", spawnId, creatureBounds.first->second->GetGUID().ToString());
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
despawnList.push_back(itr->second);
|
|
LOG_DEBUG("maps", "Despawned dead instance of spawn {} ({})", spawnId, itr->second->GetGUID().ToString());
|
|
}
|
|
}
|
|
|
|
for (Creature* despawnCreature : despawnList)
|
|
{
|
|
despawnCreature->AddObjectToRemoveList();
|
|
}
|
|
}
|
|
}
|
|
|
|
CreatureData const* data = sObjectMgr->GetCreatureData(spawnId);
|
|
if (!data)
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature (SpawnId: {}) not found in table `creature`, can't load. ", spawnId);
|
|
return false;
|
|
}
|
|
|
|
// xinef: this has to be assigned before Create function, properly loads equipment id from DB
|
|
m_creatureData = data;
|
|
m_spawnId = spawnId;
|
|
|
|
// Add to world
|
|
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
|
|
|
|
if (!Create(map->GenerateLowGuid<HighGuid::Unit>(), map, data->phaseMask, entry, 0, data->posX, data->posY, data->posZ, data->orientation, data))
|
|
return false;
|
|
|
|
//We should set first home position, because then AI calls home movement
|
|
SetHomePosition(data->posX, data->posY, data->posZ, data->orientation);
|
|
|
|
m_wanderDistance = data->wander_distance;
|
|
|
|
m_respawnDelay = data->spawntimesecs;
|
|
m_deathState = DeathState::Alive;
|
|
|
|
m_respawnTime = GetMap()->GetCreatureRespawnTime(m_spawnId);
|
|
if (m_respawnTime) // respawn on Update
|
|
{
|
|
m_deathState = DeathState::Dead;
|
|
if (CanFly())
|
|
{
|
|
float tz = map->GetHeight(GetPhaseMask(), data->posX, data->posY, data->posZ, true, MAX_FALL_DISTANCE);
|
|
if (data->posZ - tz > 0.1f && Acore::IsValidMapCoord(tz))
|
|
{
|
|
Relocate(data->posX, data->posY, tz);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 curhealth;
|
|
|
|
if (!m_regenHealth)
|
|
{
|
|
curhealth = data->curhealth;
|
|
if (curhealth)
|
|
{
|
|
curhealth = uint32(curhealth * _GetHealthMod(GetCreatureTemplate()->rank));
|
|
if (curhealth < 1)
|
|
curhealth = 1;
|
|
}
|
|
SetPower(POWER_MANA, data->curmana);
|
|
}
|
|
else
|
|
{
|
|
curhealth = GetMaxHealth();
|
|
SetPower(POWER_MANA, GetMaxPower(POWER_MANA));
|
|
}
|
|
|
|
SetHealth(m_deathState == DeathState::Alive ? curhealth : 0);
|
|
|
|
// checked at creature_template loading
|
|
m_defaultMovementType = MovementGeneratorType(data->movementType);
|
|
|
|
if (addToMap && !GetMap()->AddToMap(this))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void Creature::SetCanDualWield(bool value)
|
|
{
|
|
Unit::SetCanDualWield(value);
|
|
UpdateDamagePhysical(OFF_ATTACK);
|
|
}
|
|
|
|
void Creature::LoadEquipment(int8 id, bool force /*= false*/)
|
|
{
|
|
if (id == 0)
|
|
{
|
|
if (force)
|
|
{
|
|
for (uint8 i = 0; i < MAX_EQUIPMENT_ITEMS; ++i)
|
|
SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, 0);
|
|
m_equipmentId = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(GetEntry(), id);
|
|
if (!einfo)
|
|
return;
|
|
|
|
m_equipmentId = id;
|
|
for (uint8 i = 0; i < 3; ++i)
|
|
SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, einfo->ItemEntry[i]);
|
|
}
|
|
|
|
bool Creature::hasQuest(uint32 quest_id) const
|
|
{
|
|
QuestRelationBounds qr = sObjectMgr->GetCreatureQuestRelationBounds(GetEntry());
|
|
for (QuestRelations::const_iterator itr = qr.first; itr != qr.second; ++itr)
|
|
{
|
|
if (itr->second == quest_id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Creature::hasInvolvedQuest(uint32 quest_id) const
|
|
{
|
|
QuestRelationBounds qir = sObjectMgr->GetCreatureQuestInvolvedRelationBounds(GetEntry());
|
|
for (QuestRelations::const_iterator itr = qir.first; itr != qir.second; ++itr)
|
|
{
|
|
if (itr->second == quest_id)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Creature::DeleteFromDB()
|
|
{
|
|
if (!m_spawnId)
|
|
{
|
|
LOG_ERROR("entities.unit", "Trying to delete not saved creature: {}", GetGUID().ToString());
|
|
return;
|
|
}
|
|
|
|
GetMap()->RemoveCreatureRespawnTime(m_spawnId);
|
|
sObjectMgr->DeleteCreatureData(m_spawnId);
|
|
|
|
WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
|
|
|
|
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE);
|
|
stmt->SetData(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE_ADDON);
|
|
stmt->SetData(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAME_EVENT_CREATURE);
|
|
stmt->SetData(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAME_EVENT_MODEL_EQUIP);
|
|
stmt->SetData(0, m_spawnId);
|
|
trans->Append(stmt);
|
|
|
|
WorldDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
bool Creature::IsInvisibleDueToDespawn() const
|
|
{
|
|
if (Unit::IsInvisibleDueToDespawn())
|
|
return true;
|
|
|
|
if (IsAlive() || isDying() || m_corpseRemoveTime > GameTime::GetGameTime().count())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::CanAlwaysSee(WorldObject const* obj) const
|
|
{
|
|
if (IsAIEnabled && AI()->CanSeeAlways(obj))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Creature::IsAlwaysDetectableFor(WorldObject const* seer) const
|
|
{
|
|
if (Unit::IsAlwaysDetectableFor(seer))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (IsAIEnabled && AI()->CanAlwaysBeDetectable(seer))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Creature::CanStartAttack(Unit const* who) const
|
|
{
|
|
if (IsCivilian())
|
|
return false;
|
|
|
|
// This set of checks is should be done only for creatures
|
|
if ((IsImmuneToNPC() && !who->IsPlayer()) || // flag is valid only for non player characters
|
|
(IsImmuneToPC() && who->IsPlayer())) // immune to PC and target is a player, return false
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (Unit* owner = who->GetOwner())
|
|
if (owner->IsPlayer() && IsImmuneToPC()) // immune to PC and target has player owner
|
|
return false;
|
|
|
|
// Do not attack non-combat pets
|
|
if (who->IsCreature() && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
|
|
return false;
|
|
|
|
if (!CanFly() && (GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE + m_CombatDistance)) // too much Z difference, skip very costy vmap calculations here
|
|
return false;
|
|
|
|
if (!_IsTargetAcceptable(who))
|
|
return false;
|
|
|
|
if (IsNeutralToAll() || !IsWithinDistInMap(who, GetAggroRange(who) + m_CombatDistance, true, false, false)) // pussywizard: +m_combatDistance for turrets and similar
|
|
return false;
|
|
|
|
if (!CanCreatureAttack(who))
|
|
return false;
|
|
|
|
if (HasUnitState(UNIT_STATE_STUNNED))
|
|
return false;
|
|
|
|
if (!IsHostileTo(who))
|
|
return false;
|
|
|
|
return IsWithinLOSInMap(who);
|
|
}
|
|
|
|
/**
|
|
* @brief A creature can be in 4 different states: Alive, JustDied, Corpse, and JustRespawned. The cycle follows the next order:
|
|
* - Alive: The creature is alive and has spawned in the world
|
|
* - JustDied: The creature has just died. This is the state just before his body appeared
|
|
* - Corpse: The creature has been removed from the world, and this corpse has been spawned
|
|
* - JustRespawned: The creature has just respawned. Follow when the corpse disappears and the respawn timer is finished
|
|
*
|
|
* @param state Specify one of 4 states above
|
|
* @param despawn Despawn the creature immediately, without waiting for any movement to finish
|
|
*/
|
|
void Creature::setDeathState(DeathState state, bool despawn)
|
|
{
|
|
Unit::setDeathState(state, despawn);
|
|
|
|
if (state == DeathState::JustDied)
|
|
{
|
|
m_corpseRemoveTime = GameTime::GetGameTime().count() + m_corpseDelay;
|
|
uint32 dynamicRespawnDelay = GetMap()->ApplyDynamicModeRespawnScaling(this, m_respawnDelay);
|
|
m_respawnTime = GameTime::GetGameTime().count() + dynamicRespawnDelay + m_corpseDelay;
|
|
|
|
// always save boss respawn time at death to prevent crash cheating
|
|
if (GetMap()->IsDungeon() || isWorldBoss() || GetCreatureTemplate()->rank >= CREATURE_ELITE_ELITE)
|
|
SaveRespawnTime();
|
|
|
|
SetTarget(); // remove target selection in any cases (can be set at aura remove in Unit::setDeathState)
|
|
ReplaceAllNpcFlags(UNIT_NPC_FLAG_NONE);
|
|
|
|
Dismount(); // if creature is mounted on a virtual mount, remove it at death
|
|
|
|
if (HasSearchedAssistance())
|
|
{
|
|
SetNoSearchAssistance(false);
|
|
}
|
|
|
|
//Dismiss group if is leader
|
|
if (m_formation && m_formation->GetLeader() == this)
|
|
m_formation->FormationReset(true, false);
|
|
|
|
bool needsFalling = !despawn && (IsFlying() || IsHovering()) && !IsUnderWater();
|
|
SetHover(false);
|
|
SetDisableGravity(false);
|
|
|
|
if (needsFalling)
|
|
GetMotionMaster()->MoveFall(0, true);
|
|
|
|
Unit::setDeathState(DeathState::Corpse, despawn);
|
|
}
|
|
else if (state == DeathState::JustRespawned)
|
|
{
|
|
SetFullHealth();
|
|
SetLootRecipient(nullptr);
|
|
ResetPlayerDamageReq();
|
|
SetCannotReachTarget();
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
// Xinef: npc run by default
|
|
//SetWalk(true);
|
|
|
|
// pussywizard:
|
|
if (HasUnitMovementFlag(MOVEMENTFLAG_FALLING))
|
|
RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING);
|
|
|
|
UpdateMovementFlags();
|
|
|
|
ReplaceAllNpcFlags(NPCFlags(cinfo->npcflag));
|
|
ClearUnitState(uint32(UNIT_STATE_ALL_STATE & ~(UNIT_STATE_IGNORE_PATHFINDING | UNIT_STATE_NO_ENVIRONMENT_UPD)));
|
|
SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
|
|
|
|
Unit::setDeathState(DeathState::Alive, despawn);
|
|
|
|
Motion_Initialize();
|
|
LoadCreaturesAddon(true);
|
|
LoadSparringPct();
|
|
|
|
if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
|
|
SetPhaseMask(GetCreatureData()->phaseMask, false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param force Force the respawn by killing the creature.
|
|
*/
|
|
void Creature::Respawn(bool force)
|
|
{
|
|
if (force)
|
|
{
|
|
if (IsAlive())
|
|
{
|
|
setDeathState(DeathState::JustDied);
|
|
}
|
|
else if (getDeathState() != DeathState::Corpse)
|
|
{
|
|
setDeathState(DeathState::Corpse);
|
|
}
|
|
}
|
|
|
|
ConditionList conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_RESPAWN, GetEntry());
|
|
|
|
if (!sConditionMgr->IsObjectMeetToConditions(this, conditions) && !force)
|
|
{
|
|
// Creature should not respawn, reset respawn timer. Conditions will be checked again the next time it tries to respawn.
|
|
m_respawnTime = GameTime::GetGameTime().count() + m_respawnDelay;
|
|
return;
|
|
}
|
|
|
|
bool allowed = !IsAIEnabled || AI()->CanRespawn(); // First check if there are any scripts that prevent us respawning
|
|
if (!allowed && !force) // Will be rechecked on next Update call
|
|
return;
|
|
|
|
ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::Unit>(m_creatureData ? m_creatureData->id1 : GetEntry(), m_spawnId);
|
|
time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);
|
|
|
|
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(GetEntry());
|
|
|
|
if (!linkedRespawntime || (cInfo && cInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_HARD_RESET)) || force) // Should respawn
|
|
{
|
|
RemoveCorpse(false, false);
|
|
|
|
if (getDeathState() == DeathState::Dead)
|
|
{
|
|
if (m_spawnId)
|
|
{
|
|
GetMap()->RemoveCreatureRespawnTime(m_spawnId);
|
|
CreatureData const* data = sObjectMgr->GetCreatureData(m_spawnId);
|
|
// Respawn check if spawn has 2 entries
|
|
if (data->id2)
|
|
{
|
|
uint32 entry = GetRandomId(data->id1, data->id2, data->id3);
|
|
UpdateEntry(entry, data, true); // Select Random Entry
|
|
m_defaultMovementType = MovementGeneratorType(data->movementType); // Reload Movement Type
|
|
LoadEquipment(data->equipmentId); // Reload Equipment
|
|
AIM_Initialize(); // Reload AI
|
|
}
|
|
else
|
|
{
|
|
if (m_originalEntry != GetEntry())
|
|
UpdateEntry(m_originalEntry);
|
|
}
|
|
}
|
|
|
|
LOG_DEBUG("entities.unit", "Respawning creature {} (SpawnId: {}, {})", GetName(), GetSpawnId(), GetGUID().ToString());
|
|
m_respawnTime = 0;
|
|
ResetPickPocketLootTime();
|
|
loot.clear();
|
|
SelectLevel();
|
|
|
|
m_respawnedTime = GameTime::GetGameTime().count();
|
|
setDeathState(DeathState::JustRespawned);
|
|
|
|
// MDic - Acidmanifesto: Do not override transform auras
|
|
if (GetAuraEffectsByType(SPELL_AURA_TRANSFORM).empty())
|
|
{
|
|
CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f);
|
|
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate());
|
|
if (minfo) // Cancel load if no model defined
|
|
{
|
|
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
|
|
SetNativeDisplayId(display.CreatureDisplayID);
|
|
}
|
|
}
|
|
|
|
GetMotionMaster()->InitDefault();
|
|
|
|
//Call AI respawn virtual function
|
|
if (IsAIEnabled)
|
|
{
|
|
//reset the AI to be sure no dirty or uninitialized values will be used till next tick
|
|
AI()->Reset();
|
|
TriggerJustRespawned = true; //delay event to next tick so all creatures are created on the map before processing
|
|
}
|
|
|
|
uint32 poolid = m_spawnId ? sPoolMgr->IsPartOfAPool<Creature>(m_spawnId) : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<Creature>(poolid, m_spawnId);
|
|
|
|
//Re-initialize reactstate that could be altered by movementgenerators
|
|
InitializeReactState();
|
|
|
|
}
|
|
m_respawnedTime = GameTime::GetGameTime().count();
|
|
// xinef: relocate notifier, fixes npc appearing in corpse position after forced respawn (instead of spawn)
|
|
m_last_notify_position.Relocate(-5000.0f, -5000.0f, -5000.0f, 0.0f);
|
|
UpdateObjectVisibility(false);
|
|
|
|
}
|
|
else // the master is dead
|
|
{
|
|
ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid);
|
|
if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
|
|
{
|
|
SetRespawnTime(DAY);
|
|
}
|
|
else
|
|
{
|
|
time_t now = GameTime::GetGameTime().count();
|
|
m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little
|
|
}
|
|
SaveRespawnTime(); // also save to DB immediately
|
|
}
|
|
}
|
|
|
|
void Creature::ForcedDespawn(Milliseconds timeMSToDespawn, Seconds forceRespawnTimer)
|
|
{
|
|
if (timeMSToDespawn > 0ms)
|
|
{
|
|
ForcedDespawnDelayEvent* pEvent = new ForcedDespawnDelayEvent(*this, forceRespawnTimer);
|
|
m_Events.AddEventAtOffset(pEvent, timeMSToDespawn);
|
|
return;
|
|
}
|
|
|
|
if (IsAlive())
|
|
setDeathState(DeathState::JustDied, true);
|
|
|
|
// Xinef: Set new respawn time, ignore corpse decay time...
|
|
RemoveCorpse(true);
|
|
|
|
if (forceRespawnTimer > 0s)
|
|
if (GetMap())
|
|
GetMap()->ScheduleCreatureRespawn(GetGUID(), forceRespawnTimer);
|
|
}
|
|
|
|
void Creature::DespawnOrUnsummon(Milliseconds msTimeToDespawn /*= 0ms*/, Seconds forcedRespawnTimer /*= 0s*/)
|
|
{
|
|
if (TempSummon* summon = this->ToTempSummon())
|
|
summon->UnSummon(msTimeToDespawn);
|
|
else
|
|
ForcedDespawn(msTimeToDespawn, forcedRespawnTimer);
|
|
}
|
|
|
|
void Creature::DespawnOnEvade(Seconds respawnDelay)
|
|
{
|
|
AI()->SummonedCreatureDespawnAll();
|
|
|
|
if (respawnDelay < 2s)
|
|
{
|
|
LOG_WARN("entities.unit", "DespawnOnEvade called with delay of {} seconds, defaulting to 2.", respawnDelay.count());
|
|
respawnDelay = 2s;
|
|
}
|
|
|
|
if (TempSummon* whoSummon = ToTempSummon())
|
|
{
|
|
GetMap()->ScheduleCreatureRespawn(GetGUID(), respawnDelay, GetHomePosition());
|
|
whoSummon->UnSummon();
|
|
return;
|
|
}
|
|
|
|
DespawnOrUnsummon(0ms, respawnDelay);
|
|
}
|
|
|
|
void Creature::InitializeReactState()
|
|
{
|
|
if ((IsTotem() || IsTrigger() || IsCritter() || IsSpiritService()) && GetAIName() != "SmartAI" && !GetScriptId())
|
|
SetReactState(REACT_PASSIVE);
|
|
else
|
|
SetReactState(REACT_AGGRESSIVE);
|
|
}
|
|
|
|
bool Creature::HasMechanicTemplateImmunity(uint32 mask) const
|
|
{
|
|
return !GetOwnerGUID().IsPlayer() && (GetCreatureTemplate()->MechanicImmuneMask & mask);
|
|
}
|
|
|
|
void Creature::LoadSpellTemplateImmunity()
|
|
{
|
|
// uint32 max used for "spell id", the immunity system will not perform SpellInfo checks against invalid spells
|
|
// used so we know which immunities were loaded from template
|
|
static uint32 const placeholderSpellId = std::numeric_limits<uint32>::max();
|
|
|
|
// unapply template immunities (in case we're updating entry)
|
|
for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i)
|
|
{
|
|
ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, i, false);
|
|
}
|
|
|
|
// don't inherit immunities for hunter pets
|
|
if (GetOwnerGUID().IsPlayer() && IsHunterPet())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (uint8 mask = GetCreatureTemplate()->SpellSchoolImmuneMask)
|
|
{
|
|
for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i)
|
|
{
|
|
if (mask & (1 << i))
|
|
{
|
|
ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
|
|
{
|
|
if (!spellInfo)
|
|
return false;
|
|
|
|
if (spellInfo->HasAttribute(SPELL_ATTR0_CU_BYPASS_MECHANIC_IMMUNITY))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Xinef: this should exclude self casts...
|
|
// Spells that don't have effectMechanics.
|
|
if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Mechanic - 1)))
|
|
return true;
|
|
|
|
// This check must be done instead of 'if (GetCreatureTemplate()->MechanicImmuneMask & (1 << (spellInfo->Mechanic - 1)))' for not break
|
|
// the check of mechanic immunity on DB (tested) because GetCreatureTemplate()->MechanicImmuneMask and m_spellImmune[IMMUNITY_MECHANIC] don't have same data.
|
|
bool immunedToAllEffects = true;
|
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
if (spellInfo->Effects[i].IsEffect() && !IsImmunedToSpellEffect(spellInfo, i))
|
|
{
|
|
immunedToAllEffects = false;
|
|
break;
|
|
}
|
|
if (immunedToAllEffects)
|
|
return true;
|
|
|
|
return Unit::IsImmunedToSpell(spellInfo, spell);
|
|
}
|
|
|
|
bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const
|
|
{
|
|
// Xinef: this should exclude self casts...
|
|
if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Effects[index].Mechanic - 1)))
|
|
return true;
|
|
|
|
if (GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL && spellInfo->Effects[index].Effect == SPELL_EFFECT_HEAL)
|
|
return true;
|
|
|
|
return Unit::IsImmunedToSpellEffect(spellInfo, index);
|
|
}
|
|
|
|
SpellInfo const* Creature::reachWithSpellAttack(Unit* victim)
|
|
{
|
|
if (!victim)
|
|
return nullptr;
|
|
|
|
for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
|
{
|
|
if (!m_spells[i])
|
|
continue;
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(m_spells[i]);
|
|
if (!spellInfo)
|
|
{
|
|
LOG_ERROR("entities.unit", "WORLD: unknown spell id {}", m_spells[i]);
|
|
continue;
|
|
}
|
|
|
|
bool bcontinue = true;
|
|
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; j++)
|
|
{
|
|
if ((spellInfo->Effects[j].Effect == SPELL_EFFECT_SCHOOL_DAMAGE) ||
|
|
(spellInfo->Effects[j].Effect == SPELL_EFFECT_INSTAKILL) ||
|
|
(spellInfo->Effects[j].Effect == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE) ||
|
|
(spellInfo->Effects[j].Effect == SPELL_EFFECT_HEALTH_LEECH)
|
|
)
|
|
{
|
|
bcontinue = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bcontinue)
|
|
continue;
|
|
|
|
if (spellInfo->ManaCost > GetPower(POWER_MANA))
|
|
continue;
|
|
float range = spellInfo->GetMaxRange(false);
|
|
float minrange = spellInfo->GetMinRange(false);
|
|
float dist = GetDistance(victim);
|
|
if (dist > range || dist < minrange)
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && HasUnitFlag(UNIT_FLAG_SILENCED))
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY && HasUnitFlag(UNIT_FLAG_PACIFIED))
|
|
continue;
|
|
return spellInfo;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
SpellInfo const* Creature::reachWithSpellCure(Unit* victim)
|
|
{
|
|
if (!victim)
|
|
return nullptr;
|
|
|
|
for (uint32 i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
|
{
|
|
if (!m_spells[i])
|
|
continue;
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(m_spells[i]);
|
|
if (!spellInfo)
|
|
{
|
|
LOG_ERROR("entities.unit", "WORLD: unknown spell id {}", m_spells[i]);
|
|
continue;
|
|
}
|
|
|
|
bool bcontinue = true;
|
|
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; j++)
|
|
{
|
|
if ((spellInfo->Effects[j].Effect == SPELL_EFFECT_HEAL))
|
|
{
|
|
bcontinue = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bcontinue)
|
|
continue;
|
|
|
|
if (spellInfo->ManaCost > GetPower(POWER_MANA))
|
|
continue;
|
|
|
|
float range = spellInfo->GetMaxRange(true);
|
|
float minrange = spellInfo->GetMinRange(true);
|
|
float dist = GetDistance(victim);
|
|
|
|
if (dist > range || dist < minrange)
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && HasUnitFlag(UNIT_FLAG_SILENCED))
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY && HasUnitFlag(UNIT_FLAG_PACIFIED))
|
|
continue;
|
|
return spellInfo;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* @brief Select nearest hostile unit within the given distance (regardless of threat list).
|
|
*/
|
|
Unit* Creature::SelectNearestTarget(float dist, bool playerOnly /* = false */) const
|
|
{
|
|
if (dist == 0.0f)
|
|
{
|
|
dist = MAX_VISIBILITY_DISTANCE;
|
|
}
|
|
|
|
Unit* target = nullptr;
|
|
|
|
Acore::NearestHostileUnitCheck u_check(this, dist, playerOnly);
|
|
Acore::UnitLastSearcher<Acore::NearestHostileUnitCheck> searcher(this, target, u_check);
|
|
Cell::VisitObjects(this, searcher, dist);
|
|
return target;
|
|
}
|
|
|
|
/**
|
|
* @brief Select nearest hostile unit within the given attack distance (i.e. distance is ignored if > than ATTACK_DISTANCE), regardless of threat list.
|
|
*/
|
|
Unit* Creature::SelectNearestTargetInAttackDistance(float dist) const
|
|
{
|
|
if (dist < ATTACK_DISTANCE)
|
|
dist = ATTACK_DISTANCE;
|
|
if (dist > MAX_SEARCHER_DISTANCE)
|
|
dist = MAX_SEARCHER_DISTANCE;
|
|
|
|
Unit* target = nullptr;
|
|
Acore::NearestHostileUnitInAttackDistanceCheck u_check(this, dist);
|
|
Acore::UnitLastSearcher<Acore::NearestHostileUnitInAttackDistanceCheck> searcher(this, target, u_check);
|
|
Cell::VisitObjects(this, searcher, std::max(dist, ATTACK_DISTANCE));
|
|
|
|
return target;
|
|
}
|
|
|
|
void Creature::SendAIReaction(AiReaction reactionType)
|
|
{
|
|
WorldPacket data(SMSG_AI_REACTION, 12);
|
|
|
|
data << GetGUID();
|
|
data << uint32(reactionType);
|
|
|
|
((WorldObject*)this)->SendMessageToSet(&data, true);
|
|
|
|
LOG_DEBUG("network", "WORLD: Sent SMSG_AI_REACTION, type {}.", reactionType);
|
|
}
|
|
|
|
void Creature::CallAssistance(Unit* target /*= nullptr*/)
|
|
{
|
|
if (!target)
|
|
{
|
|
target = GetVictim();
|
|
}
|
|
|
|
if (!m_AlreadyCallAssistance && target && !IsPet() && !IsCharmed())
|
|
{
|
|
SetNoCallAssistance(true);
|
|
|
|
float radius = sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS);
|
|
|
|
if (radius > 0)
|
|
{
|
|
std::list<Creature*> assistList;
|
|
|
|
Acore::AnyAssistCreatureInRangeCheck u_check(this, target, radius);
|
|
Acore::CreatureListSearcher<Acore::AnyAssistCreatureInRangeCheck> searcher(this, assistList, u_check);
|
|
Cell::VisitObjects(this, searcher, radius);
|
|
|
|
if (!assistList.empty())
|
|
{
|
|
AssistDelayEvent* e = new AssistDelayEvent(target->GetGUID(), this);
|
|
while (!assistList.empty())
|
|
{
|
|
// Pushing guids because in delay can happen some creature gets despawned => invalid pointer
|
|
e->AddAssistant((*assistList.begin())->GetGUID());
|
|
assistList.pop_front();
|
|
}
|
|
m_Events.AddEventAtOffset(e, Milliseconds(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_DELAY)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::CallForHelp(float radius, Unit* target /*= nullptr*/)
|
|
{
|
|
if (radius <= 0.0f || IsPet() || IsCharmed())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!target)
|
|
{
|
|
target = GetVictim();
|
|
}
|
|
|
|
if (!target)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (m_alreadyCallForHelp) // avoid recursive call for help for any reason
|
|
return;
|
|
|
|
Acore::CallOfHelpCreatureInRangeDo u_do(this, target, radius);
|
|
Acore::CreatureWorker<Acore::CallOfHelpCreatureInRangeDo> worker(this, u_do);
|
|
Cell::VisitObjects(this, worker, radius);
|
|
}
|
|
|
|
bool Creature::CanAssistTo(Unit const* u, Unit const* enemy, bool checkfaction /*= true*/) const
|
|
{
|
|
// is it true?
|
|
if (!HasReactState(REACT_AGGRESSIVE))
|
|
return false;
|
|
|
|
// we don't need help from zombies :)
|
|
if (!IsAlive())
|
|
return false;
|
|
|
|
// Xinef: we cannot assist in evade mode
|
|
if (IsInEvadeMode())
|
|
return false;
|
|
|
|
// pussywizard: or if enemy is in evade mode
|
|
if (enemy && enemy->IsCreature() && enemy->ToCreature()->IsInEvadeMode())
|
|
return false;
|
|
|
|
// we don't need help from non-combatant ;)
|
|
if (IsCivilian())
|
|
return false;
|
|
|
|
if (HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE) || IsImmuneToNPC())
|
|
return false;
|
|
|
|
// skip fighting creature
|
|
if (IsEngaged())
|
|
return false;
|
|
|
|
// only free creature
|
|
if (GetCharmerOrOwnerGUID())
|
|
return false;
|
|
|
|
/// @todo: Implement aggro range, detection range and assistance range templates
|
|
if (m_creatureInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_IGNORE_ALL_ASSISTANCE_CALLS))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// only from same creature faction
|
|
if (checkfaction)
|
|
{
|
|
if (GetFaction() != u->GetFaction())
|
|
return false;
|
|
|
|
if (!RespondsToCallForHelp())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!IsFriendlyTo(u))
|
|
return false;
|
|
}
|
|
|
|
// skip non hostile to caster enemy creatures
|
|
if (!IsHostileTo(enemy))
|
|
return false;
|
|
|
|
// Check if can see the enemy
|
|
if (!CanSeeOrDetect(enemy))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// use this function to avoid having hostile creatures attack
|
|
// friendlies and other mobs they shouldn't attack
|
|
bool Creature::_IsTargetAcceptable(Unit const* target) const
|
|
{
|
|
ASSERT(target);
|
|
|
|
// if the target cannot be attacked, the target is not acceptable
|
|
if (IsFriendlyTo(target) || !target->isTargetableForAttack(false, this) || (m_vehicle && (IsOnVehicle(target) || m_vehicle->GetBase()->IsOnVehicle(target))))
|
|
return false;
|
|
|
|
if (target->HasUnitState(UNIT_STATE_DIED))
|
|
{
|
|
// some creatures can detect fake death
|
|
if (CanIgnoreFeignDeath() && target->HasUnitFlag2(UNIT_FLAG2_FEIGN_DEATH))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
Unit const* targetVictim = target->getAttackerForHelper();
|
|
|
|
// if I'm already fighting target, or I'm hostile towards the target, the target is acceptable
|
|
if (IsEngagedBy(target) || IsHostileTo(target))
|
|
return true;
|
|
|
|
// if the target's victim is friendly, and the target is neutral, the target is acceptable
|
|
if (targetVictim && !IsNeutralToAll() && IsFriendlyTo(targetVictim))
|
|
return true;
|
|
|
|
// if the target's victim is not friendly, or the target is friendly, the target is not acceptable
|
|
return false;
|
|
}
|
|
|
|
void Creature::UpdateMoveInLineOfSightState()
|
|
{
|
|
// xinef: pets, guardians and units with scripts / smartAI should be skipped
|
|
if (IsPet() || HasUnitTypeMask(UNIT_MASK_MINION | UNIT_MASK_SUMMON | UNIT_MASK_GUARDIAN | UNIT_MASK_CONTROLLABLE_GUARDIAN) ||
|
|
GetScriptId() || GetAIName() == "SmartAI")
|
|
{
|
|
m_moveInLineOfSightStrictlyDisabled = false;
|
|
m_moveInLineOfSightDisabled = false;
|
|
return;
|
|
}
|
|
|
|
if (IsTrigger() || IsCivilian() || GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET || IsCritter() || GetAIName() == "NullCreatureAI")
|
|
{
|
|
m_moveInLineOfSightDisabled = true;
|
|
m_moveInLineOfSightStrictlyDisabled = true;
|
|
return;
|
|
}
|
|
|
|
bool nonHostile = true;
|
|
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(GetFaction()))
|
|
if (factionTemplate->hostileMask || factionTemplate->enemyFaction[0] || factionTemplate->enemyFaction[1] || factionTemplate->enemyFaction[2] || factionTemplate->enemyFaction[3])
|
|
nonHostile = false;
|
|
|
|
if (nonHostile)
|
|
m_moveInLineOfSightDisabled = true;
|
|
else
|
|
m_moveInLineOfSightDisabled = false;
|
|
}
|
|
|
|
void Creature::SaveRespawnTime()
|
|
{
|
|
if (IsSummon() || !m_spawnId || (m_creatureData && !m_creatureData->dbData))
|
|
return;
|
|
|
|
GetMap()->SaveCreatureRespawnTime(m_spawnId, m_respawnTime);
|
|
}
|
|
|
|
bool Creature::CanCreatureAttack(Unit const* victim, bool skipDistCheck) const
|
|
{
|
|
if (!victim->IsInMap(this))
|
|
return false;
|
|
|
|
if (!IsValidAttackTarget(victim))
|
|
return false;
|
|
|
|
if (!victim->isInAccessiblePlaceFor(this))
|
|
return false;
|
|
|
|
if (IsAIEnabled && !AI()->CanAIAttack(victim))
|
|
return false;
|
|
|
|
// pussywizard: we cannot attack in evade mode
|
|
if (IsInEvadeMode())
|
|
return false;
|
|
|
|
// pussywizard: or if enemy is in evade mode
|
|
if (victim->IsCreature() && victim->ToCreature()->IsInEvadeMode())
|
|
return false;
|
|
|
|
// cannot attack if is during 5 second grace period, unless being attacked
|
|
if (m_respawnedTime && (GameTime::GetGameTime().count() - m_respawnedTime) < 5 && !IsEngagedBy(victim))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// if victim is in FD and we can't see that
|
|
if (victim->HasUnitFlag2(UNIT_FLAG2_FEIGN_DEATH) && !CanIgnoreFeignDeath())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!GetCharmerOrOwnerGUID().IsPlayer())
|
|
{
|
|
if (GetMap()->IsDungeon())
|
|
return true;
|
|
|
|
float visibility = std::max<float>(GetVisibilityRange(), victim->GetVisibilityRange());
|
|
|
|
// if outside visibility
|
|
if (!IsWithinDist(victim, visibility))
|
|
return false;
|
|
|
|
// pussywizard: don't check distance to home position if recently damaged (allow kiting away from spawnpoint!)
|
|
// xinef: this should include taunt auras
|
|
if (!isWorldBoss() && (GetLastLeashExtensionTime() + GetLeashTimer() > GameTime::GetGameTime().count() || HasTauntAura()))
|
|
return true;
|
|
}
|
|
|
|
if (skipDistCheck)
|
|
return true;
|
|
|
|
if (Unit* unit = GetCharmerOrOwner())
|
|
{
|
|
float visibilityDist = std::min<float>(GetMap()->GetVisibilityRange() + GetObjectSize() * 2, DEFAULT_VISIBILITY_DISTANCE);
|
|
return victim->IsWithinDist(unit, visibilityDist);
|
|
}
|
|
|
|
float dist = sWorld->getFloatConfig(CONFIG_CREATURE_LEASH_RADIUS);
|
|
if (!dist)
|
|
return true;
|
|
|
|
float x, y, z;
|
|
x = y = z = 0.0f;
|
|
if (GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE)->GetResetPosition(x, y, z))
|
|
return IsInDist2d(x, y, dist);
|
|
else
|
|
return IsInDist2d(&m_homePosition, dist);
|
|
}
|
|
|
|
CreatureAddon const* Creature::GetCreatureAddon() const
|
|
{
|
|
if (m_spawnId)
|
|
{
|
|
if (CreatureAddon const* addon = sObjectMgr->GetCreatureAddon(m_spawnId))
|
|
return addon;
|
|
}
|
|
|
|
// dependent from difficulty mode entry
|
|
return sObjectMgr->GetCreatureTemplateAddon(GetCreatureTemplate()->Entry);
|
|
}
|
|
|
|
//creature_addon table
|
|
bool Creature::LoadCreaturesAddon(bool reload)
|
|
{
|
|
CreatureAddon const* cainfo = GetCreatureAddon();
|
|
if (!cainfo)
|
|
return false;
|
|
|
|
if (cainfo->mount != 0)
|
|
Mount(cainfo->mount);
|
|
|
|
if (cainfo->bytes1 != 0)
|
|
{
|
|
// 0 StandState
|
|
// 1 FreeTalentPoints Pet only, so always 0 for default creature
|
|
// 2 StandFlags
|
|
// 3 StandMiscFlags
|
|
|
|
SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_STAND_STATE, uint8(cainfo->bytes1 & 0xFF));
|
|
//SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_PET_TALENTS, uint8((cainfo->bytes1 >> 8) & 0xFF));
|
|
SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_PET_TALENTS, 0);
|
|
SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_VIS_FLAG, uint8((cainfo->bytes1 >> 16) & 0xFF));
|
|
SetByteValue(UNIT_FIELD_BYTES_1, UNIT_BYTES_1_OFFSET_ANIM_TIER, uint8((cainfo->bytes1 >> 24) & 0xFF));
|
|
|
|
//! Suspected correlation between UNIT_FIELD_BYTES_1, offset 3, value 0x2:
|
|
//! If no inhabittype_fly (if no MovementFlag_DisableGravity or MovementFlag_CanFly flag found in sniffs)
|
|
//! Check using InhabitType as movement flags are assigned dynamically
|
|
//! basing on whether the creature is in air or not
|
|
//! Set MovementFlag_Hover. Otherwise do nothing.
|
|
if (CanHover())
|
|
AddUnitMovementFlag(MOVEMENTFLAG_HOVER);
|
|
}
|
|
|
|
if (cainfo->bytes2 != 0)
|
|
{
|
|
// 0 SheathState
|
|
// 1 Bytes2Flags
|
|
// 2 UnitRename Pet only, so always 0 for default creature
|
|
// 3 ShapeshiftForm Must be determined/set by shapeshift spell/aura
|
|
|
|
SetByteValue(UNIT_FIELD_BYTES_2, 0, uint8(cainfo->bytes2 & 0xFF));
|
|
//SetByteValue(UNIT_FIELD_BYTES_2, 1, uint8((cainfo->bytes2 >> 8) & 0xFF));
|
|
//SetByteValue(UNIT_FIELD_BYTES_2, 2, uint8((cainfo->bytes2 >> 16) & 0xFF));
|
|
SetByteValue(UNIT_FIELD_BYTES_2, 2, 0);
|
|
//SetByteValue(UNIT_FIELD_BYTES_2, 3, uint8((cainfo->bytes2 >> 24) & 0xFF));
|
|
SetByteValue(UNIT_FIELD_BYTES_2, 3, 0);
|
|
}
|
|
|
|
SetUInt32Value(UNIT_NPC_EMOTESTATE, cainfo->emote);
|
|
|
|
// Check if visibility distance different
|
|
if (cainfo->visibilityDistanceType != VisibilityDistanceType::Normal)
|
|
{
|
|
SetVisibilityDistanceOverride(cainfo->visibilityDistanceType);
|
|
}
|
|
|
|
//Load Path
|
|
if (cainfo->path_id != 0)
|
|
m_path_id = cainfo->path_id;
|
|
|
|
if (!cainfo->auras.empty())
|
|
{
|
|
for (std::vector<uint32>::const_iterator itr = cainfo->auras.begin(); itr != cainfo->auras.end(); ++itr)
|
|
{
|
|
SpellInfo const* AdditionalSpellInfo = sSpellMgr->GetSpellInfo(*itr);
|
|
if (!AdditionalSpellInfo)
|
|
{
|
|
LOG_ERROR("sql.sql", "Creature ({}) has wrong spell {} defined in `auras` field.", GetGUID().ToString(), *itr);
|
|
continue;
|
|
}
|
|
|
|
// skip already applied aura
|
|
if (HasAura(*itr))
|
|
{
|
|
if (!reload)
|
|
LOG_ERROR("sql.sql", "Creature ({}) has duplicate aura (spell {}) in `auras` field.", GetGUID().ToString(), *itr);
|
|
|
|
continue;
|
|
}
|
|
|
|
AddAura(*itr, this);
|
|
LOG_DEBUG("entities.unit", "Spell: {} added to creature ({})", *itr, GetGUID().ToString());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::LoadSparringPct()
|
|
{
|
|
ObjectGuid::LowType spawnId = GetSpawnId();
|
|
auto const& sparringData = sObjectMgr->GetSparringData();
|
|
|
|
auto itr = sparringData.find(spawnId);
|
|
if (itr != sparringData.end() && !itr->second.empty())
|
|
{
|
|
_sparringPct = itr->second[0];
|
|
}
|
|
}
|
|
|
|
/// Send a message to LocalDefense channel for players opposition team in the zone
|
|
void Creature::SendZoneUnderAttackMessage(Player* attacker)
|
|
{
|
|
WorldPacket data(SMSG_ZONE_UNDER_ATTACK, 4);
|
|
data << (uint32)GetAreaId();
|
|
sWorldSessionMgr->SendGlobalMessage(&data, nullptr, (attacker->GetTeamId() == TEAM_ALLIANCE ? TEAM_HORDE : TEAM_ALLIANCE));
|
|
}
|
|
|
|
/**
|
|
* @brief Set in combat all units in the dungeon/raid. Affect only units with IsAIEnabled.
|
|
*/
|
|
void Creature::SetInCombatWithZone()
|
|
{
|
|
if (IsAIEnabled)
|
|
AI()->DoZoneInCombat();
|
|
}
|
|
|
|
void Creature::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs)
|
|
{
|
|
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
|
|
{
|
|
if (idSchoolMask & (1 << i))
|
|
{
|
|
m_ProhibitSchoolTime[i] = GameTime::GetGameTimeMS().count() + unTimeMs;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Creature::IsSpellProhibited(SpellSchoolMask idSchoolMask) const
|
|
{
|
|
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
|
|
{
|
|
if (idSchoolMask & (1 << i))
|
|
{
|
|
if (m_ProhibitSchoolTime[i] >= GameTime::GetGameTimeMS().count())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Creature::ClearProhibitedSpellTimers()
|
|
{
|
|
for (uint8 i = SPELL_SCHOOL_NORMAL; i < MAX_SPELL_SCHOOL; ++i)
|
|
{
|
|
m_ProhibitSchoolTime[i] = 0;
|
|
}
|
|
}
|
|
|
|
void Creature::_AddCreatureSpellCooldown(uint32 spell_id, uint16 categoryId, uint32 end_time)
|
|
{
|
|
CreatureSpellCooldown spellCooldown;
|
|
spellCooldown.category = categoryId;
|
|
spellCooldown.end = GameTime::GetGameTimeMS().count() + end_time;
|
|
m_CreatureSpellCooldowns[spell_id] = std::move(spellCooldown);
|
|
}
|
|
|
|
void Creature::AddSpellCooldown(uint32 spell_id, uint32 /*itemid*/, uint32 end_time, bool /*needSendToClient*/, bool /*forceSendToSpectator*/)
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id);
|
|
if (!spellInfo)
|
|
return;
|
|
|
|
// used in proc system, otherwise normal creature cooldown
|
|
if (end_time)
|
|
{
|
|
_AddCreatureSpellCooldown(spellInfo->Id, 0, end_time);
|
|
return;
|
|
}
|
|
|
|
uint32 spellcooldown = spellInfo->RecoveryTime;
|
|
uint32 categoryId = spellInfo->GetCategory();
|
|
uint32 categorycooldown = categoryId ? spellInfo->CategoryRecoveryTime : 0;
|
|
if (Player* modOwner = GetSpellModOwner())
|
|
{
|
|
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, spellcooldown);
|
|
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COOLDOWN, categorycooldown);
|
|
}
|
|
|
|
SpellCategoryStore::const_iterator i_scstore = sSpellsByCategoryStore.find(categoryId);
|
|
if (categorycooldown && i_scstore != sSpellsByCategoryStore.end())
|
|
{
|
|
for (SpellCategorySet::const_iterator i_scset = i_scstore->second.begin(); i_scset != i_scstore->second.end(); ++i_scset)
|
|
{
|
|
_AddCreatureSpellCooldown(i_scset->second, categoryId, categorycooldown);
|
|
}
|
|
}
|
|
else if (spellcooldown)
|
|
{
|
|
_AddCreatureSpellCooldown(spellInfo->Id, 0, spellcooldown);
|
|
}
|
|
|
|
if (sSpellMgr->HasSpellCooldownOverride(spellInfo->Id))
|
|
{
|
|
if (IsCharmed() && GetCharmer()->IsPlayer())
|
|
{
|
|
WorldPacket data;
|
|
BuildCooldownPacket(data, SPELL_COOLDOWN_FLAG_NONE, spellInfo->Id, spellcooldown);
|
|
GetCharmer()->ToPlayer()->SendDirectMessage(&data);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 Creature::GetSpellCooldown(uint32 spell_id) const
|
|
{
|
|
CreatureSpellCooldowns::const_iterator itr = m_CreatureSpellCooldowns.find(spell_id);
|
|
if (itr == m_CreatureSpellCooldowns.end())
|
|
return 0;
|
|
|
|
return itr->second.end > GameTime::GetGameTimeMS().count() ? itr->second.end - GameTime::GetGameTimeMS().count() : 0;
|
|
}
|
|
|
|
bool Creature::HasSpellCooldown(uint32 spell_id) const
|
|
{
|
|
CreatureSpellCooldowns::const_iterator itr = m_CreatureSpellCooldowns.find(spell_id);
|
|
return (itr != m_CreatureSpellCooldowns.end() && itr->second.end > GameTime::GetGameTimeMS().count());
|
|
}
|
|
|
|
bool Creature::HasSpell(uint32 spellID) const
|
|
{
|
|
uint8 i;
|
|
for (i = 0; i < MAX_CREATURE_SPELLS; ++i)
|
|
if (spellID == m_spells[i])
|
|
break;
|
|
return i < MAX_CREATURE_SPELLS; //broke before end of iteration of known spells
|
|
}
|
|
|
|
time_t Creature::GetRespawnTimeEx() const
|
|
{
|
|
time_t now = GameTime::GetGameTime().count();
|
|
|
|
if (m_respawnTime > now)
|
|
return m_respawnTime;
|
|
else
|
|
return now;
|
|
}
|
|
|
|
void Creature::GetRespawnPosition(float& x, float& y, float& z, float* ori, float* dist) const
|
|
{
|
|
if (m_spawnId)
|
|
{
|
|
if (CreatureData const* data = sObjectMgr->GetCreatureData(m_spawnId))
|
|
{
|
|
x = data->posX;
|
|
y = data->posY;
|
|
z = data->posZ;
|
|
if (ori)
|
|
*ori = data->orientation;
|
|
if (dist)
|
|
*dist = data->wander_distance;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
// xinef: changed this from current position to home position, fixes world summons with infinite duration
|
|
if (GetTransport())
|
|
{
|
|
x = GetPositionX();
|
|
y = GetPositionY();
|
|
z = GetPositionZ();
|
|
if (ori)
|
|
*ori = GetOrientation();
|
|
}
|
|
else
|
|
{
|
|
Position homePos = GetHomePosition();
|
|
x = homePos.GetPositionX();
|
|
y = homePos.GetPositionY();
|
|
z = homePos.GetPositionZ();
|
|
if (ori)
|
|
*ori = homePos.GetOrientation();
|
|
}
|
|
if (dist)
|
|
*dist = 0;
|
|
}
|
|
|
|
CreatureMovementData const& Creature::GetMovementTemplate() const
|
|
{
|
|
if (CreatureMovementData const* movementOverride = sObjectMgr->GetCreatureMovementOverride(m_spawnId))
|
|
return *movementOverride;
|
|
|
|
return GetCreatureTemplate()->Movement;
|
|
}
|
|
|
|
void Creature::AllLootRemovedFromCorpse()
|
|
{
|
|
if (loot.loot_type != LOOT_SKINNING && !IsPet() && GetCreatureTemplate()->SkinLootId && hasLootRecipient())
|
|
{
|
|
if (LootTemplates_Skinning.HaveLootFor(GetCreatureTemplate()->SkinLootId))
|
|
{
|
|
SetUnitFlag(UNIT_FLAG_SKINNABLE);
|
|
}
|
|
}
|
|
|
|
time_t now = GameTime::GetGameTime().count();
|
|
if (m_corpseRemoveTime <= now)
|
|
{
|
|
return;
|
|
}
|
|
|
|
float decayRate = sWorld->getRate(RATE_CORPSE_DECAY_LOOTED);
|
|
uint32 diff = uint32((m_corpseRemoveTime - now) * decayRate);
|
|
|
|
m_respawnTime -= diff;
|
|
|
|
// corpse skinnable, but without skinning flag, and then skinned, corpse will despawn next update
|
|
if (loot.loot_type == LOOT_SKINNING)
|
|
{
|
|
m_corpseRemoveTime = GameTime::GetGameTime().count();
|
|
}
|
|
else
|
|
{
|
|
m_corpseRemoveTime -= diff;
|
|
}
|
|
}
|
|
|
|
uint8 Creature::getLevelForTarget(WorldObject const* target) const
|
|
{
|
|
if (!isWorldBoss() || !target->ToUnit())
|
|
return Unit::getLevelForTarget(target);
|
|
|
|
uint16 level = target->ToUnit()->GetLevel() + sWorld->getIntConfig(CONFIG_WORLD_BOSS_LEVEL_DIFF);
|
|
if (level < 1)
|
|
return 1;
|
|
if (level > 255)
|
|
return 255;
|
|
return uint8(level);
|
|
}
|
|
|
|
std::string const& Creature::GetAIName() const
|
|
{
|
|
return sObjectMgr->GetCreatureTemplate(GetEntry())->AIName;
|
|
}
|
|
|
|
std::string Creature::GetScriptName() const
|
|
{
|
|
return sObjectMgr->GetScriptName(GetScriptId());
|
|
}
|
|
|
|
uint32 Creature::GetScriptId() const
|
|
{
|
|
if (CreatureData const* creatureData = GetCreatureData())
|
|
{
|
|
uint32 scriptId = creatureData->ScriptId;
|
|
if (scriptId && GetEntry() == creatureData->id1)
|
|
return scriptId;
|
|
}
|
|
|
|
return sObjectMgr->GetCreatureTemplate(GetEntry())->ScriptID;
|
|
}
|
|
|
|
VendorItemData const* Creature::GetVendorItems() const
|
|
{
|
|
return sObjectMgr->GetNpcVendorItemList(GetEntry());
|
|
}
|
|
|
|
uint32 Creature::GetVendorItemCurrentCount(VendorItem const* vItem)
|
|
{
|
|
if (!vItem->maxcount)
|
|
return vItem->maxcount;
|
|
|
|
VendorItemCounts::iterator itr = m_vendorItemCounts.begin();
|
|
for (; itr != m_vendorItemCounts.end(); ++itr)
|
|
if (itr->itemId == vItem->item)
|
|
break;
|
|
|
|
if (itr == m_vendorItemCounts.end())
|
|
return vItem->maxcount;
|
|
|
|
VendorItemCount* vCount = &*itr;
|
|
|
|
time_t ptime = GameTime::GetGameTime().count();
|
|
|
|
if (time_t(vCount->lastIncrementTime + vItem->incrtime) <= ptime)
|
|
{
|
|
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(vItem->item);
|
|
|
|
uint32 diff = uint32((ptime - vCount->lastIncrementTime) / vItem->incrtime);
|
|
if ((vCount->count + diff * pProto->BuyCount) >= vItem->maxcount)
|
|
{
|
|
m_vendorItemCounts.erase(itr);
|
|
return vItem->maxcount;
|
|
}
|
|
|
|
vCount->count += diff * pProto->BuyCount;
|
|
vCount->lastIncrementTime = ptime;
|
|
}
|
|
|
|
return vCount->count;
|
|
}
|
|
|
|
uint32 Creature::UpdateVendorItemCurrentCount(VendorItem const* vItem, uint32 used_count)
|
|
{
|
|
if (!vItem->maxcount)
|
|
return 0;
|
|
|
|
VendorItemCounts::iterator itr = m_vendorItemCounts.begin();
|
|
for (; itr != m_vendorItemCounts.end(); ++itr)
|
|
if (itr->itemId == vItem->item)
|
|
break;
|
|
|
|
if (itr == m_vendorItemCounts.end())
|
|
{
|
|
uint32 new_count = vItem->maxcount > used_count ? vItem->maxcount - used_count : 0;
|
|
m_vendorItemCounts.push_back(VendorItemCount(vItem->item, new_count));
|
|
return new_count;
|
|
}
|
|
|
|
VendorItemCount* vCount = &*itr;
|
|
|
|
time_t ptime = GameTime::GetGameTime().count();
|
|
|
|
if (time_t(vCount->lastIncrementTime + vItem->incrtime) <= ptime)
|
|
{
|
|
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(vItem->item);
|
|
|
|
uint32 diff = uint32((ptime - vCount->lastIncrementTime) / vItem->incrtime);
|
|
if ((vCount->count + diff * pProto->BuyCount) < vItem->maxcount)
|
|
vCount->count += diff * pProto->BuyCount;
|
|
else
|
|
vCount->count = vItem->maxcount;
|
|
}
|
|
|
|
vCount->count = vCount->count > used_count ? vCount->count - used_count : 0;
|
|
vCount->lastIncrementTime = ptime;
|
|
return vCount->count;
|
|
}
|
|
|
|
// overwrite WorldObject function for proper name localization
|
|
std::string const& Creature::GetNameForLocaleIdx(LocaleConstant loc_idx) const
|
|
{
|
|
if (loc_idx != DEFAULT_LOCALE)
|
|
{
|
|
uint8 uloc_idx = uint8(loc_idx);
|
|
CreatureLocale const* cl = sObjectMgr->GetCreatureLocale(GetEntry());
|
|
if (cl)
|
|
{
|
|
if (cl->Name.size() > uloc_idx && !cl->Name[uloc_idx].empty())
|
|
return cl->Name[uloc_idx];
|
|
}
|
|
}
|
|
|
|
return GetName();
|
|
}
|
|
|
|
void Creature::SetPosition(float x, float y, float z, float o)
|
|
{
|
|
if (!Acore::IsValidMapCoord(x, y, z, o))
|
|
return;
|
|
|
|
GetMap()->CreatureRelocation(this, x, y, z, o);
|
|
}
|
|
|
|
bool Creature::IsDungeonBoss() const
|
|
{
|
|
if (GetOwnerGUID().IsPlayer())
|
|
return false;
|
|
|
|
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(GetEntry());
|
|
return cinfo && cinfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_DUNGEON_BOSS);
|
|
}
|
|
|
|
bool Creature::IsImmuneToKnockback() const
|
|
{
|
|
if (GetOwnerGUID().IsPlayer())
|
|
return false;
|
|
|
|
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(GetEntry());
|
|
return cinfo && cinfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK);
|
|
}
|
|
|
|
bool Creature::HasWeapon(WeaponAttackType type) const
|
|
{
|
|
if (type == OFF_ATTACK)
|
|
{
|
|
switch (_dualWieldMode)
|
|
{
|
|
case DualWieldMode::ENABLED:
|
|
return true;
|
|
case DualWieldMode::DISABLED:
|
|
return false;
|
|
case DualWieldMode::AUTO:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const uint8 slot = uint8(type);
|
|
ItemEntry const* item = sItemStore.LookupEntry(GetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + slot));
|
|
return (item && item->ClassID == ITEM_CLASS_WEAPON);
|
|
}
|
|
|
|
/**
|
|
* @brief Enable or disable the creature's walk mode by removing: MOVEMENTFLAG_WALKING. Infom also the client
|
|
*/
|
|
bool Creature::SetWalk(bool enable)
|
|
{
|
|
if (!Unit::SetWalk(enable))
|
|
return false;
|
|
|
|
WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_WALK_MODE : SMSG_SPLINE_MOVE_SET_RUN_MODE, 9);
|
|
data << GetPackGUID();
|
|
SendMessageToSet(&data, false);
|
|
return true;
|
|
}
|
|
|
|
bool Creature::SetSwim(bool enable)
|
|
{
|
|
if (!Unit::SetSwim(enable))
|
|
return false;
|
|
|
|
WorldPacket data(enable ? SMSG_SPLINE_MOVE_START_SWIM : SMSG_SPLINE_MOVE_STOP_SWIM);
|
|
data << GetPackGUID();
|
|
SendMessageToSet(&data, true);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief This method check the current flag/status of a creature and its inhabit type
|
|
*
|
|
* Pets should swim by default to properly follow the player
|
|
* NOTE: You can set the UNIT_FLAG_CANNOT_SWIM temporary to deny a creature to swim
|
|
*
|
|
*/
|
|
bool Creature::CanSwim() const
|
|
{
|
|
if (Unit::CanSwim() || (!Unit::CanSwim() && !CanFly()))
|
|
return true;
|
|
|
|
if (IsPet())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Creature::CanEnterWater() const
|
|
{
|
|
if (CanSwim())
|
|
return true;
|
|
|
|
return GetMovementTemplate().IsSwimAllowed();
|
|
}
|
|
|
|
void Creature::RefreshSwimmingFlag(bool recheck)
|
|
{
|
|
if (!_isMissingSwimmingFlagOutOfCombat || recheck)
|
|
_isMissingSwimmingFlagOutOfCombat = !HasUnitFlag(UNIT_FLAG_SWIMMING);
|
|
|
|
// Check if the creature has UNIT_FLAG_SWIMMING and add it if it's missing
|
|
// Creatures must be able to chase a target in water if they can enter water
|
|
if (_isMissingSwimmingFlagOutOfCombat && CanEnterWater())
|
|
SetUnitFlag(UNIT_FLAG_SWIMMING);
|
|
}
|
|
|
|
float Creature::GetAggroRange(Unit const* target) const
|
|
{
|
|
// Determines the aggro range for creatures
|
|
// Based on data from wowwiki due to lack of 3.3.5a data
|
|
|
|
float aggroRate = sWorld->getRate(RATE_CREATURE_AGGRO);
|
|
if (aggroRate == 0)
|
|
return 0.0f;
|
|
|
|
auto creatureLevel = target->getLevelForTarget(this);
|
|
auto playerLevel = getLevelForTarget(target);
|
|
int32 levelDiff = int32(creatureLevel) - int32(playerLevel);
|
|
|
|
// The maximum Aggro Radius is capped at 45 yards (25 level difference)
|
|
if (levelDiff < -25)
|
|
levelDiff = -25;
|
|
|
|
// The base aggro radius for mob of same level
|
|
auto aggroRadius = GetDetectionRange();
|
|
if (aggroRadius < 1)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
// Aggro Radius varies with level difference at a rate of roughly 1 yard/level
|
|
aggroRadius -= (float)levelDiff;
|
|
|
|
// detect range auras
|
|
aggroRadius += GetTotalAuraModifier(SPELL_AURA_MOD_DETECT_RANGE);
|
|
|
|
// detected range auras
|
|
aggroRadius += target->GetTotalAuraModifier(SPELL_AURA_MOD_DETECTED_RANGE);
|
|
|
|
// Just in case, we don't want pets running all over the map
|
|
if (aggroRadius > MAX_AGGRO_RADIUS)
|
|
aggroRadius = MAX_AGGRO_RADIUS;
|
|
|
|
// Minimum Aggro Radius for a mob seems to be combat range (5 yards)
|
|
// hunter pets seem to ignore minimum aggro radius so we'll default it a little higher
|
|
float minRange = IsPet() ? 10.0f : 5.0f;
|
|
if (aggroRadius < minRange)
|
|
aggroRadius = minRange;
|
|
|
|
return (aggroRadius * aggroRate);
|
|
}
|
|
|
|
void Creature::UpdateMovementFlags()
|
|
{
|
|
// Do not update movement flags if creature is controlled by a player (charm/vehicle)
|
|
if (m_movedByPlayer)
|
|
return;
|
|
|
|
CreatureTemplate const* info = GetCreatureTemplate();
|
|
if (!info)
|
|
return;
|
|
|
|
// Creatures with CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE should control MovementFlags in your own scripts
|
|
if (info->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_MOVE_FLAGS_UPDATE))
|
|
return;
|
|
|
|
float ground = GetFloorZ();
|
|
|
|
bool canHover = CanHover();
|
|
bool isInAir = (G3D::fuzzyGt(GetPositionZ(), ground + (canHover ? GetFloatValue(UNIT_FIELD_HOVERHEIGHT) : 0.0f) + GROUND_HEIGHT_TOLERANCE) || G3D::fuzzyLt(GetPositionZ(), ground - GROUND_HEIGHT_TOLERANCE)); // Can be underground too, prevent the falling
|
|
|
|
if (GetMovementTemplate().IsFlightAllowed() && isInAir && !IsFalling())
|
|
{
|
|
if (GetMovementTemplate().Flight == CreatureFlightMovementType::CanFly && !m_movementInfo.HasMovementFlag(MOVEMENTFLAG_CAN_FLY))
|
|
SetCanFly(true);
|
|
else if (!IsLevitating())
|
|
SetDisableGravity(true);
|
|
|
|
if (!HasHoverAura() && IsHovering())
|
|
SetHover(false);
|
|
}
|
|
else
|
|
{
|
|
if (m_movementInfo.HasMovementFlag(MOVEMENTFLAG_CAN_FLY))
|
|
SetCanFly(false);
|
|
|
|
if (IsLevitating())
|
|
SetDisableGravity(false);
|
|
|
|
if (IsAlive() && (GetMovementTemplate().Ground == CreatureGroundMovementType::Hover || HasHoverAura()) && !IsHovering())
|
|
SetHover(true);
|
|
}
|
|
|
|
if (!isInAir)
|
|
RemoveUnitMovementFlag(MOVEMENTFLAG_FALLING);
|
|
|
|
bool Swim = false;
|
|
LiquidData const& liquidData = GetLiquidData();
|
|
switch (liquidData.Status)
|
|
{
|
|
case LIQUID_MAP_WATER_WALK:
|
|
case LIQUID_MAP_IN_WATER:
|
|
Swim = GetPositionZ() - liquidData.DepthLevel > GetCollisionHeight() * 0.75f; // Shallow water at ~75% of collision height
|
|
break;
|
|
case LIQUID_MAP_UNDER_WATER:
|
|
Swim = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SetSwim(CanSwim() && Swim);
|
|
}
|
|
|
|
float Creature::GetNativeObjectScale() const
|
|
{
|
|
return ObjectMgr::ChooseDisplayId(GetCreatureTemplate())->DisplayScale;
|
|
}
|
|
|
|
void Creature::SetObjectScale(float scale)
|
|
{
|
|
Unit::SetObjectScale(scale);
|
|
|
|
float combatReach = DEFAULT_WORLD_OBJECT_SIZE;
|
|
|
|
if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(GetDisplayId()))
|
|
{
|
|
SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, (IsPet() ? 1.0f : minfo->bounding_radius) * scale);
|
|
if (minfo->combat_reach > 0)
|
|
combatReach = minfo->combat_reach;
|
|
}
|
|
|
|
if (IsPet())
|
|
combatReach = DEFAULT_COMBAT_REACH;
|
|
|
|
SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * scale);
|
|
}
|
|
|
|
void Creature::SetDisplayId(uint32 modelId, float displayScale /*= 1.f*/)
|
|
{
|
|
Unit::SetDisplayId(modelId, displayScale);
|
|
|
|
float combatReach = DEFAULT_WORLD_OBJECT_SIZE;
|
|
|
|
if (CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelInfo(modelId))
|
|
{
|
|
SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, (IsPet() ? 1.0f : minfo->bounding_radius) * GetObjectScale());
|
|
if (minfo->combat_reach > 0)
|
|
combatReach = minfo->combat_reach;
|
|
}
|
|
|
|
if (IsPet())
|
|
combatReach = DEFAULT_COMBAT_REACH;
|
|
|
|
SetObjectScale(displayScale);
|
|
|
|
SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * GetObjectScale());
|
|
}
|
|
|
|
void Creature::SetDisplayFromModel(uint32 modelIdx)
|
|
{
|
|
if (CreatureModel const* model = GetCreatureTemplate()->GetModelByIdx(modelIdx))
|
|
SetDisplayId(model->CreatureDisplayID, model->DisplayScale);
|
|
}
|
|
|
|
void Creature::SetTarget(ObjectGuid guid)
|
|
{
|
|
if (!_focusSpell)
|
|
SetGuidValue(UNIT_FIELD_TARGET, guid);
|
|
}
|
|
|
|
void Creature::FocusTarget(Spell const* focusSpell, WorldObject const* target)
|
|
{
|
|
// already focused
|
|
if (_focusSpell)
|
|
return;
|
|
|
|
_focusSpell = focusSpell;
|
|
|
|
SetGuidValue(UNIT_FIELD_TARGET, this == target ? ObjectGuid::Empty : target->GetGUID());
|
|
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AI_DOESNT_FACE_TARGET))
|
|
AddUnitState(UNIT_STATE_ROTATING);
|
|
|
|
// Set serverside orientation if needed (needs to be after attribute check)
|
|
if (this == target && (movespline->Finalized() || GetMotionMaster()->GetCurrentMovementGeneratorType() == CHASE_MOTION_TYPE))
|
|
SetFacingTo(GetOrientation());
|
|
else
|
|
SetInFront(target);
|
|
}
|
|
|
|
bool Creature::HasSpellFocus(Spell const* focusSpell) const
|
|
{
|
|
if (isDead()) // dead creatures cannot focus
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return focusSpell ? (focusSpell == _spellFocusInfo.Spell) : (_spellFocusInfo.Spell || _spellFocusInfo.Delay);
|
|
}
|
|
|
|
void Creature::ReleaseFocus(Spell const* focusSpell)
|
|
{
|
|
// focused to something else
|
|
if (focusSpell != _focusSpell)
|
|
return;
|
|
|
|
_focusSpell = nullptr;
|
|
if (Unit* victim = GetVictim())
|
|
SetGuidValue(UNIT_FIELD_TARGET, victim->GetGUID());
|
|
else
|
|
SetGuidValue(UNIT_FIELD_TARGET, ObjectGuid::Empty);
|
|
|
|
if (focusSpell->GetSpellInfo()->HasAttribute(SPELL_ATTR5_AI_DOESNT_FACE_TARGET))
|
|
ClearUnitState(UNIT_STATE_ROTATING);
|
|
}
|
|
|
|
float Creature::GetAttackDistance(Unit const* player) const
|
|
{
|
|
float aggroRate = sWorld->getRate(RATE_CREATURE_AGGRO);
|
|
|
|
if (aggroRate == 0)
|
|
return 0.0f;
|
|
|
|
if (!player)
|
|
return 0.0f;
|
|
|
|
uint32 playerLevel = player->getLevelForTarget(this);
|
|
uint32 creatureLevel = getLevelForTarget(player);
|
|
|
|
int32 levelDiff = static_cast<int32>(playerLevel) - static_cast<int32>(creatureLevel);
|
|
|
|
// "The maximum Aggro Radius has a cap of 25 levels under. Example: A level 30 char has the same Aggro Radius of a level 5 char on a level 60 mob."
|
|
if (levelDiff < -25)
|
|
levelDiff = -25;
|
|
|
|
// "The aggro radius of a mob having the same level as the player is roughly 20 yards"
|
|
float retDistance = 20.0f;
|
|
|
|
// "Aggro Radius varies with level difference at a rate of roughly 1 yard/level"
|
|
// radius grow if playlevel < creaturelevel
|
|
retDistance -= static_cast<float>(levelDiff);
|
|
|
|
if (creatureLevel + 5 <= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
|
|
{
|
|
// detect range auras
|
|
retDistance += static_cast<float>( GetTotalAuraModifier(SPELL_AURA_MOD_DETECT_RANGE));
|
|
|
|
// detected range auras
|
|
retDistance += static_cast<float>( player->GetTotalAuraModifier(SPELL_AURA_MOD_DETECTED_RANGE));
|
|
}
|
|
|
|
// "Minimum Aggro Radius for a mob seems to be combat range (5 yards)"
|
|
if (retDistance < 5.0f)
|
|
retDistance = 5.0f;
|
|
|
|
return (retDistance * aggroRate);
|
|
}
|
|
|
|
bool Creature::IsMovementPreventedByCasting() const
|
|
{
|
|
Spell* spell = m_currentSpells[CURRENT_CHANNELED_SPELL];
|
|
// first check if currently a movement allowed channel is active and we're not casting
|
|
if (spell && spell->getState() != SPELL_STATE_FINISHED && spell->IsChannelActive() && spell->GetSpellInfo()->IsActionAllowedChannel())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (HasSpellFocus())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (HasUnitState(UNIT_STATE_CASTING))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Creature::SetCannotReachTarget(ObjectGuid const& cannotReach)
|
|
{
|
|
if (cannotReach == m_cannotReachTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_cannotReachTarget = cannotReach;
|
|
m_cannotReachTimer = 0;
|
|
|
|
if (cannotReach)
|
|
{
|
|
LOG_DEBUG("entities.unit", "Creature::SetCannotReachTarget() called with true. Details: {}", GetDebugInfo());
|
|
}
|
|
}
|
|
|
|
bool Creature::CanNotReachTarget() const
|
|
{
|
|
return m_cannotReachTarget;
|
|
}
|
|
|
|
bool Creature::IsNotReachableAndNeedRegen() const
|
|
{
|
|
if (CanNotReachTarget())
|
|
{
|
|
return m_cannotReachTimer >= (sWorld->getIntConfig(CONFIG_NPC_REGEN_TIME_IF_NOT_REACHABLE_IN_RAID) * IN_MILLISECONDS);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<time_t> const& Creature::GetLastLeashExtensionTimePtr() const
|
|
{
|
|
if (m_lastLeashExtensionTime == nullptr)
|
|
m_lastLeashExtensionTime = std::make_shared<time_t>(GameTime::GetGameTime().count());
|
|
return m_lastLeashExtensionTime;
|
|
}
|
|
|
|
void Creature::SetLastLeashExtensionTimePtr(std::shared_ptr<time_t> const& timer)
|
|
{
|
|
m_lastLeashExtensionTime = timer;
|
|
}
|
|
|
|
void Creature::ClearLastLeashExtensionTimePtr()
|
|
{
|
|
m_lastLeashExtensionTime.reset();
|
|
}
|
|
|
|
time_t Creature::GetLastLeashExtensionTime() const
|
|
{
|
|
return *GetLastLeashExtensionTimePtr();
|
|
}
|
|
|
|
void Creature::UpdateLeashExtensionTime()
|
|
{
|
|
(*GetLastLeashExtensionTimePtr()) = GameTime::GetGameTime().count();
|
|
}
|
|
|
|
uint8 Creature::GetLeashTimer() const
|
|
{ // Based on testing on Classic, seems to range from ~11s for low level mobs (1-5) to ~16s for high level mobs (70+)
|
|
uint8 timerOffset = 11;
|
|
|
|
uint8 timerModifier = uint8(GetCreatureTemplate()->minlevel / 10) - 2;
|
|
|
|
// Formula is likely not quite correct, but better than flat timer
|
|
return std::max<uint8>(timerOffset, timerOffset + timerModifier);
|
|
}
|
|
|
|
bool Creature::CanPeriodicallyCallForAssistance() const
|
|
{
|
|
if (!IsInCombat())
|
|
return false;
|
|
|
|
if (HasUnitState(UNIT_STATE_DIED | UNIT_STATE_POSSESSED))
|
|
return false;
|
|
|
|
if (!CanHaveThreatList())
|
|
return false;
|
|
|
|
if (IsSummon() && GetMap()->Instanceable())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32 Creature::GetRandomId(uint32 id1, uint32 id2, uint32 id3)
|
|
{
|
|
uint32 id = id1;
|
|
uint8 ids = 0;
|
|
|
|
if (id2)
|
|
{
|
|
++ids;
|
|
if (id3) ++ids;
|
|
}
|
|
|
|
if (ids)
|
|
{
|
|
uint8 idNumber = urand(0, ids);
|
|
switch (idNumber)
|
|
{
|
|
case 0:
|
|
id = id1;
|
|
break;
|
|
case 1:
|
|
id = id2;
|
|
break;
|
|
case 2:
|
|
id = id3;
|
|
break;
|
|
}
|
|
}
|
|
return id;
|
|
}
|
|
|
|
void Creature::SetPickPocketLootTime()
|
|
{
|
|
lootPickPocketRestoreTime = GameTime::GetGameTime().count() + MINUTE + GetCorpseDelay() + GetRespawnTime();
|
|
}
|
|
|
|
bool Creature::CanGeneratePickPocketLoot() const
|
|
{
|
|
return (lootPickPocketRestoreTime == 0 || lootPickPocketRestoreTime < GameTime::GetGameTime().count());
|
|
}
|
|
|
|
void Creature::SetTextRepeatId(uint8 textGroup, uint8 id)
|
|
{
|
|
CreatureTextRepeatIds& repeats = m_textRepeat[textGroup];
|
|
if (std::find(repeats.begin(), repeats.end(), id) == repeats.end())
|
|
repeats.push_back(id);
|
|
else
|
|
LOG_ERROR("sql.sql", "CreatureTextMgr: TextGroup {} for Creature({}) {}, id {} already added", uint32(textGroup), GetName(), GetGUID().ToString(), uint32(id));
|
|
}
|
|
|
|
CreatureTextRepeatIds const& Creature::GetTextRepeatGroup(uint8 textGroup)
|
|
{
|
|
static CreatureTextRepeatIds const emptyIds;
|
|
|
|
CreatureTextRepeatGroup::const_iterator groupItr = m_textRepeat.find(textGroup);
|
|
if (groupItr != m_textRepeat.end())
|
|
return groupItr->second;
|
|
|
|
return emptyIds;
|
|
}
|
|
|
|
void Creature::ClearTextRepeatGroup(uint8 textGroup)
|
|
{
|
|
CreatureTextRepeatGroup::iterator groupItr = m_textRepeat.find(textGroup);
|
|
if (groupItr != m_textRepeat.end())
|
|
groupItr->second.clear();
|
|
}
|
|
|
|
void Creature::SetRespawnTime(uint32 respawn)
|
|
{
|
|
m_respawnTime = respawn ? GameTime::GetGameTime().count() + respawn : 0;
|
|
}
|
|
|
|
void Creature::SetCorpseRemoveTime(uint32 delay)
|
|
{
|
|
m_corpseRemoveTime = GameTime::GetGameTime().count() + delay;
|
|
}
|
|
|
|
void Creature::ModifyThreatPercentTemp(Unit* victim, int32 percent, Milliseconds duration)
|
|
{
|
|
if (victim)
|
|
{
|
|
float currentThreat = GetThreatMgr().GetThreat(victim);
|
|
|
|
if (percent != 0.0f)
|
|
{
|
|
GetThreatMgr().ModifyThreatByPercent(victim, percent);
|
|
}
|
|
|
|
TemporaryThreatModifierEvent* pEvent = new TemporaryThreatModifierEvent(*this, victim->GetGUID(), currentThreat);
|
|
m_Events.AddEventAtOffset(pEvent, duration);
|
|
}
|
|
}
|
|
|
|
bool Creature::IsDamageEnoughForLootingAndReward() const
|
|
{
|
|
return m_creatureInfo->HasFlagsExtra(CREATURE_FLAG_EXTRA_NO_PLAYER_DAMAGE_REQ) || (_playerDamageReq == 0 && _damagedByPlayer);
|
|
}
|
|
|
|
void Creature::LowerPlayerDamageReq(uint32 unDamage, bool damagedByPlayer /*= true*/)
|
|
{
|
|
if (_playerDamageReq)
|
|
_playerDamageReq > unDamage ? _playerDamageReq -= unDamage : _playerDamageReq = 0;
|
|
|
|
if (!_damagedByPlayer)
|
|
{
|
|
_damagedByPlayer = damagedByPlayer;
|
|
}
|
|
}
|
|
|
|
void Creature::ResetPlayerDamageReq()
|
|
{
|
|
_playerDamageReq = GetHealth() / 2;
|
|
_damagedByPlayer = false;
|
|
}
|
|
|
|
uint32 Creature::GetPlayerDamageReq() const
|
|
{
|
|
return _playerDamageReq;
|
|
}
|
|
|
|
bool Creature::CanCastSpell(uint32 spellID) const
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellID);
|
|
int32 currentPower = GetPower(getPowerType());
|
|
|
|
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED) || IsSpellProhibited(spellInfo->GetSchoolMask()))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (spellInfo && (currentPower < spellInfo->CalcPowerCost(this, spellInfo->GetSchoolMask())))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::SetCombatMovement(bool allowMovement)
|
|
{
|
|
_isCombatMovementAllowed = allowMovement;
|
|
}
|
|
|
|
ObjectGuid Creature::GetSummonerGUID() const
|
|
{
|
|
if (TempSummon const* temp = ToTempSummon())
|
|
return temp->GetSummonerGUID();
|
|
|
|
LOG_DEBUG("entities.unit", "Creature::GetSummonerGUID() called by creature that is not a summon. Creature: {} ({})", GetEntry(), GetName());
|
|
return ObjectGuid::Empty;
|
|
}
|
|
|
|
std::string Creature::GetDebugInfo() const
|
|
{
|
|
std::stringstream sstr;
|
|
sstr << Unit::GetDebugInfo() << "\n"
|
|
<< "AIName: " << GetAIName() << " ScriptName: " << GetScriptName()
|
|
<< " WaypointPath: " << GetWaypointPath() << " SpawnId: " << GetSpawnId();
|
|
return sstr.str();
|
|
}
|
|
|
|
// Note: This is called in a tight (heavy) loop, is it critical that all checks are FAST and are hopefully only simple conditionals.
|
|
bool Creature::IsUpdateNeeded()
|
|
{
|
|
if (WorldObject::IsUpdateNeeded())
|
|
return true;
|
|
|
|
if (GetMap()->isCellMarked(GetCurrentCell().GetCellCoord().GetId()))
|
|
return true;
|
|
|
|
if (IsInCombat())
|
|
return true;
|
|
|
|
if (!GetObjectVisibilityContainer().GetVisiblePlayersMap().empty())
|
|
return true;
|
|
|
|
if (ToTempSummon())
|
|
return true;
|
|
|
|
if (GetMotionMaster()->HasMovementGeneratorType(WAYPOINT_MOTION_TYPE))
|
|
return true;
|
|
|
|
if (HasUnitState(UNIT_STATE_EVADE))
|
|
return true;
|
|
|
|
if (m_formation && m_formation->GetLeader() != this)
|
|
return true;
|
|
|
|
return false;
|
|
}
|