Files
Viano-Core/src/server/game/Entities/Creature/Creature.cpp
T
Machiavelli dbbac0bdaa Core/Movement: Implement spline movement subsystem.
Spline movement controls movements of server-side controlled units (monster movement, taxi movement, etc).
Proper implementation of effects such as charge, jump, cyclic movement will rely on it.
However, need improve our states system before.

Technical changes:

* Added linear, catmullrom and bezier3 splines which based on client's algorthims. They can be reused for proper transport position interpolation.
* Precission increased. There are no more position desync issues since client's position calculation formulas used.
* Now possible to move by paths with multiple points, send whole path to client.

--
Original author of research and implementation: SilverIce. Massive kudos.
Original port for Trinity (ref #4629) Chaplain and Venugh
With the following incremental fixes during my review:

- Restore flightmaster end grid pre-loading
- Fix uninitialized Creature::m_path_id
- Add missing trinity_string entries for .movegens command
- Fix a bug in WaypointMovementGenerator that would trigger unexpected pausing at waypoints for various amounts of time

Known issues:
- Errors like WaypointMovementGenerator::LoadPath creature XXX (Entry: YYYYY GUID: ZZZZZZ) doesn't have waypoint path id: 0.
This is caused by bad DB data. This commit didn't "break" it.

Do not forget to re-run CMake before compiling.
2012-01-14 15:36:07 +01:00

2432 lines
80 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 "Common.h"
#include "DatabaseEnv.h"
#include "WorldPacket.h"
#include "World.h"
#include "ObjectMgr.h"
#include "GroupMgr.h"
#include "SpellMgr.h"
#include "Creature.h"
#include "QuestDef.h"
#include "GossipDef.h"
#include "Player.h"
#include "PoolMgr.h"
#include "Opcodes.h"
#include "Log.h"
#include "LootMgr.h"
#include "MapManager.h"
#include "CreatureAI.h"
#include "CreatureAISelector.h"
#include "Formulas.h"
#include "WaypointMovementGenerator.h"
#include "InstanceScript.h"
#include "BattlegroundMgr.h"
#include "Util.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "CellImpl.h"
#include "OutdoorPvPMgr.h"
#include "GameEventMgr.h"
#include "CreatureGroups.h"
#include "Vehicle.h"
#include "SpellAuraEffects.h"
#include "Group.h"
#include "MoveSplineInit.h"
#include "MoveSpline.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.ForcedDespawn();
return true;
}
Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapCreature(),
lootForPickPocketed(false), lootForBody(false), m_groupLootTimer(0), lootingGroupLowGUID(0),
m_PlayerDamageReq(0), m_lootMoney(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_formation(NULL), m_path_id(0)
{
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("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)
FormationMgr::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 = CreatureGroupMap.find(lowguid);
if (frmdata != CreatureGroupMap.end())
FormationMgr::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->outErrorDb("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->outErrorDb("Creature (Entry: %u) has no model defined in table `creature_template`, can't load. ", Entry);
return false;
}
uint32 displayID = sObjectMgr->ChooseDisplayId(0, GetCreatureInfo(), data);
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&displayID);
if (!minfo) // Cancel load if no model defined
{
sLog->outErrorDb("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
SetFloatValue(OBJECT_FIELD_SCALE_X, cinfo->scale);
SetLevitate(canFly());
// 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] = GetCreatureInfo()->spells[i];
return true;
}
bool Creature::UpdateEntry(uint32 Entry, uint32 team, const CreatureData* data)
{
if (!InitEntry(Entry, team, data))
return false;
CreatureTemplate const* cInfo = GetCreatureInfo();
m_regenHealth = cInfo->RegenHealth;
// creatures always have melee weapon ready if any
SetSheath(SHEATH_STATE_MELEE);
SelectLevel(GetCreatureInfo());
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_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);
}
// TODO: In fact monster move flags should be set - not movement flags.
if (cInfo->InhabitType & INHABIT_AIR)
AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_FLYING);
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_ALIVED:
// Must not be called, see Creature::setDeathState JUST_ALIVED -> ALIVE promoting.
sLog->outError("Creature (GUID: %u Entry: %u) in wrong state: JUST_ALIVED (4)", GetGUIDLow(), GetEntry());
break;
case JUST_DIED:
// Must not be called, see Creature::setDeathState JUST_DIED -> CORPSE promoting.
sLog->outError("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 = sObjectMgr->GetLinkedRespawnTime(dbtableHighGuid, GetMap()->GetInstanceId());
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->outStaticDebug("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)
AddPctN(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)
AddPctN(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_STAT_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->outErrorDb("Creature::Create(): creature template (guidlow: %u, entry: %u) does not exist.", guidlow, Entry);
return false;
}
Relocate(x, y, z, ang);
if (!IsPositionValid())
{
sLog->outError("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;
}
//oX = x; oY = y; dX = x; dY = y; m_moveTime = 0; m_startMove = 0;
if (!CreateFromProto(guidlow, Entry, vehId, team, data))
return false;
switch (GetCreatureInfo()->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();
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);
}
if (GetCreatureInfo()->InhabitType & INHABIT_AIR)
{
if (GetDefaultMovementType() == IDLE_MOTION_TYPE)
AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY);
else
SetFlying(true);
}
if (GetCreatureInfo()->InhabitType & INHABIT_WATER)
AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
LastUsedScriptID = GetCreatureInfo()->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()) && GetCreatureInfo()->trainer_type != TRAINER_TYPE_PETS)
{
sLog->outErrorDb("Creature %u (Entry: %u) have UNIT_NPC_FLAG_TRAINER but have empty trainer spell list.",
GetGUIDLow(), GetEntry());
return false;
}
switch (GetCreatureInfo()->trainer_type)
{
case TRAINER_TYPE_CLASS:
if (player->getClass() != GetCreatureInfo()->trainer_class)
{
if (msg)
{
player->PlayerTalkClass->ClearMenus();
switch (GetCreatureInfo()->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 (GetCreatureInfo()->trainer_race && player->getRace() != GetCreatureInfo()->trainer_race)
{
if (msg)
{
player->PlayerTalkClass->ClearMenus();
switch (GetCreatureInfo()->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 (GetCreatureInfo()->trainer_spell && !player->HasSpell(GetCreatureInfo()->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
&& GetCreatureInfo()->trainer_type == TRAINER_TYPE_CLASS
&& player->getClass() == GetCreatureInfo()->trainer_class;
}
void Creature::AI_SendMoveToPacket(float x, float y, float z, uint32 time, uint32 /*MovementFlags*/, uint8 /*type*/)
{
/* uint32 timeElap = getMSTime();
if ((timeElap - m_startMove) < m_moveTime)
{
oX = (dX - oX) * ((timeElap - m_startMove) / m_moveTime);
oY = (dY - oY) * ((timeElap - m_startMove) / m_moveTime);
}
else
{
oX = dX;
oY = dY;
}
dX = x;
dY = y;
m_orientation = atan2((oY - dY), (oX - dX));
m_startMove = getMSTime();
m_moveTime = time;*/
float speed = GetDistance(x, y, z) / ((float)time * 0.001f);
MonsterMoveWithSpeed(x, y, z, speed);
}
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("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 = GetCreatureInfo();
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 = GetPositionZ();
data.orientation = GetOrientation();
data.spawntimesecs = m_respawnDelay;
// prevent add data integrity problems
data.spawndist = GetDefaultMovementType() == IDLE_MOTION_TYPE ? 0 : 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();
trans->PAppend("DELETE FROM creature WHERE guid = '%u'", m_DBTableGuid);
std::ostringstream ss;
ss << "INSERT INTO creature VALUES ("
<< m_DBTableGuid << ','
<< GetEntry() << ','
<< mapid << ','
<< uint32(spawnMask) << ',' // cast to prevent save as symbol
<< uint16(GetPhaseMask()) << ',' // prevent out of range error
<< displayId << ','
<< GetEquipmentId() << ','
<< GetPositionX() << ','
<< GetPositionY() << ','
<< GetPositionZ() << ','
<< GetOrientation() << ','
<< m_respawnDelay << ',' //respawn time
<< (float) m_respawnradius << ',' //spawn distance (float)
<< (uint32) (0) << ',' //currentwaypoint
<< GetHealth() << ',' //curhealth
<< GetPower(POWER_MANA) << ',' //curmana
<< GetDefaultMovementType() << ',' //default movement generator type
<< npcflag << ','
<< unit_flags << ','
<< dynamicflags << ')';
trans->Append(ss.str().c_str());
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->outErrorDb("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->outErrorDb("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 = sObjectMgr->GetCreatureRespawnTime(m_DBTableGuid, GetInstanceId());
if (m_respawnTime) // respawn on Update
{
m_deathState = DEAD;
if (canFly())
{
float tz = map->GetHeight(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(GetCreatureInfo()->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("Trying to delete not saved creature! LowGUID: %u, Entry: %u", GetGUIDLow(), GetEntry());
return;
}
sObjectMgr->RemoveCreatureRespawnTime(m_DBTableGuid, GetInstanceId());
sObjectMgr->DeleteCreatureData(m_DBTableGuid);
SQLTransaction trans = WorldDatabase.BeginTransaction();
trans->PAppend("DELETE FROM creature WHERE guid = '%u'", m_DBTableGuid);
trans->PAppend("DELETE FROM creature_addon WHERE guid = '%u'", m_DBTableGuid);
trans->PAppend("DELETE FROM game_event_creature WHERE guid = '%u'", m_DBTableGuid);
trans->PAppend("DELETE FROM game_event_model_equip WHERE guid = '%u'", m_DBTableGuid);
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_PASSIVE))
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() && GetCreatureInfo()->SkinLootId)
if (LootTemplates_Skinning.HaveLootFor(GetCreatureInfo()->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_ALIVED)
{
//if (isPet())
// setActive(true);
SetFullHealth();
SetLootRecipient(NULL);
ResetPlayerDamageReq();
CreatureTemplate const* cinfo = GetCreatureInfo();
SetWalk(true);
if (GetCreatureInfo()->InhabitType & INHABIT_AIR)
AddUnitMovementFlag(MOVEMENTFLAG_CAN_FLY | MOVEMENTFLAG_FLYING);
if (GetCreatureInfo()->InhabitType & INHABIT_WATER)
AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING);
SetUInt32Value(UNIT_NPC_FLAGS, cinfo->npcflag);
ClearUnitState(uint32(UNIT_STAT_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)
sObjectMgr->RemoveCreatureRespawnTime(m_DBTableGuid, GetInstanceId());
sLog->outStaticDebug("Respawning...");
m_respawnTime = 0;
lootForPickPocketed = false;
lootForBody = false;
if (m_originalEntry != GetEntry())
UpdateEntry(m_originalEntry);
CreatureTemplate const* cinfo = GetCreatureInfo();
SelectLevel(cinfo);
setDeathState(JUST_ALIVED);
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;
// Spells that don't have effectMechanics.
if (!spellInfo->HasAnyEffectMechanic() && GetCreatureInfo()->MechanicImmuneMask & (1 << (spellInfo->Mechanic - 1)))
return true;
// This check must be done instead of 'if (GetCreatureInfo()->MechanicImmuneMask & (1 << (spellInfo->Mechanic - 1)))' for not break
// the check of mechanic immunity on DB (tested) because GetCreatureInfo()->MechanicImmuneMask and m_spellImmune[IMMUNITY_MECHANIC] don't have same data.
bool immunedToAllEffects = true;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
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 (GetCreatureInfo()->MechanicImmuneMask & (1 << (spellInfo->Effects[index].Mechanic - 1)))
return true;
if (GetCreatureInfo()->type == CREATURE_TYPE_MECHANICAL && spellInfo->Effects[index].Effect == SPELL_EFFECT_HEAL)
return true;
return Unit::IsImmunedToSpellEffect(spellInfo, index);
}
SpellInfo const* Creature::reachWithSpellAttack(Unit* pVictim)
{
if (!pVictim)
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("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(pVictim);
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* pVictim)
{
if (!pVictim)
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("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(pVictim);
//if (!isInFront(pVictim, 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("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_PASSIVE))
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_STAT_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;
sObjectMgr->SaveCreatureRespawnTime(m_DBTableGuid, GetInstanceId(), m_respawnTime);
}
// this should not be called by petAI or
bool Creature::canCreatureAttack(Unit const* pVictim, bool /*force*/) const
{
if (!pVictim->IsInMap(this))
return false;
if (!IsValidAttackTarget(pVictim))
return false;
if (!pVictim->isInAccessiblePlaceFor(this))
return false;
if (IsAIEnabled && !AI()->CanAIAttack(pVictim))
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(pVictim), sWorld->getFloatConfig(CONFIG_THREAT_RADIUS)) + m_CombatDistance;
if (Unit* unit = GetCharmerOrOwner())
return pVictim->IsWithinDist(unit, dist);
else
return pVictim->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(GetCreatureInfo()->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));
}
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->outErrorDb("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->outErrorDb("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("Creature entry %u call SetInCombatWithZone but creature cannot have threat list.", GetEntry());
return;
}
Map* map = GetMap();
if (!map->IsDungeon())
{
sLog->outError("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);
}
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 = GetCreatureInfo();
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
const char* 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].c_str();
}
}
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(x, y, z, o);
}
bool Creature::IsDungeonBoss() const
{
CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(GetEntry());
return cinfo && (cinfo->flags_extra & CREATURE_FLAG_EXTRA_DUNGEON_BOSS);
}
void Creature::SetWalk(bool enable)
{
if (enable)
AddUnitMovementFlag(MOVEMENTFLAG_WALKING);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_WALKING);
WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_WALK_MODE : SMSG_SPLINE_MOVE_SET_RUN_MODE, 9);
data.append(GetPackGUID());
SendMessageToSet(&data, true);
}
void Creature::SetLevitate(bool enable)
{
if (enable)
AddUnitMovementFlag(MOVEMENTFLAG_LEVITATING);
else
RemoveUnitMovementFlag(MOVEMENTFLAG_LEVITATING);
WorldPacket data(enable ? SMSG_SPLINE_MOVE_GRAVITY_DISABLE : SMSG_SPLINE_MOVE_GRAVITY_ENABLE, 9);
data.append(GetPackGUID());
SendMessageToSet(&data, true);
}