/* * Copyright (C) 2008-2013 TrinityCore * Copyright (C) 2005-2009 MaNGOS * * 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 . */ #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; } // Initialize loot duplicate count depending on raid difficulty if (GetMap()->IsRaid() && GetMap()->GetSpawnMode() & RAID_DIFFICULTY_MASK_25MAN) loot.maxDuplicates = 3; 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); } // updates spell bars for vehicles and set player's faction - should be called here, to overwrite faction that is set from the new template if (IsVehicle()) { if (Player* owner = Creature::GetCharmerOrOwnerPlayerOrPlayerItself()) // this check comes in case we don't have a player { setFaction(owner->getFaction()); // vehicles should have same as owner faction owner->VehicleSpellInitialize(); } } // trigger creature is always not selectable and can not be attacked if (isTrigger()) 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); } // Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc) float ground = GetPositionZ(); GetMap()->GetWaterOrGroundLevel(GetPositionX(), GetPositionY(), GetPositionZ(), &ground); bool isInAir = G3D::fuzzyGt(GetPositionZ(), ground + 0.05f) || G3D::fuzzyLt(GetPositionZ(), ground - 0.05f); // Can be underground too, prevent the falling if (cInfo->InhabitType & INHABIT_AIR && cInfo->InhabitType & INHABIT_GROUND && isInAir) SetCanFly(true); else if (cInfo->InhabitType & INHABIT_AIR && isInAir) SetDisableGravity(true); else { SetCanFly(false); SetDisableGravity(false); } if (cInfo->InhabitType & INHABIT_WATER && IsInWater()) AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); else RemoveUnitMovementFlag(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); } // Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc) float ground = GetPositionZ(); GetMap()->GetWaterOrGroundLevel(GetPositionX(), GetPositionY(), GetPositionZ(), &ground); bool isInAir = G3D::fuzzyGt(GetPositionZ(), ground + 0.05f) || G3D::fuzzyLt(GetPositionZ(), ground - 0.05f); // Can be underground too, prevent the falling CreatureTemplate const* cinfo = GetCreatureTemplate(); if (cinfo->InhabitType & INHABIT_AIR && cinfo->InhabitType & INHABIT_GROUND && isInAir) SetCanFly(true); else if (cinfo->InhabitType & INHABIT_AIR && isInAir) SetDisableGravity(true); else { SetCanFly(false); SetDisableGravity(false); } 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 searcher(this, creature, u_check); TypeContainerVisitor, 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_race) { 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); // Set the movement flags if the creature is in that mode. (Only fly if actually in air, only swim if in water, etc) float ground = GetPositionZ(); GetMap()->GetWaterOrGroundLevel(GetPositionX(), GetPositionY(), GetPositionZ(), &ground); bool isInAir = G3D::fuzzyGt(GetPositionZ(), ground + 0.05f) || G3D::fuzzyLt(GetPositionZ(), ground - 0.05f); // Can be underground too, prevent the falling if (cinfo->InhabitType & INHABIT_AIR && cinfo->InhabitType & INHABIT_GROUND && isInAir) SetCanFly(true); else if (cinfo->InhabitType & INHABIT_AIR && isInAir) SetDisableGravity(true); else { SetCanFly(false); SetDisableGravity(false); } if (cinfo->InhabitType & INHABIT_WATER && IsInWater()) AddUnitMovementFlag(MOVEMENTFLAG_SWIMMING); else RemoveUnitMovementFlag(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(GetDBTableGUIDLow()) : 0; if (poolid) sPoolMgr->UpdatePool(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 searcher(this, target, u_check); TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); TypeContainerVisitor, 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 searcher(this, target, u_check); TypeContainerVisitor, WorldTypeMapContainer > world_unit_searcher(searcher); TypeContainerVisitor, 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 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 assistList; { CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY())); Cell cell(p); cell.SetNoCreate(); Trinity::AnyAssistCreatureInRangeCheck u_check(this, getVictim(), radius); Trinity::CreatureListSearcher searcher(this, assistList, u_check); TypeContainerVisitor, 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 worker(this, u_do); TypeContainerVisitor, 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::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.", GetDBTableGUIDLow(), 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; } float Creature::GetAggroRange(Unit const* target) const { // Determines the aggro range for creatures (usually pets), used mainly for aggressive pet target selection. // Based on data from wowwiki due to lack of 3.3.5a data if (target && this->isPet()) { uint32 targetLevel = 0; if (target->GetTypeId() == TYPEID_PLAYER) targetLevel = target->getLevelForTarget(this); else if (target->GetTypeId() == TYPEID_UNIT) targetLevel = target->ToCreature()->getLevelForTarget(this); uint32 myLevel = getLevelForTarget(target); int32 levelDiff = int32(targetLevel) - int32(myLevel); // The maximum Aggro Radius is capped at 45 yards (25 level difference) if (levelDiff < -25) levelDiff = -25; // The base aggro radius for mob of same level float aggroRadius = 20; // Aggro Radius varies with level difference at a rate of roughly 1 yard/level aggroRadius -= (float)levelDiff; // detect range auras aggroRadius += GetTotalAuraModifier(SPELL_AURA_MOD_DETECT_RANGE); // detected range auras aggroRadius += target->GetTotalAuraModifier(SPELL_AURA_MOD_DETECTED_RANGE); // Just in case, we don't want pets running all over the map if (aggroRadius > MAX_AGGRO_RADIUS) aggroRadius = MAX_AGGRO_RADIUS; // Minimum Aggro Radius for a mob seems to be combat range (5 yards) // hunter pets seem to ignore minimum aggro radius so we'll default it a little higher if (aggroRadius < 10) aggroRadius = 10; return (aggroRadius); } // Default return 0.0f; } Unit* Creature::SelectNearestHostileUnitInAggroRange(bool useLOS) const { // Selects nearest hostile target within creature's aggro range. Used primarily by // pets set to aggressive. Will not return neutral or friendly targets. Unit* target = NULL; { Trinity::NearestHostileUnitInAggroRangeCheck u_check(this, useLOS); Trinity::UnitSearcher searcher(this, target, u_check); VisitNearbyGridObject(MAX_AGGRO_RADIUS, searcher); } return target; }