server/src/game/Creature.cpp
Schmoozerd ae7348f6b0 [12774] Merged cmangos last changes, special thanks for xfurry, Dramacydal, cala, Schmoozerd,
I'm not taking any credits of this commit.

Implement spell effects 62042, 62278 and 64767
Also limit the targets for 62577 and 62603
----------
Update git_id to reflect recent sql formatting changes
----------
Update to a safer code version and also add GO caster scenarios
----------
Allow aura 62038 to stack at every 3 seconds
----------
Improve handling of TargetMMGen
This will have impact on Chase and Follow Movement.
----------
* Refactor code to check if a new position is required for the MMGen into the new function RequiresNewPosition
* Refactor code to get the current targeted distance into function GetDynamicTargetDistance
* Change ChaseMMGen (angle = 0.0f case) chase to best contact point, not zero angle.

Thanks to Cala and X-Savior for testing. Special thanks to cala for also suggesting improved values for the magic numbers
----------
Improve ObjectPosSelector
Now a spot already occupied by the searcher will be prefered
----------
Get rid of bounding radius in GetNearPoint[2D] and ObjectPosSelector
----------
This changes how ObjectPosSelector is used.
It changes the way how the functions Object::GetNearPoint and Object::GetNearPoint2d behave.
----------
So you need to check all places where these functions are used if they are still used correctly.
----------
Especially check your scripts!
----------
Remove not required duplicate indexes
----------
Implement TARGET_92 as TARGET_SUMMONER
This target is used only as TargetA and the related spells are used only by temporary summoned creatures
----------
Implement some spell effects used by Hodir in Ulduar
Dummy spells 62797, 63499, 63545 and 64543
Periodic dummy auras: 61968, 62038, 62039 and 65272
Limit targets for spells: 62797, 63545, 64543, 62476 and 62477
----------
Implement some spells used at Algalon encounter
Spell aura entries 64345, 62018 and 64412
Positive target exception for spell 64996
Aura stacking exception for spells 62169, 62168, 65250 and 64417
----------
Implement effect for spell 63633
----------
EventAI - Ingame output of script state
----------
With this the command .npc aiinfo will give more output about the current state.
Remark that this output is only given if the LogFilter for EventAIDev mode is disabled
----------
FindGit.cmake already ships with CMake
No reason for us to ship it too. Also, we had an outdated version which
had not been used anyway, because we set the include path in such a way,
that the CMake delivered version is always found first.
----------
FindOpenSSL.cmake already ships with CMake
No reason for us to ship it too. Also, we had an outdated version which
had not been used anyway, because we set the include path in such a way,
that the CMake delivered version is always found first.
----------
EventAI - Add more developing error output
----------
Fix some target-type handling for EventAI
Also increase log-output for bad target-types
----------
Fix crash due to bad compiler (author Xfurry)
----------
Add special condition id for Ulduar
Will be used to check the availability of the siege vehicles for players
----------
Update spells 62374 and 62907
* limit spell targets of spell 62374
* implement spell effect for spell 62907
----------
Remove effect for spell 64503
Will be handled in script library. For details please check 8502cdfa64
----------
Implement spells 64489 and 64673
Both are used by Auriaya (Ulduar)
----------
Implement some spells for Ignis the Furnace Master
Spell entries: 62717, 62381, 62488, 62707, 64475 and 64503
----------
Implement spells 61187 and 61190
----------
CMake: generic way to build a script library
Added new parameter INCLUDE_BINDINGS_DIR which can be set to the name of a
folder inside src/bindings/.

Includes the script library in src/bindings/ with the defined name.
The name must correspond to the name of the folder and the folder must contain
a valid CMakeLists.txt

Note: if you currently use a script library, you will probably get a merge
problem on src/bindings/CMakeLists.txt as you will have modified this file
manually. Please use the new version of this file and rerun CMake once with the
parameter -DINCLUDE_BINDINGS_DIR=ScriptDev2 (if you are not using SD2 but
another script library, replace ScriptDev2 with the name of the folder in
src/bindings/).

If you do not use a script library you should not have any merge problems
and you don't need to do anything.
----------
Add CMake source groups to target 'game'
This is the exact same grouping as it is currently in the VC 2012 files.
These groupings will have to be refactored at some point as they are not
very logical.
----------
Add CMake source groups to target 'framework'
This is the exact same grouping as it is currently in the VC 2012 files.
This is part of cmangos/issues#67
----------
Add CMake source groups to target 'shared'
This is the exact same grouping as it is currently in the VC 2012 files.
----------
Add new parameter 'expansion' to command 'account create'
----------
Update some Sunwell Plateau spells

Limit targets and allow positive effect for spell 46650
Implement effect for spells 46289 and 46637
Remove effect for spell 44845 - will be handled in script library
----------
Implement some custom use for Effect Activate Object spells
This will fix the summoning events for the Wind Stones, Ice Stones, Skettis bosses and quest 11865
----------
Sync mangos.sql with other versions
----------
Redump sql databases to unify formatting
The main reason for this was because classic/cata has updated the sql formatting and manually syncing would be a pain so redumping from master->tbc->classic->cata is easier.

Only the formatting was changed. The values were not changed at all.

mysqldump was used however manual modifications had to be done.

Dump the database:
C:/mysql/bin/mysqldump.exe mangos > sql/mangos.sql

Split insert values into multiple rows:
Replace "),(" with "),\n("
Replace "VALUES (" with "VALUES\n("

Remove the character sets by replacing them with an empty string

Custom formatting of mangos.sql:
Move db_version to the very top
Create all dbscripts_on_* tables based on dbscripts_on_creature_movement
Preserve our custom insert formatting of spell_affect (tbc/classic), spell_bonus_data, spell_chain, spell_elixir, spell_proc_event, spell_proc_item_enchant, spell_template, spell_threat
Remove autoincrement values from insert values of pet_name_generation and remove AUTO_INCREMENT=261 value from its table structure

Custom formatting of characters.sql:
Move character_db_version to the very top

Custom formatting of realmd.sql:
Move realmd_db_version to the very top
----------
Immediately remove corpses when ForcedDespawn is used
Thanks to Neotmiren for pointing, special thanks to cala for testing!
----------
Fix use of config values related to quest-status and level
This fixes use of negative value in config values Quests.LowLevelHideDiff and Quests.HighLevelHideDiff
Also add some documentation around the related code
Thanks to Neotmiren for pointing and to cala for testing.
----------
Loot-System: Fix reference loading check
This fixes a false error output for loot references that are only used with spell loot.
Thanks to X-Savior for properly reporting both error messages and use case
----------
Add missing spell 61437 to playercreate spells for bloodelves
Thanks to NeatElves for porting from TC and pointing to this
----------
Fix load bar step for alendarMgr::LoadCalendarsFromDB
----------
Cody Style Improvements
Also remove an unused variable (thanks to Den for this!)
----------
Implement the spells used in the Chess Event encounter
Combat spell entries: 37775, 37824, 39338, 39342, 39341, 39344, 45260
Melee spells: 37142, 37143, 37147, 37149, 37150, 37220, 37227, 37228, 37337, 37339, 37345, 37348
Chess movement spells: 30012, 32312, 37388, 30284, 37144, 37146, 37148, 37151, 37152, 37153
Aura stacking exception: 32261 and 39400
----------
And more hotfixes with these format strings
----------
Hotfix to recent text loading functions
----------
Use possible changed model names with vmap extraction
----------
Fix some warnings
----------
Store how many texts are loaded for validity checks. Use this with EventAI
----------
EventAI: Use generic DoDisplayText and loading of additional text data
----------
Add generic DoDisplayText function and use additional data of dbscripts table
----------
Add const-correctness to Text related functions
----------
Add database changes to support more data for DB Script texts
----------
Add stacking exception for spells 39993 and 40041
----------
Allow spell effect 86 - Activate Object to use the misc value
----------
Allow player pets to swim
----------
Enable resummoning of warlock pets
----------
Do not remove FLY auras on Evade
Also consider npcs which have SPELL_AURA_FLY as being able to fly
----------
EventAI: Improve code
* Drop rather pointless bool to check if the number of assigned events is empty
* Before the phase was resetted on death if and only if the npc has Events defined
* DoMeleeAttackIfReady could have been called even though combat state could have changed while processing events
----------
EventAI: Implement ACTION_T_SET_THROW_MASK (46)
This Action can be used to set which AIEvents should be thrown automatically, if you need more flexibility, you can always use the manual ACTION_TH_THROW_AI_EVENT version.
* Also clean some error-log output a bit.
----------
Forward original caster GUID to script library
----------
Fixup commit 12511 Thanks to Zakamurite for pointing
Also thanks to him for giving a helping hand in correcting the commit
----------
Implement some spells for Felmyst encounter
Spell entries: 45714, 45717 and 45918. Limit targets of spell 45391
Also add stacking exception for auras 45068 and 45582
----------
Fix invisible spirit healers & such on death near them
This fixes an issue that occurs if you die close to a spirit healer/guide.
----------
Fix take ammo for most ranged spells
Fix spells like Arcane Shot not taking ammo while they should
----------
Check cast spell 51690
----------
Fix SpellDamage modifier of SPELL_AURA_MOD_DAMAGE_DONE_CREATURE
This aura modifies a flat value, not a percent value.
----------
Implement proc effect of spells 67712, 67758
related to items 47316, 47477.
----------
Improve proc of spell 50421
----------
Add and implement server-side spell 23770
----------
EventAI: Improve TargetSelection related ErrorLog output
----------
DBScripts Engine: Change behaviour to search for a different npc when using buddy-search
With this an npc buddy will be interpreted as "another npc with entry"
Also toggle command 31 - TERMINATE_SCRIPT to also look only for other npcs of entry
----------
DBScripts Engine: Allow pets as buddy
Add new flag SCRIPT_FLAG_BUDDY_IS_PET (0x20) that will search not for a normal npc with buddy-search, but also for pets
----------
DBScripts Engine: Support buddy search by guid
* Add new `data_flags` flag SCRIPT_FLAG_BUDDY_BY_GUID (0x10)
If this flag is set, the content of `search_distance` is interpreted as db-guid of the requested buddy
* Also switch most error log output to DB-error log output (though this will include false positives)
----------
2020-02-17 09:20:48 +00:00

2626 lines
89 KiB
C++

