mirror of
https://github.com/araxiaonline/TrinityCore.git
synced 2026-06-17 13:39:46 -04:00
2518 lines
83 KiB
C++
Executable File
2518 lines
83 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2008-2012 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "BattlegroundMgr.h"
|
|
#include "CellImpl.h"
|
|
#include "Common.h"
|
|
#include "CreatureAI.h"
|
|
#include "CreatureAISelector.h"
|
|
#include "CreatureGroups.h"
|
|
#include "Creature.h"
|
|
#include "DatabaseEnv.h"
|
|
#include "Formulas.h"
|
|
#include "GameEventMgr.h"
|
|
#include "GossipDef.h"
|
|
#include "GridNotifiers.h"
|
|
#include "GridNotifiersImpl.h"
|
|
#include "Group.h"
|
|
#include "GroupMgr.h"
|
|
#include "InstanceScript.h"
|
|
#include "Log.h"
|
|
#include "LootMgr.h"
|
|
#include "MapManager.h"
|
|
#include "MoveSpline.h"
|
|
#include "MoveSplineInit.h"
|
|
#include "ObjectMgr.h"
|
|
#include "Opcodes.h"
|
|
#include "OutdoorPvPMgr.h"
|
|
#include "Player.h"
|
|
#include "PoolMgr.h"
|
|
#include "QuestDef.h"
|
|
#include "SpellAuraEffects.h"
|
|
#include "SpellMgr.h"
|
|
#include "TemporarySummon.h"
|
|
#include "Util.h"
|
|
#include "Vehicle.h"
|
|
#include "WaypointMovementGenerator.h"
|
|
#include "World.h"
|
|
#include "WorldPacket.h"
|
|
|
|
// apply implementation of the singletons
|
|
|
|
TrainerSpell const* TrainerSpellData::Find(uint32 spell_id) const
|
|
{
|
|
TrainerSpellMap::const_iterator itr = spellList.find(spell_id);
|
|
if (itr != spellList.end())
|
|
return &itr->second;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 NULL;
|
|
}
|
|
|
|
uint32 CreatureTemplate::GetRandomValidModelId() const
|
|
{
|
|
uint8 c = 0;
|
|
uint32 modelIDs[4];
|
|
|
|
if (Modelid1) modelIDs[c++] = Modelid1;
|
|
if (Modelid2) modelIDs[c++] = Modelid2;
|
|
if (Modelid3) modelIDs[c++] = Modelid3;
|
|
if (Modelid4) modelIDs[c++] = Modelid4;
|
|
|
|
return ((c>0) ? modelIDs[urand(0, c-1)] : 0);
|
|
}
|
|
|
|
uint32 CreatureTemplate::GetFirstValidModelId() const
|
|
{
|
|
if (Modelid1) return Modelid1;
|
|
if (Modelid2) return Modelid2;
|
|
if (Modelid3) return Modelid3;
|
|
if (Modelid4) return Modelid4;
|
|
return 0;
|
|
}
|
|
|
|
bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
|
|
{
|
|
if (Unit* victim = Unit::GetUnit(m_owner, m_victim))
|
|
{
|
|
while (!m_assistants.empty())
|
|
{
|
|
Creature* assistant = Unit::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);
|
|
}
|
|
}
|
|
}
|
|
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(); // since we are here, we are not TempSummon as object type cannot change during runtime
|
|
return true;
|
|
}
|
|
|
|
Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapCreature(),
|
|
lootForPickPocketed(false), lootForBody(false), m_groupLootTimer(0), lootingGroupLowGUID(0),
|
|
m_PlayerDamageReq(0), m_lootRecipient(0), m_lootRecipientGroup(0), m_corpseRemoveTime(0), m_respawnTime(0),
|
|
m_respawnDelay(300), m_corpseDelay(60), m_respawnradius(0.0f), m_reactState(REACT_AGGRESSIVE),
|
|
m_defaultMovementType(IDLE_MOTION_TYPE), m_DBTableGuid(0), m_equipmentId(0), m_AlreadyCallAssistance(false),
|
|
m_AlreadySearchedAssistance(false), m_regenHealth(true), m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL),
|
|
m_creatureInfo(NULL), m_creatureData(NULL), m_path_id(0), m_formation(NULL)
|
|
{
|
|
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
|
m_valuesCount = UNIT_END;
|
|
|
|
for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i)
|
|
m_spells[i] = 0;
|
|
|
|
m_CreatureSpellCooldowns.clear();
|
|
m_CreatureCategoryCooldowns.clear();
|
|
DisableReputationGain = false;
|
|
|
|
m_SightDistance = sWorld->getFloatConfig(CONFIG_SIGHT_MONSTER);
|
|
m_CombatDistance = 0;//MELEE_RANGE;
|
|
|
|
ResetLootMode(); // restore default loot mode
|
|
TriggerJustRespawned = false;
|
|
m_isTempWorldObject = false;
|
|
}
|
|
|
|
Creature::~Creature()
|
|
{
|
|
m_vendorItemCounts.clear();
|
|
|
|
delete i_AI;
|
|
i_AI = NULL;
|
|
|
|
//if (m_uint32Values)
|
|
// sLog->outError(LOG_FILTER_UNITS, "Deconstruct Creature Entry = %u", GetEntry());
|
|
}
|
|
|
|
void Creature::AddToWorld()
|
|
{
|
|
///- Register the creature for guid lookup
|
|
if (!IsInWorld())
|
|
{
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnCreatureCreate(this);
|
|
sObjectAccessor->AddObject(this);
|
|
Unit::AddToWorld();
|
|
SearchFormation();
|
|
AIM_Initialize();
|
|
if (IsVehicle())
|
|
GetVehicleKit()->Install();
|
|
}
|
|
}
|
|
|
|
void Creature::RemoveFromWorld()
|
|
{
|
|
if (IsInWorld())
|
|
{
|
|
if (m_zoneScript)
|
|
m_zoneScript->OnCreatureRemove(this);
|
|
if (m_formation)
|
|
sFormationMgr->RemoveCreatureFromGroup(m_formation, this);
|
|
Unit::RemoveFromWorld();
|
|
sObjectAccessor->RemoveObject(this);
|
|
}
|
|
}
|
|
|
|
void Creature::DisappearAndDie()
|
|
{
|
|
DestroyForNearbyPlayers();
|
|
//SetVisibility(VISIBILITY_OFF);
|
|
//ObjectAccessor::UpdateObjectVisibility(this);
|
|
if (isAlive())
|
|
setDeathState(JUST_DIED);
|
|
RemoveCorpse(false);
|
|
}
|
|
|
|
void Creature::SearchFormation()
|
|
{
|
|
if (isSummon())
|
|
return;
|
|
|
|
uint32 lowguid = GetDBTableGUIDLow();
|
|
if (!lowguid)
|
|
return;
|
|
|
|
CreatureGroupInfoType::iterator frmdata = sFormationMgr->CreatureGroupMap.find(lowguid);
|
|
if (frmdata != sFormationMgr->CreatureGroupMap.end())
|
|
sFormationMgr->AddCreatureToGroup(frmdata->second->leaderGUID, this);
|
|
}
|
|
|
|
void Creature::RemoveCorpse(bool setSpawnTime)
|
|
{
|
|
if (getDeathState() != CORPSE)
|
|
return;
|
|
|
|
m_corpseRemoveTime = time(NULL);
|
|
setDeathState(DEAD);
|
|
RemoveAllAuras();
|
|
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 = time(NULL) + respawnDelay;
|
|
|
|
float x, y, z, o;
|
|
GetRespawnPosition(x, y, z, &o);
|
|
SetHomePosition(x, y, z, o);
|
|
GetMap()->CreatureRelocation(this, x, y, z, o);
|
|
}
|
|
|
|
/**
|
|
* change the entry of creature until respawn
|
|
*/
|
|
bool Creature::InitEntry(uint32 Entry, uint32 /*team*/, const CreatureData* data)
|
|
{
|
|
CreatureTemplate const* normalInfo = sObjectMgr->GetCreatureTemplate(Entry);
|
|
if (!normalInfo)
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature::InitEntry creature entry %u does not exist.", Entry);
|
|
return false;
|
|
}
|
|
|
|
// get difficulty 1 mode entry
|
|
CreatureTemplate const* cinfo = normalInfo;
|
|
for (uint8 diff = uint8(GetMap()->GetSpawnMode()); diff > 0;)
|
|
{
|
|
// 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->GetFirstValidModelId()))
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature (Entry: %u) has no model defined in table `creature_template`, can't load. ", Entry);
|
|
return false;
|
|
}
|
|
|
|
uint32 displayID = sObjectMgr->ChooseDisplayId(0, GetCreatureTemplate(), data);
|
|
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&displayID);
|
|
if (!minfo) // Cancel load if no model defined
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature (Entry: %u) has no model defined in table `creature_template`, can't load. ", Entry);
|
|
return false;
|
|
}
|
|
|
|
SetDisplayId(displayID);
|
|
SetNativeDisplayId(displayID);
|
|
SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender);
|
|
|
|
// Load creature equipment
|
|
if (!data || data->equipmentId == 0) // use default from the template
|
|
LoadEquipment(cinfo->equipmentId);
|
|
else if (data && data->equipmentId != -1) // override, -1 means no equipment
|
|
LoadEquipment(data->equipmentId);
|
|
|
|
SetName(normalInfo->Name); // at normal entry always
|
|
|
|
SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, minfo->bounding_radius);
|
|
SetFloatValue(UNIT_FIELD_COMBATREACH, minfo->combat_reach);
|
|
|
|
SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
|
|
|
|
SetSpeed(MOVE_WALK, cinfo->speed_walk);
|
|
SetSpeed(MOVE_RUN, cinfo->speed_run);
|
|
SetSpeed(MOVE_SWIM, 1.0f); // using 1.0 rate
|
|
SetSpeed(MOVE_FLIGHT, 1.0f); // using 1.0 rate
|
|
|
|
SetObjectScale(cinfo->scale);
|
|
|
|
SetFloatValue(UNIT_FIELD_HOVERHEIGHT, cinfo->HoverHeight);
|
|
|
|
// checked at loading
|
|
m_defaultMovementType = MovementGeneratorType(cinfo->MovementType);
|
|
if (!m_respawnradius && m_defaultMovementType == RANDOM_MOTION_TYPE)
|
|
m_defaultMovementType = IDLE_MOTION_TYPE;
|
|
|
|
for (uint8 i=0; i < CREATURE_MAX_SPELLS; ++i)
|
|
m_spells[i] = GetCreatureTemplate()->spells[i];
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::UpdateEntry(uint32 Entry, uint32 team, const CreatureData* data)
|
|
{
|
|
if (!InitEntry(Entry, team, 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);
|
|
|
|
SelectLevel(GetCreatureTemplate());
|
|
if (team == HORDE)
|
|
setFaction(cInfo->faction_H);
|
|
else
|
|
setFaction(cInfo->faction_A);
|
|
|
|
uint32 npcflag, unit_flags, dynamicflags;
|
|
ObjectMgr::ChooseCreatureFlags(cInfo, npcflag, unit_flags, dynamicflags, data);
|
|
|
|
if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_WORLDEVENT)
|
|
SetUInt32Value(UNIT_NPC_FLAGS, npcflag | sGameEventMgr->GetNPCFlag(this));
|
|
else
|
|
SetUInt32Value(UNIT_NPC_FLAGS, npcflag);
|
|
|
|
SetAttackTime(BASE_ATTACK, cInfo->baseattacktime);
|
|
SetAttackTime(OFF_ATTACK, cInfo->baseattacktime);
|
|
SetAttackTime(RANGED_ATTACK, cInfo->rangeattacktime);
|
|
|
|
SetUInt32Value(UNIT_FIELD_FLAGS, unit_flags);
|
|
SetUInt32Value(UNIT_FIELD_FLAGS_2, cInfo->unit_flags2);
|
|
|
|
SetUInt32Value(UNIT_DYNAMIC_FLAGS, dynamicflags);
|
|
|
|
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT);
|
|
|
|
SetMeleeDamageSchool(SpellSchools(cInfo->dmgschool));
|
|
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(getLevel(), cInfo->unit_class);
|
|
float armor = (float)stats->GenerateArmor(cInfo); // TODO: Why is this treated as uint32 when it's a float?
|
|
SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, armor);
|
|
SetModifierValue(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_HOLY]));
|
|
SetModifierValue(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FIRE]));
|
|
SetModifierValue(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_NATURE]));
|
|
SetModifierValue(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_FROST]));
|
|
SetModifierValue(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_SHADOW]));
|
|
SetModifierValue(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(cInfo->resistance[SPELL_SCHOOL_ARCANE]));
|
|
|
|
SetCanModifyStats(true);
|
|
UpdateAllStats();
|
|
|
|
// checked and error show at loading templates
|
|
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction_A))
|
|
{
|
|
if (factionTemplate->factionFlags & FACTION_TEMPLATE_FLAG_PVP)
|
|
SetPvP(true);
|
|
else
|
|
SetPvP(false);
|
|
}
|
|
|
|
// trigger creature is always not selectable and can not be attacked
|
|
if (isTrigger())
|
|
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE);
|
|
|
|
InitializeReactState();
|
|
|
|
if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_NO_TAUNT)
|
|
{
|
|
ApplySpellImmune(0, IMMUNITY_STATE, SPELL_AURA_MOD_TAUNT, true);
|
|
ApplySpellImmune(0, IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, true);
|
|
}
|
|
|
|
//! Suspect it works this way:
|
|
//! If creature can walk and fly (usually with pathing)
|
|
//! Set MOVEMENTFLAG_CAN_FLY. Otherwise if it can only fly
|
|
//! Set MOVEMENTFLAG_DISABLE_GRAVITY
|
|
//! The only time I saw Movement Flags: DisableGravity, CanFly, Flying (50332672) on the same unit
|
|
//! it was a vehicle
|
|
if (cInfo->InhabitType & INHABIT_AIR && cInfo->InhabitType & INHABIT_GROUND)
|
|
SetCanFly(true);
|
|
else if (cInfo->InhabitType & INHABIT_AIR)
|
|
SetDisableGravity(true);
|
|
/*! Implemented in LoadCreatureAddon. Suspect there's a rule for UNIT_BYTE_1_FLAG_HOVER
|
|
in relation to DisableGravity also.
|
|
|
|
else if (GetByteValue(UNIT_FIELD_BYTES_1, 3) & UNIT_BYTE_1_FLAG_HOVER)
|
|
SetHover(true);
|
|
|
|
*/
|
|
|
|
// TODO: Shouldn't we check whether or not the creature is in water first?
|
|
if (cInfo->InhabitType & INHABIT_WATER)
|
|
AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Creature::Update(uint32 diff)
|
|
{
|
|
if (IsAIEnabled && TriggerJustRespawned)
|
|
{
|
|
TriggerJustRespawned = false;
|
|
AI()->JustRespawned();
|
|
if (m_vehicleKit)
|
|
m_vehicleKit->Reset();
|
|
}
|
|
|
|
if (IsInWater())
|
|
{
|
|
if (canSwim())
|
|
AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
|
}
|
|
else
|
|
{
|
|
if (canWalk())
|
|
RemoveUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
|
}
|
|
|
|
switch (m_deathState)
|
|
{
|
|
case JUST_RESPAWNED:
|
|
// Must not be called, see Creature::setDeathState JUST_RESPAWNED -> ALIVE promoting.
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature (GUID: %u Entry: %u) in wrong state: JUST_RESPAWNED (4)", GetGUIDLow(), GetEntry());
|
|
break;
|
|
case JUST_DIED:
|
|
// Must not be called, see Creature::setDeathState JUST_DIED -> CORPSE promoting.
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature (GUID: %u Entry: %u) in wrong state: JUST_DEAD (1)", GetGUIDLow(), GetEntry());
|
|
break;
|
|
case DEAD:
|
|
{
|
|
time_t now = time(NULL);
|
|
if (m_respawnTime <= now)
|
|
{
|
|
bool allowed = IsAIEnabled ? AI()->CanRespawn() : true; // First check if there are any scripts that object to us respawning
|
|
if (!allowed) // Will be rechecked on next Update call
|
|
break;
|
|
|
|
uint64 dbtableHighGuid = MAKE_NEW_GUID(m_DBTableGuid, GetEntry(), HIGHGUID_UNIT);
|
|
time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);
|
|
if (!linkedRespawntime) // Can respawn
|
|
Respawn();
|
|
else // the master is dead
|
|
{
|
|
uint64 targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid);
|
|
if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
|
|
SetRespawnTime(DAY);
|
|
else
|
|
m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime)+urand(5, MINUTE); // else copy time from master and add a little
|
|
SaveRespawnTime(); // also save to DB immediately
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case CORPSE:
|
|
{
|
|
Unit::Update(diff);
|
|
// deathstate changed on spells update, prevent problems
|
|
if (m_deathState != CORPSE)
|
|
break;
|
|
|
|
if (m_groupLootTimer && lootingGroupLowGUID)
|
|
{
|
|
if (m_groupLootTimer <= diff)
|
|
{
|
|
Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID);
|
|
if (group)
|
|
group->EndRoll(&loot);
|
|
m_groupLootTimer = 0;
|
|
lootingGroupLowGUID = 0;
|
|
}
|
|
else m_groupLootTimer -= diff;
|
|
}
|
|
else if (m_corpseRemoveTime <= time(NULL))
|
|
{
|
|
RemoveCorpse(false);
|
|
sLog->outDebug(LOG_FILTER_UNITS, "Removing corpse... %u ", GetUInt32Value(OBJECT_FIELD_ENTRY));
|
|
}
|
|
break;
|
|
}
|
|
case 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;
|
|
}
|
|
|
|
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;
|
|
|
|
if (m_regenTimer > 0)
|
|
{
|
|
if (diff >= m_regenTimer)
|
|
m_regenTimer = 0;
|
|
else
|
|
m_regenTimer -= diff;
|
|
}
|
|
|
|
if (m_regenTimer != 0)
|
|
break;
|
|
|
|
bool bInCombat = isInCombat() && (!getVictim() || // if isInCombat() is true and this has no victim
|
|
!getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself() || // or the victim/owner/charmer is not a player
|
|
!getVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()->isGameMaster()); // or the victim/owner/charmer is not a GameMaster
|
|
|
|
/*if (m_regenTimer <= diff)
|
|
{*/
|
|
if (!IsInEvadeMode() && (!bInCombat || IsPolymorphed())) // regenerate health if not in combat or if polymorphed
|
|
RegenerateHealth();
|
|
|
|
if (getPowerType() == POWER_ENERGY)
|
|
{
|
|
if (!IsVehicle() || GetVehicleKit()->GetVehicleInfo()->m_powerType != POWER_PYRITE)
|
|
Regenerate(POWER_ENERGY);
|
|
}
|
|
else
|
|
RegenerateMana();
|
|
|
|
/*if (!bIsPolymorphed) // only increase the timer if not polymorphed
|
|
m_regenTimer += CREATURE_REGEN_INTERVAL - diff;
|
|
}
|
|
else
|
|
if (!bIsPolymorphed) // if polymorphed, skip the timer
|
|
m_regenTimer -= diff;*/
|
|
m_regenTimer = CREATURE_REGEN_INTERVAL;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
sScriptMgr->OnCreatureUpdate(this, diff);
|
|
}
|
|
|
|
void Creature::RegenerateMana()
|
|
{
|
|
uint32 curValue = GetPower(POWER_MANA);
|
|
uint32 maxValue = GetMaxPower(POWER_MANA);
|
|
|
|
if (curValue >= maxValue)
|
|
return;
|
|
|
|
uint32 addvalue = 0;
|
|
|
|
// Combat and any controlled creature
|
|
if (isInCombat() || GetCharmerOrOwnerGUID())
|
|
{
|
|
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;
|
|
|
|
// Apply modifiers (if any).
|
|
AuraEffectList const& ModPowerRegenPCTAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN_PERCENT);
|
|
for (AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
|
|
if ((*i)->GetMiscValue() == POWER_MANA)
|
|
AddPct(addvalue, (*i)->GetAmount());
|
|
|
|
addvalue += GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_POWER_REGEN, POWER_MANA) * CREATURE_REGEN_INTERVAL / (5 * IN_MILLISECONDS);
|
|
|
|
ModifyPower(POWER_MANA, 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
|
|
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);
|
|
}
|
|
else
|
|
addvalue = maxValue/3;
|
|
|
|
// Apply modifiers (if any).
|
|
AuraEffectList const& ModPowerRegenPCTAuras = GetAuraEffectsByType(SPELL_AURA_MOD_HEALTH_REGEN_PERCENT);
|
|
for (AuraEffectList::const_iterator i = ModPowerRegenPCTAuras.begin(); i != ModPowerRegenPCTAuras.end(); ++i)
|
|
AddPct(addvalue, (*i)->GetAmount());
|
|
|
|
addvalue += GetTotalAuraModifier(SPELL_AURA_MOD_REGEN) * CREATURE_REGEN_INTERVAL / (5 * IN_MILLISECONDS);
|
|
|
|
ModifyHealth(addvalue);
|
|
}
|
|
|
|
void Creature::DoFleeToGetAssistance()
|
|
{
|
|
if (!getVictim())
|
|
return;
|
|
|
|
if (HasAuraType(SPELL_AURA_PREVENTS_FLEEING))
|
|
return;
|
|
|
|
float radius = sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_FLEE_ASSISTANCE_RADIUS);
|
|
if (radius >0)
|
|
{
|
|
Creature* creature = NULL;
|
|
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
cell.SetNoCreate();
|
|
Trinity::NearestAssistCreatureInCreatureRangeCheck u_check(this, getVictim(), radius);
|
|
Trinity::CreatureLastSearcher<Trinity::NearestAssistCreatureInCreatureRangeCheck> searcher(this, creature, u_check);
|
|
|
|
TypeContainerVisitor<Trinity::CreatureLastSearcher<Trinity::NearestAssistCreatureInCreatureRangeCheck>, GridTypeMapContainer > grid_creature_searcher(searcher);
|
|
|
|
cell.Visit(p, grid_creature_searcher, *GetMap(), *this, 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);
|
|
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)
|
|
{
|
|
sLog->outDebug(LOG_FILTER_TSCR, "AIM_Initialize: failed to init, locked.");
|
|
return false;
|
|
}
|
|
|
|
UnitAI* oldAI = i_AI;
|
|
|
|
Motion_Initialize();
|
|
|
|
i_AI = ai ? ai : FactorySelector::selectAI(this);
|
|
delete oldAI;
|
|
IsAIEnabled = true;
|
|
i_AI->InitializeAI();
|
|
// Initialize vehicle
|
|
if (GetVehicleKit())
|
|
GetVehicleKit()->Reset();
|
|
return true;
|
|
}
|
|
|
|
void Creature::Motion_Initialize()
|
|
{
|
|
if (!m_formation)
|
|
i_motionMaster.Initialize();
|
|
else if (m_formation->getLeader() == this)
|
|
{
|
|
m_formation->FormationReset(false);
|
|
i_motionMaster.Initialize();
|
|
}
|
|
else if (m_formation->isFormed())
|
|
i_motionMaster.MoveIdle(); //wait the order of leader
|
|
else
|
|
i_motionMaster.Initialize();
|
|
}
|
|
|
|
bool Creature::Create(uint32 guidlow, Map* map, uint32 phaseMask, uint32 Entry, uint32 vehId, uint32 team, 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)
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature::Create(): creature template (guidlow: %u, entry: %u) 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);
|
|
|
|
//oX = x; oY = y; dX = x; dY = y; m_moveTime = 0; m_startMove = 0;
|
|
if (!CreateFromProto(guidlow, Entry, vehId, team, data))
|
|
return false;
|
|
|
|
if (!IsPositionValid())
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature::Create(): given coordinates for creature (guidlow %d, entry %d) are not valid (X: %f, Y: %f, Z: %f, O: %f)", guidlow, Entry, x, y, z, ang);
|
|
return false;
|
|
}
|
|
|
|
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:
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_WORLDBOSS);
|
|
break;
|
|
default:
|
|
m_corpseDelay = sWorld->getIntConfig(CONFIG_CORPSE_DECAY_NORMAL);
|
|
break;
|
|
}
|
|
|
|
LoadCreaturesAddon();
|
|
|
|
//! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there
|
|
if (HasUnitMovementFlag(MOVEMENTFLAG_HOVER))
|
|
{
|
|
z += GetFloatValue(UNIT_FIELD_HOVERHEIGHT);
|
|
|
|
//! Relocate again with updated Z coord
|
|
Relocate(x, y, z, ang);
|
|
}
|
|
|
|
uint32 displayID = GetNativeDisplayId();
|
|
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&displayID);
|
|
if (minfo && !isTotem()) // Cancel load if no model defined or if totem
|
|
{
|
|
SetDisplayId(displayID);
|
|
SetNativeDisplayId(displayID);
|
|
SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender);
|
|
}
|
|
|
|
LastUsedScriptID = GetCreatureTemplate()->ScriptID;
|
|
|
|
// TODO: Replace with spell, handle from DB
|
|
if (isSpiritHealer() || isSpiritGuide())
|
|
{
|
|
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_GHOST);
|
|
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_GHOST);
|
|
}
|
|
|
|
if (Entry == VISUAL_WAYPOINT)
|
|
SetVisible(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::isCanTrainingOf(Player* player, bool msg) const
|
|
{
|
|
if (!isTrainer())
|
|
return false;
|
|
|
|
TrainerSpellData const* trainer_spells = GetTrainerSpells();
|
|
|
|
if ((!trainer_spells || trainer_spells->spellList.empty()) && GetCreatureTemplate()->trainer_type != TRAINER_TYPE_PETS)
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature %u (Entry: %u) have UNIT_NPC_FLAG_TRAINER but have empty trainer spell list.",
|
|
GetGUIDLow(), GetEntry());
|
|
return false;
|
|
}
|
|
|
|
switch (GetCreatureTemplate()->trainer_type)
|
|
{
|
|
case TRAINER_TYPE_CLASS:
|
|
if (player->getClass() != GetCreatureTemplate()->trainer_class)
|
|
{
|
|
if (msg)
|
|
{
|
|
player->PlayerTalkClass->ClearMenus();
|
|
switch (GetCreatureTemplate()->trainer_class)
|
|
{
|
|
case CLASS_DRUID: player->PlayerTalkClass->SendGossipMenu(4913, GetGUID()); break;
|
|
case CLASS_HUNTER: player->PlayerTalkClass->SendGossipMenu(10090, GetGUID()); break;
|
|
case CLASS_MAGE: player->PlayerTalkClass->SendGossipMenu(328, GetGUID()); break;
|
|
case CLASS_PALADIN:player->PlayerTalkClass->SendGossipMenu(1635, GetGUID()); break;
|
|
case CLASS_PRIEST: player->PlayerTalkClass->SendGossipMenu(4436, GetGUID()); break;
|
|
case CLASS_ROGUE: player->PlayerTalkClass->SendGossipMenu(4797, GetGUID()); break;
|
|
case CLASS_SHAMAN: player->PlayerTalkClass->SendGossipMenu(5003, GetGUID()); break;
|
|
case CLASS_WARLOCK:player->PlayerTalkClass->SendGossipMenu(5836, GetGUID()); break;
|
|
case CLASS_WARRIOR:player->PlayerTalkClass->SendGossipMenu(4985, GetGUID()); break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
case TRAINER_TYPE_PETS:
|
|
if (player->getClass() != CLASS_HUNTER)
|
|
{
|
|
player->PlayerTalkClass->ClearMenus();
|
|
player->PlayerTalkClass->SendGossipMenu(3620, GetGUID());
|
|
return false;
|
|
}
|
|
break;
|
|
case TRAINER_TYPE_MOUNTS:
|
|
if (GetCreatureTemplate()->trainer_race && player->getRace() != GetCreatureTemplate()->trainer_race)
|
|
{
|
|
if (msg)
|
|
{
|
|
player->PlayerTalkClass->ClearMenus();
|
|
switch (GetCreatureTemplate()->trainer_class)
|
|
{
|
|
case RACE_DWARF: player->PlayerTalkClass->SendGossipMenu(5865, GetGUID()); break;
|
|
case RACE_GNOME: player->PlayerTalkClass->SendGossipMenu(4881, GetGUID()); break;
|
|
case RACE_HUMAN: player->PlayerTalkClass->SendGossipMenu(5861, GetGUID()); break;
|
|
case RACE_NIGHTELF: player->PlayerTalkClass->SendGossipMenu(5862, GetGUID()); break;
|
|
case RACE_ORC: player->PlayerTalkClass->SendGossipMenu(5863, GetGUID()); break;
|
|
case RACE_TAUREN: player->PlayerTalkClass->SendGossipMenu(5864, GetGUID()); break;
|
|
case RACE_TROLL: player->PlayerTalkClass->SendGossipMenu(5816, GetGUID()); break;
|
|
case RACE_UNDEAD_PLAYER:player->PlayerTalkClass->SendGossipMenu(624, GetGUID()); break;
|
|
case RACE_BLOODELF: player->PlayerTalkClass->SendGossipMenu(5862, GetGUID()); break;
|
|
case RACE_DRAENEI: player->PlayerTalkClass->SendGossipMenu(5864, GetGUID()); break;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
case TRAINER_TYPE_TRADESKILLS:
|
|
if (GetCreatureTemplate()->trainer_spell && !player->HasSpell(GetCreatureTemplate()->trainer_spell))
|
|
{
|
|
if (msg)
|
|
{
|
|
player->PlayerTalkClass->ClearMenus();
|
|
player->PlayerTalkClass->SendGossipMenu(11031, GetGUID());
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
default:
|
|
return false; // checked and error output at creature_template loading
|
|
}
|
|
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))
|
|
{
|
|
player->PlayerTalkClass->ClearMenus();
|
|
switch (bgTypeId)
|
|
{
|
|
case BATTLEGROUND_AV: player->PlayerTalkClass->SendGossipMenu(7616, GetGUID()); break;
|
|
case BATTLEGROUND_WS: player->PlayerTalkClass->SendGossipMenu(7599, GetGUID()); break;
|
|
case BATTLEGROUND_AB: player->PlayerTalkClass->SendGossipMenu(7642, GetGUID()); 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: player->PlayerTalkClass->SendGossipMenu(10024, GetGUID()); break;
|
|
default: break;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Creature::isCanTrainingAndResetTalentsOf(Player* player) const
|
|
{
|
|
return player->getLevel() >= 10
|
|
&& GetCreatureTemplate()->trainer_type == TRAINER_TYPE_CLASS
|
|
&& player->getClass() == GetCreatureTemplate()->trainer_class;
|
|
}
|
|
|
|
Player* Creature::GetLootRecipient() const
|
|
{
|
|
if (!m_lootRecipient)
|
|
return NULL;
|
|
return ObjectAccessor::FindPlayer(m_lootRecipient);
|
|
}
|
|
|
|
Group* Creature::GetLootRecipientGroup() const
|
|
{
|
|
if (!m_lootRecipientGroup)
|
|
return NULL;
|
|
return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup);
|
|
}
|
|
|
|
void Creature::SetLootRecipient(Unit* unit)
|
|
{
|
|
// set the player whose group should receive the right
|
|
// to loot the creature after it dies
|
|
// should be set to NULL after the loot disappears
|
|
|
|
if (!unit)
|
|
{
|
|
m_lootRecipient = 0;
|
|
m_lootRecipientGroup = 0;
|
|
RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE|UNIT_DYNFLAG_TAPPED);
|
|
return;
|
|
}
|
|
|
|
if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle())
|
|
return;
|
|
|
|
Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself();
|
|
if (!player) // normal creature, no player involved
|
|
return;
|
|
|
|
m_lootRecipient = player->GetGUID();
|
|
if (Group* group = player->GetGroup())
|
|
m_lootRecipientGroup = group->GetLowGUID();
|
|
|
|
SetFlag(UNIT_DYNAMIC_FLAGS, 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_DBTableGuid);
|
|
if (!data)
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature::SaveToDB failed, cannot get creature data!");
|
|
return;
|
|
}
|
|
|
|
SaveToDB(GetMapId(), data->spawnMask, GetPhaseMask());
|
|
}
|
|
|
|
void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask)
|
|
{
|
|
// update in loaded data
|
|
if (!m_DBTableGuid)
|
|
m_DBTableGuid = GetGUIDLow();
|
|
CreatureData& data = sObjectMgr->NewOrExistCreatureData(m_DBTableGuid);
|
|
|
|
uint32 displayId = GetNativeDisplayId();
|
|
uint32 npcflag = GetUInt32Value(UNIT_NPC_FLAGS);
|
|
uint32 unit_flags = GetUInt32Value(UNIT_FIELD_FLAGS);
|
|
uint32 dynamicflags = GetUInt32Value(UNIT_DYNAMIC_FLAGS);
|
|
|
|
// check if it's a custom model and if not, use 0 for displayId
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
if (cinfo)
|
|
{
|
|
if (displayId == cinfo->Modelid1 || displayId == cinfo->Modelid2 ||
|
|
displayId == cinfo->Modelid3 || displayId == cinfo->Modelid4)
|
|
displayId = 0;
|
|
|
|
if (npcflag == cinfo->npcflag)
|
|
npcflag = 0;
|
|
|
|
if (unit_flags == cinfo->unit_flags)
|
|
unit_flags = 0;
|
|
|
|
if (dynamicflags == cinfo->dynamicflags)
|
|
dynamicflags = 0;
|
|
}
|
|
|
|
// data->guid = guid must not be updated at save
|
|
data.id = GetEntry();
|
|
data.mapid = mapid;
|
|
data.phaseMask = phaseMask;
|
|
data.displayid = displayId;
|
|
data.equipmentId = GetEquipmentId();
|
|
data.posX = GetPositionX();
|
|
data.posY = GetPositionY();
|
|
data.posZ = GetPositionZMinusOffset();
|
|
data.orientation = GetOrientation();
|
|
data.spawntimesecs = m_respawnDelay;
|
|
// prevent add data integrity problems
|
|
data.spawndist = GetDefaultMovementType() == IDLE_MOTION_TYPE ? 0.0f : m_respawnradius;
|
|
data.currentwaypoint = 0;
|
|
data.curhealth = GetHealth();
|
|
data.curmana = GetPower(POWER_MANA);
|
|
// prevent add data integrity problems
|
|
data.movementType = !m_respawnradius && 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
|
|
SQLTransaction trans = WorldDatabase.BeginTransaction();
|
|
|
|
PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE);
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
trans->Append(stmt);
|
|
|
|
uint8 index = 0;
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_CREATURE);
|
|
stmt->setUInt32(index++, m_DBTableGuid);
|
|
stmt->setUInt32(index++, GetEntry());
|
|
stmt->setUInt16(index++, uint16(mapid));
|
|
stmt->setUInt8(index++, spawnMask);
|
|
stmt->setUInt16(index++, uint16(GetPhaseMask()));
|
|
stmt->setUInt32(index++, displayId);
|
|
stmt->setInt32(index++, int32(GetEquipmentId()));
|
|
stmt->setFloat(index++, GetPositionX());
|
|
stmt->setFloat(index++, GetPositionY());
|
|
stmt->setFloat(index++, GetPositionZ());
|
|
stmt->setFloat(index++, GetOrientation());
|
|
stmt->setUInt32(index++, m_respawnDelay);
|
|
stmt->setFloat(index++, m_respawnradius);
|
|
stmt->setUInt32(index++, 0);
|
|
stmt->setUInt32(index++, GetHealth());
|
|
stmt->setUInt32(index++, GetPower(POWER_MANA));
|
|
stmt->setUInt8(index++, uint8(GetDefaultMovementType()));
|
|
stmt->setUInt32(index++, npcflag);
|
|
stmt->setUInt32(index++, unit_flags);
|
|
stmt->setUInt32(index++, dynamicflags);
|
|
trans->Append(stmt);
|
|
|
|
WorldDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
void Creature::SelectLevel(const CreatureTemplate* cinfo)
|
|
{
|
|
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);
|
|
SetLevel(level);
|
|
|
|
CreatureBaseStats const* stats = sObjectMgr->GetCreatureBaseStats(level, cinfo->unit_class);
|
|
|
|
// health
|
|
float healthmod = _GetHealthMod(rank);
|
|
|
|
uint32 basehp = 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)
|
|
|
|
SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)health);
|
|
SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)mana);
|
|
|
|
//damage
|
|
float damagemod = 1.0f;//_GetDamageMod(rank);
|
|
|
|
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, cinfo->mindmg * damagemod);
|
|
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, cinfo->maxdmg * damagemod);
|
|
|
|
SetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE, cinfo->minrangedmg * damagemod);
|
|
SetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE, cinfo->maxrangedmg * damagemod);
|
|
|
|
SetModifierValue(UNIT_MOD_ATTACK_POWER, BASE_VALUE, cinfo->attackpower * damagemod);
|
|
|
|
}
|
|
|
|
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(uint32 guidlow, uint32 Entry, uint32 vehId, uint32 team, const CreatureData* data)
|
|
{
|
|
SetZoneScript();
|
|
if (m_zoneScript && data)
|
|
{
|
|
Entry = m_zoneScript->GetCreatureEntry(guidlow, data);
|
|
if (!Entry)
|
|
return false;
|
|
}
|
|
|
|
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(Entry);
|
|
if (!cinfo)
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature::CreateFromProto(): creature template (guidlow: %u, entry: %u) does not exist.", guidlow, Entry);
|
|
return false;
|
|
}
|
|
|
|
SetOriginalEntry(Entry);
|
|
|
|
if (!vehId)
|
|
vehId = cinfo->VehicleId;
|
|
|
|
if (vehId && !CreateVehicleKit(vehId, Entry))
|
|
vehId = 0;
|
|
|
|
Object::_Create(guidlow, Entry, vehId ? HIGHGUID_VEHICLE : HIGHGUID_UNIT);
|
|
|
|
if (!UpdateEntry(Entry, team, data))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::LoadCreatureFromDB(uint32 guid, Map* map, bool addToMap)
|
|
{
|
|
CreatureData const* data = sObjectMgr->GetCreatureData(guid);
|
|
|
|
if (!data)
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature (GUID: %u) not found in table `creature`, can't load. ", guid);
|
|
return false;
|
|
}
|
|
|
|
m_DBTableGuid = guid;
|
|
if (map->GetInstanceId() == 0)
|
|
{
|
|
if (map->GetCreature(MAKE_NEW_GUID(guid, data->id, HIGHGUID_UNIT)))
|
|
return false;
|
|
}
|
|
else
|
|
guid = sObjectMgr->GenerateLowGuid(HIGHGUID_UNIT);
|
|
|
|
uint16 team = 0;
|
|
if (!Create(guid, map, data->phaseMask, data->id, 0, team, 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_respawnradius = data->spawndist;
|
|
|
|
m_respawnDelay = data->spawntimesecs;
|
|
m_deathState = ALIVE;
|
|
|
|
m_respawnTime = GetMap()->GetCreatureRespawnTime(m_DBTableGuid);
|
|
if (m_respawnTime) // respawn on Update
|
|
{
|
|
m_deathState = DEAD;
|
|
if (CanFly())
|
|
{
|
|
float tz = map->GetHeight(GetPhaseMask(), data->posX, data->posY, data->posZ, false);
|
|
if (data->posZ - tz > 0.1f)
|
|
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 == ALIVE ? curhealth : 0);
|
|
|
|
// checked at creature_template loading
|
|
m_defaultMovementType = MovementGeneratorType(data->movementType);
|
|
|
|
m_creatureData = data;
|
|
|
|
if (addToMap && !GetMap()->AddToMap(this))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void Creature::LoadEquipment(uint32 equip_entry, bool force)
|
|
{
|
|
if (equip_entry == 0)
|
|
{
|
|
if (force)
|
|
{
|
|
for (uint8 i = 0; i < 3; ++i)
|
|
SetUInt32Value(UNIT_VIRTUAL_ITEM_SLOT_ID + i, 0);
|
|
m_equipmentId = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
EquipmentInfo const* einfo = sObjectMgr->GetEquipmentInfo(equip_entry);
|
|
if (!einfo)
|
|
return;
|
|
|
|
m_equipmentId = equip_entry;
|
|
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_DBTableGuid)
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "Trying to delete not saved creature! LowGUID: %u, Entry: %u", GetGUIDLow(), GetEntry());
|
|
return;
|
|
}
|
|
|
|
GetMap()->RemoveCreatureRespawnTime(m_DBTableGuid);
|
|
sObjectMgr->DeleteCreatureData(m_DBTableGuid);
|
|
|
|
SQLTransaction trans = WorldDatabase.BeginTransaction();
|
|
|
|
PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE);
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE_ADDON);
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAME_EVENT_CREATURE);
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
trans->Append(stmt);
|
|
|
|
stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAME_EVENT_MODEL_EQUIP);
|
|
stmt->setUInt32(0, m_DBTableGuid);
|
|
trans->Append(stmt);
|
|
|
|
WorldDatabase.CommitTransaction(trans);
|
|
}
|
|
|
|
bool Creature::IsInvisibleDueToDespawn() const
|
|
{
|
|
if (Unit::IsInvisibleDueToDespawn())
|
|
return true;
|
|
|
|
if (isAlive() || m_corpseRemoveTime > time(NULL))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Creature::CanAlwaysSee(WorldObject const* obj) const
|
|
{
|
|
if (IsAIEnabled && AI()->CanSeeAlways(obj))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Creature::canStartAttack(Unit const* who, bool force) const
|
|
{
|
|
if (isCivilian())
|
|
return false;
|
|
|
|
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC))
|
|
return false;
|
|
|
|
// Do not attack non-combat pets
|
|
if (who->GetTypeId() == TYPEID_UNIT && who->GetCreatureType() == CREATURE_TYPE_NON_COMBAT_PET)
|
|
return false;
|
|
|
|
if (!CanFly() && (GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE + m_CombatDistance))
|
|
//|| who->IsControlledByPlayer() && who->IsFlying()))
|
|
// we cannot check flying for other creatures, too much map/vmap calculation
|
|
// TODO: should switch to range attack
|
|
return false;
|
|
|
|
if (!force)
|
|
{
|
|
if (!_IsTargetAcceptable(who))
|
|
return false;
|
|
|
|
if (who->isInCombat() && IsWithinDist(who, ATTACK_DISTANCE))
|
|
if (Unit* victim = who->getAttackerForHelper())
|
|
if (IsWithinDistInMap(victim, sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS)))
|
|
force = true;
|
|
|
|
if (!force && (IsNeutralToAll() || !IsWithinDistInMap(who, GetAttackDistance(who) + m_CombatDistance)))
|
|
return false;
|
|
}
|
|
|
|
if (!canCreatureAttack(who, force))
|
|
return false;
|
|
|
|
return IsWithinLOSInMap(who);
|
|
}
|
|
|
|
float Creature::GetAttackDistance(Unit const* player) const
|
|
{
|
|
float aggroRate = sWorld->getRate(RATE_CREATURE_AGGRO);
|
|
if (aggroRate == 0)
|
|
return 0.0f;
|
|
|
|
uint32 playerlevel = player->getLevelForTarget(this);
|
|
uint32 creaturelevel = getLevelForTarget(player);
|
|
|
|
int32 leveldif = int32(playerlevel) - 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 (leveldif < - 25)
|
|
leveldif = -25;
|
|
|
|
// "The aggro radius of a mob having the same level as the player is roughly 20 yards"
|
|
float RetDistance = 20;
|
|
|
|
// "Aggro Radius varies with level difference at a rate of roughly 1 yard/level"
|
|
// radius grow if playlevel < creaturelevel
|
|
RetDistance -= (float)leveldif;
|
|
|
|
if (creaturelevel+5 <= sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL))
|
|
{
|
|
// detect range auras
|
|
RetDistance += GetTotalAuraModifier(SPELL_AURA_MOD_DETECT_RANGE);
|
|
|
|
// detected range auras
|
|
RetDistance += player->GetTotalAuraModifier(SPELL_AURA_MOD_DETECTED_RANGE);
|
|
}
|
|
|
|
// "Minimum Aggro Radius for a mob seems to be combat range (5 yards)"
|
|
if (RetDistance < 5)
|
|
RetDistance = 5;
|
|
|
|
return (RetDistance*aggroRate);
|
|
}
|
|
|
|
void Creature::setDeathState(DeathState s)
|
|
{
|
|
Unit::setDeathState(s);
|
|
|
|
if (s == JUST_DIED)
|
|
{
|
|
m_corpseRemoveTime = time(NULL) + m_corpseDelay;
|
|
m_respawnTime = time(NULL) + m_respawnDelay + m_corpseDelay;
|
|
|
|
// always save boss respawn time at death to prevent crash cheating
|
|
if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY) || isWorldBoss())
|
|
SaveRespawnTime();
|
|
|
|
SetTarget(0); // remove target selection in any cases (can be set at aura remove in Unit::setDeathState)
|
|
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
|
|
|
|
setActive(false);
|
|
|
|
if (!isPet() && GetCreatureTemplate()->SkinLootId)
|
|
if (LootTemplates_Skinning.HaveLootFor(GetCreatureTemplate()->SkinLootId))
|
|
if (hasLootRecipient())
|
|
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
|
|
|
|
if (HasSearchedAssistance())
|
|
{
|
|
SetNoSearchAssistance(false);
|
|
UpdateSpeed(MOVE_RUN, false);
|
|
}
|
|
|
|
//Dismiss group if is leader
|
|
if (m_formation && m_formation->getLeader() == this)
|
|
m_formation->FormationReset(true);
|
|
|
|
if ((CanFly() || IsFlying()))
|
|
i_motionMaster.MoveFall();
|
|
|
|
Unit::setDeathState(CORPSE);
|
|
}
|
|
else if (s == JUST_RESPAWNED)
|
|
{
|
|
//if (isPet())
|
|
// setActive(true);
|
|
SetFullHealth();
|
|
SetLootRecipient(NULL);
|
|
ResetPlayerDamageReq();
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
SetWalk(true);
|
|
if (cinfo->InhabitType & INHABIT_AIR && cinfo->InhabitType & INHABIT_GROUND)
|
|
SetCanFly(true);
|
|
else if (cinfo->InhabitType & INHABIT_AIR)
|
|
SetDisableGravity(true);
|
|
if (cinfo->InhabitType & INHABIT_WATER)
|
|
AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
|
|
SetUInt32Value(UNIT_NPC_FLAGS, cinfo->npcflag);
|
|
ClearUnitState(uint32(UNIT_STATE_ALL_STATE));
|
|
SetMeleeDamageSchool(SpellSchools(cinfo->dmgschool));
|
|
LoadCreaturesAddon(true);
|
|
Motion_Initialize();
|
|
if (GetCreatureData() && GetPhaseMask() != GetCreatureData()->phaseMask)
|
|
SetPhaseMask(GetCreatureData()->phaseMask, false);
|
|
Unit::setDeathState(ALIVE);
|
|
}
|
|
}
|
|
|
|
void Creature::Respawn(bool force)
|
|
{
|
|
DestroyForNearbyPlayers();
|
|
|
|
if (force)
|
|
{
|
|
if (isAlive())
|
|
setDeathState(JUST_DIED);
|
|
else if (getDeathState() != CORPSE)
|
|
setDeathState(CORPSE);
|
|
}
|
|
|
|
RemoveCorpse(false);
|
|
|
|
if (getDeathState() == DEAD)
|
|
{
|
|
if (m_DBTableGuid)
|
|
GetMap()->RemoveCreatureRespawnTime(m_DBTableGuid);
|
|
|
|
sLog->outDebug(LOG_FILTER_UNITS, "Respawning creature %s (GuidLow: %u, Full GUID: " UI64FMTD " Entry: %u)",
|
|
GetName().c_str(), GetGUIDLow(), GetGUID(), GetEntry());
|
|
m_respawnTime = 0;
|
|
lootForPickPocketed = false;
|
|
lootForBody = false;
|
|
|
|
if (m_originalEntry != GetEntry())
|
|
UpdateEntry(m_originalEntry);
|
|
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
SelectLevel(cinfo);
|
|
|
|
setDeathState(JUST_RESPAWNED);
|
|
|
|
uint32 displayID = GetNativeDisplayId();
|
|
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&displayID);
|
|
if (minfo) // Cancel load if no model defined
|
|
{
|
|
SetDisplayId(displayID);
|
|
SetNativeDisplayId(displayID);
|
|
SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender);
|
|
}
|
|
|
|
GetMotionMaster()->InitDefault();
|
|
|
|
//Call AI respawn virtual function
|
|
if (IsAIEnabled)
|
|
TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing
|
|
|
|
uint32 poolid = GetDBTableGUIDLow() ? sPoolMgr->IsPartOfAPool<Creature>(GetDBTableGUIDLow()) : 0;
|
|
if (poolid)
|
|
sPoolMgr->UpdatePool<Creature>(poolid, GetDBTableGUIDLow());
|
|
|
|
//Re-initialize reactstate that could be altered by movementgenerators
|
|
InitializeReactState();
|
|
}
|
|
|
|
UpdateObjectVisibility();
|
|
}
|
|
|
|
void Creature::ForcedDespawn(uint32 timeMSToDespawn)
|
|
{
|
|
if (timeMSToDespawn)
|
|
{
|
|
ForcedDespawnDelayEvent* pEvent = new ForcedDespawnDelayEvent(*this);
|
|
|
|
m_Events.AddEvent(pEvent, m_Events.CalculateTime(timeMSToDespawn));
|
|
return;
|
|
}
|
|
|
|
if (isAlive())
|
|
setDeathState(JUST_DIED);
|
|
|
|
RemoveCorpse(false);
|
|
}
|
|
|
|
void Creature::DespawnOrUnsummon(uint32 msTimeToDespawn /*= 0*/)
|
|
{
|
|
if (TempSummon* summon = this->ToTempSummon())
|
|
summon->UnSummon(msTimeToDespawn);
|
|
else
|
|
ForcedDespawn(msTimeToDespawn);
|
|
}
|
|
|
|
bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo)
|
|
{
|
|
if (!spellInfo)
|
|
return false;
|
|
|
|
// Creature is immune to main mechanic of the spell
|
|
if (GetCreatureTemplate()->MechanicImmuneMask & (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())
|
|
continue;
|
|
if (!IsImmunedToSpellEffect(spellInfo, i))
|
|
{
|
|
immunedToAllEffects = false;
|
|
break;
|
|
}
|
|
}
|
|
if (immunedToAllEffects)
|
|
return true;
|
|
|
|
return Unit::IsImmunedToSpell(spellInfo);
|
|
}
|
|
|
|
bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const
|
|
{
|
|
if (GetCreatureTemplate()->MechanicImmuneMask & (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 NULL;
|
|
|
|
for (uint32 i=0; i < CREATURE_MAX_SPELLS; ++i)
|
|
{
|
|
if (!m_spells[i])
|
|
continue;
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(m_spells[i]);
|
|
if (!spellInfo)
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "WORLD: unknown spell id %i", 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 && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
|
|
continue;
|
|
return spellInfo;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
SpellInfo const* Creature::reachWithSpellCure(Unit* victim)
|
|
{
|
|
if (!victim)
|
|
return NULL;
|
|
|
|
for (uint32 i=0; i < CREATURE_MAX_SPELLS; ++i)
|
|
{
|
|
if (!m_spells[i])
|
|
continue;
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(m_spells[i]);
|
|
if (!spellInfo)
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "WORLD: unknown spell id %i", 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 (!isInFront(victim, range) && spellInfo->AttributesEx)
|
|
// continue;
|
|
if (dist > range || dist < minrange)
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_SILENCE && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
|
|
continue;
|
|
if (spellInfo->PreventionType == SPELL_PREVENTION_TYPE_PACIFY && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
|
|
continue;
|
|
return spellInfo;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// select nearest hostile unit within the given distance (regardless of threat list).
|
|
Unit* Creature::SelectNearestTarget(float dist) const
|
|
{
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
cell.SetNoCreate();
|
|
|
|
Unit* target = NULL;
|
|
|
|
{
|
|
if (dist == 0.0f)
|
|
dist = MAX_VISIBILITY_DISTANCE;
|
|
|
|
Trinity::NearestHostileUnitCheck u_check(this, dist);
|
|
Trinity::UnitLastSearcher<Trinity::NearestHostileUnitCheck> searcher(this, target, u_check);
|
|
|
|
TypeContainerVisitor<Trinity::UnitLastSearcher<Trinity::NearestHostileUnitCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
|
|
TypeContainerVisitor<Trinity::UnitLastSearcher<Trinity::NearestHostileUnitCheck>, GridTypeMapContainer > grid_unit_searcher(searcher);
|
|
|
|
cell.Visit(p, world_unit_searcher, *GetMap(), *this, dist);
|
|
cell.Visit(p, grid_unit_searcher, *GetMap(), *this, dist);
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
// 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
|
|
{
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
cell.SetNoCreate();
|
|
|
|
Unit* target = NULL;
|
|
|
|
if (dist > MAX_VISIBILITY_DISTANCE)
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature (GUID: %u Entry: %u) SelectNearestTargetInAttackDistance called with dist > MAX_VISIBILITY_DISTANCE. Distance set to ATTACK_DISTANCE.", GetGUIDLow(), GetEntry());
|
|
dist = ATTACK_DISTANCE;
|
|
}
|
|
|
|
{
|
|
Trinity::NearestHostileUnitInAttackDistanceCheck u_check(this, dist);
|
|
Trinity::UnitLastSearcher<Trinity::NearestHostileUnitInAttackDistanceCheck> searcher(this, target, u_check);
|
|
|
|
TypeContainerVisitor<Trinity::UnitLastSearcher<Trinity::NearestHostileUnitInAttackDistanceCheck>, WorldTypeMapContainer > world_unit_searcher(searcher);
|
|
TypeContainerVisitor<Trinity::UnitLastSearcher<Trinity::NearestHostileUnitInAttackDistanceCheck>, GridTypeMapContainer > grid_unit_searcher(searcher);
|
|
|
|
cell.Visit(p, world_unit_searcher, *GetMap(), *this, ATTACK_DISTANCE > dist ? ATTACK_DISTANCE : dist);
|
|
cell.Visit(p, grid_unit_searcher, *GetMap(), *this, ATTACK_DISTANCE > dist ? ATTACK_DISTANCE : dist);
|
|
}
|
|
|
|
return target;
|
|
}
|
|
|
|
Player* Creature::SelectNearestPlayer(float distance) const
|
|
{
|
|
Player* target = NULL;
|
|
|
|
Trinity::NearestPlayerInObjectRangeCheck checker(this, distance);
|
|
Trinity::PlayerLastSearcher<Trinity::NearestPlayerInObjectRangeCheck> searcher(this, target, checker);
|
|
VisitNearbyObject(distance, searcher);
|
|
|
|
return target;
|
|
}
|
|
|
|
void Creature::SendAIReaction(AiReaction reactionType)
|
|
{
|
|
WorldPacket data(SMSG_AI_REACTION, 12);
|
|
|
|
data << uint64(GetGUID());
|
|
data << uint32(reactionType);
|
|
|
|
((WorldObject*)this)->SendMessageToSet(&data, true);
|
|
|
|
sLog->outDebug(LOG_FILTER_NETWORKIO, "WORLD: Sent SMSG_AI_REACTION, type %u.", reactionType);
|
|
}
|
|
|
|
void Creature::CallAssistance()
|
|
{
|
|
if (!m_AlreadyCallAssistance && getVictim() && !isPet() && !isCharmed())
|
|
{
|
|
SetNoCallAssistance(true);
|
|
|
|
float radius = sWorld->getFloatConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_RADIUS);
|
|
|
|
if (radius > 0)
|
|
{
|
|
std::list<Creature*> assistList;
|
|
|
|
{
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
cell.SetNoCreate();
|
|
|
|
Trinity::AnyAssistCreatureInRangeCheck u_check(this, getVictim(), radius);
|
|
Trinity::CreatureListSearcher<Trinity::AnyAssistCreatureInRangeCheck> searcher(this, assistList, u_check);
|
|
|
|
TypeContainerVisitor<Trinity::CreatureListSearcher<Trinity::AnyAssistCreatureInRangeCheck>, GridTypeMapContainer > grid_creature_searcher(searcher);
|
|
|
|
cell.Visit(p, grid_creature_searcher, *GetMap(), *this, radius);
|
|
}
|
|
|
|
if (!assistList.empty())
|
|
{
|
|
AssistDelayEvent* e = new AssistDelayEvent(getVictim()->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.AddEvent(e, m_Events.CalculateTime(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_DELAY)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::CallForHelp(float radius)
|
|
{
|
|
if (radius <= 0.0f || !getVictim() || isPet() || isCharmed())
|
|
return;
|
|
|
|
CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()));
|
|
Cell cell(p);
|
|
cell.SetNoCreate();
|
|
|
|
Trinity::CallOfHelpCreatureInRangeDo u_do(this, getVictim(), radius);
|
|
Trinity::CreatureWorker<Trinity::CallOfHelpCreatureInRangeDo> worker(this, u_do);
|
|
|
|
TypeContainerVisitor<Trinity::CreatureWorker<Trinity::CallOfHelpCreatureInRangeDo>, GridTypeMapContainer > grid_creature_searcher(worker);
|
|
|
|
cell.Visit(p, grid_creature_searcher, *GetMap(), *this, radius);
|
|
}
|
|
|
|
bool Creature::CanAssistTo(const Unit* u, const Unit* 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;
|
|
|
|
// we don't need help from non-combatant ;)
|
|
if (isCivilian())
|
|
return false;
|
|
|
|
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE | UNIT_FLAG_IMMUNE_TO_NPC))
|
|
return false;
|
|
|
|
// skip fighting creature
|
|
if (isInCombat())
|
|
return false;
|
|
|
|
// only free creature
|
|
if (GetCharmerOrOwnerGUID())
|
|
return false;
|
|
|
|
// only from same creature faction
|
|
if (checkfaction)
|
|
{
|
|
if (getFaction() != u->getFaction())
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (!IsFriendlyTo(u))
|
|
return false;
|
|
}
|
|
|
|
// skip non hostile to caster enemy creatures
|
|
if (!IsHostileTo(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(const Unit* target) const
|
|
{
|
|
ASSERT(target);
|
|
|
|
// if the target cannot be attacked, the target is not acceptable
|
|
if (IsFriendlyTo(target)
|
|
|| !target->isTargetableForAttack(false)
|
|
|| (m_vehicle && (IsOnVehicle(target) || m_vehicle->GetBase()->IsOnVehicle(target))))
|
|
return false;
|
|
|
|
if (target->HasUnitState(UNIT_STATE_DIED))
|
|
{
|
|
// guards can detect fake death
|
|
if (isGuard() && target->HasFlag(UNIT_FIELD_FLAGS_2, UNIT_FLAG2_FEIGN_DEATH))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
const Unit* myVictim = getAttackerForHelper();
|
|
const Unit* targetVictim = target->getAttackerForHelper();
|
|
|
|
// if I'm already fighting target, or I'm hostile towards the target, the target is acceptable
|
|
if (myVictim == target || targetVictim == this || IsHostileTo(target))
|
|
return true;
|
|
|
|
// if the target's victim is friendly, and the target is neutral, the target is acceptable
|
|
if (targetVictim && 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::SaveRespawnTime()
|
|
{
|
|
if (isSummon() || !m_DBTableGuid || (m_creatureData && !m_creatureData->dbData))
|
|
return;
|
|
|
|
GetMap()->SaveCreatureRespawnTime(m_DBTableGuid, m_respawnTime);
|
|
}
|
|
|
|
// this should not be called by petAI or
|
|
bool Creature::canCreatureAttack(Unit const* victim, bool /*force*/) 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;
|
|
|
|
if (sMapStore.LookupEntry(GetMapId())->IsDungeon())
|
|
return true;
|
|
|
|
//Use AttackDistance in distance check if threat radius is lower. This prevents creature bounce in and out of combat every update tick.
|
|
float dist = std::max(GetAttackDistance(victim), sWorld->getFloatConfig(CONFIG_THREAT_RADIUS)) + m_CombatDistance;
|
|
|
|
if (Unit* unit = GetCharmerOrOwner())
|
|
return victim->IsWithinDist(unit, dist);
|
|
else
|
|
return victim->IsInDist(&m_homePosition, dist);
|
|
}
|
|
|
|
CreatureAddon const* Creature::GetCreatureAddon() const
|
|
{
|
|
if (m_DBTableGuid)
|
|
{
|
|
if (CreatureAddon const* addon = sObjectMgr->GetCreatureAddon(m_DBTableGuid))
|
|
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, 0, uint8(cainfo->bytes1 & 0xFF));
|
|
//SetByteValue(UNIT_FIELD_BYTES_1, 1, uint8((cainfo->bytes1 >> 8) & 0xFF));
|
|
SetByteValue(UNIT_FIELD_BYTES_1, 1, 0);
|
|
SetByteValue(UNIT_FIELD_BYTES_1, 2, uint8((cainfo->bytes1 >> 16) & 0xFF));
|
|
SetByteValue(UNIT_FIELD_BYTES_1, 3, uint8((cainfo->bytes1 >> 24) & 0xFF));
|
|
|
|
//! Suspected correlation between UNIT_FIELD_BYTES_1, offset 3, value 0x2:
|
|
//! If no inhabittype_fly (if no MovementFlag_DisableGravity flag found in sniffs)
|
|
//! Set MovementFlag_Hover. Otherwise do nothing.
|
|
if (GetByteValue(UNIT_FIELD_BYTES_1, 3) & UNIT_BYTE1_FLAG_HOVER && !IsLevitating())
|
|
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);
|
|
}
|
|
|
|
if (cainfo->emote != 0)
|
|
SetUInt32Value(UNIT_NPC_EMOTESTATE, cainfo->emote);
|
|
|
|
//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)
|
|
{
|
|
sLog->outError(LOG_FILTER_SQL, "Creature (GUID: %u Entry: %u) has wrong spell %u defined in `auras` field.", GetGUIDLow(), GetEntry(), *itr);
|
|
continue;
|
|
}
|
|
|
|
// skip already applied aura
|
|
if (HasAura(*itr))
|
|
{
|
|
if (!reload)
|
|
sLog->outError(LOG_FILTER_SQL, "Creature (GUID: %u Entry: %u) has duplicate aura (spell %u) in `auras` field.", GetGUIDLow(), GetEntry(), *itr);
|
|
|
|
continue;
|
|
}
|
|
|
|
AddAura(*itr, this);
|
|
sLog->outDebug(LOG_FILTER_UNITS, "Spell: %u added to creature (GUID: %u Entry: %u)", *itr, GetGUIDLow(), GetEntry());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Send a message to LocalDefense channel for players opposition team in the zone
|
|
void Creature::SendZoneUnderAttackMessage(Player* attacker)
|
|
{
|
|
uint32 enemy_team = attacker->GetTeam();
|
|
|
|
WorldPacket data(SMSG_ZONE_UNDER_ATTACK, 4);
|
|
data << (uint32)GetAreaId();
|
|
sWorld->SendGlobalMessage(&data, NULL, (enemy_team == ALLIANCE ? HORDE : ALLIANCE));
|
|
}
|
|
|
|
void Creature::SetInCombatWithZone()
|
|
{
|
|
if (!CanHaveThreatList())
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature entry %u call SetInCombatWithZone but creature cannot have threat list.", GetEntry());
|
|
return;
|
|
}
|
|
|
|
Map* map = GetMap();
|
|
|
|
if (!map->IsDungeon())
|
|
{
|
|
sLog->outError(LOG_FILTER_UNITS, "Creature entry %u call SetInCombatWithZone for map (id: %u) that isn't an instance.", GetEntry(), map->GetId());
|
|
return;
|
|
}
|
|
|
|
Map::PlayerList const &PlList = map->GetPlayers();
|
|
|
|
if (PlList.isEmpty())
|
|
return;
|
|
|
|
for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i)
|
|
{
|
|
if (Player* player = i->getSource())
|
|
{
|
|
if (player->isGameMaster())
|
|
continue;
|
|
|
|
if (player->isAlive())
|
|
{
|
|
this->SetInCombatWith(player);
|
|
player->SetInCombatWith(this);
|
|
AddThreat(player, 0.0f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Creature::_AddCreatureSpellCooldown(uint32 spell_id, time_t end_time)
|
|
{
|
|
m_CreatureSpellCooldowns[spell_id] = end_time;
|
|
}
|
|
|
|
void Creature::_AddCreatureCategoryCooldown(uint32 category, time_t apply_time)
|
|
{
|
|
m_CreatureCategoryCooldowns[category] = apply_time;
|
|
}
|
|
|
|
void Creature::AddCreatureSpellCooldown(uint32 spellid)
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellid);
|
|
if (!spellInfo)
|
|
return;
|
|
|
|
uint32 cooldown = spellInfo->GetRecoveryTime();
|
|
if (Player* modOwner = GetSpellModOwner())
|
|
modOwner->ApplySpellMod(spellid, SPELLMOD_COOLDOWN, cooldown);
|
|
|
|
if (cooldown)
|
|
_AddCreatureSpellCooldown(spellid, time(NULL) + cooldown/IN_MILLISECONDS);
|
|
|
|
if (spellInfo->Category)
|
|
_AddCreatureCategoryCooldown(spellInfo->Category, time(NULL));
|
|
}
|
|
|
|
bool Creature::HasCategoryCooldown(uint32 spell_id) const
|
|
{
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id);
|
|
if (!spellInfo)
|
|
return false;
|
|
|
|
CreatureSpellCooldowns::const_iterator itr = m_CreatureCategoryCooldowns.find(spellInfo->Category);
|
|
return(itr != m_CreatureCategoryCooldowns.end() && time_t(itr->second + (spellInfo->CategoryRecoveryTime / IN_MILLISECONDS)) > time(NULL));
|
|
}
|
|
|
|
bool Creature::HasSpellCooldown(uint32 spell_id) const
|
|
{
|
|
CreatureSpellCooldowns::const_iterator itr = m_CreatureSpellCooldowns.find(spell_id);
|
|
return (itr != m_CreatureSpellCooldowns.end() && itr->second > time(NULL)) || HasCategoryCooldown(spell_id);
|
|
}
|
|
|
|
void Creature::ProhibitSpellSchool(SpellSchoolMask idSchoolMask, uint32 unTimeMs)
|
|
{
|
|
time_t curTime = time(NULL);
|
|
for (uint8 i = 0; i < CREATURE_MAX_SPELLS; ++i)
|
|
{
|
|
if (m_spells[i] == 0)
|
|
continue;
|
|
|
|
uint32 unSpellId = m_spells[i];
|
|
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(unSpellId);
|
|
if (!spellInfo)
|
|
{
|
|
ASSERT(spellInfo);
|
|
continue;
|
|
}
|
|
|
|
// Not send cooldown for this spells
|
|
if (spellInfo->Attributes & SPELL_ATTR0_DISABLED_WHILE_ACTIVE)
|
|
continue;
|
|
|
|
if (spellInfo->PreventionType != SPELL_PREVENTION_TYPE_SILENCE)
|
|
continue;
|
|
|
|
if ((idSchoolMask & spellInfo->GetSchoolMask()) && GetCreatureSpellCooldownDelay(unSpellId) < unTimeMs)
|
|
{
|
|
_AddCreatureSpellCooldown(unSpellId, curTime + unTimeMs/IN_MILLISECONDS);
|
|
if (UnitAI* ai = GetAI())
|
|
ai->SpellInterrupted(unSpellId, unTimeMs);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Creature::HasSpell(uint32 spellID) const
|
|
{
|
|
uint8 i;
|
|
for (i = 0; i < CREATURE_MAX_SPELLS; ++i)
|
|
if (spellID == m_spells[i])
|
|
break;
|
|
return i < CREATURE_MAX_SPELLS; //broke before end of iteration of known spells
|
|
}
|
|
|
|
time_t Creature::GetRespawnTimeEx() const
|
|
{
|
|
time_t now = time(NULL);
|
|
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_DBTableGuid)
|
|
{
|
|
if (CreatureData const* data = sObjectMgr->GetCreatureData(GetDBTableGUIDLow()))
|
|
{
|
|
x = data->posX;
|
|
y = data->posY;
|
|
z = data->posZ;
|
|
if (ori)
|
|
*ori = data->orientation;
|
|
if (dist)
|
|
*dist = data->spawndist;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
x = GetPositionX();
|
|
y = GetPositionY();
|
|
z = GetPositionZ();
|
|
if (ori)
|
|
*ori = GetOrientation();
|
|
if (dist)
|
|
*dist = 0;
|
|
}
|
|
|
|
void Creature::AllLootRemovedFromCorpse()
|
|
{
|
|
if (!HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE))
|
|
{
|
|
time_t now = time(NULL);
|
|
if (m_corpseRemoveTime <= now)
|
|
return;
|
|
|
|
float decayRate;
|
|
CreatureTemplate const* cinfo = GetCreatureTemplate();
|
|
|
|
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 (cinfo && cinfo->SkinLootId)
|
|
m_corpseRemoveTime = time(NULL);
|
|
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 Creature::GetAIName() const
|
|
{
|
|
return sObjectMgr->GetCreatureTemplate(GetEntry())->AIName;
|
|
}
|
|
|
|
std::string Creature::GetScriptName() const
|
|
{
|
|
return sObjectMgr->GetScriptName(GetScriptId());
|
|
}
|
|
|
|
uint32 Creature::GetScriptId() const
|
|
{
|
|
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 = time(NULL);
|
|
|
|
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 = time(NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
TrainerSpellData const* Creature::GetTrainerSpells() const
|
|
{
|
|
return sObjectMgr->GetNpcTrainerSpells(GetEntry());
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
|
|
//Do not if this works or not, moving creature to another map is very dangerous
|
|
void Creature::FarTeleportTo(Map* map, float X, float Y, float Z, float O)
|
|
{
|
|
CleanupBeforeRemoveFromMap(false);
|
|
GetMap()->RemoveFromMap(this, false);
|
|
Relocate(X, Y, Z, O);
|
|
SetMap(map);
|
|
GetMap()->AddToMap(this);
|
|
}
|
|
|
|
void Creature::SetPosition(float x, float y, float z, float o)
|
|
{
|
|
// prevent crash when a bad coord is sent by the client
|
|
if (!Trinity::IsValidMapCoord(x, y, z, o))
|
|
{
|
|
sLog->outDebug(LOG_FILTER_UNITS, "Creature::SetPosition(%f, %f, %f) .. bad coordinates!", x, y, z);
|
|
return;
|
|
}
|
|
|
|
GetMap()->CreatureRelocation(ToCreature(), x, y, z, o);
|
|
if (IsVehicle())
|
|
GetVehicleKit()->RelocatePassengers();
|
|
}
|
|
|
|
bool Creature::IsDungeonBoss() const
|
|
{
|
|
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(GetEntry());
|
|
return cinfo && (cinfo->flags_extra & CREATURE_FLAG_EXTRA_DUNGEON_BOSS);
|
|
}
|
|
|
|
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.append(GetPackGUID());
|
|
SendMessageToSet(&data, false);
|
|
return true;
|
|
}
|
|
|
|
bool Creature::SetDisableGravity(bool disable, bool packetOnly/*=false*/)
|
|
{
|
|
//! It's possible only a packet is sent but moveflags are not updated
|
|
//! Need more research on this
|
|
if (!packetOnly && !Unit::SetDisableGravity(disable))
|
|
return false;
|
|
|
|
if (!movespline->Initialized())
|
|
return true;
|
|
|
|
WorldPacket data(disable ? SMSG_SPLINE_MOVE_GRAVITY_DISABLE : SMSG_SPLINE_MOVE_GRAVITY_ENABLE, 9);
|
|
data.append(GetPackGUID());
|
|
SendMessageToSet(&data, false);
|
|
return true;
|
|
}
|
|
|
|
bool Creature::SetHover(bool enable)
|
|
{
|
|
if (!Unit::SetHover(enable))
|
|
return false;
|
|
|
|
//! Unconfirmed for players:
|
|
if (enable)
|
|
SetByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER);
|
|
else
|
|
RemoveByteFlag(UNIT_FIELD_BYTES_1, 3, UNIT_BYTE1_FLAG_HOVER);
|
|
|
|
if (!movespline->Initialized())
|
|
return true;
|
|
|
|
//! Not always a packet is sent
|
|
WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_HOVER : SMSG_SPLINE_MOVE_UNSET_HOVER, 9);
|
|
data.append(GetPackGUID());
|
|
SendMessageToSet(&data, false);
|
|
return true;
|
|
}
|