/**
* This code is part of MaNGOS. Contributor & Copyright details are in AUTHORS/THANKS.
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "Creature.h"
#include "Database/DatabaseEnv.h"
#include "WorldPacket.h"
#include "World.h"
#include "ObjectMgr.h"
#include "ScriptMgr.h"
#include "ObjectGuid.h"
#include "SQLStorages.h"
#include "SpellMgr.h"
#include "QuestDef.h"
#include "GossipDef.h"
#include "Player.h"
#include "GameEventMgr.h"
#include "PoolManager.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 "InstanceData.h"
#include "MapPersistentStateMgr.h"
#include "BattleGround/BattleGroundMgr.h"
#include "OutdoorPvP/OutdoorPvP.h"
#include "Spell.h"
#include "Util.h"
#include "GridNotifiers.h"
#include "GridNotifiersImpl.h"
#include "CellImpl.h"
#include "movement/MoveSplineInit.h"
#include "CreatureLinkingMgr.h"
// apply implementation of the singletons
#include "Policies/Singleton.h"
ObjectGuid CreatureData::GetObjectGuid(uint32 lowguid) const
{
// info existence checked at loading
return ObjectMgr::GetCreatureTemplate(id)->GetObjectGuid(lowguid);
}
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, uint8 type)
{
bool found = false;
for (VendorItemList::iterator i = m_items.begin(); i != m_items.end();)
{
// can have many examples
if ((*i)->item == item_id && (*i)->type == type)
{
i = m_items.erase(i);
found = true;
}
else
++i;
}
return found;
}
VendorItem const* VendorItemData::FindItemCostPair(uint32 item_id, uint8 type, uint32 extendedCost) const
{
for (VendorItemList::const_iterator i = m_items.begin(); i != m_items.end(); ++i)
{
// Skip checking for conditions, condition system is powerfull enough to not require additional entries only for the conditions
if ((*i)->item == item_id && (*i)->ExtendedCost == extendedCost && (*i)->type == type)
return *i;
}
return NULL;
}
bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
{
m_owner.ForcedDespawn();
return true;
}
void CreatureCreatePos::SelectFinalPoint(Creature* cr)
{
// if object provided then selected point at specific dist/angle from object forward look
if (m_closeObject)
{
if (m_dist == 0.0f)
{
m_pos.x = m_closeObject->GetPositionX();
m_pos.y = m_closeObject->GetPositionY();
m_pos.z = m_closeObject->GetPositionZ();
}
else
m_closeObject->GetClosePoint(m_pos.x, m_pos.y, m_pos.z, cr->GetObjectBoundingRadius(), m_dist, m_angle);
}
}
bool CreatureCreatePos::Relocate(Creature* cr) const
{
cr->Relocate(m_pos.x, m_pos.y, m_pos.z, m_pos.o);
if (!cr->IsPositionValid())
{
sLog.outError("%s not created. Suggested coordinates isn't valid (X: %f Y: %f)", cr->GetGuidStr().c_str(), cr->GetPositionX(), cr->GetPositionY());
return false;
}
return true;
}
Creature::Creature(CreatureSubtype subtype) : Unit(),
i_AI(NULL),
loot(this),
lootForPickPocketed(false), lootForBody(false), lootForSkin(false),
m_groupLootTimer(0), m_groupLootId(0),
m_lootMoney(0), m_lootGroupRecipientId(0),
m_corpseDecayTimer(0), m_respawnTime(0), m_respawnDelay(25), m_corpseDelay(60), m_respawnradius(5.0f),
m_subtype(subtype), m_defaultMovementType(IDLE_MOTION_TYPE), m_equipmentId(0),
m_AlreadyCallAssistance(false), m_AlreadySearchedAssistance(false),
m_regenHealth(true), m_AI_locked(false), m_isDeadByDefault(false),
m_temporaryFactionFlags(TEMPFACTION_NONE),
m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0),
m_creatureInfo(NULL)
{
m_regenTimer = 200;
m_holyPowerRegenTimer = REGEN_TIME_HOLY_POWER;
m_valuesCount = UNIT_END;
for (int i = 0; i < CREATURE_MAX_SPELLS; ++i)
m_spells[i] = 0;
m_CreatureSpellCooldowns.clear();
m_CreatureCategoryCooldowns.clear();
SetWalk(true, true);
}
Creature::~Creature()
{
CleanupsBeforeDelete();
m_vendorItemCounts.clear();
delete i_AI;
i_AI = NULL;
}
void Creature::AddToWorld()
{
///- Register the creature for guid lookup
if (!IsInWorld() && GetObjectGuid().IsCreatureOrVehicle())
GetMap()->GetObjectsStore().insert<Creature>(GetObjectGuid(), (Creature*)this);
Unit::AddToWorld();
}
void Creature::RemoveFromWorld()
{
///- Remove the creature from the accessor
if (IsInWorld() && GetObjectGuid().IsCreatureOrVehicle())
GetMap()->GetObjectsStore().erase<Creature>(GetObjectGuid(), (Creature*)NULL);
Unit::RemoveFromWorld();
}
void Creature::RemoveCorpse()
{
// since pool system can fail to roll unspawned object, this one can remain spawned, so must set respawn nevertheless
if (uint16 poolid = sPoolMgr.IsPartOfAPool<Creature>(GetGUIDLow()))
sPoolMgr.UpdatePool<Creature>(*GetMap()->GetPersistentState(), poolid, GetGUIDLow());
if (!IsInWorld()) // can be despawned by update pool
return;
if ((getDeathState() != CORPSE && !m_isDeadByDefault) || (getDeathState() != ALIVE && m_isDeadByDefault))
return;
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Removing corpse of %s ", GetGuidStr().c_str());
m_corpseDecayTimer = 0;
SetDeathState(DEAD);
UpdateObjectVisibility();
// stop loot rolling before loot clear and for close client dialogs
StopGroupLoot();
loot.clear();
uint32 respawnDelay = 0;
if (AI())
AI()->CorpseRemoved(respawnDelay);
// script can set time (in seconds) explicit, override the original
if (respawnDelay)
m_respawnTime = time(NULL) + respawnDelay;
float x, y, z, o;
GetRespawnCoord(x, y, z, &o);
GetMap()->CreatureRelocation(this, x, y, z, o);
// forced recreate creature object at clients
UnitVisibility currentVis = GetVisibility();
SetVisibility(VISIBILITY_REMOVE_CORPSE);
UpdateObjectVisibility();
SetVisibility(currentVis); // restore visibility state
UpdateObjectVisibility();
}
/*
* change the entry of creature until respawn
*/
bool Creature::InitEntry(uint32 Entry, CreatureData const* data /*=NULL*/, GameEventCreatureData const* eventData /*=NULL*/)
{
// use game event entry if any instead default suggested
if (eventData && eventData->entry_id)
Entry = eventData->entry_id;
CreatureInfo const* normalInfo = ObjectMgr::GetCreatureTemplate(Entry);
if (!normalInfo)
{
sLog.outErrorDb("Creature::UpdateEntry creature entry %u does not exist.", Entry);
return false;
}
CreatureInfo const* cinfo = normalInfo;
for (Difficulty diff = GetMap()->GetDifficulty(); diff > REGULAR_DIFFICULTY; diff = GetPrevDifficulty(diff, GetMap()->IsRaid()))
{
// we already have valid Map pointer for current creature!
if (normalInfo->DifficultyEntry[diff - 1])
{
cinfo = ObjectMgr::GetCreatureTemplate(normalInfo->DifficultyEntry[diff - 1]);
if (cinfo)
break; // template found
// check and reported at startup, so just ignore (restore normalInfo)
cinfo = normalInfo;
}
}
SetEntry(Entry); // normal entry always
m_creatureInfo = cinfo; // map mode related always
SetObjectScale(cinfo->scale);
// 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));
uint32 display_id = ChooseDisplayId(GetCreatureInfo(), data, eventData);
if (!display_id) // Cancel load if no display id
{
sLog.outErrorDb("Creature (Entry: %u) has no model defined in table `creature_template`, can't load.", Entry);
return false;
}
CreatureModelInfo const* minfo = sObjectMgr.GetCreatureModelRandomGender(display_id);
if (!minfo) // Cancel load if no model defined
{
sLog.outErrorDb("Creature (Entry: %u) has no model info defined in table `creature_model_info`, can't load.", Entry);
return false;
}
display_id = minfo->modelid; // it can be different (for another gender)
SetNativeDisplayId(display_id);
// normally the same as native, but some has exceptions (Spell::DoSummonTotem)
SetDisplayId(display_id);
SetByteValue(UNIT_FIELD_BYTES_0, 2, minfo->gender);
// Load creature equipment
if (eventData && eventData->equipment_id)
{
LoadEquipment(eventData->equipment_id); // use event equipment if any for active event
}
else if (!data || data->equipmentId == 0)
{
if (cinfo->equipmentId == 0)
LoadEquipment(normalInfo->equipmentId); // use default from normal template if diff does not have any
else
LoadEquipment(cinfo->equipmentId); // else use from diff template
}
else if (data && data->equipmentId != -1)
{
// override, -1 means no equipment
LoadEquipment(data->equipmentId);
}
SetName(normalInfo->Name); // at normal entry always
SetFloatValue(UNIT_MOD_CAST_SPEED, 1.0f);
// update speed for the new CreatureInfo base speed mods
UpdateSpeed(MOVE_WALK, false);
UpdateSpeed(MOVE_RUN, false);
SetLevitate(cinfo->InhabitType & INHABIT_AIR);
// checked at loading
m_defaultMovementType = MovementGeneratorType(cinfo->MovementType);
return true;
}
bool Creature::UpdateEntry(uint32 Entry, Team team, const CreatureData* data /*=NULL*/, GameEventCreatureData const* eventData /*=NULL*/, bool preserveHPAndPower /*=true*/)
{
if (!InitEntry(Entry, data, eventData))
return false;
m_regenHealth = GetCreatureInfo()->RegenHealth;
// creatures always have melee weapon ready if any
SetSheath(SHEATH_STATE_MELEE);
SelectLevel(GetCreatureInfo(), preserveHPAndPower ? GetHealthPercent() : 100.0f, 100.0f);
if (team == HORDE)
setFaction(GetCreatureInfo()->faction_H);
else
setFaction(GetCreatureInfo()->faction_A);
SetUInt32Value(UNIT_NPC_FLAGS, GetCreatureInfo()->npcflag);
uint32 attackTimer = GetCreatureInfo()->baseattacktime;
SetAttackTime(BASE_ATTACK, attackTimer);
SetAttackTime(OFF_ATTACK, attackTimer - attackTimer / 4);
SetAttackTime(RANGED_ATTACK, GetCreatureInfo()->rangeattacktime);
uint32 unitFlags = GetCreatureInfo()->unit_flags;
// we may need to append or remove additional flags
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IN_COMBAT))
unitFlags |= UNIT_FLAG_IN_COMBAT;
SetUInt32Value(UNIT_FIELD_FLAGS, unitFlags);
// preserve all current dynamic flags if exist
uint32 dynFlags = GetUInt32Value(UNIT_DYNAMIC_FLAGS);
SetUInt32Value(UNIT_DYNAMIC_FLAGS, dynFlags ? dynFlags : GetCreatureInfo()->dynamicflags);
SetModifierValue(UNIT_MOD_ARMOR, BASE_VALUE, float(GetCreatureInfo()->armor));
SetModifierValue(UNIT_MOD_RESISTANCE_HOLY, BASE_VALUE, float(GetCreatureInfo()->resistance1));
SetModifierValue(UNIT_MOD_RESISTANCE_FIRE, BASE_VALUE, float(GetCreatureInfo()->resistance2));
SetModifierValue(UNIT_MOD_RESISTANCE_NATURE, BASE_VALUE, float(GetCreatureInfo()->resistance3));
SetModifierValue(UNIT_MOD_RESISTANCE_FROST, BASE_VALUE, float(GetCreatureInfo()->resistance4));
SetModifierValue(UNIT_MOD_RESISTANCE_SHADOW, BASE_VALUE, float(GetCreatureInfo()->resistance5));
SetModifierValue(UNIT_MOD_RESISTANCE_ARCANE, BASE_VALUE, float(GetCreatureInfo()->resistance6));
SetCanModifyStats(true);
UpdateAllStats();
// checked and error show at loading templates
if (FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(GetCreatureInfo()->faction_A))
{
if (factionTemplate->factionFlags & FACTION_TEMPLATE_FLAG_PVP)
SetPvP(true);
else
SetPvP(false);
}
// Try difficulty dependend version before falling back to base entry
CreatureTemplateSpells const* templateSpells = sCreatureTemplateSpellsStorage.LookupEntry<CreatureTemplateSpells>(GetCreatureInfo()->Entry);
if (!templateSpells)
templateSpells = sCreatureTemplateSpellsStorage.LookupEntry<CreatureTemplateSpells>(GetEntry());
if (templateSpells)
for (int i = 0; i < CREATURE_MAX_SPELLS; ++i)
m_spells[i] = templateSpells->spells[i];
SetVehicleId(GetCreatureInfo()->vehicleId, 0);
// if eventData set then event active and need apply spell_start
if (eventData)
ApplyGameEventSpells(eventData, true);
return true;
}
uint32 Creature::ChooseDisplayId(const CreatureInfo* cinfo, const CreatureData* data /*= NULL*/, GameEventCreatureData const* eventData /*=NULL*/)
{
// Use creature event model explicit, override any other static models
if (eventData && eventData->modelid)
return eventData->modelid;
// Use creature model explicit, override template (creature.modelid)
if (data && data->modelid_override)
return data->modelid_override;
// use defaults from the template
uint32 display_id = 0;
// models may be categorized as (in this order):
// if mod4 && mod3 && mod2 && mod1 use any, by 25%-chance (other gender is selected and replaced after this function)
// if mod3 && mod2 && mod1 use mod3 unless mod2 has modelid_alt_model (then all by 33%-chance)
// if mod2 use mod2 unless mod2 has modelid_alt_model (then both by 50%-chance)
// if mod1 use mod1
// model selected here may be replaced with other_gender using own function
if (cinfo->ModelId[3] && cinfo->ModelId[2] && cinfo->ModelId[1] && cinfo->ModelId[0])
{
display_id = cinfo->ModelId[urand(0, 3)];
}
else if (cinfo->ModelId[2] && cinfo->ModelId[1] && cinfo->ModelId[0])
{
uint32 modelid_tmp = sObjectMgr.GetCreatureModelAlternativeModel(cinfo->ModelId[1]);
display_id = modelid_tmp ? cinfo->ModelId[urand(0, 2)] : cinfo->ModelId[2];
}
else if (cinfo->ModelId[1])
{
// We use this to eliminate invisible models vs. "dummy" models (infernals, etc).
// Where it's expected to select one of two, model must have a alternative model defined (alternative model is normally the same as defined in ModelId1).
// Same pattern is used in the above model selection, but the result may be ModelId3 and not ModelId2 as here.
uint32 modelid_tmp = sObjectMgr.GetCreatureModelAlternativeModel(cinfo->ModelId[1]);
display_id = modelid_tmp ? cinfo->ModelId[urand(0, 1)] : cinfo->ModelId[1];
}
else if (cinfo->ModelId[0])
{
display_id = cinfo->ModelId[0];
}
// fail safe, we use creature entry 1 and make error
if (!display_id)
{
sLog.outErrorDb("Call customer support, ChooseDisplayId can not select native model for creature entry %u, model from creature entry 1 will be used instead.", cinfo->Entry);
if (const CreatureInfo* creatureDefault = ObjectMgr::GetCreatureTemplate(1))
display_id = creatureDefault->ModelId[0];
}
return display_id;
}
void Creature::Update(uint32 update_diff, uint32 diff)
{
switch (m_deathState)
{
case JUST_ALIVED:
// Don't must be called, see Creature::SetDeathState JUST_ALIVED -> ALIVE promoting.
sLog.outError("Creature (GUIDLow: %u Entry: %u ) in wrong state: JUST_ALIVED (4)", GetGUIDLow(), GetEntry());
break;
case JUST_DIED:
// Don't must be called, see Creature::SetDeathState JUST_DIED -> CORPSE promoting.
sLog.outError("Creature (GUIDLow: %u Entry: %u ) in wrong state: JUST_DEAD (1)", GetGUIDLow(), GetEntry());
break;
case DEAD:
{
if (m_respawnTime <= time(NULL) && (!m_isSpawningLinked || GetMap()->GetCreatureLinkingHolder()->CanSpawn(this)))
{
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "Respawning...");
m_respawnTime = 0;
lootForPickPocketed = false;
lootForBody = false;
lootForSkin = false;
// Clear possible auras having IsDeathPersistent() attribute
RemoveAllAuras();
if (m_originalEntry != GetEntry())
{
// need preserver gameevent state
GameEventCreatureData const* eventData = sGameEventMgr.GetCreatureUpdateDataForActiveEvent(GetGUIDLow());
UpdateEntry(m_originalEntry, TEAM_NONE, NULL, eventData);
}
CreatureInfo const* cinfo = GetCreatureInfo();
SelectLevel(cinfo);
SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE);
if (m_isDeadByDefault)
{
SetDeathState(JUST_DIED);
SetHealth(0);
i_motionMaster.Clear();
clearUnitState(UNIT_STAT_ALL_STATE);
LoadCreatureAddon(true);
}
else
SetDeathState(JUST_ALIVED);
// Call AI respawn virtual function
if (AI())
AI()->JustRespawned();
if (m_isCreatureLinkingTrigger)
GetMap()->GetCreatureLinkingHolder()->DoCreatureLinkingEvent(LINKING_EVENT_RESPAWN, this);
GetMap()->Add(this);
}
break;
}
case CORPSE:
{
Unit::Update(update_diff, diff);
if (m_isDeadByDefault)
break;
if (m_corpseDecayTimer <= update_diff)
{
RemoveCorpse();
break;
}
else
m_corpseDecayTimer -= update_diff;
if (m_groupLootId) // Loot is stopped already if corpse got removed.
{
if (m_groupLootTimer <= update_diff)
StopGroupLoot();
else
m_groupLootTimer -= update_diff;
}
break;
}
case ALIVE:
{
if (m_isDeadByDefault)
{
if (m_corpseDecayTimer <= update_diff)
{
RemoveCorpse();
break;
}
else
m_corpseDecayTimer -= update_diff;
}
Unit::Update(update_diff, 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 (!IsInEvadeMode())
{
if (AI())
{
// do not allow the AI to be changed during update
m_AI_locked = true;
AI()->UpdateAI(diff); // AI not react good at real update delays (while freeze in non-active part of map)
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;
RegenerateAll(update_diff);
break;
}
default:
break;
}
}
void Creature::StartGroupLoot(Group* group, uint32 timer)
{
m_groupLootId = group->GetId();
m_groupLootTimer = timer;
}
void Creature::StopGroupLoot()
{
if (!m_groupLootId)
return;
if (Group* group = sObjectMgr.GetGroupById(m_groupLootId))
group->EndRoll();
m_groupLootTimer = 0;
m_groupLootId = 0;
}
void Creature::RegenerateAll(uint32 update_diff)
{
if (m_regenTimer > 0)
{
if (update_diff >= m_regenTimer)
m_regenTimer = 0;
else
m_regenTimer -= update_diff;
}
if (m_regenTimer != 0)
return;
if (!isInCombat() || IsPolymorphed())
RegenerateHealth();
RegenerateMana();
m_regenTimer = REGEN_TIME_FULL;
}
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())
{
float ManaIncreaseRate = sWorld.getConfig(CONFIG_FLOAT_RATE_POWER_MANA);
float Spirit = GetStat(STAT_SPIRIT);
addvalue = uint32((Spirit / 5.0f + 17.0f) * ManaIncreaseRate);
}
else
addvalue = maxValue / 3;
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.getConfig(CONFIG_FLOAT_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;
ModifyHealth(addvalue);
}
void Creature::DoFleeToGetAssistance()
{
if (!getVictim())
return;
float radius = sWorld.getConfig(CONFIG_FLOAT_CREATURE_FAMILY_FLEE_ASSISTANCE_RADIUS);
if (radius > 0)
{
Creature* pCreature = NULL;
MaNGOS::NearestAssistCreatureInCreatureRangeCheck u_check(this, getVictim(), radius);
MaNGOS::CreatureLastSearcher<MaNGOS::NearestAssistCreatureInCreatureRangeCheck> searcher(pCreature, u_check);
Cell::VisitGridObjects(this, searcher, radius);
SetNoSearchAssistance(true);
UpdateSpeed(MOVE_RUN, false);
if (!pCreature)
SetFeared(true, getVictim()->GetObjectGuid(), 0 , sWorld.getConfig(CONFIG_UINT32_CREATURE_FAMILY_FLEE_DELAY));
else
GetMotionMaster()->MoveSeekAssistance(pCreature->GetPositionX(), pCreature->GetPositionY(), pCreature->GetPositionZ());
}
}
bool Creature::AIM_Initialize()
{
// make sure nothing can change the AI during AI update
if (m_AI_locked)
{
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "AIM_Initialize: failed to init, locked.");
return false;
}
CreatureAI* oldAI = i_AI;
i_motionMaster.Initialize();
i_AI = FactorySelector::selectAI(this);
delete oldAI;
return true;
}
bool Creature::Create(uint32 guidlow, CreatureCreatePos& cPos, CreatureInfo const* cinfo, Team team /*= TEAM_NONE*/, const CreatureData* data /*= NULL*/, GameEventCreatureData const* eventData /*= NULL*/)
{
SetMap(cPos.GetMap());
SetPhaseMask(cPos.GetPhaseMask(), false);
if (!CreateFromProto(guidlow, cinfo, team, data, eventData))
return false;
cPos.SelectFinalPoint(this);
if (!cPos.Relocate(this))
return false;
// Notify the outdoor pvp script
if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(GetZoneId()))
outdoorPvP->HandleCreatureCreate(this);
// Notify the map's instance data.
// Only works if you create the object in it, not if it is moves to that map.
// Normally non-players do not teleport to other maps.
if (InstanceData* iData = GetMap()->GetInstanceData())
iData->OnCreatureCreate(this);
switch (GetCreatureInfo()->rank)
{
case CREATURE_ELITE_RARE:
m_corpseDelay = sWorld.getConfig(CONFIG_UINT32_CORPSE_DECAY_RARE);
break;
case CREATURE_ELITE_ELITE:
m_corpseDelay = sWorld.getConfig(CONFIG_UINT32_CORPSE_DECAY_ELITE);
break;
case CREATURE_ELITE_RAREELITE:
m_corpseDelay = sWorld.getConfig(CONFIG_UINT32_CORPSE_DECAY_RAREELITE);
break;
case CREATURE_ELITE_WORLDBOSS:
m_corpseDelay = sWorld.getConfig(CONFIG_UINT32_CORPSE_DECAY_WORLDBOSS);
break;
default:
m_corpseDelay = sWorld.getConfig(CONFIG_UINT32_CORPSE_DECAY_NORMAL);
break;
}
// Add to CreatureLinkingHolder if needed
if (sCreatureLinkingMgr.GetLinkedTriggerInformation(this))
cPos.GetMap()->GetCreatureLinkingHolder()->AddSlaveToHolder(this);
if (sCreatureLinkingMgr.IsLinkedEventTrigger(this))
{
m_isCreatureLinkingTrigger = true;
cPos.GetMap()->GetCreatureLinkingHolder()->AddMasterToHolder(this);
}
LoadCreatureAddon(false);
return true;
}
bool Creature::IsTrainerOf(Player* pPlayer, bool msg) const
{
if (!isTrainer())
return false;
// pet trainers not have spells in fact now
if (GetCreatureInfo()->trainer_type != TRAINER_TYPE_PETS)
{
TrainerSpellData const* cSpells = GetTrainerSpells();
TrainerSpellData const* tSpells = GetTrainerTemplateSpells();
// for not pet trainer expected not empty trainer list always
if ((!cSpells || cSpells->spellList.empty()) && (!tSpells || tSpells->spellList.empty()))
{
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 (pPlayer->getClass() != GetCreatureInfo()->trainer_class)
{
if (msg)
{
pPlayer->PlayerTalkClass->ClearMenus();
switch (GetCreatureInfo()->trainer_class)
{
case CLASS_DRUID: pPlayer->PlayerTalkClass->SendGossipMenu(4913, GetObjectGuid()); break;
case CLASS_HUNTER: pPlayer->PlayerTalkClass->SendGossipMenu(10090, GetObjectGuid()); break;
case CLASS_MAGE: pPlayer->PlayerTalkClass->SendGossipMenu(328, GetObjectGuid()); break;
case CLASS_PALADIN: pPlayer->PlayerTalkClass->SendGossipMenu(1635, GetObjectGuid()); break;
case CLASS_PRIEST: pPlayer->PlayerTalkClass->SendGossipMenu(4436, GetObjectGuid()); break;
case CLASS_ROGUE: pPlayer->PlayerTalkClass->SendGossipMenu(4797, GetObjectGuid()); break;
case CLASS_SHAMAN: pPlayer->PlayerTalkClass->SendGossipMenu(5003, GetObjectGuid()); break;
case CLASS_WARLOCK: pPlayer->PlayerTalkClass->SendGossipMenu(5836, GetObjectGuid()); break;
case CLASS_WARRIOR: pPlayer->PlayerTalkClass->SendGossipMenu(4985, GetObjectGuid()); break;
}
}
return false;
}
break;
case TRAINER_TYPE_PETS:
if (pPlayer->getClass() != CLASS_HUNTER)
{
if (msg)
{
pPlayer->PlayerTalkClass->ClearMenus();
pPlayer->PlayerTalkClass->SendGossipMenu(3620, GetObjectGuid());
}
return false;
}
break;
case TRAINER_TYPE_MOUNTS:
if (GetCreatureInfo()->trainer_race && pPlayer->getRace() != GetCreatureInfo()->trainer_race)
{
// Allowed to train if exalted
if (FactionTemplateEntry const* faction_template = getFactionTemplateEntry())
{
if (pPlayer->GetReputationRank(faction_template->faction) == REP_EXALTED)
return true;
}
if (msg)
{
pPlayer->PlayerTalkClass->ClearMenus();
switch (GetCreatureInfo()->trainer_class)
{
case RACE_DWARF: pPlayer->PlayerTalkClass->SendGossipMenu(5865, GetObjectGuid()); break;
case RACE_GNOME: pPlayer->PlayerTalkClass->SendGossipMenu(4881, GetObjectGuid()); break;
case RACE_HUMAN: pPlayer->PlayerTalkClass->SendGossipMenu(5861, GetObjectGuid()); break;
case RACE_NIGHTELF: pPlayer->PlayerTalkClass->SendGossipMenu(5862, GetObjectGuid()); break;
case RACE_ORC: pPlayer->PlayerTalkClass->SendGossipMenu(5863, GetObjectGuid()); break;
case RACE_TAUREN: pPlayer->PlayerTalkClass->SendGossipMenu(5864, GetObjectGuid()); break;
case RACE_TROLL: pPlayer->PlayerTalkClass->SendGossipMenu(5816, GetObjectGuid()); break;
case RACE_UNDEAD: pPlayer->PlayerTalkClass->SendGossipMenu(624, GetObjectGuid()); break;
case RACE_BLOODELF: pPlayer->PlayerTalkClass->SendGossipMenu(5862, GetObjectGuid()); break;
case RACE_DRAENEI: pPlayer->PlayerTalkClass->SendGossipMenu(5864, GetObjectGuid()); break;
}
}
return false;
}
break;
case TRAINER_TYPE_TRADESKILLS:
if (GetCreatureInfo()->trainer_spell && !pPlayer->HasSpell(GetCreatureInfo()->trainer_spell))
{
if (msg)
{
pPlayer->PlayerTalkClass->ClearMenus();
pPlayer->PlayerTalkClass->SendGossipMenu(11031, GetObjectGuid());
}
return false;
}
break;
default:
return false; // checked and error output at creature_template loading
}
return true;
}
bool Creature::CanInteractWithBattleMaster(Player* pPlayer, bool msg) const
{
if (!isBattleMaster())
return false;
BattleGroundTypeId bgTypeId = sBattleGroundMgr.GetBattleMasterBG(GetEntry());
if (bgTypeId == BATTLEGROUND_TYPE_NONE)
return false;
if (!msg)
return pPlayer->GetBGAccessByLevel(bgTypeId);
if (!pPlayer->GetBGAccessByLevel(bgTypeId))
{
pPlayer->PlayerTalkClass->ClearMenus();
switch (bgTypeId)
{
case BATTLEGROUND_AV: pPlayer->PlayerTalkClass->SendGossipMenu(7616, GetObjectGuid()); break;
case BATTLEGROUND_WS: pPlayer->PlayerTalkClass->SendGossipMenu(7599, GetObjectGuid()); break;
case BATTLEGROUND_AB: pPlayer->PlayerTalkClass->SendGossipMenu(7642, GetObjectGuid()); 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: pPlayer->PlayerTalkClass->SendGossipMenu(10024, GetObjectGuid()); break;
default: break;
}
return false;
}
return true;
}
bool Creature::CanTrainAndResetTalentsOf(Player* pPlayer) const
{
return pPlayer->getLevel() >= 10
&& GetCreatureInfo()->trainer_type == TRAINER_TYPE_CLASS
&& pPlayer->getClass() == GetCreatureInfo()->trainer_class;
}
void Creature::PrepareBodyLootState()
{
loot.clear();
// if have normal loot then prepare it access
if (!lootForBody)
{
// have normal loot
if (GetCreatureInfo()->maxgold > 0 || GetCreatureInfo()->lootid ||
// ... or can have skinning after
(GetCreatureInfo()->SkinLootId && sWorld.getConfig(CONFIG_BOOL_CORPSE_EMPTY_LOOT_SHOW)))
{
SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
return;
}
}
lootForBody = true; // pass this loot mode
// if not have normal loot allow skinning if need
if (!lootForSkin && GetCreatureInfo()->SkinLootId)
{
RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
return;
}
RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_LOOTABLE);
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
}
/**
* Return original player who tap creature, it can be different from player/group allowed to loot so not use it for loot code
*/
Player* Creature::GetOriginalLootRecipient() const
{
return m_lootRecipientGuid ? ObjectAccessor::FindPlayer(m_lootRecipientGuid) : NULL;
}
/**
* Return group if player tap creature as group member, independent is player after leave group or stil be group member
*/
Group* Creature::GetGroupLootRecipient() const
{
// original recipient group if set and not disbanded
return m_lootGroupRecipientId ? sObjectMgr.GetGroupById(m_lootGroupRecipientId) : NULL;
}
/**
* Return player who can loot tapped creature (member of group or single player)
*
* In case when original player tap creature as group member then group tap prefered.
* This is for example important if player after tap leave group.
* If group not exist or disbanded or player tap creature not as group member return player
*/
Player* Creature::GetLootRecipient() const
{
// original recipient group if set and not disbanded
Group* group = GetGroupLootRecipient();
// original recipient player if online
Player* player = GetOriginalLootRecipient();
// if group not set or disbanded return original recipient player if any
if (!group)
return player;
// group case
// return player if it still be in original recipient group
if (player && player->GetGroup() == group)
return player;
// find any in group
for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next())
if (Player* p = itr->getSource())
return p;
return NULL;
}
/**
* Set player and group (if player group member) who tap creature
*/
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_lootRecipientGuid.Clear();
m_lootGroupRecipientId = 0;
RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED);
RemoveFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED_BY_PLAYER);
return;
}
Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself();
if (!player) // normal creature, no player involved
return;
// set player for non group case or if group will disbanded
m_lootRecipientGuid = player->GetObjectGuid();
// set group for group existing case including if player will leave group at loot time
if (Group* group = player->GetGroup())
m_lootGroupRecipientId = group->GetId();
SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED);
SetFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_TAPPED_BY_PLAYER);
}
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(GetGUIDLow());
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
CreatureData& data = sObjectMgr.NewOrExistCreatureData(GetGUIDLow());
uint32 displayId = GetNativeDisplayId();
// check if it's a custom model and if not, use 0 for displayId
CreatureInfo const* cinfo = GetCreatureInfo();
if (cinfo)
{
if (displayId != cinfo->ModelId[0] && displayId != cinfo->ModelId[1] &&
displayId != cinfo->ModelId[2] && displayId != cinfo->ModelId[3])
{
for (int i = 0; i < MAX_CREATURE_MODEL && displayId; ++i)
if (cinfo->ModelId[i])
if (CreatureModelInfo const* minfo = sObjectMgr.GetCreatureModelInfo(cinfo->ModelId[i]))
if (displayId == minfo->modelid_other_gender)
displayId = 0;
}
else
displayId = 0;
}
// data->guid = guid don't must be update at save
data.id = GetEntry();
data.mapid = mapid;
data.spawnMask = spawnMask;
data.phaseMask = phaseMask;
data.modelid_override = 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);
data.is_dead = m_isDeadByDefault;
// prevent add data integrity problems
data.movementType = !m_respawnradius && GetDefaultMovementType() == RANDOM_MOTION_TYPE
? IDLE_MOTION_TYPE : GetDefaultMovementType();
// updated in DB
WorldDatabase.BeginTransaction();
WorldDatabase.PExecuteLog("DELETE FROM creature WHERE guid=%u", GetGUIDLow());
std::ostringstream ss;
ss << "INSERT INTO creature VALUES ("
<< GetGUIDLow() << ","
<< data.id << ","
<< data.mapid << ","
<< uint32(data.spawnMask) << "," // cast to prevent save as symbol
<< uint32(data.phaseMask) << "," // prevent out of range error
<< data.modelid_override << ","
<< data.equipmentId << ","
<< data.posX << ","
<< data.posY << ","
<< data.posZ << ","
<< data.orientation << ","
<< data.spawntimesecs << "," // respawn time
<< (float) data.spawndist << "," // spawn distance (float)
<< data.currentwaypoint << "," // currentwaypoint
<< data.curhealth << "," // curhealth
<< data.curmana << "," // curmana
<< (data.is_dead ? 1 : 0) << "," // is_dead
<< uint32(data.movementType) << ")"; // default movement generator type, cast to prevent save as symbol
WorldDatabase.PExecuteLog("%s", ss.str().c_str());
WorldDatabase.CommitTransaction();
}
void Creature::SelectLevel(const CreatureInfo* cinfo, float percentHealth, float percentMana)
{
uint32 rank = IsPet() ? 0 : cinfo->rank;
// level
uint32 minlevel = std::min(cinfo->maxlevel, cinfo->minlevel);
uint32 maxlevel = std::max(cinfo->maxlevel, cinfo->minlevel);
uint32 level = minlevel == maxlevel ? minlevel : urand(minlevel, maxlevel);
SetLevel(level);
float rellevel = maxlevel == minlevel ? 0 : (float(level - minlevel)) / (maxlevel - minlevel);
// health
float healthmod = _GetHealthMod(rank);
uint32 minhealth = std::min(cinfo->maxhealth, cinfo->minhealth);
uint32 maxhealth = std::max(cinfo->maxhealth, cinfo->minhealth);
uint32 health = uint32(healthmod * (minhealth + uint32(rellevel * (maxhealth - minhealth))));
SetCreateHealth(health);
SetMaxHealth(health);
if (percentHealth == 100.0f)
SetHealth(health);
else
SetHealthPercent(percentHealth);
// mana
uint32 minmana = std::min(cinfo->maxmana, cinfo->minmana);
uint32 maxmana = std::max(cinfo->maxmana, cinfo->minmana);
uint32 mana = minmana + uint32(rellevel * (maxmana - minmana));
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 = _GetDamageMod(rank);
SetBaseWeaponDamage(BASE_ATTACK, MINDAMAGE, cinfo->mindmg * damagemod);
SetBaseWeaponDamage(BASE_ATTACK, MAXDAMAGE, cinfo->maxdmg * damagemod);
SetBaseWeaponDamage(OFF_ATTACK, MINDAMAGE, cinfo->mindmg * damagemod);
SetBaseWeaponDamage(OFF_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.getConfig(CONFIG_FLOAT_RATE_CREATURE_NORMAL_HP);
case CREATURE_ELITE_ELITE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_ELITE_HP);
case CREATURE_ELITE_RAREELITE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_RAREELITE_HP);
case CREATURE_ELITE_WORLDBOSS:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_WORLDBOSS_HP);
case CREATURE_ELITE_RARE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_RARE_HP);
default:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_ELITE_HP);
}
}
float Creature::_GetDamageMod(int32 Rank)
{
switch (Rank) // define rates for each elite rank
{
case CREATURE_ELITE_NORMAL:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_NORMAL_DAMAGE);
case CREATURE_ELITE_ELITE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_ELITE_DAMAGE);
case CREATURE_ELITE_RAREELITE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_RAREELITE_DAMAGE);
case CREATURE_ELITE_WORLDBOSS:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_WORLDBOSS_DAMAGE);
case CREATURE_ELITE_RARE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_RARE_DAMAGE);
default:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_ELITE_DAMAGE);
}
}
float Creature::GetSpellDamageMod(int32 Rank)
{
switch (Rank) // define rates for each elite rank
{
case CREATURE_ELITE_NORMAL:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_NORMAL_SPELLDAMAGE);
case CREATURE_ELITE_ELITE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE);
case CREATURE_ELITE_RAREELITE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_RAREELITE_SPELLDAMAGE);
case CREATURE_ELITE_WORLDBOSS:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_WORLDBOSS_SPELLDAMAGE);
case CREATURE_ELITE_RARE:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_RARE_SPELLDAMAGE);
default:
return sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_ELITE_ELITE_SPELLDAMAGE);
}
}
bool Creature::CreateFromProto(uint32 guidlow, CreatureInfo const* cinfo, Team team, const CreatureData* data /*=NULL*/, GameEventCreatureData const* eventData /*=NULL*/)
{
m_originalEntry = cinfo->Entry;
Object::_Create(guidlow, cinfo->Entry, cinfo->GetHighGuid());
if (!UpdateEntry(cinfo->Entry, team, data, eventData, false))
return false;
return true;
}
bool Creature::LoadFromDB(uint32 guidlow, Map* map)
{
CreatureData const* data = sObjectMgr.GetCreatureData(guidlow);
if (!data)
{
sLog.outErrorDb("Creature (GUID: %u) not found in table `creature`, can't load. ", guidlow);
return false;
}
CreatureInfo const* cinfo = ObjectMgr::GetCreatureTemplate(data->id);
if (!cinfo)
{
sLog.outErrorDb("Creature (Entry: %u) not found in table `creature_template`, can't load. ", data->id);
return false;
}
GameEventCreatureData const* eventData = sGameEventMgr.GetCreatureUpdateDataForActiveEvent(guidlow);
// Creature can be loaded already in map if grid has been unloaded while creature walk to another grid
if (map->GetCreature(cinfo->GetObjectGuid(guidlow)))
return false;
CreatureCreatePos pos(map, data->posX, data->posY, data->posZ, data->orientation, data->phaseMask);
if (!Create(guidlow, pos, cinfo, TEAM_NONE, data, eventData))
return false;
SetRespawnCoord(pos);
m_respawnradius = data->spawndist;
m_respawnDelay = data->spawntimesecs;
m_corpseDelay = std::min(m_respawnDelay * 9 / 10, m_corpseDelay); // set corpse delay to 90% of the respawn delay
m_isDeadByDefault = data->is_dead;
m_deathState = m_isDeadByDefault ? DEAD : ALIVE;
m_respawnTime = map->GetPersistentState()->GetCreatureRespawnTime(GetGUIDLow());
if (m_respawnTime > time(NULL)) // not ready to respawn
{
m_deathState = DEAD;
if (CanFly())
{
float tz = GetTerrain()->GetHeightStatic(data->posX, data->posY, data->posZ, false);
if (data->posZ - tz > 0.1)
Relocate(data->posX, data->posY, tz);
}
}
else if (m_respawnTime) // respawn time set but expired
{
m_respawnTime = 0;
GetMap()->GetPersistentState()->SaveCreatureRespawnTime(GetGUIDLow(), 0);
}
uint32 curhealth = data->curhealth;
if (curhealth)
{
curhealth = uint32(curhealth * _GetHealthMod(GetCreatureInfo()->rank));
if (curhealth < 1)
curhealth = 1;
}
if (sCreatureLinkingMgr.IsSpawnedByLinkedMob(this))
{
m_isSpawningLinked = true;
if (m_deathState == ALIVE && !GetMap()->GetCreatureLinkingHolder()->CanSpawn(this))
{
m_deathState = DEAD;
// Just set to dead, so need to relocate like above
if (CanFly())
{
float tz = GetTerrain()->GetHeightStatic(data->posX, data->posY, data->posZ, false);
if (data->posZ - tz > 0.1)
Relocate(data->posX, data->posY, tz);
}
}
}
SetHealth(m_deathState == ALIVE ? curhealth : 0);
SetPower(POWER_MANA, data->curmana);
SetMeleeDamageSchool(SpellSchools(GetCreatureInfo()->dmgschool));
// checked at creature_template loading
m_defaultMovementType = MovementGeneratorType(data->movementType);
AIM_Initialize();
// Creature Linking, Initial load is handled like respawn
if (m_isCreatureLinkingTrigger && isAlive())
GetMap()->GetCreatureLinkingHolder()->DoCreatureLinkingEvent(LINKING_EVENT_RESPAWN, this);
// check if it is rabbit day
if (isAlive() && sWorld.getConfig(CONFIG_UINT32_RABBIT_DAY))
{
time_t rabbit_day = time_t(sWorld.getConfig(CONFIG_UINT32_RABBIT_DAY));
tm rabbit_day_tm = *localtime(&rabbit_day);
tm now_tm = *localtime(&sWorld.GetGameTime());
if (now_tm.tm_mon == rabbit_day_tm.tm_mon && now_tm.tm_mday == rabbit_day_tm.tm_mday)
CastSpell(this, 10710 + urand(0, 2), true);
}
return true;
}
void Creature::LoadEquipment(uint32 equip_entry, bool force)
{
if (equip_entry == 0)
{
if (force)
{
for (uint8 i = 0; i < MAX_VIRTUAL_ITEM_SLOT; ++i)
SetVirtualItem(VirtualItemSlot(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 < MAX_VIRTUAL_ITEM_SLOT; ++i)
SetVirtualItem(VirtualItemSlot(i), einfo->equipentry[i]);
}
bool Creature::HasQuest(uint32 quest_id) const
{
QuestRelationsMapBounds bounds = sObjectMgr.GetCreatureQuestRelationsMapBounds(GetEntry());
for (QuestRelationsMap::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
{
if (itr->second == quest_id)
return true;
}
return false;
}
bool Creature::HasInvolvedQuest(uint32 quest_id) const
{
QuestRelationsMapBounds bounds = sObjectMgr.GetCreatureQuestInvolvedRelationsMapBounds(GetEntry());
for (QuestRelationsMap::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
{
if (itr->second == quest_id)
return true;
}
return false;
}
struct CreatureRespawnDeleteWorker
{
explicit CreatureRespawnDeleteWorker(uint32 guid) : i_guid(guid) {}
void operator()(MapPersistentState* state)
{
state->SaveCreatureRespawnTime(i_guid, 0);
}
uint32 i_guid;
};
void Creature::DeleteFromDB()
{
CreatureData const* data = sObjectMgr.GetCreatureData(GetGUIDLow());
if (!data)
{
DEBUG_LOG("Trying to delete not saved creature!");
return;
}
DeleteFromDB(GetGUIDLow(), data);
}
void Creature::DeleteFromDB(uint32 lowguid, CreatureData const* data)
{
CreatureRespawnDeleteWorker worker(lowguid);
sMapPersistentStateMgr.DoForAllStatesWithMapId(data->mapid, worker);
sObjectMgr.DeleteCreatureData(lowguid);
WorldDatabase.BeginTransaction();
WorldDatabase.PExecuteLog("DELETE FROM creature WHERE guid=%u", lowguid);
WorldDatabase.PExecuteLog("DELETE FROM creature_addon WHERE guid=%u", lowguid);
WorldDatabase.PExecuteLog("DELETE FROM creature_movement WHERE id=%u", lowguid);
WorldDatabase.PExecuteLog("DELETE FROM game_event_creature WHERE guid=%u", lowguid);
WorldDatabase.PExecuteLog("DELETE FROM game_event_creature_data WHERE guid=%u", lowguid);
WorldDatabase.PExecuteLog("DELETE FROM creature_battleground WHERE guid=%u", lowguid);
WorldDatabase.PExecuteLog("DELETE FROM creature_linking WHERE guid=%u OR master_guid=%u", lowguid, lowguid);
WorldDatabase.CommitTransaction();
}
float Creature::GetAttackDistance(Unit const* pl) const
{
float aggroRate = sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_AGGRO);
if (aggroRate == 0)
return 0.0f;
uint32 playerlevel = pl->GetLevelForTarget(this);
uint32 creaturelevel = GetLevelForTarget(pl);
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.getConfig(CONFIG_UINT32_MAX_PLAYER_LEVEL))
{
// detect range auras
RetDistance += GetTotalAuraModifier(SPELL_AURA_MOD_DETECT_RANGE);
// detected range auras
RetDistance += pl->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)
{
if ((s == JUST_DIED && !m_isDeadByDefault) || (s == JUST_ALIVED && m_isDeadByDefault))
{
m_corpseDecayTimer = m_corpseDelay * IN_MILLISECONDS; // the max/default time for corpse decay (before creature is looted/AllLootRemovedFromCorpse() is called)
m_respawnTime = time(NULL) + m_respawnDelay; // respawn delay (spawntimesecs)
// always save boss respawn time at death to prevent crash cheating
if (sWorld.getConfig(CONFIG_BOOL_SAVE_RESPAWN_TIME_IMMEDIATELY) || IsWorldBoss())
SaveRespawnTime();
}
Unit::SetDeathState(s);
if (s == JUST_DIED)
{
SetTargetGuid(ObjectGuid()); // remove target selection in any cases (can be set at aura remove in Unit::SetDeathState)
SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE);
if (HasSearchedAssistance())
{
SetNoSearchAssistance(false);
UpdateSpeed(MOVE_RUN, false);
}
if (CanFly())
i_motionMaster.MoveFall();
Unit::SetDeathState(CORPSE);
}
if (s == JUST_ALIVED)
{
clearUnitState(UNIT_STAT_ALL_STATE);
Unit::SetDeathState(ALIVE);
SetHealth(GetMaxHealth());
SetLootRecipient(NULL);
if (GetTemporaryFactionFlags() & TEMPFACTION_RESTORE_RESPAWN)
ClearTemporaryFaction();
SetMeleeDamageSchool(SpellSchools(GetCreatureInfo()->dmgschool));
// Dynamic flags may be adjusted by spells. Clear them
// first and let spell from *addon apply where needed.
SetUInt32Value(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_NONE);
LoadCreatureAddon(true);
// Flags after LoadCreatureAddon. Any spell in *addon
// will not be able to adjust these.
SetUInt32Value(UNIT_NPC_FLAGS, GetCreatureInfo()->npcflag);
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE);
SetWalk(true, true);
i_motionMaster.Initialize();
}
}
void Creature::Respawn()
{
if (!IsInWorld()) // Could be removed as part of a pool (in which case respawn-time is handled with pool-system)
return;
RemoveCorpse();
if (IsDespawned())
{
if (HasStaticDBSpawnData())
GetMap()->GetPersistentState()->SaveCreatureRespawnTime(GetGUIDLow(), 0);
m_respawnTime = time(NULL); // respawn at next tick
}
}
void Creature::ForcedDespawn(uint32 timeMSToDespawn)
{
if (timeMSToDespawn)
{
ForcedDespawnDelayEvent* pEvent = new ForcedDespawnDelayEvent(*this);
m_Events.AddEvent(pEvent, m_Events.CalculateTime(timeMSToDespawn));
return;
}
if (IsDespawned())
return;
if (isAlive())
SetDeathState(JUST_DIED);
RemoveCorpse();
SetHealth(0); // just for nice GM-mode view
}
bool Creature::IsImmuneToSpell(SpellEntry const* spellInfo, bool castOnSelf)
{
if (!spellInfo)
return false;
if (!castOnSelf && GetCreatureInfo()->MechanicImmuneMask & (1 << (spellInfo->GetMechanic() - 1)))
return true;
return Unit::IsImmuneToSpell(spellInfo, castOnSelf);
}
bool Creature::IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index, bool castOnSelf) const
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(index);
if (!spellEffect)
return false;
if (!castOnSelf && GetCreatureInfo()->MechanicImmuneMask & (1 << (spellEffect->EffectMechanic - 1)))
return true;
// Taunt immunity special flag check
if (GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_NOT_TAUNTABLE)
{
// Taunt aura apply check
if (spellEffect->Effect == SPELL_EFFECT_APPLY_AURA)
{
if (spellEffect->EffectApplyAuraName == SPELL_AURA_MOD_TAUNT)
return true;
}
// Spell effect taunt check
else if (spellEffect->Effect == SPELL_EFFECT_ATTACK_ME)
return true;
}
return Unit::IsImmuneToSpellEffect(spellInfo, index, castOnSelf);
}
SpellEntry const* Creature::ReachWithSpellAttack(Unit* pVictim)
{
if (!pVictim)
return NULL;
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
{
if (!m_spells[i])
continue;
SpellEntry const* spellInfo = sSpellStore.LookupEntry(m_spells[i]);
if (!spellInfo)
{
sLog.outError("WORLD: unknown spell id %i", m_spells[i]);
continue;
}
bool bcontinue = true;
for (int j = 0; j < MAX_EFFECT_INDEX; ++j)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(j));
if(!spellEffect)
continue;
if( (spellEffect->Effect == SPELL_EFFECT_SCHOOL_DAMAGE ) ||
(spellEffect->Effect == SPELL_EFFECT_INSTAKILL) ||
(spellEffect->Effect == SPELL_EFFECT_ENVIRONMENTAL_DAMAGE) ||
(spellEffect->Effect == SPELL_EFFECT_HEALTH_LEECH )
)
{
bcontinue = false;
break;
}
}
if (bcontinue)
continue;
if(spellInfo->GetManaCost() > GetPower(POWER_MANA))
continue;
SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex);
float range = GetSpellMaxRange(srange);
float minrange = GetSpellMinRange(srange);
float dist = GetCombatDistance(pVictim, spellInfo->rangeIndex == SPELL_RANGE_IDX_COMBAT);
// if(!isInFront( pVictim, range ) && spellInfo->AttributesEx )
// continue;
if (dist > range || dist < minrange)
continue;
if(spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_SILENCE && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
continue;
if(spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_PACIFY && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
continue;
return spellInfo;
}
return NULL;
}
SpellEntry const* Creature::ReachWithSpellCure(Unit* pVictim)
{
if (!pVictim)
return NULL;
for (uint32 i = 0; i < CREATURE_MAX_SPELLS; ++i)
{
if (!m_spells[i])
continue;
SpellEntry const* spellInfo = sSpellStore.LookupEntry(m_spells[i]);
if (!spellInfo)
{
sLog.outError("WORLD: unknown spell id %i", m_spells[i]);
continue;
}
bool bcontinue = true;
for (int j = 0; j < MAX_EFFECT_INDEX; ++j)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(j));
if (spellEffect && spellEffect->Effect == SPELL_EFFECT_HEAL)
{
bcontinue = false;
break;
}
}
if (bcontinue)
continue;
if(spellInfo->GetManaCost() > GetPower(POWER_MANA))
continue;
SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(spellInfo->rangeIndex);
float range = GetSpellMaxRange(srange);
float minrange = GetSpellMinRange(srange);
float dist = GetCombatDistance(pVictim, spellInfo->rangeIndex == SPELL_RANGE_IDX_COMBAT);
// if(!isInFront( pVictim, range ) && spellInfo->AttributesEx )
// continue;
if (dist > range || dist < minrange)
continue;
if(spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_SILENCE && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SILENCED))
continue;
if(spellInfo->GetPreventionType() == SPELL_PREVENTION_TYPE_PACIFY && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PACIFIED))
continue;
return spellInfo;
}
return NULL;
}
bool Creature::IsVisibleInGridForPlayer(Player* pl) const
{
// gamemaster in GM mode see all, including ghosts
if (pl->isGameMaster())
return true;
if (GetCreatureInfo()->flags_extra & CREATURE_FLAG_EXTRA_INVISIBLE)
return false;
// Live player (or with not release body see live creatures or death creatures with corpse disappearing time > 0
if (pl->isAlive() || pl->GetDeathTimer() > 0)
{
return (isAlive() || m_corpseDecayTimer > 0 || (m_isDeadByDefault && m_deathState == CORPSE));
}
// Dead player see live creatures near own corpse
if (isAlive())
{
Corpse* corpse = pl->GetCorpse();
if (corpse)
{
// 20 - aggro distance for same level, 25 - max additional distance if player level less that creature level
if (corpse->IsWithinDistInMap(this, (20 + 25)*sWorld.getConfig(CONFIG_FLOAT_RATE_CREATURE_AGGRO)))
return true;
}
}
// Dead player can see ghosts
if (GetCreatureInfo()->type_flags & CREATURE_TYPEFLAGS_GHOST_VISIBLE)
return true;
// and not see any other
return false;
}
void Creature::SendAIReaction(AiReaction reactionType)
{
WorldPacket data(SMSG_AI_REACTION, 12);
data << GetObjectGuid();
data << uint32(reactionType);
((WorldObject*)this)->SendMessageToSet(&data, true);
DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "WORLD: Sent SMSG_AI_REACTION, type %u.", reactionType);
}
void Creature::CallAssistance()
{
// FIXME: should player pets call for assistance?
if (!m_AlreadyCallAssistance && getVictim() && !isCharmed())
{
SetNoCallAssistance(true);
AI()->SendAIEventAround(AI_EVENT_CALL_ASSISTANCE, getVictim(), sWorld.getConfig(CONFIG_UINT32_CREATURE_FAMILY_ASSISTANCE_DELAY), sWorld.getConfig(CONFIG_FLOAT_CREATURE_FAMILY_ASSISTANCE_RADIUS));
}
}
void Creature::CallForHelp(float fRadius)
{
if (fRadius <= 0.0f || !getVictim() || IsPet() || isCharmed())
return;
MaNGOS::CallOfHelpCreatureInRangeDo u_do(this, getVictim(), fRadius);
MaNGOS::CreatureWorker<MaNGOS::CallOfHelpCreatureInRangeDo> worker(this, u_do);
Cell::VisitGridObjects(this, worker, fRadius);
}
/// if enemy provided, check for initial combat help against enemy
bool Creature::CanAssistTo(const Unit* u, const Unit* enemy, bool checkfaction /*= true*/) const
{
// 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 (enemy && 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 (enemy && !IsHostileTo(enemy))
return false;
return true;
}
bool Creature::CanInitiateAttack()
{
if (hasUnitState(UNIT_STAT_STUNNED | UNIT_STAT_DIED))
return false;
if (HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_NOT_SELECTABLE))
return false;
if (isPassiveToHostile())
return false;
return true;
}
void Creature::SaveRespawnTime()
{
if (IsPet() || !HasStaticDBSpawnData())
return;
if (m_respawnTime > time(NULL)) // dead (no corpse)
GetMap()->GetPersistentState()->SaveCreatureRespawnTime(GetGUIDLow(), m_respawnTime);
else if (m_corpseDecayTimer > 0) // dead (corpse)
GetMap()->GetPersistentState()->SaveCreatureRespawnTime(GetGUIDLow(), time(NULL) + m_respawnDelay + m_corpseDecayTimer / IN_MILLISECONDS);
}
bool Creature::IsOutOfThreatArea(Unit* pVictim) const
{
if (!pVictim)
return true;
if (!pVictim->IsInMap(this))
return true;
if (!pVictim->isTargetableForAttack())
return true;
if (!pVictim->isInAccessablePlaceFor(this))
return true;
if (!pVictim->isVisibleForOrDetect(this, this, false))
return true;
if (sMapStore.LookupEntry(GetMapId())->IsDungeon())
return false;
float AttackDist = GetAttackDistance(pVictim);
float ThreatRadius = sWorld.getConfig(CONFIG_FLOAT_THREAT_RADIUS);
// Use AttackDistance in distance check if threat radius is lower. This prevents creature bounce in and out of combat every update tick.
return !pVictim->IsWithinDist3d(m_combatStartX, m_combatStartY, m_combatStartZ,
ThreatRadius > AttackDist ? ThreatRadius : AttackDist);
}
CreatureDataAddon const* Creature::GetCreatureAddon() const
{
if (CreatureDataAddon const* addon = ObjectMgr::GetCreatureAddon(GetGUIDLow()))
return addon;
// dependent from difficulty mode entry
if (GetEntry() != GetCreatureInfo()->Entry)
{
// If CreatureTemplateAddon for difficulty_entry_N exist, it's there for a reason
if (CreatureDataAddon const* addon = ObjectMgr::GetCreatureTemplateAddon(GetCreatureInfo()->Entry))
return addon;
}
// Return CreatureTemplateAddon when nothing else exist
return ObjectMgr::GetCreatureTemplateAddon(GetEntry());
}
// creature_addon table
bool Creature::LoadCreatureAddon(bool reload)
{
CreatureDataAddon 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));
}
// UNIT_FIELD_BYTES_2
// 0 SheathState
// 1 UnitPVPStateFlags Set at Creature::UpdateEntry (SetPvp())
// 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, cainfo->sheath_state);
if (cainfo->pvp_state != 0)
SetByteValue(UNIT_FIELD_BYTES_2, 1, cainfo->pvp_state);
// SetByteValue(UNIT_FIELD_BYTES_2, 2, 0);
// SetByteValue(UNIT_FIELD_BYTES_2, 3, 0);
if (cainfo->emote != 0)
SetUInt32Value(UNIT_NPC_EMOTESTATE, cainfo->emote);
if (cainfo->splineFlags & SPLINEFLAG_FLYING)
SetLevitate(true);
if (cainfo->auras)
{
for (uint32 const* cAura = cainfo->auras; *cAura; ++cAura)
{
if (HasAuraOfDifficulty(*cAura))
{
if (!reload)
sLog.outErrorDb("Creature (GUIDLow: %u Entry: %u) has spell %u in `auras` field, but aura is already applied.", GetGUIDLow(), GetEntry(), *cAura);
continue;
}
SpellEntry const* spellInfo = sSpellStore.LookupEntry(*cAura); // Already checked on load
// Get Difficulty mode for initial case (npc not yet added to world)
if (spellInfo->SpellDifficultyId && !reload && GetMap()->IsDungeon())
if (SpellEntry const* spellEntry = GetSpellEntryByDifficulty(spellInfo->SpellDifficultyId, GetMap()->GetDifficulty(), GetMap()->IsRaid()))
spellInfo = spellEntry;
CastSpell(this, spellInfo, true);
}
}
return true;
}
/// Sends a message to LocalDefense and WorldDefense channels for players of the other team
void Creature::SendZoneUnderAttackMessage(Player* attacker)
{
sWorld.SendZoneUnderAttackMessage(GetZoneId(), attacker->GetTeam() == ALLIANCE ? HORDE : ALLIANCE);
}
void Creature::SetInCombatWithZone()
{
if (!CanHaveThreatList())
{
sLog.outError("Creature entry %u call SetInCombatWithZone but creature cannot have threat list.", GetEntry());
return;
}
Map* pMap = GetMap();
if (!pMap->IsDungeon())
{
sLog.outError("Creature entry %u call SetInCombatWithZone for map (id: %u) that isn't an instance.", GetEntry(), pMap->GetId());
return;
}
Map::PlayerList const& PlList = pMap->GetPlayers();
if (PlList.isEmpty())
return;
for (Map::PlayerList::const_iterator i = PlList.begin(); i != PlList.end(); ++i)
{
if (Player* pPlayer = i->getSource())
{
if (pPlayer->isGameMaster())
continue;
if (pPlayer->isAlive() && !IsFriendlyTo(pPlayer))
{
pPlayer->SetInCombatWith(this);
AddThreat(pPlayer);
}
}
}
}
bool Creature::MeetsSelectAttackingRequirement(Unit* pTarget, SpellEntry const* pSpellInfo, uint32 selectFlags) const
{
if (selectFlags & SELECT_FLAG_PLAYER && pTarget->GetTypeId() != TYPEID_PLAYER)
return false;
if (selectFlags & SELECT_FLAG_POWER_MANA && pTarget->getPowerType() != POWER_MANA)
return false;
else if (selectFlags & SELECT_FLAG_POWER_RAGE && pTarget->getPowerType() != POWER_RAGE)
return false;
else if (selectFlags & SELECT_FLAG_POWER_ENERGY && pTarget->getPowerType() != POWER_ENERGY)
return false;
else if (selectFlags & SELECT_FLAG_POWER_RUNIC && pTarget->getPowerType() != POWER_RUNIC_POWER)
return false;
if (selectFlags & SELECT_FLAG_IN_MELEE_RANGE && !CanReachWithMeleeAttack(pTarget))
return false;
if (selectFlags & SELECT_FLAG_NOT_IN_MELEE_RANGE && CanReachWithMeleeAttack(pTarget))
return false;
if (selectFlags & SELECT_FLAG_IN_LOS && !IsWithinLOSInMap(pTarget))
return false;
if (pSpellInfo)
{
switch (pSpellInfo->rangeIndex)
{
case SPELL_RANGE_IDX_SELF_ONLY: return false;
case SPELL_RANGE_IDX_ANYWHERE: return true;
case SPELL_RANGE_IDX_COMBAT: return CanReachWithMeleeAttack(pTarget);
}
SpellRangeEntry const* srange = sSpellRangeStore.LookupEntry(pSpellInfo->rangeIndex);
float max_range = GetSpellMaxRange(srange);
float min_range = GetSpellMinRange(srange);
float dist = GetCombatDistance(pTarget, false);
return dist < max_range && dist >= min_range;
}
return true;
}
Unit* Creature::SelectAttackingTarget(AttackingTarget target, uint32 position, uint32 uiSpellEntry, uint32 selectFlags) const
{
return SelectAttackingTarget(target, position, sSpellStore.LookupEntry(uiSpellEntry), selectFlags);
}
Unit* Creature::SelectAttackingTarget(AttackingTarget target, uint32 position, SpellEntry const* pSpellInfo /*= NULL*/, uint32 selectFlags/*= 0*/) const
{
if (!CanHaveThreatList())
return NULL;
// ThreatList m_threatlist;
ThreatList const& threatlist = getThreatManager().getThreatList();
ThreatList::const_iterator itr = threatlist.begin();
ThreatList::const_reverse_iterator ritr = threatlist.rbegin();
if (position >= threatlist.size() || !threatlist.size())
return NULL;
switch (target)
{
case ATTACKING_TARGET_RANDOM:
{
std::vector<Unit*> suitableUnits;
suitableUnits.reserve(threatlist.size() - position);
advance(itr, position);
for (; itr != threatlist.end(); ++itr)
if (Unit* pTarget = GetMap()->GetUnit((*itr)->getUnitGuid()))
if (!selectFlags || MeetsSelectAttackingRequirement(pTarget, pSpellInfo, selectFlags))
suitableUnits.push_back(pTarget);
if (!suitableUnits.empty())
return suitableUnits[urand(0, suitableUnits.size() - 1)];
break;
}
case ATTACKING_TARGET_TOPAGGRO:
{
advance(itr, position);
for (; itr != threatlist.end(); ++itr)
if (Unit* pTarget = GetMap()->GetUnit((*itr)->getUnitGuid()))
if (!selectFlags || MeetsSelectAttackingRequirement(pTarget, pSpellInfo, selectFlags))
return pTarget;
break;
}
case ATTACKING_TARGET_BOTTOMAGGRO:
{
advance(ritr, position);
for (; ritr != threatlist.rend(); ++ritr)
if (Unit* pTarget = GetMap()->GetUnit((*itr)->getUnitGuid()))
if (!selectFlags || MeetsSelectAttackingRequirement(pTarget, pSpellInfo, selectFlags))
return pTarget;
break;
}
}
return NULL;
}
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)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellid);
if (!spellInfo)
return;
uint32 cooldown = GetSpellRecoveryTime(spellInfo);
if (cooldown)
_AddCreatureSpellCooldown(spellid, time(NULL) + cooldown / IN_MILLISECONDS);
if(uint32 category = spellInfo->GetCategory())
_AddCreatureCategoryCooldown(category, time(NULL));
}
bool Creature::HasCategoryCooldown(uint32 spell_id) const
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell_id);
if (!spellInfo)
return false;
CreatureSpellCooldowns::const_iterator itr = m_CreatureCategoryCooldowns.find(spellInfo->GetCategory());
return (itr != m_CreatureCategoryCooldowns.end() && time_t(itr->second + (spellInfo->GetCategoryRecoveryTime() / 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);
}
uint8 Creature::getRace() const
{
uint8 race = Unit::getRace();
return race ? race : GetCreatureModelRace(GetNativeDisplayId());
}
bool Creature::IsInEvadeMode() const
{
return !i_motionMaster.empty() && i_motionMaster.GetCurrentMovementGeneratorType() == HOME_MOTION_TYPE;
}
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; // break before end of iteration of known spells
}
time_t Creature::GetRespawnTimeEx() const
{
time_t now = time(NULL);
if (m_respawnTime > now) // dead (no corpse)
return m_respawnTime;
else if (m_corpseDecayTimer > 0) // dead (corpse)
return now + m_respawnDelay + m_corpseDecayTimer / IN_MILLISECONDS;
else
return now;
}
void Creature::GetRespawnCoord(float& x, float& y, float& z, float* ori, float* dist) const
{
x = m_respawnPos.x;
y = m_respawnPos.y;
z = m_respawnPos.z;
if (ori)
*ori = m_respawnPos.o;
if (dist)
*dist = GetRespawnRadius();
// lets check if our creatures have valid spawn coordinates
MANGOS_ASSERT(MaNGOS::IsValidMapCoord(x, y, z) || PrintCoordinatesError(x, y, z, "respawn"));
}
void Creature::ResetRespawnCoord()
{
if (CreatureData const* data = sObjectMgr.GetCreatureData(GetGUIDLow()))
{
m_respawnPos.x = data->posX;
m_respawnPos.y = data->posY;
m_respawnPos.z = data->posZ;
m_respawnPos.o = data->orientation;
}
}
void Creature::AllLootRemovedFromCorpse()
{
if (lootForBody && !HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_SKINNABLE))
{
uint32 corpseLootedDelay;
if (!lootForSkin) // corpse was not skinned -> apply corpseLootedDelay
{
// use a static spawntimesecs/3 modifier (guessed/made up value) unless config are more than 0.0
// spawntimesecs=3min: corpse decay after 1min
// spawntimesecs=4hour: corpse decay after 1hour 20min
if (sWorld.getConfig(CONFIG_FLOAT_RATE_CORPSE_DECAY_LOOTED) > 0.0f)
corpseLootedDelay = (uint32)((m_corpseDelay * IN_MILLISECONDS) * sWorld.getConfig(CONFIG_FLOAT_RATE_CORPSE_DECAY_LOOTED));
else
corpseLootedDelay = (m_respawnDelay * IN_MILLISECONDS) / 3;
}
else // corpse was skinned, corpse will despawn next update
corpseLootedDelay = 0;
// if m_respawnTime is not expired already
if (m_respawnTime >= time(NULL))
{
// if spawntimesecs is larger than default corpse delay always use corpseLootedDelay
if (m_respawnDelay > m_corpseDelay)
{
m_corpseDecayTimer = corpseLootedDelay;
}
else
{
// if m_respawnDelay is relatively short and corpseDecayTimer is larger than corpseLootedDelay
if (m_corpseDecayTimer > corpseLootedDelay)
m_corpseDecayTimer = corpseLootedDelay;
}
}
else
{
m_corpseDecayTimer = 0;
// TODO: reaching here, means mob will respawn at next tick.
// This might be a place to set some aggro delay so creature has
// ~5 seconds before it can react to hostile surroundings.
// It's worth noting that it will not be fully correct either way.
// At this point another "instance" of the creature are presumably expected to
// be spawned already, while this corpse will not appear in respawned form.
}
}
}
uint32 Creature::GetLevelForTarget(Unit const* target) const
{
if (!IsWorldBoss())
return Unit::GetLevelForTarget(target);
uint32 level = target->getLevel() + sWorld.getConfig(CONFIG_UINT32_WORLD_BOSS_LEVEL_DIFF);
if (level < 1)
return 1;
if (level > 255)
return 255;
return level;
}
std::string Creature::GetAIName() const
{
return ObjectMgr::GetCreatureTemplate(GetEntry())->AIName;
}
std::string Creature::GetScriptName() const
{
return sScriptMgr.GetScriptName(GetScriptId());
}
uint32 Creature::GetScriptId() const
{
return ObjectMgr::GetCreatureTemplate(GetEntry())->ScriptID;
}
VendorItemData const* Creature::GetVendorItems() const
{
return sObjectMgr.GetNpcVendorItemList(GetEntry());
}
VendorItemData const* Creature::GetVendorTemplateItems() const
{
uint32 vendorId = GetCreatureInfo()->vendorId;
return vendorId ? sObjectMgr.GetNpcVendorTemplateItemList(vendorId) : NULL;
}
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 (vCount->lastIncrementTime + vItem->incrtime <= ptime)
{
ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(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 (vCount->lastIncrementTime + vItem->incrtime <= ptime)
{
ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(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::GetTrainerTemplateSpells() const
{
uint32 trainerId = GetCreatureInfo()->trainerId;
return trainerId ? sObjectMgr.GetNpcTrainerTemplateSpells(trainerId) : NULL;
}
TrainerSpellData const* Creature::GetTrainerSpells() const
{
return sObjectMgr.GetNpcTrainerSpells(GetEntry());
}
// overwrite WorldObject function for proper name localization
const char* Creature::GetNameForLocaleIdx(int32 loc_idx) const
{
char const* name = GetName();
sObjectMgr.GetCreatureLocaleStrings(GetEntry(), loc_idx, &name);
return name;
}
void Creature::SetFactionTemporary(uint32 factionId, uint32 tempFactionFlags)
{
m_temporaryFactionFlags = tempFactionFlags;
setFaction(factionId);
if (m_temporaryFactionFlags & TEMPFACTION_TOGGLE_NON_ATTACKABLE)
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
if (m_temporaryFactionFlags & TEMPFACTION_TOGGLE_OOC_NOT_ATTACK)
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_OOC_NOT_ATTACKABLE);
if (m_temporaryFactionFlags & TEMPFACTION_TOGGLE_PASSIVE)
RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PASSIVE);
}
void Creature::ClearTemporaryFaction()
{
// No restore if creature is charmed/possessed.
// For later we may consider extend to restore to charmer faction where charmer is creature.
// This can also be done by update any pet/charmed of creature at any faction change to charmer.
if (isCharmed())
return;
// Reset to original faction
setFaction(GetCreatureInfo()->faction_A);
// Reset UNIT_FLAG_NON_ATTACKABLE, UNIT_FLAG_OOC_NOT_ATTACKABLE or UNIT_FLAG_PASSIVE flags
if (m_temporaryFactionFlags & TEMPFACTION_TOGGLE_NON_ATTACKABLE && GetCreatureInfo()->unit_flags & UNIT_FLAG_NON_ATTACKABLE)
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NON_ATTACKABLE);
if (m_temporaryFactionFlags & TEMPFACTION_TOGGLE_OOC_NOT_ATTACK && GetCreatureInfo()->unit_flags & UNIT_FLAG_OOC_NOT_ATTACKABLE && !isInCombat())
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_OOC_NOT_ATTACKABLE);
if (m_temporaryFactionFlags & TEMPFACTION_TOGGLE_PASSIVE && GetCreatureInfo()->unit_flags & UNIT_FLAG_PASSIVE)
SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PASSIVE);
m_temporaryFactionFlags = TEMPFACTION_NONE;
}
void Creature::SendAreaSpiritHealerQueryOpcode(Player* pl)
{
uint32 next_resurrect = 0;
if (Spell* pcurSpell = GetCurrentSpell(CURRENT_CHANNELED_SPELL))
next_resurrect = pcurSpell->GetCastedTime();
WorldPacket data(SMSG_AREA_SPIRIT_HEALER_TIME, 8 + 4);
data << ObjectGuid(GetObjectGuid());
data << uint32(next_resurrect);
pl->SendDirectMessage(&data);
}
void Creature::ApplyGameEventSpells(GameEventCreatureData const* eventData, bool activated)
{
uint32 cast_spell = activated ? eventData->spell_id_start : eventData->spell_id_end;
uint32 remove_spell = activated ? eventData->spell_id_end : eventData->spell_id_start;
if (remove_spell)
if (SpellEntry const* spellEntry = sSpellStore.LookupEntry(remove_spell))
if (IsSpellAppliesAura(spellEntry))
RemoveAurasDueToSpell(remove_spell);
if (cast_spell)
CastSpell(this, cast_spell, true);
}
void Creature::FillGuidsListFromThreatList(GuidVector& guids, uint32 maxamount /*= 0*/)
{
if (!CanHaveThreatList())
return;
ThreatList const& threats = getThreatManager().getThreatList();
maxamount = maxamount > 0 ? std::min(maxamount, uint32(threats.size())) : threats.size();
guids.reserve(guids.size() + maxamount);
for (ThreatList::const_iterator itr = threats.begin(); maxamount && itr != threats.end(); ++itr, --maxamount)
guids.push_back((*itr)->getUnitGuid());
}
struct AddCreatureToRemoveListInMapsWorker
{
AddCreatureToRemoveListInMapsWorker(ObjectGuid guid) : i_guid(guid) {}
void operator()(Map* map)
{
if (Creature* pCreature = map->GetCreature(i_guid))
pCreature->AddObjectToRemoveList();
}
ObjectGuid i_guid;
};
void Creature::AddToRemoveListInMaps(uint32 db_guid, CreatureData const* data)
{
AddCreatureToRemoveListInMapsWorker worker(data->GetObjectGuid(db_guid));
sMapMgr.DoForAllMapsWithMapId(data->mapid, worker);
}
struct SpawnCreatureInMapsWorker
{
SpawnCreatureInMapsWorker(uint32 guid, CreatureData const* data)
: i_guid(guid), i_data(data) {}
void operator()(Map* map)
{
// We use spawn coords to spawn
if (map->IsLoaded(i_data->posX, i_data->posY))
{
Creature* pCreature = new Creature;
// DEBUG_LOG("Spawning creature %u",*itr);
if (!pCreature->LoadFromDB(i_guid, map))
{
delete pCreature;
}
else
{
map->Add(pCreature);
}
}
}
uint32 i_guid;
CreatureData const* i_data;
};
void Creature::SpawnInMaps(uint32 db_guid, CreatureData const* data)
{
SpawnCreatureInMapsWorker worker(db_guid, data);
sMapMgr.DoForAllMapsWithMapId(data->mapid, worker);
}
bool Creature::HasStaticDBSpawnData() const
{
return sObjectMgr.GetCreatureData(GetGUIDLow()) != NULL;
}
void Creature::SetWalk(bool enable, bool asDefault)
{
if (asDefault)
{
if (enable)
clearUnitState(UNIT_STAT_RUNNING);
else
addUnitState(UNIT_STAT_RUNNING);
}
// Nothing changed?
if (enable == m_movementInfo.HasMovementFlag(MOVEFLAG_WALK_MODE))
return;
if (enable)
m_movementInfo.AddMovementFlag(MOVEFLAG_WALK_MODE);
else
m_movementInfo.RemoveMovementFlag(MOVEFLAG_WALK_MODE);
if (IsInWorld())
{
WorldPacket data(enable ? SMSG_SPLINE_MOVE_SET_WALK_MODE : SMSG_SPLINE_MOVE_SET_RUN_MODE, 9);
if (enable)
{
data.WriteGuidMask<7, 6, 5, 1, 3, 4, 2, 0>(GetObjectGuid());
data.WriteGuidBytes<4, 2, 1, 6, 5, 0, 7, 3>(GetObjectGuid());
}
else
{
data.WriteGuidMask<5, 6, 3, 7, 2, 0, 4, 1>(GetObjectGuid());
data.WriteGuidBytes<7, 0, 4, 6, 5, 1, 2, 3>(GetObjectGuid());
}
SendMessageToSet(&data, true);
}
}
void Creature::SetLevitate(bool enable)
{
if (enable)
m_movementInfo.AddMovementFlag(MOVEFLAG_LEVITATING);
else
m_movementInfo.RemoveMovementFlag(MOVEFLAG_LEVITATING);
if (IsInWorld())
{
WorldPacket data(enable ? SMSG_SPLINE_MOVE_GRAVITY_DISABLE : SMSG_SPLINE_MOVE_GRAVITY_ENABLE, 9);
if (enable)
{
data.WriteGuidMask<7, 3, 4, 2, 5, 1, 0, 6>(GetObjectGuid());
data.WriteGuidBytes<7, 1, 3, 4, 6, 2, 5, 0>(GetObjectGuid());
}
else
{
data.WriteGuidMask<5, 4, 7, 1, 3, 6, 2, 0>(GetObjectGuid());
data.WriteGuidBytes<7, 3, 4, 2, 1, 6, 0, 5>(GetObjectGuid());
}
SendMessageToSet(&data, true);
}
}
void Creature::SetRoot(bool enable)
{
if (enable)
m_movementInfo.AddMovementFlag(MOVEFLAG_ROOT);
else
m_movementInfo.RemoveMovementFlag(MOVEFLAG_ROOT);
if (IsInWorld())
{
WorldPacket data(enable ? SMSG_SPLINE_MOVE_ROOT : SMSG_SPLINE_MOVE_UNROOT, 9);
if (enable)
{
data.WriteGuidMask<5, 4, 6, 1, 3, 7, 2, 0>(GetObjectGuid());
data.WriteGuidBytes<2, 1, 7, 3, 5, 0, 6, 4>(GetObjectGuid());
}
else
{
data.WriteGuidMask<0, 1, 6, 5, 3, 2, 7, 4>(GetObjectGuid());
data.WriteGuidBytes<6, 3, 1, 5, 2, 0, 7, 4>(GetObjectGuid());
}
SendMessageToSet(&data, true);
}
}
void Creature::SetWaterWalk(bool enable)
{
if (enable)
m_movementInfo.AddMovementFlag(MOVEFLAG_WATERWALKING);
else
m_movementInfo.RemoveMovementFlag(MOVEFLAG_WATERWALKING);
if (IsInWorld())
{
WorldPacket data(enable ? SMSG_SPLINE_MOVE_WATER_WALK : SMSG_SPLINE_MOVE_LAND_WALK, 9);
if (enable)
{
data.WriteGuidMask<6, 1, 4, 2, 3, 7, 5, 0>(GetObjectGuid());
data.WriteGuidBytes<0, 6, 3, 7, 4, 2, 5, 1>(GetObjectGuid());
}
else
{
data.WriteGuidMask<5, 0, 4, 6, 7, 2, 3, 1>(GetObjectGuid());
data.WriteGuidBytes<5, 7, 3, 4, 1, 2, 0, 6>(GetObjectGuid());
}
SendMessageToSet(&data, true);
}
}