diff options
| author | Rat <none@none> | 2010-06-05 23:40:08 +0200 |
|---|---|---|
| committer | Rat <none@none> | 2010-06-05 23:40:08 +0200 |
| commit | 75b80d9f5b02a643c983b2fb1ededed79fd5d133 (patch) | |
| tree | ebd1c2cc12a2715909dd04c1ed147a260c6ceb14 /src/server/game/Entities/Object | |
| parent | 6a9357b13d7ea6bd7d77dbfc6587af9028caa401 (diff) | |
rearranged core files
--HG--
branch : trunk
Diffstat (limited to 'src/server/game/Entities/Object')
| -rw-r--r-- | src/server/game/Entities/Object/Corpse.cpp | 243 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/Corpse.h | 97 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/DynamicObject.cpp | 189 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/DynamicObject.h | 61 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/Object.cpp | 2374 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/Object.h | 743 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/ObjectAccessor.cpp | 375 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/ObjectAccessor.h | 257 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/ObjectDefines.h | 127 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/ObjectMgr.cpp | 8724 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/ObjectMgr.h | 1085 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/UpdateData.cpp | 157 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/UpdateData.h | 74 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/UpdateFields.h | 435 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/UpdateMask.h | 127 |
15 files changed, 15068 insertions, 0 deletions
diff --git a/src/server/game/Entities/Object/Corpse.cpp b/src/server/game/Entities/Object/Corpse.cpp new file mode 100644 index 00000000000..510ea13e78b --- /dev/null +++ b/src/server/game/Entities/Object/Corpse.cpp @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 "Common.h" +#include "Corpse.h" +#include "Player.h" +#include "UpdateMask.h" +#include "ObjectAccessor.h" +#include "Database/DatabaseEnv.h" +#include "Opcodes.h" +#include "GossipDef.h" +#include "World.h" + +Corpse::Corpse(CorpseType type) : WorldObject() +, m_type(type) +{ + m_objectType |= TYPEMASK_CORPSE; + m_objectTypeId = TYPEID_CORPSE; + + m_updateFlag = (UPDATEFLAG_HIGHGUID | UPDATEFLAG_HAS_POSITION | UPDATEFLAG_POSITION); + + m_valuesCount = CORPSE_END; + + m_time = time(NULL); + + lootForBody = false; + + if (type != CORPSE_BONES) + m_isWorldObject = true; +} + +Corpse::~Corpse() +{ +} + +void Corpse::AddToWorld() +{ + ///- Register the corpse for guid lookup + if (!IsInWorld()) + ObjectAccessor::Instance().AddObject(this); + + Object::AddToWorld(); +} + +void Corpse::RemoveFromWorld() +{ + ///- Remove the corpse from the accessor + if (IsInWorld()) + ObjectAccessor::Instance().RemoveObject(this); + + Object::RemoveFromWorld(); +} + +bool Corpse::Create(uint32 guidlow, Map *map) +{ + SetMap(map); + Object::_Create(guidlow, 0, HIGHGUID_CORPSE); + return true; +} + +bool Corpse::Create(uint32 guidlow, Player *owner) +{ + ASSERT(owner); + + Relocate(owner->GetPositionX(), owner->GetPositionY(), owner->GetPositionZ(), owner->GetOrientation()); + + if (!IsPositionValid()) + { + sLog.outError("Corpse (guidlow %d, owner %s) not created. Suggested coordinates isn't valid (X: %f Y: %f)", + guidlow, owner->GetName(), owner->GetPositionX(), owner->GetPositionY()); + return false; + } + + //we need to assign owner's map for corpse + //in other way we will get a crash in Corpse::SaveToDB() + SetMap(owner->GetMap()); + + WorldObject::_Create(guidlow, HIGHGUID_CORPSE, owner->GetPhaseMask()); + + SetFloatValue(OBJECT_FIELD_SCALE_X, 1); + SetUInt64Value(CORPSE_FIELD_OWNER, owner->GetGUID()); + + m_grid = Trinity::ComputeGridPair(GetPositionX(), GetPositionY()); + + return true; +} + +void Corpse::SaveToDB() +{ + // prevent DB data inconsistence problems and duplicates + CharacterDatabase.BeginTransaction(); + DeleteFromDB(); + + std::ostringstream ss; + ss << "INSERT INTO corpse (guid,player,position_x,position_y,position_z,orientation,zone,map,data,time,corpse_type,instance,phaseMask) VALUES (" + << GetGUIDLow() << ", " + << GUID_LOPART(GetOwnerGUID()) << ", " + << GetPositionX() << ", " + << GetPositionY() << ", " + << GetPositionZ() << ", " + << GetOrientation() << ", " + << GetZoneId() << ", " + << GetMapId() << ", '"; + for (uint16 i = 0; i < m_valuesCount; ++i) + ss << GetUInt32Value(i) << " "; + ss << "'," + << uint64(m_time) <<", " + << uint32(GetType()) << ", " + << int(GetInstanceId()) << ", " + << uint16(GetPhaseMask()) << ")"; // prevent out of range error + CharacterDatabase.Execute(ss.str().c_str()); + CharacterDatabase.CommitTransaction(); +} + +void Corpse::DeleteBonesFromWorld() +{ + assert(GetType() == CORPSE_BONES); + Corpse* corpse = ObjectAccessor::GetCorpse(*this, GetGUID()); + + if (!corpse) + { + sLog.outError("Bones %u not found in world.", GetGUIDLow()); + return; + } + + AddObjectToRemoveList(); +} + +void Corpse::DeleteFromDB() +{ + if (GetType() == CORPSE_BONES) + // only specific bones + CharacterDatabase.PExecute("DELETE FROM corpse WHERE guid = '%d'", GetGUIDLow()); + else + // all corpses (not bones) + CharacterDatabase.PExecute("DELETE FROM corpse WHERE player = '%d' AND corpse_type <> '0'", GUID_LOPART(GetOwnerGUID())); +} + +/* +bool Corpse::LoadFromDB(uint32 guid, QueryResult *result, uint32 InstanceId) +{ + bool external = (result != NULL); + if (!external) + // 0 1 2 3 4 5 6 7 8 9 + result = CharacterDatabase.PQuery("SELECT position_x,position_y,position_z,orientation,map,data,time,corpse_type,instance,phaseMask FROM corpse WHERE guid = '%u'",guid); + + if (!result) + { + sLog.outError("Corpse (GUID: %u) not found in table `corpse`, can't load. ",guid); + return false; + } + + Field *fields = result->Fetch(); + + if (!LoadFromDB(guid, fields)) + { + if (!external) + delete result; + + return false; + } + + if (!external) + delete result; + + return true; +}*/ + +bool Corpse::LoadFromDB(uint32 guid, Field *fields) +{ + float positionX = fields[0].GetFloat(); + float positionY = fields[1].GetFloat(); + float positionZ = fields[2].GetFloat(); + float ort = fields[3].GetFloat(); + uint32 mapid = fields[4].GetUInt32(); + + Object::_Create(guid, 0, HIGHGUID_CORPSE); + + if (!LoadValues(fields[5].GetString())) + { + sLog.outError("Corpse #%d have broken data in `data` field. Can't be loaded.",guid); + return false; + } + + m_time = time_t(fields[6].GetUInt64()); + m_type = CorpseType(fields[7].GetUInt32()); + + if (m_type >= MAX_CORPSE_TYPE) + { + sLog.outError("Corpse (guidlow %d, owner %d) have wrong corpse type, not load.",GetGUIDLow(),GUID_LOPART(GetOwnerGUID())); + return false; + } + + if (m_type != CORPSE_BONES) + m_isWorldObject = true; + + uint32 instanceid = fields[8].GetUInt32(); + + uint32 phaseMask = fields[9].GetUInt32(); + + // overwrite possible wrong/corrupted guid + SetUInt64Value(OBJECT_FIELD_GUID, MAKE_NEW_GUID(guid, 0, HIGHGUID_CORPSE)); + + // place + SetLocationInstanceId(instanceid); + SetLocationMapId(mapid); + SetPhaseMask(phaseMask, false); + Relocate(positionX, positionY, positionZ, ort); + + if (!IsPositionValid()) + { + sLog.outError("Corpse (guidlow %d, owner %d) not created. Suggested coordinates isn't valid (X: %f Y: %f)", + GetGUIDLow(), GUID_LOPART(GetOwnerGUID()), GetPositionX(), GetPositionY()); + return false; + } + + m_grid = Trinity::ComputeGridPair(GetPositionX(), GetPositionY()); + + return true; +} + +bool Corpse::isVisibleForInState(Player const* u, bool inVisibleList) const +{ + return IsInWorld() && u->IsInWorld() && IsWithinDistInMap(u->m_seer, World::GetMaxVisibleDistanceForObject() + (inVisibleList ? World::GetVisibleObjectGreyDistance() : 0.0f), false); +} + diff --git a/src/server/game/Entities/Object/Corpse.h b/src/server/game/Entities/Object/Corpse.h new file mode 100644 index 00000000000..bab95e99d14 --- /dev/null +++ b/src/server/game/Entities/Object/Corpse.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef TRINITYCORE_CORPSE_H +#define TRINITYCORE_CORPSE_H + +#include "Object.h" +#include "Database/DatabaseEnv.h" +#include "GridDefines.h" +#include "LootMgr.h" + +enum CorpseType +{ + CORPSE_BONES = 0, + CORPSE_RESURRECTABLE_PVE = 1, + CORPSE_RESURRECTABLE_PVP = 2 +}; +#define MAX_CORPSE_TYPE 3 + +// Value equal client resurrection dialog show radius. +#define CORPSE_RECLAIM_RADIUS 39 + +enum CorpseFlags +{ + CORPSE_FLAG_NONE = 0x00, + CORPSE_FLAG_BONES = 0x01, + CORPSE_FLAG_UNK1 = 0x02, + CORPSE_FLAG_UNK2 = 0x04, + CORPSE_FLAG_HIDE_HELM = 0x08, + CORPSE_FLAG_HIDE_CLOAK = 0x10, + CORPSE_FLAG_LOOTABLE = 0x20 +}; + +class Corpse : public WorldObject, public GridObject<Corpse> +{ + public: + explicit Corpse(CorpseType type = CORPSE_BONES); + ~Corpse(); + + void AddToWorld(); + void RemoveFromWorld(); + + bool Create(uint32 guidlow, Map *map); + bool Create(uint32 guidlow, Player *owner); + + void SaveToDB(); + //bool LoadFromDB(uint32 guid, QueryResult *result, uint32 InstanceId); + bool LoadFromDB(uint32 guid, Field *fields); + + void DeleteBonesFromWorld(); + void DeleteFromDB(); + + uint64 const& GetOwnerGUID() const { return GetUInt64Value(CORPSE_FIELD_OWNER); } + + time_t const& GetGhostTime() const { return m_time; } + void ResetGhostTime() { m_time = time(NULL); } + CorpseType GetType() const { return m_type; } + + GridPair const& GetGrid() const { return m_grid; } + void SetGrid(GridPair const& grid) { m_grid = grid; } + + bool isVisibleForInState(Player const* u, bool inVisibleList) const; + + Loot loot; // remove insignia ONLY at BG + Player* lootRecipient; + bool lootForBody; + + void Say(int32 textId, uint32 language, uint64 TargetGuid) { MonsterSay(textId,language,TargetGuid); } + void Yell(int32 textId, uint32 language, uint64 TargetGuid) { MonsterYell(textId,language,TargetGuid); } + void TextEmote(int32 textId, uint64 TargetGuid) { MonsterTextEmote(textId,TargetGuid); } + void Whisper(int32 textId,uint64 receiver) { MonsterWhisper(textId,receiver); } + void YellToZone(int32 textId, uint32 language, uint64 TargetGuid) { MonsterYellToZone(textId,language,TargetGuid); } + + private: + CorpseType m_type; + time_t m_time; + GridPair m_grid; // gride for corpse position for fast search +}; +#endif + diff --git a/src/server/game/Entities/Object/DynamicObject.cpp b/src/server/game/Entities/Object/DynamicObject.cpp new file mode 100644 index 00000000000..48179190a6e --- /dev/null +++ b/src/server/game/Entities/Object/DynamicObject.cpp @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 "Common.h" +#include "UpdateMask.h" +#include "Opcodes.h" +#include "World.h" +#include "ObjectAccessor.h" +#include "Database/DatabaseEnv.h" +#include "GridNotifiers.h" +#include "CellImpl.h" +#include "GridNotifiersImpl.h" + +DynamicObject::DynamicObject() : WorldObject() +{ + m_objectType |= TYPEMASK_DYNAMICOBJECT; + m_objectTypeId = TYPEID_DYNAMICOBJECT; + + m_updateFlag = (UPDATEFLAG_HIGHGUID | UPDATEFLAG_HAS_POSITION | UPDATEFLAG_POSITION); + + m_valuesCount = DYNAMICOBJECT_END; + + m_aura = 0; + m_duration = 0; +} + +void DynamicObject::AddToWorld() +{ + ///- Register the dynamicObject for guid lookup + if (!IsInWorld()) + { + ObjectAccessor::Instance().AddObject(this); + WorldObject::AddToWorld(); + } +} + +void DynamicObject::RemoveFromWorld() +{ + ///- Remove the dynamicObject from the accessor + if (IsInWorld()) + { + if (m_isWorldObject) + { + if (Unit *caster = GetCaster()) + { + if (caster->GetTypeId() == TYPEID_PLAYER) + caster->ToPlayer()->SetViewpoint(this, false); + } + else + { + sLog.outCrash("DynamicObject::RemoveFromWorld cannot find viewpoint owner"); + } + } + WorldObject::RemoveFromWorld(); + ObjectAccessor::Instance().RemoveObject(this); + } +} + +bool DynamicObject::Create(uint32 guidlow, Unit *caster, uint32 spellId, const Position &pos, float radius, bool active) +{ + SetMap(caster->GetMap()); + Relocate(pos); + if (!IsPositionValid()) + { + sLog.outError("DynamicObject (spell %u) not created. Suggested coordinates isn't valid (X: %f Y: %f)",spellId,GetPositionX(),GetPositionY()); + return false; + } + + WorldObject::_Create(guidlow, HIGHGUID_DYNAMICOBJECT, caster->GetPhaseMask()); + + SetEntry(spellId); + SetFloatValue(OBJECT_FIELD_SCALE_X, 1); + SetUInt64Value(DYNAMICOBJECT_CASTER, caster->GetGUID()); + + // The lower word of DYNAMICOBJECT_BYTES must be 0x0001. This value means that the visual radius will be overriden + // by client for most of the "ground patch" visual effect spells and a few "skyfall" ones like Hurricane. + // If any other value is used, the client will _always_ use the radius provided in DYNAMICOBJECT_RADIUS, but + // precompensation is necessary (eg radius *= 2) for many spells. Anyway, blizz sends 0x0001 for all the spells + // I saw sniffed... + SetUInt32Value(DYNAMICOBJECT_BYTES, 0x00000001); + SetUInt32Value(DYNAMICOBJECT_SPELLID, spellId); + SetFloatValue(DYNAMICOBJECT_RADIUS, radius); + SetUInt32Value(DYNAMICOBJECT_CASTTIME, getMSTime()); + + m_isWorldObject = active; + return true; +} + +Unit* DynamicObject::GetCaster() const +{ + // can be not found in some cases + return ObjectAccessor::GetUnit(*this, GetCasterGUID()); +} + +void DynamicObject::Update(uint32 p_time) +{ + // caster can be not in world at time dynamic object update, but dynamic object not yet deleted in Unit destructor + Unit* caster = GetCaster(); + if (!caster) + { + Delete(); + return; + } + + bool expired = false; + + if (m_aura) + { + if (!m_aura->IsRemoved()) + m_aura->UpdateOwner(p_time, this); + + // m_aura may be set to null in Unit::RemoveGameObject call + if (m_aura && (m_aura->IsRemoved() || m_aura->IsExpired())) + expired = true; + } + else + { + if (GetDuration() > int32(p_time)) + m_duration -= p_time; + else + expired = true; + } + + if (expired) + { + caster->RemoveDynObjectWithGUID(GetGUID()); + Delete(); + } +} + +void DynamicObject::Delete() +{ + if (m_aura) + { + // dynObj may be removed in Aura::Remove - we cannot delete there + // so recheck aura here + if (!m_aura->IsRemoved()) + m_aura->_Remove(AURA_REMOVE_BY_DEFAULT); + delete m_aura; + m_aura = NULL; + } + SendObjectDeSpawnAnim(GetGUID()); + RemoveFromWorld(); + AddObjectToRemoveList(); +} + +int32 DynamicObject::GetDuration() const +{ + if (!m_aura) + return m_duration; + else + return m_aura->GetDuration(); +} + +void DynamicObject::SetDuration(int32 newDuration) +{ + if (!m_aura) + m_duration = newDuration; + else + m_aura->SetDuration(newDuration); +} + +void DynamicObject::Delay(int32 delaytime) +{ + SetDuration(GetDuration() - delaytime); +} + +bool DynamicObject::isVisibleForInState(Player const* u, bool inVisibleList) const +{ + return IsInWorld() && u->IsInWorld() + && (IsWithinDistInMap(u->m_seer,World::GetMaxVisibleDistanceForObject()+(inVisibleList ? World::GetVisibleObjectGreyDistance() : 0.0f), false)); +} diff --git a/src/server/game/Entities/Object/DynamicObject.h b/src/server/game/Entities/Object/DynamicObject.h new file mode 100644 index 00000000000..9a70407fd2d --- /dev/null +++ b/src/server/game/Entities/Object/DynamicObject.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef TRINITYCORE_DYNAMICOBJECT_H +#define TRINITYCORE_DYNAMICOBJECT_H + +#include "Object.h" + +class Unit; +class Aura; +struct SpellEntry; + +class DynamicObject : public WorldObject, public GridObject<DynamicObject> +{ + public: + explicit DynamicObject(); + + void AddToWorld(); + void RemoveFromWorld(); + + bool Create(uint32 guidlow, Unit *caster, uint32 spellId, const Position &pos, float radius, bool active); + void Update(uint32 p_time); + void Delete(); + void SetDuration(int32 newDuration); + int32 GetDuration() const; + void SetAura(Aura * aura) {assert (!m_aura && aura); m_aura = aura;} + void Delay(int32 delaytime); + uint32 GetSpellId() const { return GetUInt32Value(DYNAMICOBJECT_SPELLID); } + uint64 GetCasterGUID() const { return GetUInt64Value(DYNAMICOBJECT_CASTER); } + float GetRadius() const { return GetFloatValue(DYNAMICOBJECT_RADIUS); } + Unit* GetCaster() const; + bool isVisibleForInState(Player const* u, bool inVisibleList) const; + + void Say(int32 textId, uint32 language, uint64 TargetGuid) { MonsterSay(textId,language,TargetGuid); } + void Yell(int32 textId, uint32 language, uint64 TargetGuid) { MonsterYell(textId,language,TargetGuid); } + void TextEmote(int32 textId, uint64 TargetGuid) { MonsterTextEmote(textId,TargetGuid); } + void Whisper(int32 textId,uint64 receiver) { MonsterWhisper(textId,receiver); } + void YellToZone(int32 textId, uint32 language, uint64 TargetGuid) { MonsterYellToZone(textId,language,TargetGuid); } + + protected: + int32 m_duration; // for non-aura dynobjects + Aura * m_aura; +}; +#endif diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp new file mode 100644 index 00000000000..2a7397db8b8 --- /dev/null +++ b/src/server/game/Entities/Object/Object.cpp @@ -0,0 +1,2374 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 "Common.h" +#include "SharedDefines.h" +#include "WorldPacket.h" +#include "Opcodes.h" +#include "Log.h" +#include "World.h" +#include "Object.h" +#include "Creature.h" +#include "Player.h" +#include "Vehicle.h" +#include "ObjectMgr.h" +#include "UpdateData.h" +#include "UpdateMask.h" +#include "Util.h" +#include "MapManager.h" +#include "ObjectAccessor.h" +#include "Log.h" +#include "Transports.h" +#include "TargetedMovementGenerator.h" +#include "WaypointMovementGenerator.h" +#include "VMapFactory.h" +#include "CellImpl.h" +#include "GridNotifiers.h" +#include "GridNotifiersImpl.h" +#include "SpellAuraEffects.h" + +#include "TemporarySummon.h" +#include "Totem.h" +#include "OutdoorPvPMgr.h" + +uint32 GuidHigh2TypeId(uint32 guid_hi) +{ + switch(guid_hi) + { + case HIGHGUID_ITEM: return TYPEID_ITEM; + //case HIGHGUID_CONTAINER: return TYPEID_CONTAINER; HIGHGUID_CONTAINER == HIGHGUID_ITEM currently + case HIGHGUID_UNIT: return TYPEID_UNIT; + case HIGHGUID_PET: return TYPEID_UNIT; + case HIGHGUID_PLAYER: return TYPEID_PLAYER; + case HIGHGUID_GAMEOBJECT: return TYPEID_GAMEOBJECT; + case HIGHGUID_DYNAMICOBJECT:return TYPEID_DYNAMICOBJECT; + case HIGHGUID_CORPSE: return TYPEID_CORPSE; + case HIGHGUID_MO_TRANSPORT: return TYPEID_GAMEOBJECT; + case HIGHGUID_VEHICLE: return TYPEID_UNIT; + } + return NUM_CLIENT_OBJECT_TYPES; // unknown +} + +Object::Object() : m_PackGUID(sizeof(uint64)+1) +{ + m_objectTypeId = TYPEID_OBJECT; + m_objectType = TYPEMASK_OBJECT; + + m_uint32Values = 0; + m_uint32Values_mirror = 0; + m_valuesCount = 0; + + m_inWorld = false; + m_objectUpdated = false; + + m_PackGUID.appendPackGUID(0); +} + +WorldObject::~WorldObject() +{ + // this may happen because there are many !create/delete + if (m_isWorldObject && m_currMap) + { + if (GetTypeId() == TYPEID_CORPSE) + { + sLog.outCrash("Object::~Object Corpse guid="UI64FMTD", type=%d, entry=%u deleted but still in map!!", GetGUID(), ((Corpse*)this)->GetType(), GetEntry()); + assert(false); + } + ResetMap(); + } +} + +Object::~Object() +{ + if (IsInWorld()) + { + sLog.outCrash("Object::~Object - guid="UI64FMTD", typeid=%d, entry=%u deleted but still in world!!", GetGUID(), GetTypeId(), GetEntry()); + if (isType(TYPEMASK_ITEM)) + sLog.outCrash("Item slot %u", ((Item*)this)->GetSlot()); + assert(false); + RemoveFromWorld(); + } + + if (m_objectUpdated) + { + sLog.outCrash("Object::~Object - guid="UI64FMTD", typeid=%d, entry=%u deleted but still in update list!!", GetGUID(), GetTypeId(), GetEntry()); + assert(false); + ObjectAccessor::Instance().RemoveUpdateObject(this); + } + + if (m_uint32Values) + { + //DEBUG_LOG("Object desctr 1 check (%p)",(void*)this); + delete [] m_uint32Values; + delete [] m_uint32Values_mirror; + //DEBUG_LOG("Object desctr 2 check (%p)",(void*)this); + } +} + +void Object::_InitValues() +{ + m_uint32Values = new uint32[ m_valuesCount ]; + memset(m_uint32Values, 0, m_valuesCount*sizeof(uint32)); + + m_uint32Values_mirror = new uint32[ m_valuesCount ]; + memset(m_uint32Values_mirror, 0, m_valuesCount*sizeof(uint32)); + + m_objectUpdated = false; +} + +void Object::_Create(uint32 guidlow, uint32 entry, HighGuid guidhigh) +{ + if (!m_uint32Values) _InitValues(); + + uint64 guid = MAKE_NEW_GUID(guidlow, entry, guidhigh); + SetUInt64Value(OBJECT_FIELD_GUID, guid); + uint32 type = 0; + switch(m_objectType) + { + //case TYPEID_ITEM: type = 3; break; + //case TYPEID_CONTAINER: type = 7; break; //+4 + //case TYPEID_UNIT: type = 9; break; //+2 + //case TYPEID_PLAYER: type = 25; break; //+16 + //case TYPEID_GAMEOBJECT: type = 33; break; //+8 + case TYPEID_DYNAMICOBJECT: type = 65; break; //+32 + //case TYPEID_CORPSE: type = 129; break; //+64 + default: type = m_objectType; break; + } + SetUInt32Value(OBJECT_FIELD_TYPE, type); + //SetUInt32Value(OBJECT_FIELD_TYPE, m_objectType); + m_PackGUID.wpos(0); + m_PackGUID.appendPackGUID(GetGUID()); +} + +void Object::BuildMovementUpdateBlock(UpdateData * data, uint32 flags) const +{ + ByteBuffer buf(500); + + buf << uint8(UPDATETYPE_MOVEMENT); + buf.append(GetPackGUID()); + + _BuildMovementUpdate(&buf, flags); + + data->AddUpdateBlock(buf); +} + +void Object::BuildCreateUpdateBlockForPlayer(UpdateData *data, Player *target) const +{ + if (!target) + return; + + uint8 updatetype = UPDATETYPE_CREATE_OBJECT; + uint16 flags = m_updateFlag; + + /** lower flag1 **/ + if (target == this) // building packet for yourself + flags |= UPDATEFLAG_SELF; + + if (flags & UPDATEFLAG_HAS_POSITION) + { + // UPDATETYPE_CREATE_OBJECT2 dynamic objects, corpses... + if (isType(TYPEMASK_DYNAMICOBJECT) || isType(TYPEMASK_CORPSE) || isType(TYPEMASK_PLAYER)) + updatetype = UPDATETYPE_CREATE_OBJECT2; + + // UPDATETYPE_CREATE_OBJECT2 for pets... + if (target->GetPetGUID() == GetGUID()) + updatetype = UPDATETYPE_CREATE_OBJECT2; + + // UPDATETYPE_CREATE_OBJECT2 for some gameobject types... + if (isType(TYPEMASK_GAMEOBJECT)) + { + switch(((GameObject*)this)->GetGoType()) + { + case GAMEOBJECT_TYPE_TRAP: + case GAMEOBJECT_TYPE_DUEL_ARBITER: + case GAMEOBJECT_TYPE_FLAGSTAND: + case GAMEOBJECT_TYPE_FLAGDROP: + updatetype = UPDATETYPE_CREATE_OBJECT2; + break; + case GAMEOBJECT_TYPE_TRANSPORT: + flags |= UPDATEFLAG_TRANSPORT; + break; + default: + break; + } + } + + if (isType(TYPEMASK_UNIT)) + { + if (((Unit*)this)->getVictim()) + flags |= UPDATEFLAG_HAS_TARGET; + } + } + + //sLog.outDebug("BuildCreateUpdate: update-type: %u, object-type: %u got flags: %X, flags2: %X", updatetype, m_objectTypeId, flags, flags2); + + ByteBuffer buf(500); + buf << (uint8)updatetype; + buf.append(GetPackGUID()); + buf << (uint8)m_objectTypeId; + + _BuildMovementUpdate(&buf, flags); + + UpdateMask updateMask; + updateMask.SetCount(m_valuesCount); + _SetCreateBits(&updateMask, target); + _BuildValuesUpdate(updatetype, &buf, &updateMask, target); + data->AddUpdateBlock(buf); +} + +void Object::SendUpdateToPlayer(Player* player) +{ + // send create update to player + UpdateData upd; + WorldPacket packet; + + BuildCreateUpdateBlockForPlayer(&upd, player); + upd.BuildPacket(&packet); + player->GetSession()->SendPacket(&packet); +} + +void Object::BuildValuesUpdateBlockForPlayer(UpdateData *data, Player *target) const +{ + ByteBuffer buf(500); + + buf << (uint8) UPDATETYPE_VALUES; + buf.append(GetPackGUID()); + + UpdateMask updateMask; + updateMask.SetCount(m_valuesCount); + + _SetUpdateBits(&updateMask, target); + _BuildValuesUpdate(UPDATETYPE_VALUES, &buf, &updateMask, target); + + data->AddUpdateBlock(buf); +} + +void Object::BuildOutOfRangeUpdateBlock(UpdateData * data) const +{ + data->AddOutOfRangeGUID(GetGUID()); +} + +void Object::DestroyForPlayer(Player *target, bool anim) const +{ + ASSERT(target); + + WorldPacket data(SMSG_DESTROY_OBJECT, 8 + 1); + data << uint64(GetGUID()); + data << uint8(anim ? 1 : 0); // WotLK (bool), may be despawn animation + target->GetSession()->SendPacket(&data); +} + +void Object::_BuildMovementUpdate(ByteBuffer * data, uint16 flags) const +{ + *data << (uint16)flags; // update flags + + // 0x20 + if (flags & UPDATEFLAG_LIVING) + { + ((Unit*)this)->BuildMovementPacket(data); + + *data << ((Unit*)this)->GetSpeed(MOVE_WALK); + *data << ((Unit*)this)->GetSpeed(MOVE_RUN); + *data << ((Unit*)this)->GetSpeed(MOVE_SWIM_BACK); + *data << ((Unit*)this)->GetSpeed(MOVE_SWIM); + *data << ((Unit*)this)->GetSpeed(MOVE_RUN_BACK); + *data << ((Unit*)this)->GetSpeed(MOVE_FLIGHT); + *data << ((Unit*)this)->GetSpeed(MOVE_FLIGHT_BACK); + *data << ((Unit*)this)->GetSpeed(MOVE_TURN_RATE); + *data << ((Unit*)this)->GetSpeed(MOVE_PITCH_RATE); + + // 0x08000000 + if (GetTypeId() == TYPEID_PLAYER && this->ToPlayer()->isInFlight()) + { + //WPAssert(this->ToPlayer()->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE); + + FlightPathMovementGenerator *fmg = (FlightPathMovementGenerator*)(const_cast<Player*>(this->ToPlayer())->GetMotionMaster()->top()); + + uint32 flags3 = MOVEFLAG_GLIDE; + + *data << uint32(flags3); // splines flag? + + if (flags3 & 0x20000) // may be orientation + { + *data << (float)0; + } + else + { + if (flags3 & 0x8000) // probably x,y,z coords there + { + *data << (float)0; + *data << (float)0; + *data << (float)0; + } + + if (flags3 & 0x10000) // probably guid there + { + *data << uint64(0); + } + } + + Path &path = fmg->GetPath(); + + float x, y, z; + this->ToPlayer()->GetPosition(x, y, z); + + uint32 inflighttime = uint32(path.GetPassedLength(fmg->GetCurrentNode(), x, y, z) * 32); + uint32 traveltime = uint32(path.GetTotalLength() * 32); + + *data << uint32(inflighttime); // passed move time? + *data << uint32(traveltime); // full move time? + *data << uint32(0); // ticks count? + + *data << float(0); // added in 3.1 + *data << float(0); // added in 3.1 + *data << float(0); // added in 3.1 + + *data << uint32(0); // added in 3.1 + + uint32 poscount = uint32(path.Size()); + *data << uint32(poscount); // points count + + for (uint32 i = 0; i < poscount; ++i) + { + *data << path.GetNodes()[i].x; + *data << path.GetNodes()[i].y; + *data << path.GetNodes()[i].z; + } + + *data << uint8(0); // added in 3.0.8 + + *data << path.GetNodes()[poscount-1].x; + *data << path.GetNodes()[poscount-1].y; + *data << path.GetNodes()[poscount-1].z; + } + } + else + { + if (flags & UPDATEFLAG_POSITION) + { + *data << uint8(0); // unk PGUID! + *data << ((WorldObject*)this)->GetPositionX(); + *data << ((WorldObject*)this)->GetPositionY(); + *data << ((WorldObject*)this)->GetPositionZ(); + *data << ((WorldObject*)this)->GetPositionX(); + *data << ((WorldObject*)this)->GetPositionY(); + *data << ((WorldObject*)this)->GetPositionZ(); + *data << ((WorldObject*)this)->GetOrientation(); + + if (GetTypeId() == TYPEID_CORPSE) + *data << float(((WorldObject*)this)->GetOrientation()); + else + *data << float(0); + } + else + { + // 0x40 + if (flags & UPDATEFLAG_HAS_POSITION) + { + // 0x02 + if (flags & UPDATEFLAG_TRANSPORT && ((GameObject*)this)->GetGoType() == GAMEOBJECT_TYPE_MO_TRANSPORT) + { + *data << (float)0; + *data << (float)0; + *data << (float)0; + *data << ((WorldObject *)this)->GetOrientation(); + } + else + { + *data << ((WorldObject *)this)->GetPositionX(); + *data << ((WorldObject *)this)->GetPositionY(); + *data << ((WorldObject *)this)->GetPositionZ(); + *data << ((WorldObject *)this)->GetOrientation(); + } + } + } + } + + // 0x8 + if (flags & UPDATEFLAG_LOWGUID) + { + switch(GetTypeId()) + { + case TYPEID_OBJECT: + case TYPEID_ITEM: + case TYPEID_CONTAINER: + case TYPEID_GAMEOBJECT: + case TYPEID_DYNAMICOBJECT: + case TYPEID_CORPSE: + *data << uint32(GetGUIDLow()); // GetGUIDLow() + break; + case TYPEID_UNIT: + { + if (this->ToCreature()->canFly()) + flags |= MOVEMENTFLAG_LEVITATING; + + *data << uint32(0x0000000B); // unk, can be 0xB or 0xC + break; + } + case TYPEID_PLAYER: + if (flags & UPDATEFLAG_SELF) + *data << uint32(0x0000002F); // unk, can be 0x15 or 0x22 + else + *data << uint32(0x00000008); // unk, can be 0x7 or 0x8 + break; + default: + *data << uint32(0x00000000); // unk + break; + } + } + + // 0x10 + if (flags & UPDATEFLAG_HIGHGUID) + { + // not high guid + *data << uint32(0x00000000); // unk + } + + // 0x4 + if (flags & UPDATEFLAG_HAS_TARGET) // packed guid (current target guid) + { + if (Unit *victim = ((Unit*)this)->getVictim()) + data->append(victim->GetPackGUID()); + else + *data << uint8(0); + } + + // 0x2 + if (flags & UPDATEFLAG_TRANSPORT) + { + *data << uint32(getMSTime()); // ms time + } + + // 0x80 + if (flags & UPDATEFLAG_VEHICLE) // unused for now + { + *data << uint32(((Unit*)this)->GetVehicleKit()->GetVehicleInfo()->m_ID); // vehicle id + *data << float(0); // facing adjustment + } + + // 0x200 + if (flags & UPDATEFLAG_ROTATION) + { + *data << uint64(((GameObject*)this)->GetRotation()); + } +} + +void Object::_BuildValuesUpdate(uint8 updatetype, ByteBuffer * data, UpdateMask *updateMask, Player *target) const +{ + if (!target) + return; + + bool IsActivateToQuest = false; + if (updatetype == UPDATETYPE_CREATE_OBJECT || updatetype == UPDATETYPE_CREATE_OBJECT2) + { + if (isType(TYPEMASK_GAMEOBJECT) && !((GameObject*)this)->IsDynTransport()) + { + if (((GameObject*)this)->ActivateToQuest(target) || target->isGameMaster()) + IsActivateToQuest = true; + + updateMask->SetBit(GAMEOBJECT_DYNAMIC); + + if (((GameObject*)this)->GetGoArtKit()) + updateMask->SetBit(GAMEOBJECT_BYTES_1); + } + else if (isType(TYPEMASK_UNIT)) + { + if (((Unit*)this)->HasFlag(UNIT_FIELD_AURASTATE, PER_CASTER_AURA_STATE_MASK)) + { + updateMask->SetBit(UNIT_FIELD_AURASTATE); + } + } + } + else // case UPDATETYPE_VALUES + { + if (isType(TYPEMASK_GAMEOBJECT) && !((GameObject*)this)->IsTransport()) + { + if (((GameObject*)this)->ActivateToQuest(target) || target->isGameMaster()) + { + IsActivateToQuest = true; + } + updateMask->SetBit(GAMEOBJECT_DYNAMIC); + updateMask->SetBit(GAMEOBJECT_BYTES_1); + } + else if (isType(TYPEMASK_UNIT)) + { + if (((Unit*)this)->HasFlag(UNIT_FIELD_AURASTATE, PER_CASTER_AURA_STATE_MASK)) + { + updateMask->SetBit(UNIT_FIELD_AURASTATE); + } + } + } + + WPAssert(updateMask && updateMask->GetCount() == m_valuesCount); + + *data << (uint8)updateMask->GetBlockCount(); + data->append(updateMask->GetMask(), updateMask->GetLength()); + + // 2 specialized loops for speed optimization in non-unit case + if (isType(TYPEMASK_UNIT)) // unit (creature/player) case + { + for (uint16 index = 0; index < m_valuesCount; ++index) + { + if (updateMask->GetBit(index)) + { + if (index == UNIT_NPC_FLAGS) + { + // remove custom flag before sending + uint32 appendValue = m_uint32Values[ index ] & ~(UNIT_NPC_FLAG_GUARD + UNIT_NPC_FLAG_OUTDOORPVP); + + if (GetTypeId() == TYPEID_UNIT) + { + if (!target->canSeeSpellClickOn(this->ToCreature())) + appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK; + + if (appendValue & UNIT_NPC_FLAG_TRAINER) + { + if (!this->ToCreature()->isCanTrainingOf(target, false)) + appendValue &= ~(UNIT_NPC_FLAG_TRAINER | UNIT_NPC_FLAG_TRAINER_CLASS | UNIT_NPC_FLAG_TRAINER_PROFESSION); + } + } + + *data << uint32(appendValue); + } + else if (index == UNIT_FIELD_AURASTATE) + { + // Check per caster aura states to not enable using a pell in client if specified aura is not by target + *data << ((Unit*)this)->BuildAuraStateUpdateForTarget(target); + } + // FIXME: Some values at server stored in float format but must be sent to client in uint32 format + else if (index >= UNIT_FIELD_BASEATTACKTIME && index <= UNIT_FIELD_RANGEDATTACKTIME) + { + // convert from float to uint32 and send + *data << uint32(m_floatValues[ index ] < 0 ? 0 : m_floatValues[ index ]); + } + // there are some float values which may be negative or can't get negative due to other checks + else if ((index >= UNIT_FIELD_NEGSTAT0 && index <= UNIT_FIELD_NEGSTAT4) || + (index >= UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE + 6)) || + (index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index <= (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + 6)) || + (index >= UNIT_FIELD_POSSTAT0 && index <= UNIT_FIELD_POSSTAT4)) + { + *data << uint32(m_floatValues[ index ]); + } + // Gamemasters should be always able to select units - remove not selectable flag + else if (index == UNIT_FIELD_FLAGS) + { + if (target->isGameMaster()) + *data << (m_uint32Values[ index ] & ~UNIT_FLAG_NOT_SELECTABLE); + else + *data << m_uint32Values[ index ]; + } + // use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures + else if (index == UNIT_FIELD_DISPLAYID) + { + if (GetTypeId() == TYPEID_UNIT) + { + const CreatureInfo* cinfo = this->ToCreature()->GetCreatureInfo(); + if (cinfo->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER) + { + if (target->isGameMaster()) + { + if (cinfo->Modelid1) + *data << cinfo->Modelid1;//Modelid1 is a visible model for gms + else + *data << 17519; // world invisible trigger's model + } + else + { + if (cinfo->Modelid2) + *data << cinfo->Modelid2;//Modelid2 is an invisible model for players + else + *data << 11686; // world invisible trigger's model + } + } + else + *data << m_uint32Values[ index ]; + } + else + *data << m_uint32Values[ index ]; + } + // hide lootable animation for unallowed players + else if (index == UNIT_DYNAMIC_FLAGS) + { + uint32 dynamicFlags = m_uint32Values[index]; + + if (const Creature* creature = ToCreature()) + { + if (creature->hasLootRecipient()) + { + if (creature->isTappedBy(target)) + { + dynamicFlags |= (UNIT_DYNFLAG_TAPPED|UNIT_DYNFLAG_TAPPED_BY_PLAYER); + } + else + { + dynamicFlags |= UNIT_DYNFLAG_TAPPED; + dynamicFlags &= ~UNIT_DYNFLAG_TAPPED_BY_PLAYER; + } + } + else + { + dynamicFlags &= ~UNIT_DYNFLAG_TAPPED; + dynamicFlags &= ~UNIT_DYNFLAG_TAPPED_BY_PLAYER; + } + + if (!target->isAllowedToLoot(ToCreature())) + dynamicFlags &= ~UNIT_DYNFLAG_LOOTABLE; + } + + *data << dynamicFlags; + } + // FG: pretend that OTHER players in own group are friendly ("blue") + else if (index == UNIT_FIELD_BYTES_2 || index == UNIT_FIELD_FACTIONTEMPLATE) + { + if (((Unit*)this)->IsControlledByPlayer() && target != this && sWorld.getConfig(CONFIG_ALLOW_TWO_SIDE_INTERACTION_GROUP) && ((Unit*)this)->IsInRaidWith(target)) + { + FactionTemplateEntry const *ft1, *ft2; + ft1 = ((Unit*)this)->getFactionTemplateEntry(); + ft2 = target->getFactionTemplateEntry(); + if (ft1 && ft2 && !ft1->IsFriendlyTo(*ft2)) + { + if (index == UNIT_FIELD_BYTES_2) + { + // Allow targetting opposite faction in party when enabled in config + *data << (m_uint32Values[ index ] & ((UNIT_BYTE2_FLAG_SANCTUARY /*| UNIT_BYTE2_FLAG_AURAS | UNIT_BYTE2_FLAG_UNK5*/) << 8)); // this flag is at uint8 offset 1 !! + } + else + { + // pretend that all other HOSTILE players have own faction, to allow follow, heal, rezz (trade wont work) + uint32 faction = target->getFaction(); + *data << uint32(faction); + } + } + else + *data << m_uint32Values[ index ]; + } + else + *data << m_uint32Values[ index ]; + } + else + { + // send in current format (float as float, uint32 as uint32) + *data << m_uint32Values[ index ]; + } + } + } + } + else if (isType(TYPEMASK_GAMEOBJECT)) // gameobject case + { + for (uint16 index = 0; index < m_valuesCount; ++index) + { + if (updateMask->GetBit(index)) + { + // send in current format (float as float, uint32 as uint32) + if (index == GAMEOBJECT_DYNAMIC) + { + if (IsActivateToQuest) + { + switch(((GameObject*)this)->GetGoType()) + { + case GAMEOBJECT_TYPE_CHEST: + // enable quest object. Represent 9, but 1 for client before 2.3.0 + *data << uint16(9); + *data << uint16(-1); + break; + case GAMEOBJECT_TYPE_GOOBER: + *data << uint16(1); + *data << uint16(-1); + break; + default: + // unknown, not happen. + *data << uint16(0); + *data << uint16(-1); + break; + } + } + else + { + // disable quest object + *data << uint16(0); + *data << uint16(-1); + } + } + else + *data << m_uint32Values[ index ]; // other cases + } + } + } + else // other objects case (no special index checks) + { + for (uint16 index = 0; index < m_valuesCount; ++index) + { + if (updateMask->GetBit(index)) + { + // send in current format (float as float, uint32 as uint32) + *data << m_uint32Values[ index ]; + } + } + } +} + +void Object::ClearUpdateMask(bool remove) +{ + memcpy(m_uint32Values_mirror, m_uint32Values, m_valuesCount*sizeof(uint32)); + + if (m_objectUpdated) + { + if (remove) + ObjectAccessor::Instance().RemoveUpdateObject(this); + m_objectUpdated = false; + } +} + +void Object::BuildFieldsUpdate(Player *pl, UpdateDataMapType &data_map) const +{ + UpdateDataMapType::iterator iter = data_map.find(pl); + + if (iter == data_map.end()) + { + std::pair<UpdateDataMapType::iterator, bool> p = data_map.insert(UpdateDataMapType::value_type(pl, UpdateData())); + assert(p.second); + iter = p.first; + } + + BuildValuesUpdateBlockForPlayer(&iter->second, iter->first); +} + +bool Object::LoadValues(const char* data) +{ + if (!m_uint32Values) _InitValues(); + + Tokens tokens = StrSplit(data, " "); + + if (tokens.size() != m_valuesCount) + return false; + + Tokens::iterator iter; + int index; + for (iter = tokens.begin(), index = 0; index < m_valuesCount; ++iter, ++index) + { + m_uint32Values[index] = atol((*iter).c_str()); + } + + return true; +} + +void Object::_SetUpdateBits(UpdateMask *updateMask, Player* /*target*/) const +{ + uint32 *value = m_uint32Values; + uint32 *mirror = m_uint32Values_mirror; + + for (uint16 index = 0; index < m_valuesCount; ++index, ++value, ++mirror) + { + if (*mirror != *value) + updateMask->SetBit(index); + } +} + +void Object::_SetCreateBits(UpdateMask *updateMask, Player* /*target*/) const +{ + uint32 *value = m_uint32Values; + + for (uint16 index = 0; index < m_valuesCount; ++index, ++value) + { + if (*value) + updateMask->SetBit(index); + } +} + +void Object::SetInt32Value(uint16 index, int32 value) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (m_int32Values[ index ] != value) + { + m_int32Values[ index ] = value; + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetUInt32Value(uint16 index, uint32 value) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (m_uint32Values[ index ] != value) + { + m_uint32Values[ index ] = value; + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::UpdateUInt32Value(uint16 index, uint32 value) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + m_uint32Values[ index ] = value; +} + +void Object::SetUInt64Value(uint16 index, const uint64 &value) +{ + ASSERT(index + 1 < m_valuesCount || PrintIndexError(index, true)); + if (*((uint64*)&(m_uint32Values[ index ])) != value) + { + m_uint32Values[ index ] = *((uint32*)&value); + m_uint32Values[ index + 1 ] = *(((uint32*)&value) + 1); + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +bool Object::AddUInt64Value(uint16 index, const uint64 &value) +{ + ASSERT(index + 1 < m_valuesCount || PrintIndexError(index , true)); + if (value && !*((uint64*)&(m_uint32Values[index]))) + { + m_uint32Values[ index ] = *((uint32*)&value); + m_uint32Values[ index + 1 ] = *(((uint32*)&value) + 1); + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + return true; + } + return false; +} + +bool Object::RemoveUInt64Value(uint16 index, const uint64 &value) +{ + ASSERT(index + 1 < m_valuesCount || PrintIndexError(index , true)); + if (value && *((uint64*)&(m_uint32Values[index])) == value) + { + m_uint32Values[ index ] = 0; + m_uint32Values[ index + 1 ] = 0; + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + return true; + } + return false; +} + +void Object::SetFloatValue(uint16 index, float value) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (m_floatValues[ index ] != value) + { + m_floatValues[ index ] = value; + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetByteValue(uint16 index, uint8 offset, uint8 value) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (offset > 4) + { + sLog.outError("Object::SetByteValue: wrong offset %u", offset); + return; + } + + if (uint8(m_uint32Values[ index ] >> (offset * 8)) != value) + { + m_uint32Values[ index ] &= ~uint32(uint32(0xFF) << (offset * 8)); + m_uint32Values[ index ] |= uint32(uint32(value) << (offset * 8)); + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetUInt16Value(uint16 index, uint8 offset, uint16 value) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (offset > 2) + { + sLog.outError("Object::SetUInt16Value: wrong offset %u", offset); + return; + } + + if (uint16(m_uint32Values[ index ] >> (offset * 16)) != value) + { + m_uint32Values[ index ] &= ~uint32(uint32(0xFFFF) << (offset * 16)); + m_uint32Values[ index ] |= uint32(uint32(value) << (offset * 16)); + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetStatFloatValue(uint16 index, float value) +{ + if (value < 0) + value = 0.0f; + + SetFloatValue(index, value); +} + +void Object::SetStatInt32Value(uint16 index, int32 value) +{ + if (value < 0) + value = 0; + + SetUInt32Value(index, uint32(value)); +} + +void Object::ApplyModUInt32Value(uint16 index, int32 val, bool apply) +{ + int32 cur = GetUInt32Value(index); + cur += (apply ? val : -val); + if (cur < 0) + cur = 0; + SetUInt32Value(index, cur); +} + +void Object::ApplyModInt32Value(uint16 index, int32 val, bool apply) +{ + int32 cur = GetInt32Value(index); + cur += (apply ? val : -val); + SetInt32Value(index, cur); +} + +void Object::ApplyModSignedFloatValue(uint16 index, float val, bool apply) +{ + float cur = GetFloatValue(index); + cur += (apply ? val : -val); + SetFloatValue(index, cur); +} + +void Object::ApplyModPositiveFloatValue(uint16 index, float val, bool apply) +{ + float cur = GetFloatValue(index); + cur += (apply ? val : -val); + if (cur < 0) + cur = 0; + SetFloatValue(index, cur); +} + +void Object::SetFlag(uint16 index, uint32 newFlag) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + uint32 oldval = m_uint32Values[ index ]; + uint32 newval = oldval | newFlag; + + if (oldval != newval) + { + m_uint32Values[ index ] = newval; + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::RemoveFlag(uint16 index, uint32 oldFlag) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + uint32 oldval = m_uint32Values[ index ]; + uint32 newval = oldval & ~oldFlag; + + if (oldval != newval) + { + m_uint32Values[ index ] = newval; + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::SetByteFlag(uint16 index, uint8 offset, uint8 newFlag) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (offset > 4) + { + sLog.outError("Object::SetByteFlag: wrong offset %u", offset); + return; + } + + if (!(uint8(m_uint32Values[ index ] >> (offset * 8)) & newFlag)) + { + m_uint32Values[ index ] |= uint32(uint32(newFlag) << (offset * 8)); + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +void Object::RemoveByteFlag(uint16 index, uint8 offset, uint8 oldFlag) +{ + ASSERT(index < m_valuesCount || PrintIndexError(index, true)); + + if (offset > 4) + { + sLog.outError("Object::RemoveByteFlag: wrong offset %u", offset); + return; + } + + if (uint8(m_uint32Values[ index ] >> (offset * 8)) & oldFlag) + { + m_uint32Values[ index ] &= ~uint32(uint32(oldFlag) << (offset * 8)); + + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } + } +} + +bool Object::PrintIndexError(uint32 index, bool set) const +{ + sLog.outError("Attempt %s non-existed value field: %u (count: %u) for object typeid: %u type mask: %u",(set ? "set value to" : "get value from"),index,m_valuesCount,GetTypeId(),m_objectType); + + // assert must fail after function call + return false; +} + +bool Position::HasInLine(const Unit * const target, float distance, float width) const +{ + if (!HasInArc(M_PI, target) || !target->IsWithinDist3d(m_positionX, m_positionY, m_positionZ, distance)) + return false; + width += target->GetObjectSize(); + float angle = GetRelativeAngle(target); + return abs(sin(angle)) * GetExactDist2d(target->GetPositionX(), target->GetPositionY()) < width; +} + +WorldObject::WorldObject() + : WorldLocation(), m_InstanceId(0), m_phaseMask(PHASEMASK_NORMAL), m_currMap(NULL) + , m_zoneScript(NULL) + , m_isActive(false), m_isWorldObject(false) + , m_name("") + , m_notifyflags(0), m_executed_notifies(0) +{ +} + +void WorldObject::SetWorldObject(bool on) +{ + if (!IsInWorld()) + return; + + GetMap()->AddObjectToSwitchList(this, on); +} + +void WorldObject::setActive(bool on) +{ + if (m_isActive == on) + return; + + if (GetTypeId() == TYPEID_PLAYER) + return; + + m_isActive = on; + + if (!IsInWorld()) + return; + + Map *map = FindMap(); + if (!map) + return; + + if (on) + { + if (GetTypeId() == TYPEID_UNIT) + map->AddToActive(this->ToCreature()); + else if (GetTypeId() == TYPEID_DYNAMICOBJECT) + map->AddToActive((DynamicObject*)this); + } + else + { + if (GetTypeId() == TYPEID_UNIT) + map->RemoveFromActive(this->ToCreature()); + else if (GetTypeId() == TYPEID_DYNAMICOBJECT) + map->RemoveFromActive((DynamicObject*)this); + } +} + +void WorldObject::CleanupsBeforeDelete(bool /*finalCleanup*/) +{ +} + +void WorldObject::_Create(uint32 guidlow, HighGuid guidhigh, uint32 phaseMask) +{ + Object::_Create(guidlow, 0, guidhigh); + m_phaseMask = phaseMask; +} + +uint32 WorldObject::GetZoneId() const +{ + return GetBaseMap()->GetZoneId(m_positionX, m_positionY, m_positionZ); +} + +uint32 WorldObject::GetAreaId() const +{ + return GetBaseMap()->GetAreaId(m_positionX, m_positionY, m_positionZ); +} + +void WorldObject::GetZoneAndAreaId(uint32& zoneid, uint32& areaid) const +{ + GetBaseMap()->GetZoneAndAreaId(zoneid, areaid, m_positionX, m_positionY, m_positionZ); +} + +InstanceData* WorldObject::GetInstanceData() +{ + Map *map = GetMap(); + return map->IsDungeon() ? ((InstanceMap*)map)->GetInstanceData() : NULL; +} + +float WorldObject::GetDistanceZ(const WorldObject* obj) const +{ + float dz = fabs(GetPositionZ() - obj->GetPositionZ()); + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + float dist = dz - sizefactor; + return (dist > 0 ? dist : 0); +} + +bool WorldObject::_IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D) const +{ + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float distsq = dx*dx + dy*dy; + if (is3D) + { + float dz = GetPositionZ() - obj->GetPositionZ(); + distsq += dz*dz; + } + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + float maxdist = dist2compare + sizefactor; + + return distsq < maxdist * maxdist; +} + +bool WorldObject::IsWithinLOSInMap(const WorldObject* obj) const +{ + if (!IsInMap(obj)) return false; + float ox,oy,oz; + obj->GetPosition(ox,oy,oz); + return(IsWithinLOS(ox, oy, oz)); +} + +bool WorldObject::IsWithinLOS(float ox, float oy, float oz) const +{ + float x,y,z; + GetPosition(x,y,z); + VMAP::IVMapManager *vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); + return vMapManager->isInLineOfSight(GetMapId(), x, y, z+2.0f, ox, oy, oz+2.0f); +} + +bool WorldObject::GetDistanceOrder(WorldObject const* obj1, WorldObject const* obj2, bool is3D /* = true */) const +{ + float dx1 = GetPositionX() - obj1->GetPositionX(); + float dy1 = GetPositionY() - obj1->GetPositionY(); + float distsq1 = dx1*dx1 + dy1*dy1; + if (is3D) + { + float dz1 = GetPositionZ() - obj1->GetPositionZ(); + distsq1 += dz1*dz1; + } + + float dx2 = GetPositionX() - obj2->GetPositionX(); + float dy2 = GetPositionY() - obj2->GetPositionY(); + float distsq2 = dx2*dx2 + dy2*dy2; + if (is3D) + { + float dz2 = GetPositionZ() - obj2->GetPositionZ(); + distsq2 += dz2*dz2; + } + + return distsq1 < distsq2; +} + +bool WorldObject::IsInRange(WorldObject const* obj, float minRange, float maxRange, bool is3D /* = true */) const +{ + float dx = GetPositionX() - obj->GetPositionX(); + float dy = GetPositionY() - obj->GetPositionY(); + float distsq = dx*dx + dy*dy; + if (is3D) + { + float dz = GetPositionZ() - obj->GetPositionZ(); + distsq += dz*dz; + } + + float sizefactor = GetObjectSize() + obj->GetObjectSize(); + + // check only for real range + if (minRange > 0.0f) + { + float mindist = minRange + sizefactor; + if (distsq < mindist * mindist) + return false; + } + + float maxdist = maxRange + sizefactor; + return distsq < maxdist * maxdist; +} + +bool WorldObject::IsInRange2d(float x, float y, float minRange, float maxRange) const +{ + float dx = GetPositionX() - x; + float dy = GetPositionY() - y; + float distsq = dx*dx + dy*dy; + + float sizefactor = GetObjectSize(); + + // check only for real range + if (minRange > 0.0f) + { + float mindist = minRange + sizefactor; + if (distsq < mindist * mindist) + return false; + } + + float maxdist = maxRange + sizefactor; + return distsq < maxdist * maxdist; +} + +bool WorldObject::IsInRange3d(float x, float y, float z, float minRange, float maxRange) const +{ + float dx = GetPositionX() - x; + float dy = GetPositionY() - y; + float dz = GetPositionZ() - z; + float distsq = dx*dx + dy*dy + dz*dz; + + float sizefactor = GetObjectSize(); + + // check only for real range + if (minRange > 0.0f) + { + float mindist = minRange + sizefactor; + if (distsq < mindist * mindist) + return false; + } + + float maxdist = maxRange + sizefactor; + return distsq < maxdist * maxdist; +} + +float Position::GetAngle(const Position *obj) const +{ + if (!obj) return 0; + return GetAngle(obj->GetPositionX(), obj->GetPositionY()); +} + +// Return angle in range 0..2*pi +float Position::GetAngle(const float x, const float y) const +{ + float dx = x - GetPositionX(); + float dy = y - GetPositionY(); + + float ang = atan2(dy, dx); + ang = (ang >= 0) ? ang : 2 * M_PI + ang; + return ang; +} + +void Position::GetSinCos(const float x, const float y, float &vsin, float &vcos) const +{ + float dx = GetPositionX() - x; + float dy = GetPositionY() - y; + + if (dx < 0.001f && dy < 0.001f) + { + float angle = rand_norm()*2*M_PI; + vcos = cos(angle); + vsin = sin(angle); + } + else + { + float dist = sqrt((dx*dx) + (dy*dy)); + vcos = dx / dist; + vsin = dy / dist; + } +} + +bool Position::HasInArc(float arc, const Position *obj) const +{ + // always have self in arc + if (obj == this) + return true; + + // move arc to range 0.. 2*pi + while (arc >= 2.0f * M_PI) + arc -= 2.0f * M_PI; + while (arc < 0) + arc += 2.0f * M_PI; + + float angle = GetAngle(obj); + angle -= m_orientation; + + //if (angle > 100 || angle < -100) + //{ + // sLog.outCrash("Invalid Angle %f: this %u %u %f %f %f %f, that %u %u %f %f %f %f", angle, + // GetEntry(), GetGUIDLow(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation(), + // obj->GetEntry(), obj->GetGUIDLow(), obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), obj->GetOrientation()); + // assert(false); + // return false; + //} + + // move angle to range -pi ... +pi + while (angle > M_PI) + angle -= 2.0f * M_PI; + while (angle < -M_PI) + angle += 2.0f * M_PI; + + float lborder = -1 * (arc/2.0f); // in range -pi..0 + float rborder = (arc/2.0f); // in range 0..pi + return ((angle >= lborder) && (angle <= rborder)); +} + +bool WorldObject::IsInBetween(const WorldObject *obj1, const WorldObject *obj2, float size) const +{ + if (GetPositionX() > std::max(obj1->GetPositionX(), obj2->GetPositionX()) + || GetPositionX() < std::min(obj1->GetPositionX(), obj2->GetPositionX()) + || GetPositionY() > std::max(obj1->GetPositionY(), obj2->GetPositionY()) + || GetPositionY() < std::min(obj1->GetPositionY(), obj2->GetPositionY())) + return false; + + if (!size) + size = GetObjectSize() / 2; + + float angle = obj1->GetAngle(this) - obj1->GetAngle(obj2); + return abs(sin(angle)) * GetExactDist2d(obj1->GetPositionX(), obj1->GetPositionY()) < size; +} + +bool WorldObject::isInFront(WorldObject const* target, float distance, float arc) const +{ + return IsWithinDist(target, distance) && HasInArc(arc, target); +} + +bool WorldObject::isInBack(WorldObject const* target, float distance, float arc) const +{ + return IsWithinDist(target, distance) && !HasInArc(2 * M_PI - arc, target); +} + +void WorldObject::GetRandomPoint(const Position &pos, float distance, float &rand_x, float &rand_y, float &rand_z) const +{ + if (!distance) + { + pos.GetPosition(rand_x, rand_y, rand_z); + return; + } + + // angle to face `obj` to `this` + float angle = rand_norm()*2*M_PI; + float new_dist = rand_norm()*distance; + + rand_x = pos.m_positionX + new_dist * cos(angle); + rand_y = pos.m_positionY + new_dist * sin(angle); + rand_z = pos.m_positionZ; + + Trinity::NormalizeMapCoord(rand_x); + Trinity::NormalizeMapCoord(rand_y); + UpdateGroundPositionZ(rand_x,rand_y,rand_z); // update to LOS height if available +} + +void WorldObject::UpdateGroundPositionZ(float x, float y, float &z) const +{ + float new_z = GetBaseMap()->GetHeight(x,y,z,true); + if (new_z > INVALID_HEIGHT) + z = new_z+ 0.05f; // just to be sure that we are not a few pixel under the surface +} + +bool Position::IsPositionValid() const +{ + return Trinity::IsValidMapCoord(m_positionX,m_positionY,m_positionZ,m_orientation); +} + +void WorldObject::MonsterSay(const char* text, uint32 language, uint64 TargetGuid) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,CHAT_MSG_MONSTER_SAY,text,language,GetName(),TargetGuid); + SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY),true); +} + +void WorldObject::MonsterYell(const char* text, uint32 language, uint64 TargetGuid) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,CHAT_MSG_MONSTER_YELL,text,language,GetName(),TargetGuid); + SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL),true); +} + +void WorldObject::MonsterTextEmote(const char* text, uint64 TargetGuid, bool IsBossEmote) +{ + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE,text,LANG_UNIVERSAL,GetName(),TargetGuid); + SendMessageToSetInRange(&data,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE),true); +} + +void WorldObject::MonsterWhisper(const char* text, uint64 receiver, bool IsBossWhisper) +{ + Player *player = objmgr.GetPlayer(receiver); + if (!player || !player->GetSession()) + return; + + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER,text,LANG_UNIVERSAL,GetName(),receiver); + + player->GetSession()->SendPacket(&data); +} + +void WorldObject::SendPlaySound(uint32 Sound, bool OnlySelf) +{ + WorldPacket data(SMSG_PLAY_SOUND, 4); + data << Sound; + if (OnlySelf && GetTypeId() == TYPEID_PLAYER) + this->ToPlayer()->GetSession()->SendPacket(&data); + else + SendMessageToSet(&data, true); // ToSelf ignored in this case +} + +void Object::ForceValuesUpdateAtIndex(uint32 i) +{ + m_uint32Values_mirror[i] = GetUInt32Value(i) + 1; // makes server think the field changed + if (m_inWorld) + { + if (!m_objectUpdated) + { + ObjectAccessor::Instance().AddUpdateObject(this); + m_objectUpdated = true; + } + } +} + +namespace Trinity +{ + class MonsterChatBuilder + { + public: + MonsterChatBuilder(WorldObject const& obj, ChatMsg msgtype, int32 textId, uint32 language, uint64 targetGUID) + : i_object(obj), i_msgtype(msgtype), i_textId(textId), i_language(language), i_targetGUID(targetGUID) {} + void operator()(WorldPacket& data, int32 loc_idx) + { + char const* text = objmgr.GetTrinityString(i_textId,loc_idx); + + // TODO: i_object.GetName() also must be localized? + i_object.BuildMonsterChat(&data,i_msgtype,text,i_language,i_object.GetNameForLocaleIdx(loc_idx),i_targetGUID); + } + + private: + WorldObject const& i_object; + ChatMsg i_msgtype; + int32 i_textId; + uint32 i_language; + uint64 i_targetGUID; + }; +} // namespace Trinity + +void WorldObject::MonsterSay(int32 textId, uint32 language, uint64 TargetGuid) +{ + CellPair p = Trinity::ComputeCellPair(GetPositionX(), GetPositionY()); + + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::MonsterChatBuilder say_build(*this, CHAT_MSG_MONSTER_SAY, textId,language,TargetGuid); + Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> say_do(say_build); + Trinity::PlayerDistWorker<Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> > say_worker(this,sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY),say_do); + TypeContainerVisitor<Trinity::PlayerDistWorker<Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> >, WorldTypeMapContainer > message(say_worker); + cell.Visit(p, message, *GetMap(), *this, sWorld.getConfig(CONFIG_LISTEN_RANGE_SAY)); +} + +void WorldObject::MonsterYell(int32 textId, uint32 language, uint64 TargetGuid) +{ + CellPair p = Trinity::ComputeCellPair(GetPositionX(), GetPositionY()); + + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::MonsterChatBuilder say_build(*this, CHAT_MSG_MONSTER_YELL, textId,language,TargetGuid); + Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> say_do(say_build); + Trinity::PlayerDistWorker<Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> > say_worker(this,sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL),say_do); + TypeContainerVisitor<Trinity::PlayerDistWorker<Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> >, WorldTypeMapContainer > message(say_worker); + cell.Visit(p, message, *GetMap(), *this, sWorld.getConfig(CONFIG_LISTEN_RANGE_YELL)); +} + +void WorldObject::MonsterYellToZone(int32 textId, uint32 language, uint64 TargetGuid) +{ + Trinity::MonsterChatBuilder say_build(*this, CHAT_MSG_MONSTER_YELL, textId,language,TargetGuid); + Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> say_do(say_build); + + uint32 zoneid = GetZoneId(); + + Map::PlayerList const& pList = GetMap()->GetPlayers(); + for (Map::PlayerList::const_iterator itr = pList.begin(); itr != pList.end(); ++itr) + if (itr->getSource()->GetZoneId() == zoneid) + say_do(itr->getSource()); +} + +void WorldObject::MonsterTextEmote(int32 textId, uint64 TargetGuid, bool IsBossEmote) +{ + CellPair p = Trinity::ComputeCellPair(GetPositionX(), GetPositionY()); + + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::MonsterChatBuilder say_build(*this, IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, textId,LANG_UNIVERSAL,TargetGuid); + Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> say_do(say_build); + Trinity::PlayerDistWorker<Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> > say_worker(this,sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE),say_do); + TypeContainerVisitor<Trinity::PlayerDistWorker<Trinity::LocalizedPacketDo<Trinity::MonsterChatBuilder> >, WorldTypeMapContainer > message(say_worker); + cell.Visit(p, message, *GetMap(), *this, sWorld.getConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)); +} + +void WorldObject::MonsterWhisper(int32 textId, uint64 receiver, bool IsBossWhisper) +{ + Player *player = objmgr.GetPlayer(receiver); + if (!player || !player->GetSession()) + return; + + uint32 loc_idx = player->GetSession()->GetSessionDbLocaleIndex(); + char const* text = objmgr.GetTrinityString(textId,loc_idx); + + WorldPacket data(SMSG_MESSAGECHAT, 200); + BuildMonsterChat(&data,IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER,text,LANG_UNIVERSAL,GetNameForLocaleIdx(loc_idx),receiver); + + player->GetSession()->SendPacket(&data); +} + +void WorldObject::BuildMonsterChat(WorldPacket *data, uint8 msgtype, char const* text, uint32 language, char const* name, uint64 targetGuid) const +{ + *data << (uint8)msgtype; + *data << (uint32)language; + *data << (uint64)GetGUID(); + *data << (uint32)0; // 2.1.0 + *data << (uint32)(strlen(name)+1); + *data << name; + *data << (uint64)targetGuid; // Unit Target + if (targetGuid && !IS_PLAYER_GUID(targetGuid)) + { + *data << (uint32)1; // target name length + *data << (uint8)0; // target name + } + *data << (uint32)(strlen(text)+1); + *data << text; + *data << (uint8)0; // ChatTag +} + +void Unit::BuildHeartBeatMsg(WorldPacket *data) const +{ + data->Initialize(MSG_MOVE_HEARTBEAT, 32); + data->append(GetPackGUID()); + BuildMovementPacket(data); +} + +void WorldObject::SendMessageToSet(WorldPacket *data, bool /*fake*/) +{ + Trinity::MessageDistDeliverer notifier(this, data, GetMap()->GetVisibilityDistance()); + VisitNearbyWorldObject(GetMap()->GetVisibilityDistance(), notifier); +} + +void WorldObject::SendMessageToSetInRange(WorldPacket *data, float dist, bool /*bToSelf*/) +{ + Trinity::MessageDistDeliverer notifier(this, data, dist); + VisitNearbyWorldObject(dist, notifier); +} + +void WorldObject::SendMessageToSet(WorldPacket *data, Player const* skipped_rcvr) +{ + Trinity::MessageDistDeliverer notifier(this, data, GetMap()->GetVisibilityDistance(), false, skipped_rcvr); + VisitNearbyWorldObject(GetMap()->GetVisibilityDistance(), notifier); +} + +void WorldObject::SendObjectDeSpawnAnim(uint64 guid) +{ + WorldPacket data(SMSG_GAMEOBJECT_DESPAWN_ANIM, 8); + data << uint64(guid); + SendMessageToSet(&data, true); +} + +void WorldObject::SetMap(Map * map) +{ + ASSERT(map); + ASSERT(!IsInWorld() || GetTypeId() == TYPEID_CORPSE); + if (m_currMap == map) // command add npc: first create, than loadfromdb + return; + if (m_currMap) + { + sLog.outCrash("WorldObject::SetMap: obj %u new map %u %u, old map %u %u", (uint32)GetTypeId(), map->GetId(), map->GetInstanceId(), m_currMap->GetId(), m_currMap->GetInstanceId()); + assert(false); + } + m_currMap = map; + m_mapId = map->GetId(); + m_InstanceId = map->GetInstanceId(); + if (m_isWorldObject) + m_currMap->AddWorldObject(this); +} + +void WorldObject::ResetMap() +{ + ASSERT(m_currMap); + ASSERT(!IsInWorld()); + if (m_isWorldObject) + m_currMap->RemoveWorldObject(this); + m_currMap = NULL; + //maybe not for corpse + //m_mapId = 0; + //m_InstanceId = 0; +} + +Map const* WorldObject::GetBaseMap() const +{ + ASSERT(m_currMap); + return m_currMap->GetParent(); +} + +void WorldObject::AddObjectToRemoveList() +{ + assert(m_uint32Values); + + Map* map = FindMap(); + if (!map) + { + sLog.outError("Object (TypeId: %u Entry: %u GUID: %u) at attempt add to move list not have valid map (Id: %u).",GetTypeId(),GetEntry(),GetGUIDLow(),GetMapId()); + return; + } + + map->AddObjectToRemoveList(this); +} + +TempSummon *Map::SummonCreature(uint32 entry, const Position &pos, SummonPropertiesEntry const *properties, uint32 duration, Unit *summoner, uint32 vehId) +{ + uint32 mask = UNIT_MASK_SUMMON; + if (properties) + { + switch(properties->Category) + { + case SUMMON_CATEGORY_PET: mask = UNIT_MASK_GUARDIAN; break; + case SUMMON_CATEGORY_PUPPET: mask = UNIT_MASK_PUPPET; break; + case SUMMON_CATEGORY_VEHICLE: mask = UNIT_MASK_MINION; break; + default: + switch(properties->Type) + { + case SUMMON_TYPE_MINION: + case SUMMON_TYPE_GUARDIAN: + case SUMMON_TYPE_GUARDIAN2: + mask = UNIT_MASK_GUARDIAN; break; + case SUMMON_TYPE_TOTEM: + mask = UNIT_MASK_TOTEM; break; + case SUMMON_TYPE_VEHICLE: + case SUMMON_TYPE_VEHICLE2: + mask = UNIT_MASK_SUMMON; break; + case SUMMON_TYPE_MINIPET: + mask = UNIT_MASK_MINION; break; + default: + if (properties->Flags & 512) // Mirror Image, Summon Gargoyle + mask = UNIT_MASK_GUARDIAN; + break; + } + break; + } + } + + uint32 phase = PHASEMASK_NORMAL, team = 0; + if (summoner) + { + phase = summoner->GetPhaseMask(); + if (summoner->GetTypeId() == TYPEID_PLAYER) + team = summoner->ToPlayer()->GetTeam(); + } + + TempSummon *summon = NULL; + switch(mask) + { + case UNIT_MASK_SUMMON: summon = new TempSummon (properties, summoner); break; + case UNIT_MASK_GUARDIAN: summon = new Guardian (properties, summoner); break; + case UNIT_MASK_PUPPET: summon = new Puppet (properties, summoner); break; + case UNIT_MASK_TOTEM: summon = new Totem (properties, summoner); break; + case UNIT_MASK_MINION: summon = new Minion (properties, summoner); break; + default: return NULL; + } + + if (!summon->Create(objmgr.GenerateLowGuid(HIGHGUID_UNIT), this, phase, entry, vehId, team, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation())) + { + delete summon; + return NULL; + } + + summon->SetHomePosition(pos); + + summon->InitStats(duration); + Add(summon->ToCreature()); + summon->InitSummon(); + + //ObjectAccessor::UpdateObjectVisibility(summon); + + return summon; +} + +void WorldObject::SetZoneScript() +{ + if (Map *map = FindMap()) + { + if (map->IsDungeon()) + m_zoneScript = (ZoneScript*)((InstanceMap*)map)->GetInstanceData(); + else if (!map->IsBattleGroundOrArena()) + m_zoneScript = sOutdoorPvPMgr.GetZoneScript(GetZoneId()); + } +} + +TempSummon* WorldObject::SummonCreature(uint32 entry, const Position &pos, TempSummonType spwtype, uint32 duration, uint32 /*vehId*/) const +{ + if (Map *map = FindMap()) + { + if (TempSummon *summon = map->SummonCreature(entry, pos, NULL, duration, isType(TYPEMASK_UNIT) ? (Unit*)this : NULL)) + { + summon->SetTempSummonType(spwtype); + return summon; + } + } + + return NULL; +} + +Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 duration) +{ + Pet* pet = new Pet(this, petType); + + if (petType == SUMMON_PET && pet->LoadPetFromDB(this, entry)) + { + // Remove Demonic Sacrifice auras (known pet) + Unit::AuraEffectList const& auraClassScripts = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();) + { + if ((*itr)->GetMiscValue() == 2228) + { + RemoveAurasDueToSpell((*itr)->GetId()); + itr = auraClassScripts.begin(); + } + else + ++itr; + } + + if (duration > 0) + pet->SetDuration(duration); + + return NULL; + } + + // petentry == 0 for hunter "call pet" (current pet summoned if any) + if (!entry) + { + delete pet; + return NULL; + } + + pet->Relocate(x, y, z, ang); + if (!pet->IsPositionValid()) + { + sLog.outError("ERROR: Pet (guidlow %d, entry %d) not summoned. Suggested coordinates isn't valid (X: %f Y: %f)",pet->GetGUIDLow(),pet->GetEntry(),pet->GetPositionX(),pet->GetPositionY()); + delete pet; + return NULL; + } + + Map *map = GetMap(); + uint32 pet_number = objmgr.GeneratePetNumber(); + if (!pet->Create(objmgr.GenerateLowGuid(HIGHGUID_PET), map, GetPhaseMask(), entry, pet_number)) + { + sLog.outError("no such creature entry %u", entry); + delete pet; + return NULL; + } + + pet->SetCreatorGUID(GetGUID()); + pet->SetUInt32Value(UNIT_FIELD_FACTIONTEMPLATE, getFaction()); + + pet->setPowerType(POWER_MANA); + pet->SetUInt32Value(UNIT_NPC_FLAGS , 0); + pet->SetUInt32Value(UNIT_FIELD_BYTES_1,0); + pet->InitStatsForLevel(getLevel()); + + SetMinion(pet, true); + + switch(petType) + { + case SUMMON_PET: + // this enables pet details window (Shift+P) + pet->GetCharmInfo()->SetPetNumber(pet_number, true); + pet->SetUInt32Value(UNIT_FIELD_BYTES_0, 2048); + pet->SetUInt32Value(UNIT_FIELD_PETEXPERIENCE, 0); + pet->SetUInt32Value(UNIT_FIELD_PETNEXTLEVELEXP, 1000); + pet->SetHealth(pet->GetMaxHealth()); + pet->SetPower(POWER_MANA, pet->GetMaxPower(POWER_MANA)); + pet->SetUInt32Value(UNIT_FIELD_PET_NAME_TIMESTAMP, time(NULL)); + break; + } + + map->Add(pet->ToCreature()); + + switch(petType) + { + case SUMMON_PET: + pet->InitPetCreateSpells(); + pet->InitTalentForLevel(); + pet->SavePetToDB(PET_SAVE_AS_CURRENT); + PetSpellInitialize(); + break; + } + + if (petType == SUMMON_PET) + { + // Remove Demonic Sacrifice auras (known pet) + Unit::AuraEffectList const& auraClassScripts = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS); + for (Unit::AuraEffectList::const_iterator itr = auraClassScripts.begin(); itr != auraClassScripts.end();) + { + if ((*itr)->GetMiscValue() == 2228) + { + RemoveAurasDueToSpell((*itr)->GetId()); + itr = auraClassScripts.begin(); + } + else + ++itr; + } + } + + if (duration > 0) + pet->SetDuration(duration); + + //ObjectAccessor::UpdateObjectVisibility(pet); + + return pet; +} + +GameObject* WorldObject::SummonGameObject(uint32 entry, float x, float y, float z, float ang, float rotation0, float rotation1, float rotation2, float rotation3, uint32 respawnTime) +{ + if (!IsInWorld()) + return NULL; + + GameObjectInfo const* goinfo = objmgr.GetGameObjectInfo(entry); + if (!goinfo) + { + sLog.outErrorDb("Gameobject template %u not found in database!", entry); + return NULL; + } + Map *map = GetMap(); + GameObject *go = new GameObject(); + if (!go->Create(objmgr.GenerateLowGuid(HIGHGUID_GAMEOBJECT), entry, map, GetPhaseMask(), x,y,z,ang,rotation0,rotation1,rotation2,rotation3,100,GO_STATE_READY)) + { + delete go; + return NULL; + } + go->SetRespawnTime(respawnTime); + if (GetTypeId() == TYPEID_PLAYER || GetTypeId() == TYPEID_UNIT) //not sure how to handle this + ((Unit*)this)->AddGameObject(go); + else + go->SetSpawnedByDefault(false); + map->Add(go); + + return go; +} + +Creature* WorldObject::SummonTrigger(float x, float y, float z, float ang, uint32 duration, CreatureAI* (*GetAI)(Creature*)) +{ + TempSummonType summonType = (duration == 0) ? TEMPSUMMON_DEAD_DESPAWN : TEMPSUMMON_TIMED_DESPAWN; + Creature* summon = SummonCreature(WORLD_TRIGGER, x, y, z, ang, summonType, duration); + if (!summon) + return NULL; + + //summon->SetName(GetName()); + if (GetTypeId() == TYPEID_PLAYER || GetTypeId() == TYPEID_UNIT) + { + summon->setFaction(((Unit*)this)->getFaction()); + summon->SetLevel(((Unit*)this)->getLevel()); + } + + if (GetAI) + summon->AIM_Initialize(GetAI(summon)); + return summon; +} + +Creature* WorldObject::FindNearestCreature(uint32 entry, float range, bool alive) +{ + Creature *creature = NULL; + Trinity::NearestCreatureEntryWithLiveStateInObjectRangeCheck checker(*this, entry, alive, range); + Trinity::CreatureLastSearcher<Trinity::NearestCreatureEntryWithLiveStateInObjectRangeCheck> searcher(this, creature, checker); + VisitNearbyObject(range, searcher); + return creature; +} + +GameObject* WorldObject::FindNearestGameObject(uint32 entry, float range) +{ + GameObject *go = NULL; + Trinity::NearestGameObjectEntryInObjectRangeCheck checker(*this, entry, range); + Trinity::GameObjectLastSearcher<Trinity::NearestGameObjectEntryInObjectRangeCheck> searcher(this, go, checker); + VisitNearbyGridObject(range, searcher); + return go; +} + +void WorldObject::GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, uint32 uiEntry, float fMaxSearchRange) +{ + CellPair pair(Trinity::ComputeCellPair(this->GetPositionX(), this->GetPositionY())); + Cell cell(pair); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::AllGameObjectsWithEntryInRange check(this, uiEntry, fMaxSearchRange); + Trinity::GameObjectListSearcher<Trinity::AllGameObjectsWithEntryInRange> searcher(this, lList, check); + TypeContainerVisitor<Trinity::GameObjectListSearcher<Trinity::AllGameObjectsWithEntryInRange>, GridTypeMapContainer> visitor(searcher); + + cell.Visit(pair, visitor, *(this->GetMap())); +} + +void WorldObject::GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, uint32 uiEntry, float fMaxSearchRange) +{ + CellPair pair(Trinity::ComputeCellPair(this->GetPositionX(), this->GetPositionY())); + Cell cell(pair); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::AllCreaturesOfEntryInRange check(this, uiEntry, fMaxSearchRange); + Trinity::CreatureListSearcher<Trinity::AllCreaturesOfEntryInRange> searcher(this, lList, check); + TypeContainerVisitor<Trinity::CreatureListSearcher<Trinity::AllCreaturesOfEntryInRange>, GridTypeMapContainer> visitor(searcher); + + cell.Visit(pair, visitor, *(this->GetMap())); +} + +/* +namespace Trinity +{ + class NearUsedPosDo + { + public: + NearUsedPosDo(WorldObject const& obj, WorldObject const* searcher, float angle, ObjectPosSelector& selector) + : i_object(obj), i_searcher(searcher), i_angle(angle), i_selector(selector) {} + + void operator()(Corpse*) const {} + void operator()(DynamicObject*) const {} + + void operator()(Creature* c) const + { + // skip self or target + if (c == i_searcher || c == &i_object) + return; + + float x,y,z; + + if (!c->isAlive() || c->hasUnitState(UNIT_STAT_ROOT | UNIT_STAT_STUNNED | UNIT_STAT_DISTRACTED) || + !c->GetMotionMaster()->GetDestination(x,y,z)) + { + x = c->GetPositionX(); + y = c->GetPositionY(); + } + + add(c,x,y); + } + + template<class T> + void operator()(T* u) const + { + // skip self or target + if (u == i_searcher || u == &i_object) + return; + + float x,y; + + x = u->GetPositionX(); + y = u->GetPositionY(); + + add(u,x,y); + } + + // we must add used pos that can fill places around center + void add(WorldObject* u, float x, float y) const + { + // u is too nearest/far away to i_object + if (!i_object.IsInRange2d(x,y,i_selector.m_dist - i_selector.m_size,i_selector.m_dist + i_selector.m_size)) + return; + + float angle = i_object.GetAngle(u)-i_angle; + + // move angle to range -pi ... +pi + while (angle > M_PI) + angle -= 2.0f * M_PI; + while (angle < -M_PI) + angle += 2.0f * M_PI; + + // dist include size of u + float dist2d = i_object.GetDistance2d(x,y); + i_selector.AddUsedPos(u->GetObjectSize(),angle,dist2d + i_object.GetObjectSize()); + } + private: + WorldObject const& i_object; + WorldObject const* i_searcher; + float i_angle; + ObjectPosSelector& i_selector; + }; +} // namespace Trinity +*/ + +//=================================================================================================== + +void WorldObject::GetNearPoint2D(float &x, float &y, float distance2d, float absAngle) const +{ + x = GetPositionX() + (GetObjectSize() + distance2d) * cos(absAngle); + y = GetPositionY() + (GetObjectSize() + distance2d) * sin(absAngle); + + Trinity::NormalizeMapCoord(x); + Trinity::NormalizeMapCoord(y); +} + +void WorldObject::GetNearPoint(WorldObject const* /*searcher*/, float &x, float &y, float &z, float searcher_size, float distance2d, float absAngle) const +{ + GetNearPoint2D(x,y,distance2d+searcher_size,absAngle); + z = GetPositionZ(); + UpdateGroundPositionZ(x,y,z); + + /* + // if detection disabled, return first point + if (!sWorld.getConfig(CONFIG_DETECT_POS_COLLISION)) + { + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + return; + } + + // or remember first point + float first_x = x; + float first_y = y; + bool first_los_conflict = false; // first point LOS problems + + // prepare selector for work + ObjectPosSelector selector(GetPositionX(),GetPositionY(),GetObjectSize(),distance2d+searcher_size); + + // adding used positions around object + { + CellPair p(Trinity::ComputeCellPair(GetPositionX(), GetPositionY())); + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + + Trinity::NearUsedPosDo u_do(*this,searcher,absAngle,selector); + Trinity::WorldObjectWorker<Trinity::NearUsedPosDo> worker(this,u_do); + + TypeContainerVisitor<Trinity::WorldObjectWorker<Trinity::NearUsedPosDo>, GridTypeMapContainer > grid_obj_worker(worker); + TypeContainerVisitor<Trinity::WorldObjectWorker<Trinity::NearUsedPosDo>, WorldTypeMapContainer > world_obj_worker(worker); + + CellLock<GridReadGuard> cell_lock(cell, p); + cell_lock->Visit(cell_lock, grid_obj_worker, *GetMap(), *this, distance2d); + cell_lock->Visit(cell_lock, world_obj_worker, *GetMap(), *this, distance2d); + } + + // maybe can just place in primary position + if (selector.CheckOriginal()) + { + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + + if (IsWithinLOS(x,y,z)) + return; + + first_los_conflict = true; // first point have LOS problems + } + + float angle; // candidate of angle for free pos + + // special case when one from list empty and then empty side preferred + if (selector.FirstAngle(angle)) + { + GetNearPoint2D(x,y,distance2d,absAngle+angle); + z = GetPositionZ(); + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + + if (IsWithinLOS(x,y,z)) + return; + } + + // set first used pos in lists + selector.InitializeAngle(); + + // select in positions after current nodes (selection one by one) + while (selector.NextAngle(angle)) // angle for free pos + { + GetNearPoint2D(x,y,distance2d,absAngle+angle); + z = GetPositionZ(); + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + + if (IsWithinLOS(x,y,z)) + return; + } + + // BAD NEWS: not free pos (or used or have LOS problems) + // Attempt find _used_ pos without LOS problem + + if (!first_los_conflict) + { + x = first_x; + y = first_y; + + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + return; + } + + // special case when one from list empty and then empty side preferred + if (selector.IsNonBalanced()) + { + if (!selector.FirstAngle(angle)) // _used_ pos + { + GetNearPoint2D(x,y,distance2d,absAngle+angle); + z = GetPositionZ(); + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + + if (IsWithinLOS(x,y,z)) + return; + } + } + + // set first used pos in lists + selector.InitializeAngle(); + + // select in positions after current nodes (selection one by one) + while (selector.NextUsedAngle(angle)) // angle for used pos but maybe without LOS problem + { + GetNearPoint2D(x,y,distance2d,absAngle+angle); + z = GetPositionZ(); + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + + if (IsWithinLOS(x,y,z)) + return; + } + + // BAD BAD NEWS: all found pos (free and used) have LOS problem :( + x = first_x; + y = first_y; + + UpdateGroundPositionZ(x,y,z); // update to LOS height if available + */ +} + +void WorldObject::MovePosition(Position &pos, float dist, float angle) +{ + angle += m_orientation; + pos.m_positionX += dist * cos(angle); + pos.m_positionY += dist * sin(angle); + Trinity::NormalizeMapCoord(pos.m_positionX); + Trinity::NormalizeMapCoord(pos.m_positionY); + UpdateGroundPositionZ(pos.m_positionX, pos.m_positionY, pos.m_positionZ); + pos.m_orientation = m_orientation; +} + +void WorldObject::SetPhaseMask(uint32 newPhaseMask, bool update) +{ + m_phaseMask = newPhaseMask; + + if (update && IsInWorld()) + UpdateObjectVisibility(); +} + +void WorldObject::PlayDistanceSound(uint32 sound_id, Player* target /*= NULL*/) +{ + WorldPacket data(SMSG_PLAY_OBJECT_SOUND,4+8); + data << uint32(sound_id); + data << uint64(GetGUID()); + if (target) + target->SendDirectMessage(&data); + else + SendMessageToSet(&data, true); +} + +void WorldObject::PlayDirectSound(uint32 sound_id, Player* target /*= NULL*/) +{ + WorldPacket data(SMSG_PLAY_SOUND, 4); + data << uint32(sound_id); + if (target) + target->SendDirectMessage(&data); + else + SendMessageToSet(&data, true); +} + +void WorldObject::DestroyForNearbyPlayers() +{ + if (!IsInWorld()) + return; + + std::list<Player*> targets; + Trinity::AnyPlayerInObjectRangeCheck check(this, GetMap()->GetVisibilityDistance()); + Trinity::PlayerListSearcher<Trinity::AnyPlayerInObjectRangeCheck> searcher(this, targets, check); + VisitNearbyWorldObject(GetMap()->GetVisibilityDistance(), searcher); + for (std::list<Player*>::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) + { + Player *plr = (*iter); + + if (plr == this) + continue; + + if (!plr->HaveAtClient(this)) + continue; + + if (isType(TYPEMASK_UNIT) && ((Unit*)this)->GetCharmerGUID() == plr->GetGUID()) // TODO: this is for puppet + continue; + + DestroyForPlayer(plr); + plr->m_clientGUIDs.erase(GetGUID()); + } +} + +void WorldObject::UpdateObjectVisibility(bool /*forced*/) +{ + //updates object's visibility for nearby players + Trinity::VisibleChangesNotifier notifier(*this); + VisitNearbyWorldObject(GetMap()->GetVisibilityDistance(), notifier); +} + +struct WorldObjectChangeAccumulator +{ + UpdateDataMapType &i_updateDatas; + WorldObject &i_object; + std::set<uint64> plr_list; + WorldObjectChangeAccumulator(WorldObject &obj, UpdateDataMapType &d) : i_updateDatas(d), i_object(obj) {} + void Visit(PlayerMapType &m) + { + for (PlayerMapType::iterator iter = m.begin(); iter != m.end(); ++iter) + { + BuildPacket(iter->getSource()); + if (!iter->getSource()->GetSharedVisionList().empty()) + { + SharedVisionList::const_iterator it = iter->getSource()->GetSharedVisionList().begin(); + for (; it != iter->getSource()->GetSharedVisionList().end(); ++it) + BuildPacket(*it); + } + } + } + + void Visit(CreatureMapType &m) + { + for (CreatureMapType::iterator iter = m.begin(); iter != m.end(); ++iter) + { + if (!iter->getSource()->GetSharedVisionList().empty()) + { + SharedVisionList::const_iterator it = iter->getSource()->GetSharedVisionList().begin(); + for (; it != iter->getSource()->GetSharedVisionList().end(); ++it) + BuildPacket(*it); + } + } + } + void Visit(DynamicObjectMapType &m) + { + for (DynamicObjectMapType::iterator iter = m.begin(); iter != m.end(); ++iter) + { + uint64 guid = iter->getSource()->GetCasterGUID(); + if (IS_PLAYER_GUID(guid)) + { + //Caster may be NULL if DynObj is in removelist + if (Player *caster = ObjectAccessor::FindPlayer(guid)) + if (caster->GetUInt64Value(PLAYER_FARSIGHT) == iter->getSource()->GetGUID()) + BuildPacket(caster); + } + } + } + void BuildPacket(Player* plr) + { + // Only send update once to a player + if (plr_list.find(plr->GetGUID()) == plr_list.end() && plr->HaveAtClient(&i_object)) + { + i_object.BuildFieldsUpdate(plr, i_updateDatas); + plr_list.insert(plr->GetGUID()); + } + } + + template<class SKIP> void Visit(GridRefManager<SKIP> &) {} +}; + +void WorldObject::BuildUpdate(UpdateDataMapType& data_map) +{ + CellPair p = Trinity::ComputeCellPair(GetPositionX(), GetPositionY()); + Cell cell(p); + cell.data.Part.reserved = ALL_DISTRICT; + cell.SetNoCreate(); + WorldObjectChangeAccumulator notifier(*this, data_map); + TypeContainerVisitor<WorldObjectChangeAccumulator, WorldTypeMapContainer > player_notifier(notifier); + Map& map = *GetMap(); + //we must build packets for all visible players + cell.Visit(p, player_notifier, map, *this, map.GetVisibilityDistance()); + + ClearUpdateMask(false); +} diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h new file mode 100644 index 00000000000..c5a412c3ab7 --- /dev/null +++ b/src/server/game/Entities/Object/Object.h @@ -0,0 +1,743 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef _OBJECT_H +#define _OBJECT_H + +#include "Common.h" +#include "UpdateFields.h" +#include "UpdateData.h" +#include "GameSystem/GridReference.h" +#include "ObjectDefines.h" +#include "GridDefines.h" +#include "Map.h" + +#include <set> +#include <string> + +#define CONTACT_DISTANCE 0.5f +#define INTERACTION_DISTANCE 5.0f +#define ATTACK_DISTANCE 5.0f +#define MAX_VISIBILITY_DISTANCE 333.0f // max distance for visible object show, limited in 333 yards +#define DEFAULT_VISIBILITY_DISTANCE 90.0f // default visible distance, 90 yards on continents +#define DEFAULT_VISIBILITY_INSTANCE 120.0f // default visible distance in instances, 120 yards +#define DEFAULT_VISIBILITY_BGARENAS 180.0f // default visible distance in BG/Arenas, 180 yards + +#define DEFAULT_WORLD_OBJECT_SIZE 0.388999998569489f // player size, also currently used (correctly?) for any non Unit world objects +#define DEFAULT_COMBAT_REACH 1.5f +#define MIN_MELEE_REACH 2.0f +#define NOMINAL_MELEE_RANGE 5.0f +#define MELEE_RANGE (NOMINAL_MELEE_RANGE - MIN_MELEE_REACH * 2) //center to center for players + +enum TypeMask +{ + TYPEMASK_OBJECT = 0x0001, + TYPEMASK_ITEM = 0x0002, + TYPEMASK_CONTAINER = 0x0006, // TYPEMASK_ITEM | 0x0004 + TYPEMASK_UNIT = 0x0008, //creature or player + TYPEMASK_PLAYER = 0x0010, + TYPEMASK_GAMEOBJECT = 0x0020, + TYPEMASK_DYNAMICOBJECT = 0x0040, + TYPEMASK_CORPSE = 0x0080, + TYPEMASK_SEER = TYPEMASK_UNIT | TYPEMASK_DYNAMICOBJECT +}; + +enum TypeID +{ + TYPEID_OBJECT = 0, + TYPEID_ITEM = 1, + TYPEID_CONTAINER = 2, + TYPEID_UNIT = 3, + TYPEID_PLAYER = 4, + TYPEID_GAMEOBJECT = 5, + TYPEID_DYNAMICOBJECT = 6, + TYPEID_CORPSE = 7 +}; + +#define NUM_CLIENT_OBJECT_TYPES 8 + +uint32 GuidHigh2TypeId(uint32 guid_hi); + +enum TempSummonType +{ + TEMPSUMMON_TIMED_OR_DEAD_DESPAWN = 1, // despawns after a specified time OR when the creature disappears + TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN = 2, // despawns after a specified time OR when the creature dies + TEMPSUMMON_TIMED_DESPAWN = 3, // despawns after a specified time + TEMPSUMMON_TIMED_DESPAWN_OUT_OF_COMBAT = 4, // despawns after a specified time after the creature is out of combat + TEMPSUMMON_CORPSE_DESPAWN = 5, // despawns instantly after death + TEMPSUMMON_CORPSE_TIMED_DESPAWN = 6, // despawns after a specified time after death + TEMPSUMMON_DEAD_DESPAWN = 7, // despawns when the creature disappears + TEMPSUMMON_MANUAL_DESPAWN = 8 // despawns when UnSummon() is called +}; + +enum PhaseMasks +{ + PHASEMASK_NORMAL = 0x00000001, + PHASEMASK_ANYWHERE = 0xFFFFFFFF +}; + +enum NotifyFlags +{ + NOTIFY_NONE = 0x00, + NOTIFY_AI_RELOCATION = 0x01, + NOTIFY_VISIBILITY_CHANGED = 0x02, + NOTIFY_ALL = 0xFF +}; + +class WorldPacket; +class UpdateData; +class ByteBuffer; +class WorldSession; +class Creature; +class Player; +class UpdateMask; +class InstanceData; +class GameObject; +class TempSummon; +class Vehicle; +class CreatureAI; +class ZoneScript; +class Unit; + +typedef UNORDERED_MAP<Player*, UpdateData> UpdateDataMapType; + +class Object +{ + public: + virtual ~Object (); + + const bool IsInWorld() const { return m_inWorld; } + virtual void AddToWorld() + { + if (m_inWorld) + return; + + assert(m_uint32Values); + + m_inWorld = true; + + // synchronize values mirror with values array (changes will send in updatecreate opcode any way + ClearUpdateMask(true); + } + virtual void RemoveFromWorld() + { + if (!m_inWorld) + return; + + m_inWorld = false; + + // if we remove from world then sending changes not required + ClearUpdateMask(true); + } + + const uint64& GetGUID() const { return GetUInt64Value(0); } + uint32 GetGUIDLow() const { return GUID_LOPART(GetUInt64Value(0)); } + uint32 GetGUIDMid() const { return GUID_ENPART(GetUInt64Value(0)); } + uint32 GetGUIDHigh() const { return GUID_HIPART(GetUInt64Value(0)); } + const ByteBuffer& GetPackGUID() const { return m_PackGUID; } + uint32 GetEntry() const { return GetUInt32Value(OBJECT_FIELD_ENTRY); } + void SetEntry(uint32 entry) { SetUInt32Value(OBJECT_FIELD_ENTRY, entry); } + + TypeID GetTypeId() const { return m_objectTypeId; } + bool isType(uint16 mask) const { return (mask & m_objectType); } + + virtual void BuildCreateUpdateBlockForPlayer(UpdateData *data, Player *target) const; + void SendUpdateToPlayer(Player* player); + + void BuildValuesUpdateBlockForPlayer(UpdateData *data, Player *target) const; + void BuildOutOfRangeUpdateBlock(UpdateData *data) const; + void BuildMovementUpdateBlock(UpdateData * data, uint32 flags = 0) const; + + virtual void DestroyForPlayer(Player *target, bool anim = false) const; + + const int32& GetInt32Value(uint16 index) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + return m_int32Values[ index ]; + } + + const uint32& GetUInt32Value(uint16 index) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + return m_uint32Values[ index ]; + } + + const uint64& GetUInt64Value(uint16 index) const + { + ASSERT(index + 1 < m_valuesCount || PrintIndexError(index , false)); + return *((uint64*)&(m_uint32Values[ index ])); + } + + const float& GetFloatValue(uint16 index) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + return m_floatValues[ index ]; + } + + uint8 GetByteValue(uint16 index, uint8 offset) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + ASSERT(offset < 4); + return *(((uint8*)&m_uint32Values[ index ])+offset); + } + + uint16 GetUInt16Value(uint16 index, uint8 offset) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + ASSERT(offset < 2); + return *(((uint16*)&m_uint32Values[ index ])+offset); + } + + void SetInt32Value(uint16 index, int32 value); + void SetUInt32Value(uint16 index, uint32 value); + void UpdateUInt32Value(uint16 index, uint32 value); + void SetUInt64Value(uint16 index, const uint64 &value); + void SetFloatValue(uint16 index, float value); + void SetByteValue(uint16 index, uint8 offset, uint8 value); + void SetUInt16Value(uint16 index, uint8 offset, uint16 value); + void SetInt16Value(uint16 index, uint8 offset, int16 value) { SetUInt16Value(index,offset,(uint16)value); } + void SetStatFloatValue(uint16 index, float value); + void SetStatInt32Value(uint16 index, int32 value); + + bool AddUInt64Value(uint16 index, const uint64 &value); + bool RemoveUInt64Value(uint16 index, const uint64 &value); + + void ApplyModUInt32Value(uint16 index, int32 val, bool apply); + void ApplyModInt32Value(uint16 index, int32 val, bool apply); + void ApplyModUInt64Value(uint16 index, int32 val, bool apply); + void ApplyModPositiveFloatValue(uint16 index, float val, bool apply); + void ApplyModSignedFloatValue(uint16 index, float val, bool apply); + + void ApplyPercentModFloatValue(uint16 index, float val, bool apply) + { + val = val != -100.0f ? val : -99.9f ; + SetFloatValue(index, GetFloatValue(index) * (apply?(100.0f+val)/100.0f : 100.0f / (100.0f+val))); + } + + void SetFlag(uint16 index, uint32 newFlag); + void RemoveFlag(uint16 index, uint32 oldFlag); + + void ToggleFlag(uint16 index, uint32 flag) + { + if (HasFlag(index, flag)) + RemoveFlag(index, flag); + else + SetFlag(index, flag); + } + + bool HasFlag(uint16 index, uint32 flag) const + { + if (index >= m_valuesCount && !PrintIndexError(index , false)) return false; + return (m_uint32Values[ index ] & flag) != 0; + } + + void SetByteFlag(uint16 index, uint8 offset, uint8 newFlag); + void RemoveByteFlag(uint16 index, uint8 offset, uint8 newFlag); + + void ToggleFlag(uint16 index, uint8 offset, uint8 flag) + { + if (HasByteFlag(index, offset, flag)) + RemoveByteFlag(index, offset, flag); + else + SetByteFlag(index, offset, flag); + } + + bool HasByteFlag(uint16 index, uint8 offset, uint8 flag) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + ASSERT(offset < 4); + return (((uint8*)&m_uint32Values[index])[offset] & flag) != 0; + } + + void ApplyModFlag(uint16 index, uint32 flag, bool apply) + { + if (apply) SetFlag(index,flag); else RemoveFlag(index,flag); + } + + void SetFlag64(uint16 index, uint64 newFlag) + { + uint64 oldval = GetUInt64Value(index); + uint64 newval = oldval | newFlag; + SetUInt64Value(index,newval); + } + + void RemoveFlag64(uint16 index, uint64 oldFlag) + { + uint64 oldval = GetUInt64Value(index); + uint64 newval = oldval & ~oldFlag; + SetUInt64Value(index,newval); + } + + void ToggleFlag64(uint16 index, uint64 flag) + { + if (HasFlag64(index, flag)) + RemoveFlag64(index, flag); + else + SetFlag64(index, flag); + } + + bool HasFlag64(uint16 index, uint64 flag) const + { + ASSERT(index < m_valuesCount || PrintIndexError(index , false)); + return (GetUInt64Value(index) & flag) != 0; + } + + void ApplyModFlag64(uint16 index, uint64 flag, bool apply) + { + if (apply) SetFlag64(index,flag); else RemoveFlag64(index,flag); + } + + void ClearUpdateMask(bool remove); + + bool LoadValues(const char* data); + + uint16 GetValuesCount() const { return m_valuesCount; } + + virtual bool hasQuest(uint32 /* quest_id */) const { return false; } + virtual bool hasInvolvedQuest(uint32 /* quest_id */) const { return false; } + virtual void BuildUpdate(UpdateDataMapType&) {} + void BuildFieldsUpdate(Player *, UpdateDataMapType &) const; + + // FG: some hacky helpers + void ForceValuesUpdateAtIndex(uint32); + + Player* ToPlayer(){ if (GetTypeId() == TYPEID_PLAYER) return reinterpret_cast<Player*>(this); else return NULL; } + const Player* ToPlayer() const { if (GetTypeId() == TYPEID_PLAYER) return (const Player*)((Player*)this); else return NULL; } + Creature* ToCreature(){ if (GetTypeId() == TYPEID_UNIT) return reinterpret_cast<Creature*>(this); else return NULL; } + const Creature* ToCreature() const {if (GetTypeId() == TYPEID_UNIT) return (const Creature*)((Creature*)this); else return NULL; } + + protected: + + Object (); + + void _InitValues(); + void _Create (uint32 guidlow, uint32 entry, HighGuid guidhigh); + + virtual void _SetUpdateBits(UpdateMask *updateMask, Player *target) const; + + virtual void _SetCreateBits(UpdateMask *updateMask, Player *target) const; + void _BuildMovementUpdate(ByteBuffer * data, uint16 flags) const; + void _BuildValuesUpdate(uint8 updatetype, ByteBuffer *data, UpdateMask *updateMask, Player *target) const; + + uint16 m_objectType; + + TypeID m_objectTypeId; + uint16 m_updateFlag; + + union + { + int32 *m_int32Values; + uint32 *m_uint32Values; + float *m_floatValues; + }; + + uint32 *m_uint32Values_mirror; + + uint16 m_valuesCount; + + bool m_objectUpdated; + + private: + bool m_inWorld; + + ByteBuffer m_PackGUID; + + // for output helpfull error messages from asserts + bool PrintIndexError(uint32 index, bool set) const; + Object(const Object&); // prevent generation copy constructor + Object& operator=(Object const&); // prevent generation assigment operator +}; + +struct Position +{ + float m_positionX; + float m_positionY; + float m_positionZ; + float m_orientation; + + void Relocate(float x, float y) + { m_positionX = x; m_positionY = y;} + void Relocate(float x, float y, float z) + { m_positionX = x; m_positionY = y; m_positionZ = z; } + void Relocate(float x, float y, float z, float orientation) + { m_positionX = x; m_positionY = y; m_positionZ = z; m_orientation = orientation; } + void Relocate(const Position &pos) + { m_positionX = pos.m_positionX; m_positionY = pos.m_positionY; m_positionZ = pos.m_positionZ; m_orientation = pos.m_orientation; } + void Relocate(const Position *pos) + { m_positionX = pos->m_positionX; m_positionY = pos->m_positionY; m_positionZ = pos->m_positionZ; m_orientation = pos->m_orientation; } + void SetOrientation(float orientation) + { m_orientation = orientation; } + + float GetPositionX() const { return m_positionX; } + float GetPositionY() const { return m_positionY; } + float GetPositionZ() const { return m_positionZ; } + float GetOrientation() const { return m_orientation; } + + void GetPosition(float &x, float &y) const + { x = m_positionX; y = m_positionY; } + void GetPosition(float &x, float &y, float &z) const + { x = m_positionX; y = m_positionY; z = m_positionZ; } + void GetPosition(float &x, float &y, float &z, float &o) const + { x = m_positionX; y = m_positionY; z = m_positionZ; o = m_orientation; } + void GetPosition(Position *pos) const + { + if (pos) + pos->Relocate(m_positionX, m_positionY, m_positionZ, m_orientation); + } + + bool IsPositionValid() const; + + float GetExactDist2dSq(float x, float y) const + { float dx = m_positionX - x; float dy = m_positionY - y; return dx*dx + dy*dy; } + float GetExactDist2d(const float x, const float y) const + { return sqrt(GetExactDist2dSq(x, y)); } + float GetExactDist2dSq(const Position *pos) const + { float dx = m_positionX - pos->m_positionX; float dy = m_positionY - pos->m_positionY; return dx*dx + dy*dy; } + float GetExactDist2d(const Position *pos) const + { return sqrt(GetExactDist2dSq(pos)); } + float GetExactDistSq(float x, float y, float z) const + { float dz = m_positionZ - z; return GetExactDist2dSq(x, y) + dz*dz; } + float GetExactDist(float x, float y, float z) const + { return sqrt(GetExactDistSq(x, y, z)); } + float GetExactDistSq(const Position *pos) const + { float dx = m_positionX - pos->m_positionX; float dy = m_positionY - pos->m_positionY; float dz = m_positionZ - pos->m_positionZ; return dx*dx + dy*dy + dz*dz; } + float GetExactDist(const Position *pos) const + { return sqrt(GetExactDistSq(pos)); } + + float GetAngle(const Position *pos) const; + float GetAngle(float x, float y) const; + float GetRelativeAngle(const Position *pos) const + { return GetAngle(pos) - m_orientation; } + float GetRelativeAngle(float x, float y) const { return GetAngle(x, y) - m_orientation; } + void GetSinCos(float x, float y, float &vsin, float &vcos) const; + + bool IsInDist2d(float x, float y, float dist) const + { return GetExactDist2dSq(x, y) < dist * dist; } + bool IsInDist2d(const Position *pos, float dist) const + { return GetExactDist2dSq(pos) < dist * dist; } + bool IsInDist(float x, float y, float z, float dist) const + { return GetExactDistSq(x, y, z) < dist * dist; } + bool IsInDist(const Position *pos, float dist) const + { return GetExactDistSq(pos) < dist * dist; } + bool HasInArc(float arcangle, const Position *pos) const; + bool HasInLine(const Unit *target, float distance, float width) const; +}; + +#define MAPID_INVALID 0xFFFFFFFF + +class WorldLocation : public Position +{ + public: + explicit WorldLocation(uint32 _mapid = MAPID_INVALID, float _x = 0, float _y = 0, float _z = 0, float _o = 0) + : m_mapId(_mapid) { Relocate(_x, _y, _z, _o); } + WorldLocation(const WorldLocation &loc) { WorldRelocate(loc); } + + void WorldRelocate(const WorldLocation &loc) + { m_mapId = loc.GetMapId(); Relocate(loc); } + uint32 GetMapId() const { return m_mapId; } + + uint32 m_mapId; +}; + +template<class T> +class GridObject +{ + public: + GridReference<T> &GetGridRef() { return m_gridRef; } + protected: + GridReference<T> m_gridRef; +}; + +class WorldObject : public Object, public WorldLocation +{ + public: + virtual ~WorldObject(); + + virtual void Update (uint32 /*time_diff*/) { } + + void _Create(uint32 guidlow, HighGuid guidhigh, uint32 phaseMask); + + void GetNearPoint2D(float &x, float &y, float distance, float absAngle) const; + void GetNearPoint(WorldObject const* searcher, float &x, float &y, float &z, float searcher_size, float distance2d,float absAngle) const; + void GetClosePoint(float &x, float &y, float &z, float size, float distance2d = 0, float angle = 0) const + { + // angle calculated from current orientation + GetNearPoint(NULL,x,y,z,size,distance2d,GetOrientation() + angle); + } + void MovePosition(Position &pos, float dist, float angle); + void GetNearPosition(Position &pos, float dist, float angle) + { + GetPosition(&pos); + MovePosition(pos, dist, angle); + } + void GetRandomNearPosition(Position &pos, float radius) + { + GetPosition(&pos); + MovePosition(pos, radius * rand_norm(), rand_norm() * 2 * M_PI); + } + + void GetContactPoint(const WorldObject* obj, float &x, float &y, float &z, float distance2d = CONTACT_DISTANCE) const + { + // angle to face `obj` to `this` using distance includes size of `obj` + GetNearPoint(obj,x,y,z,obj->GetObjectSize(),distance2d,GetAngle(obj)); + } + + float GetObjectSize() const + { + return (m_valuesCount > UNIT_FIELD_COMBATREACH) ? m_floatValues[UNIT_FIELD_COMBATREACH] : DEFAULT_WORLD_OBJECT_SIZE; + } + void UpdateGroundPositionZ(float x, float y, float &z) const; + + void GetRandomPoint(const Position &srcPos, float distance, float &rand_x, float &rand_y, float &rand_z) const; + void GetRandomPoint(const Position &srcPos, float distance, Position &pos) const + { + float x, y, z; + GetRandomPoint(srcPos, distance, x, y, z); + pos.Relocate(x, y, z, GetOrientation()); + } + + uint32 GetInstanceId() const { return m_InstanceId; } + + virtual void SetPhaseMask(uint32 newPhaseMask, bool update); + uint32 GetPhaseMask() const { return m_phaseMask; } + bool InSamePhase(WorldObject const* obj) const { return InSamePhase(obj->GetPhaseMask()); } + bool InSamePhase(uint32 phasemask) const { return (GetPhaseMask() & phasemask); } + + uint32 GetZoneId() const; + uint32 GetAreaId() const; + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid) const; + + InstanceData* GetInstanceData(); + + const char* GetName() const { return m_name.c_str(); } + void SetName(const std::string& newname) { m_name=newname; } + + virtual const char* GetNameForLocaleIdx(int32 /*locale_idx*/) const { return GetName(); } + + float GetDistance(const WorldObject *obj) const + { + float d = GetExactDist(obj) - GetObjectSize() - obj->GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + float GetDistance(const Position &pos) const + { + float d = GetExactDist(&pos) - GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + float GetDistance(float x, float y, float z) const + { + float d = GetExactDist(x, y, z) - GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + float GetDistance2d(const WorldObject* obj) const + { + float d = GetExactDist2d(obj) - GetObjectSize() - obj->GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + float GetDistance2d(float x, float y) const + { + float d = GetExactDist2d(x, y) - GetObjectSize(); + return d > 0.0f ? d : 0.0f; + } + float GetDistanceZ(const WorldObject* obj) const; + + bool IsInMap(const WorldObject* obj) const + { + if (obj) + return IsInWorld() && obj->IsInWorld() && (GetMap() == obj->GetMap()) && InSamePhase(obj); + else + return false; + } + bool IsWithinDist3d(float x, float y, float z, float dist) const + { return IsInDist(x, y, z, dist + GetObjectSize()); } + bool IsWithinDist3d(const Position *pos, float dist) const + { return IsInDist(pos, dist + GetObjectSize()); } + bool IsWithinDist2d(float x, float y, float dist) const + { return IsInDist2d(x, y, dist + GetObjectSize()); } + bool IsWithinDist2d(const Position *pos, float dist) const + { return IsInDist2d(pos, dist + GetObjectSize()); } + bool _IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D) const; + // use only if you will sure about placing both object at same map + bool IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D = true) const + { + return obj && _IsWithinDist(obj,dist2compare,is3D); + } + bool IsWithinDistInMap(WorldObject const* obj, float dist2compare, bool is3D = true) const + { + return obj && IsInMap(obj) && _IsWithinDist(obj,dist2compare,is3D); + } + bool IsWithinLOS(float x, float y, float z) const; + bool IsWithinLOSInMap(const WorldObject* obj) const; + bool GetDistanceOrder(WorldObject const* obj1, WorldObject const* obj2, bool is3D = true) const; + bool IsInRange(WorldObject const* obj, float minRange, float maxRange, bool is3D = true) const; + bool IsInRange2d(float x, float y, float minRange, float maxRange) const; + bool IsInRange3d(float x, float y, float z, float minRange, float maxRange) const; + bool isInFront(WorldObject const* target,float distance, float arc = M_PI) const; + bool isInBack(WorldObject const* target, float distance, float arc = M_PI) const; + + bool IsInBetween(const WorldObject *obj1, const WorldObject *obj2, float size = 0) const; + + virtual void CleanupsBeforeDelete(bool finalCleanup = true); // used in destructor or explicitly before mass creature delete to remove cross-references to already deleted units + + virtual void SendMessageToSet(WorldPacket *data, bool self); + virtual void SendMessageToSetInRange(WorldPacket *data, float dist, bool self); + virtual void SendMessageToSet(WorldPacket *data, Player const* skipped_rcvr); + + void MonsterSay(const char* text, uint32 language, uint64 TargetGuid); + void MonsterYell(const char* text, uint32 language, uint64 TargetGuid); + void MonsterTextEmote(const char* text, uint64 TargetGuid, bool IsBossEmote = false); + void MonsterWhisper(const char* text, uint64 receiver, bool IsBossWhisper = false); + void MonsterSay(int32 textId, uint32 language, uint64 TargetGuid); + void MonsterYell(int32 textId, uint32 language, uint64 TargetGuid); + void MonsterTextEmote(int32 textId, uint64 TargetGuid, bool IsBossEmote = false); + void MonsterWhisper(int32 textId, uint64 receiver, bool IsBossWhisper = false); + void MonsterYellToZone(int32 textId, uint32 language, uint64 TargetGuid); + void BuildMonsterChat(WorldPacket *data, uint8 msgtype, char const* text, uint32 language, char const* name, uint64 TargetGuid) const; + + void PlayDistanceSound(uint32 sound_id, Player* target = NULL); + void PlayDirectSound(uint32 sound_id, Player* target = NULL); + + void SendObjectDeSpawnAnim(uint64 guid); + + virtual void SaveRespawnTime() {} + void AddObjectToRemoveList(); + + // main visibility check function in normal case (ignore grey zone distance check) + bool isVisibleFor(Player const* u) const { return isVisibleForInState(u,false); } + + // low level function for visibility change code, must be define in all main world object subclasses + virtual bool isVisibleForInState(Player const* u, bool inVisibleList) const = 0; + + // Low Level Packets + void SendPlaySound(uint32 Sound, bool OnlySelf); + + virtual void SetMap(Map * map); + virtual void ResetMap(); + Map * GetMap() const { ASSERT(m_currMap); return m_currMap; } + Map * FindMap() const { return m_currMap; } + //used to check all object's GetMap() calls when object is not in world! + + //this function should be removed in nearest time... + Map const* GetBaseMap() const; + + void SetZoneScript(); + ZoneScript * GetZoneScript() const { return m_zoneScript; } + + TempSummon* SummonCreature(uint32 id, const Position &pos, TempSummonType spwtype = TEMPSUMMON_MANUAL_DESPAWN, uint32 despwtime = 0, uint32 vehId = 0) const; + TempSummon* SummonCreature(uint32 id, float x, float y, float z, float ang = 0, TempSummonType spwtype = TEMPSUMMON_MANUAL_DESPAWN, uint32 despwtime = 0) + { + if (!x && !y && !z) + { + GetClosePoint(x, y, z, GetObjectSize()); + ang = GetOrientation(); + } + Position pos = {x, y, z, ang}; + return SummonCreature(id, pos, spwtype, despwtime, 0); + } + GameObject* SummonGameObject(uint32 entry, float x, float y, float z, float ang, float rotation0, float rotation1, float rotation2, float rotation3, uint32 respawnTime); + Creature* SummonTrigger(float x, float y, float z, float ang, uint32 dur, CreatureAI* (*GetAI)(Creature*) = NULL); + + Creature* FindNearestCreature(uint32 entry, float range, bool alive = true); + GameObject* FindNearestGameObject(uint32 entry, float range); + + void GetGameObjectListWithEntryInGrid(std::list<GameObject*>& lList, uint32 uiEntry, float fMaxSearchRange); + void GetCreatureListWithEntryInGrid(std::list<Creature*>& lList, uint32 uiEntry, float fMaxSearchRange); + + void DestroyForNearbyPlayers(); + virtual void UpdateObjectVisibility(bool forced = true); + void BuildUpdate(UpdateDataMapType&); + + //relocation and visibility system functions + void AddToNotify(uint16 f) { m_notifyflags |= f;} + bool isNeedNotify(uint16 f) const { return m_notifyflags & f;} + uint16 GetNotifyFlags() const { return m_notifyflags; } + bool NotifyExecuted(uint16 f) const { return m_executed_notifies & f;} + void SetNotified(uint16 f) { m_executed_notifies |= f;} + void ResetAllNotifies() { m_notifyflags = 0; m_executed_notifies = 0; } + + bool isActiveObject() const { return m_isActive; } + void setActive(bool isActiveObject); + void SetWorldObject(bool apply); + template<class NOTIFIER> void VisitNearbyObject(const float &radius, NOTIFIER ¬ifier) const { GetMap()->VisitAll(GetPositionX(), GetPositionY(), radius, notifier); } + template<class NOTIFIER> void VisitNearbyGridObject(const float &radius, NOTIFIER ¬ifier) const { GetMap()->VisitGrid(GetPositionX(), GetPositionY(), radius, notifier); } + template<class NOTIFIER> void VisitNearbyWorldObject(const float &radius, NOTIFIER ¬ifier) const { GetMap()->VisitWorld(GetPositionX(), GetPositionY(), radius, notifier); } + +#ifdef MAP_BASED_RAND_GEN + int32 irand(int32 min, int32 max) const { return int32 (GetMap()->mtRand.randInt(max - min)) + min; } + uint32 urand(uint32 min, uint32 max) const { return GetMap()->mtRand.randInt(max - min) + min;} + int32 rand32() const { return GetMap()->mtRand.randInt();} + double rand_norm() const { return GetMap()->mtRand.randExc();} + double rand_chance() const { return GetMap()->mtRand.randExc(100.0);} +#endif + + bool m_isWorldObject; + uint32 LastUsedScriptID; + protected: + explicit WorldObject(); + std::string m_name; + bool m_isActive; + ZoneScript *m_zoneScript; + + //these functions are used mostly for Relocate() and Corpse/Player specific stuff... + //use them ONLY in LoadFromDB()/Create() funcs and nowhere else! + //mapId/instanceId should be set in SetMap() function! + void SetLocationMapId(uint32 _mapId) { m_mapId = _mapId; } + void SetLocationInstanceId(uint32 _instanceId) { m_InstanceId = _instanceId; } + + private: + Map * m_currMap; //current object's Map location + + //uint32 m_mapId; // object at map with map_id + uint32 m_InstanceId; // in map copy with instance id + uint32 m_phaseMask; // in area phase state + + uint16 m_notifyflags; + uint16 m_executed_notifies; +}; + +namespace Trinity +{ + template<class T> + void RandomResizeList(std::list<T> &_list, uint32 _size) + { + while (_list.size() > _size) + { + typename std::list<T>::iterator itr = _list.begin(); + advance(itr, urand(0, _list.size() - 1)); + _list.erase(itr); + } + } + + // Binary predicate to sort WorldObjects based on the distance to a reference WorldObject + class ObjectDistanceOrderPred + { + public: + ObjectDistanceOrderPred(const WorldObject *pRefObj, bool ascending = true) : m_refObj(pRefObj), m_ascending(ascending) {} + bool operator()(const WorldObject *pLeft, const WorldObject *pRight) const + { + return m_ascending ? m_refObj->GetDistanceOrder(pLeft, pRight) : !m_refObj->GetDistanceOrder(pLeft, pRight); + } + private: + const WorldObject *m_refObj; + const bool m_ascending; + }; +} + +#endif diff --git a/src/server/game/Entities/Object/ObjectAccessor.cpp b/src/server/game/Entities/Object/ObjectAccessor.cpp new file mode 100644 index 00000000000..cf5bc728c6e --- /dev/null +++ b/src/server/game/Entities/Object/ObjectAccessor.cpp @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 "ObjectAccessor.h" +#include "ObjectMgr.h" +#include "Policies/SingletonImp.h" +#include "Player.h" +#include "Creature.h" +#include "GameObject.h" +#include "DynamicObject.h" +#include "Vehicle.h" +#include "WorldPacket.h" +#include "Item.h" +#include "Corpse.h" +#include "GridNotifiers.h" +#include "MapManager.h" +#include "Map.h" +#include "CellImpl.h" +#include "GridNotifiersImpl.h" +#include "Opcodes.h" +#include "ObjectDefines.h" +#include "MapInstanced.h" +#include "World.h" + +#include <cmath> + +#define CLASS_LOCK Trinity::ClassLevelLockable<ObjectAccessor, ACE_Thread_Mutex> +INSTANTIATE_SINGLETON_2(ObjectAccessor, CLASS_LOCK); +INSTANTIATE_CLASS_MUTEX(ObjectAccessor, ACE_Thread_Mutex); + +ObjectAccessor::ObjectAccessor() +{ +} + +ObjectAccessor::~ObjectAccessor() +{ + for (Player2CorpsesMapType::const_iterator itr = i_player2corpse.begin(); itr != i_player2corpse.end(); ++itr) + { + itr->second->RemoveFromWorld(); + delete itr->second; + } +} + +Creature* ObjectAccessor::GetCreatureOrPetOrVehicle(WorldObject const& u, uint64 guid) +{ + if (IS_PLAYER_GUID(guid)) + return NULL; + + if (IS_PET_GUID(guid)) + return GetPet(guid); + + return u.IsInWorld() ? u.GetMap()->GetCreature(guid) : NULL; +} + +Corpse* ObjectAccessor::GetCorpse(WorldObject const& u, uint64 guid) +{ + Corpse* ret = GetObjectInWorld(guid, (Corpse*)NULL); + + if (!ret) + return NULL; + + if (ret->GetMapId() != u.GetMapId()) + return NULL; + + if (ret->GetInstanceId() != u.GetInstanceId()) + return NULL; + + return ret; +} + +WorldObject* ObjectAccessor::GetWorldObject(WorldObject const& p, uint64 guid) +{ + switch (GUID_HIPART(guid)) + { + case HIGHGUID_PLAYER: return FindPlayer(guid); + case HIGHGUID_GAMEOBJECT: return p.GetMap()->GetGameObject(guid); + case HIGHGUID_VEHICLE: + case HIGHGUID_UNIT: return p.GetMap()->GetCreature(guid); + case HIGHGUID_PET: return GetPet(guid); + case HIGHGUID_DYNAMICOBJECT: return p.GetMap()->GetDynamicObject(guid); + case HIGHGUID_TRANSPORT: return NULL; + case HIGHGUID_CORPSE: return GetCorpse(p,guid); + case HIGHGUID_MO_TRANSPORT: return NULL; + default: return NULL; + } +} + +Object* ObjectAccessor::GetObjectByTypeMask(WorldObject const& p, uint64 guid, uint32 typemask) +{ + switch (GUID_HIPART(guid)) + { + case HIGHGUID_ITEM: + if (typemask & TYPEMASK_ITEM && p.GetTypeId() == TYPEID_PLAYER) + return ((Player const&)p).GetItemByGuid(guid); + break; + case HIGHGUID_PLAYER: + if (typemask & TYPEMASK_PLAYER) + return FindPlayer(guid); + break; + case HIGHGUID_GAMEOBJECT: + if (typemask & TYPEMASK_GAMEOBJECT) + return p.GetMap()->GetGameObject(guid); + break; + case HIGHGUID_UNIT: + case HIGHGUID_VEHICLE: + if (typemask & TYPEMASK_UNIT) + return p.GetMap()->GetCreature(guid); + break; + case HIGHGUID_PET: + if (typemask & TYPEMASK_UNIT) + return GetPet(guid); + break; + case HIGHGUID_DYNAMICOBJECT: + if (typemask & TYPEMASK_DYNAMICOBJECT) + return p.GetMap()->GetDynamicObject(guid); + break; + case HIGHGUID_TRANSPORT: + case HIGHGUID_CORPSE: + case HIGHGUID_MO_TRANSPORT: + break; + } + + return NULL; +} + +Player* ObjectAccessor::FindPlayer(uint64 guid) +{ + Player* plr = GetObjectInWorld(guid, (Player*)NULL); + if (!plr || !plr->IsInWorld()) + return NULL; + + return plr; +} + +Player* ObjectAccessor::FindPlayerByName(const char* name) +{ + Guard guard(*HashMapHolder<Player>::GetLock()); + HashMapHolder<Player>::MapType& m = HashMapHolder<Player>::GetContainer(); + for (HashMapHolder<Player>::MapType::iterator iter = m.begin(); iter != m.end(); ++iter) + if (iter->second->IsInWorld() && strcmp(name, iter->second->GetName()) == 0) + return iter->second; + + return NULL; +} + +void ObjectAccessor::SaveAllPlayers() +{ + Guard guard(*HashMapHolder<Player>::GetLock()); + HashMapHolder<Player>::MapType& m = HashMapHolder<Player>::GetContainer(); + for (HashMapHolder<Player>::MapType::iterator itr = m.begin(); itr != m.end(); ++itr) + itr->second->SaveToDB(); +} + +Pet* ObjectAccessor::GetPet(uint64 guid) +{ + return GetObjectInWorld(guid, (Pet*)NULL); +} + +Corpse* ObjectAccessor::GetCorpseForPlayerGUID(uint64 guid) +{ + Guard guard(i_corpseGuard); + + Player2CorpsesMapType::iterator iter = i_player2corpse.find(guid); + if (iter == i_player2corpse.end()) + return NULL; + + assert(iter->second->GetType() != CORPSE_BONES); + + return iter->second; +} + +void ObjectAccessor::RemoveCorpse(Corpse* corpse) +{ + assert(corpse && corpse->GetType() != CORPSE_BONES); + + if (corpse->FindMap()) + corpse->FindMap()->Remove(corpse, false); + else + corpse->RemoveFromWorld(); + + // Critical section + { + Guard guard(i_corpseGuard); + + Player2CorpsesMapType::iterator iter = i_player2corpse.find(corpse->GetOwnerGUID()); + if (iter == i_player2corpse.end()) // TODO: Fix this + return; + + // build mapid*cellid -> guid_set map + CellPair cell_pair = Trinity::ComputeCellPair(corpse->GetPositionX(), corpse->GetPositionY()); + uint32 cell_id = (cell_pair.y_coord * TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord; + + objmgr.DeleteCorpseCellData(corpse->GetMapId(), cell_id, corpse->GetOwnerGUID()); + + i_player2corpse.erase(iter); + } +} + +void ObjectAccessor::AddCorpse(Corpse* corpse) +{ + assert(corpse && corpse->GetType() != CORPSE_BONES); + + // Critical section + { + Guard guard(i_corpseGuard); + + assert(i_player2corpse.find(corpse->GetOwnerGUID()) == i_player2corpse.end()); + i_player2corpse[corpse->GetOwnerGUID()] = corpse; + + // build mapid*cellid -> guid_set map + CellPair cell_pair = Trinity::ComputeCellPair(corpse->GetPositionX(), corpse->GetPositionY()); + uint32 cell_id = (cell_pair.y_coord * TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord; + + objmgr.AddCorpseCellData(corpse->GetMapId(), cell_id, corpse->GetOwnerGUID(), corpse->GetInstanceId()); + } +} + +void ObjectAccessor::AddCorpsesToGrid(GridPair const& gridpair, GridType& grid, Map* map) +{ + Guard guard(i_corpseGuard); + + for (Player2CorpsesMapType::iterator iter = i_player2corpse.begin(); iter != i_player2corpse.end(); ++iter) + { + if (iter->second->GetGrid() == gridpair) + { + // verify, if the corpse in our instance (add only corpses which are) + if (map->Instanceable()) + { + if (iter->second->GetInstanceId() == map->GetInstanceId()) + grid.AddWorldObject(iter->second); + } + else + grid.AddWorldObject(iter->second); + } + } +} + +Corpse* ObjectAccessor::ConvertCorpseForPlayer(uint64 player_guid, bool /*insignia*/) +{ + Corpse* corpse = GetCorpseForPlayerGUID(player_guid); + if (!corpse) + { + //in fact this function is called from several places + //even when player doesn't have a corpse, not an error + // TODO: really, now... + //sLog.outError("Try remove corpse that not in map for GUID %ul", player_guid); + return NULL; + } + + DEBUG_LOG("Deleting Corpse and spawned bones."); + + //Map* map = corpse->FindMap(); + + // remove corpse from player_guid -> corpse map + RemoveCorpse(corpse); + + // done in removecorpse + // remove resurrectable corpse from grid object registry (loaded state checked into call) + // do not load the map if it's not loaded + //Map *map = MapManager::Instance().FindMap(corpse->GetMapId(), corpse->GetInstanceId()); + //if (map) + // map->Remove(corpse, false); + + // remove corpse from DB + corpse->DeleteFromDB(); + + // we don't want bones to save some cpu.. :) + delete corpse; + return NULL; + + /* + Corpse* bones = NULL; + // create the bones only if the map and the grid is loaded at the corpse's location + // ignore bones creating option in case insignia + if (map && (insignia || + (map->IsBattleGroundOrArena() ? sWorld.getConfig(CONFIG_DEATH_BONES_BG_OR_ARENA) : sWorld.getConfig(CONFIG_DEATH_BONES_WORLD))) && + !map->IsRemovalGrid(corpse->GetPositionX(), corpse->GetPositionY())) + { + // Create bones, don't change Corpse + bones = new Corpse; + bones->Create(corpse->GetGUIDLow(), map); + + for (int i = 3; i < CORPSE_END; ++i) // don't overwrite guid and object type + bones->SetUInt32Value(i, corpse->GetUInt32Value(i)); + + bones->SetGrid(corpse->GetGrid()); + // bones->m_time = m_time; // don't overwrite time + // bones->m_inWorld = m_inWorld; // don't overwrite in-world state + // bones->m_type = m_type; // don't overwrite type + bones->Relocate(corpse->GetPositionX(), corpse->GetPositionY(), corpse->GetPositionZ(), corpse->GetOrientation()); + bones->SetPhaseMask(corpse->GetPhaseMask(), false); + + bones->SetUInt32Value(CORPSE_FIELD_FLAGS, CORPSE_FLAG_UNK2 | CORPSE_FLAG_BONES); + bones->SetUInt64Value(CORPSE_FIELD_OWNER, 0); + + for (int i = 0; i < EQUIPMENT_SLOT_END; ++i) + { + if (corpse->GetUInt32Value(CORPSE_FIELD_ITEM + i)) + bones->SetUInt32Value(CORPSE_FIELD_ITEM + i, 0); + } + + // add bones in grid store if grid loaded where corpse placed + map->Add(bones); + } + + // all references to the corpse should be removed at this point + delete corpse; + + return bones; + */ +} + +void ObjectAccessor::Update(uint32 /*diff*/) +{ + UpdateDataMapType update_players; + + // Critical section + { + Guard guard(i_updateGuard); + + while (!i_objects.empty()) + { + Object* obj = *i_objects.begin(); + assert(obj && obj->IsInWorld()); + i_objects.erase(i_objects.begin()); + obj->BuildUpdate(update_players); + } + } + + WorldPacket packet; // here we allocate a std::vector with a size of 0x10000 + for (UpdateDataMapType::iterator iter = update_players.begin(); iter != update_players.end(); ++iter) + { + iter->second.BuildPacket(&packet); + iter->first->GetSession()->SendPacket(&packet); + packet.clear(); // clean the string + } +} + +/// Define the static members of HashMapHolder + +template <class T> UNORDERED_MAP< uint64, T* > HashMapHolder<T>::m_objectMap; +template <class T> ACE_Thread_Mutex HashMapHolder<T>::i_lock; + +/// Global definitions for the hashmap storage + +template class HashMapHolder<Player>; +template class HashMapHolder<Pet>; +template class HashMapHolder<GameObject>; +template class HashMapHolder<DynamicObject>; +template class HashMapHolder<Creature>; +template class HashMapHolder<Corpse>; + +template Player* ObjectAccessor::GetObjectInWorld<Player>(uint32 mapid, float x, float y, uint64 guid, Player* /*fake*/); +template Pet* ObjectAccessor::GetObjectInWorld<Pet>(uint32 mapid, float x, float y, uint64 guid, Pet* /*fake*/); +template Creature* ObjectAccessor::GetObjectInWorld<Creature>(uint32 mapid, float x, float y, uint64 guid, Creature* /*fake*/); +template Corpse* ObjectAccessor::GetObjectInWorld<Corpse>(uint32 mapid, float x, float y, uint64 guid, Corpse* /*fake*/); +template GameObject* ObjectAccessor::GetObjectInWorld<GameObject>(uint32 mapid, float x, float y, uint64 guid, GameObject* /*fake*/); +template DynamicObject* ObjectAccessor::GetObjectInWorld<DynamicObject>(uint32 mapid, float x, float y, uint64 guid, DynamicObject* /*fake*/); diff --git a/src/server/game/Entities/Object/ObjectAccessor.h b/src/server/game/Entities/Object/ObjectAccessor.h new file mode 100644 index 00000000000..8e64eb48fa0 --- /dev/null +++ b/src/server/game/Entities/Object/ObjectAccessor.h @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef TRINITY_OBJECTACCESSOR_H +#define TRINITY_OBJECTACCESSOR_H + +#include "Platform/Define.h" +#include "Policies/Singleton.h" +#include <ace/Thread_Mutex.h> +#include "Utilities/UnorderedMap.h" +#include "Policies/ThreadingModel.h" + +#include "UpdateData.h" + +#include "GridDefines.h" +#include "Object.h" +#include "Player.h" + +#include <set> + +class Creature; +class Corpse; +class Unit; +class GameObject; +class DynamicObject; +class WorldObject; +class Map; + +template <class T> +class HashMapHolder +{ + public: + + typedef UNORDERED_MAP<uint64, T*> MapType; + typedef ACE_Thread_Mutex LockType; + typedef Trinity::GeneralLock<LockType> Guard; + + static void Insert(T* o) + { + Guard guard(i_lock); + m_objectMap[o->GetGUID()] = o; + } + + static void Remove(T* o) + { + Guard guard(i_lock); + m_objectMap.erase(o->GetGUID()); + } + + static T* Find(uint64 guid) + { + Guard guard(i_lock); + typename MapType::iterator itr = m_objectMap.find(guid); + return (itr != m_objectMap.end()) ? itr->second : NULL; + } + + static MapType& GetContainer() { return m_objectMap; } + + static LockType* GetLock() { return &i_lock; } + + private: + + //Non instanceable only static + HashMapHolder() {} + + static LockType i_lock; + static MapType m_objectMap; +}; + +class ObjectAccessor : public Trinity::Singleton<ObjectAccessor, Trinity::ClassLevelLockable<ObjectAccessor, ACE_Thread_Mutex> > +{ + friend class Trinity::OperatorNew<ObjectAccessor>; + ObjectAccessor(); + ~ObjectAccessor(); + ObjectAccessor(const ObjectAccessor&); + ObjectAccessor& operator=(const ObjectAccessor&); + + public: + + typedef UNORDERED_MAP<uint64, Corpse*> Player2CorpsesMapType; + typedef UNORDERED_MAP<Player*, UpdateData>::value_type UpdateDataValueType; + + template<class T> static T* GetObjectInWorld(uint64 guid, T* /*fake*/) + { + return HashMapHolder<T>::Find(guid); + } + + static Unit* GetObjectInWorld(uint64 guid, Unit* /*fake*/) + { + if (!guid) + return NULL; + + if (IS_PLAYER_GUID(guid)) + { + Unit* u = (Unit*)HashMapHolder<Player>::Find(guid); + if (!u || !u->IsInWorld()) + return NULL; + + return u; + } + + if (IS_PET_GUID(guid)) + return (Unit*)HashMapHolder<Pet>::Find(guid); + + return (Unit*)HashMapHolder<Creature>::Find(guid); + } + + static Unit* GetUnitInOrOutOfWorld(uint64 guid, Unit* /*fake*/) + { + if (!guid) + return NULL; + + if (IS_PLAYER_GUID(guid)) + { + Unit* u = (Unit*)HashMapHolder<Player>::Find(guid); + if (!u) + return NULL; + + return u; + } + + // Other object types than player are unloaded while out of world + return GetObjectInWorld(guid, (Unit*)NULL); + } + + template<class T> static T* GetObjectInWorld(uint32 mapid, float x, float y, uint64 guid, T* /*fake*/) + { + T* obj = HashMapHolder<T>::Find(guid); + if (!obj || obj->GetMapId() != mapid) + return NULL; + + CellPair p = Trinity::ComputeCellPair(x, y); + if (p.x_coord >= TOTAL_NUMBER_OF_CELLS_PER_MAP || p.y_coord >= TOTAL_NUMBER_OF_CELLS_PER_MAP) + { + sLog.outError("ObjectAccessor::GetObjectInWorld: invalid coordinates supplied X:%f Y:%f grid cell [%u:%u]", x, y, p.x_coord, p.y_coord); + return NULL; + } + + CellPair q = Trinity::ComputeCellPair(obj->GetPositionX(), obj->GetPositionY()); + if (q.x_coord >= TOTAL_NUMBER_OF_CELLS_PER_MAP || q.y_coord >= TOTAL_NUMBER_OF_CELLS_PER_MAP) + { + sLog.outError("ObjectAccessor::GetObjecInWorld: object (GUID: %u TypeId: %u) has invalid coordinates X:%f Y:%f grid cell [%u:%u]", obj->GetGUIDLow(), obj->GetTypeId(), obj->GetPositionX(), obj->GetPositionY(), q.x_coord, q.y_coord); + return NULL; + } + + int32 dx = int32(p.x_coord) - int32(q.x_coord); + int32 dy = int32(p.y_coord) - int32(q.y_coord); + + if (dx > -2 && dx < 2 && dy > -2 && dy < 2) + return obj; + else + return NULL; + } + + static WorldObject* GetWorldObject(WorldObject const&, uint64); + static Object* GetObjectByTypeMask(WorldObject const&, uint64, uint32 typemask); + static Creature* GetCreatureOrPetOrVehicle(WorldObject const&, uint64); + static Unit* GetUnit(WorldObject const&, uint64 guid) { return GetObjectInWorld(guid, (Unit*)NULL); } + static Unit* GetUnitInOrOutOfWorld(WorldObject const&, uint64 guid) { return GetUnitInOrOutOfWorld(guid, (Unit*)NULL); } + static Pet* GetPet(Unit const&, uint64 guid) { return GetPet(guid); } + static Player* GetPlayer(Unit const&, uint64 guid) { return FindPlayer(guid); } + static Corpse* GetCorpse(WorldObject const& u, uint64 guid); + static Pet* GetPet(uint64 guid); + static Player* FindPlayer(uint64); + + Player* FindPlayerByName(const char* name) ; + + // when using this, you must use the hashmapholder's lock + HashMapHolder<Player>::MapType& GetPlayers() + { + return HashMapHolder<Player>::GetContainer(); + } + + // when using this, you must use the hashmapholder's lock + HashMapHolder<Creature>::MapType& GetCreatures() + { + return HashMapHolder<Creature>::GetContainer(); + } + + // when using this, you must use the hashmapholder's lock + HashMapHolder<GameObject>::MapType& GetGameObjects() + { + return HashMapHolder<GameObject>::GetContainer(); + } + + template<class T> void AddObject(T* object) + { + HashMapHolder<T>::Insert(object); + } + + template<class T> void RemoveObject(T* object) + { + HashMapHolder<T>::Remove(object); + } + + void RemoveObject(Player* pl) + { + HashMapHolder<Player>::Remove(pl); + RemoveUpdateObject((Object*)pl); + } + + void SaveAllPlayers(); + + void AddUpdateObject(Object* obj) + { + Guard guard(i_updateGuard); + i_objects.insert(obj); + } + + void RemoveUpdateObject(Object* obj) + { + Guard guard(i_updateGuard); + i_objects.erase(obj); + } + + void Update(uint32 diff); + + Corpse* GetCorpseForPlayerGUID(uint64 guid); + void RemoveCorpse(Corpse* corpse); + void AddCorpse(Corpse* corpse); + void AddCorpsesToGrid(GridPair const& gridpair, GridType& grid, Map* map); + Corpse* ConvertCorpseForPlayer(uint64 player_guid, bool insignia = false); + + typedef ACE_Thread_Mutex LockType; + typedef Trinity::GeneralLock<LockType> Guard; + + private: + + Player2CorpsesMapType i_player2corpse; + + static void _buildChangeObjectForPlayer(WorldObject*, UpdateDataMapType&); + static void _buildPacket(Player*, Object*, UpdateDataMapType&); + void _update(); + + std::set<Object*> i_objects; + + LockType i_updateGuard; + LockType i_corpseGuard; +}; +#endif diff --git a/src/server/game/Entities/Object/ObjectDefines.h b/src/server/game/Entities/Object/ObjectDefines.h new file mode 100644 index 00000000000..a0e5ac1952d --- /dev/null +++ b/src/server/game/Entities/Object/ObjectDefines.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef TRINITY_OBJECTDEFINES_H +#define TRINITY_OBJECTDEFINES_H + +#include "Platform/Define.h" + +// used for creating values for respawn for example +#define MAKE_PAIR64(l, h) uint64(uint32(l) | (uint64(h) << 32)) +#define PAIR64_HIPART(x) (uint32)((uint64(x) >> 32) & UI64LIT(0x00000000FFFFFFFF)) +#define PAIR64_LOPART(x) (uint32)(uint64(x) & UI64LIT(0x00000000FFFFFFFF)) + +#define MAKE_PAIR16(l, h) uint16(uint8(l) | (uint16(h) << 8)) +#define MAKE_PAIR32(l, h) uint32(uint16(l) | (uint32(h) << 16)) +#define PAIR32_HIPART(x) (uint16)((uint32(x) >> 16) & 0x0000FFFF) +#define PAIR32_LOPART(x) (uint16)(uint32(x) & 0x0000FFFF) + +enum HighGuid +{ + HIGHGUID_ITEM = 0x4000, // blizz 4000 + HIGHGUID_CONTAINER = 0x4000, // blizz 4000 + HIGHGUID_PLAYER = 0x0000, // blizz 0000 + HIGHGUID_GAMEOBJECT = 0xF110, // blizz F110 + HIGHGUID_TRANSPORT = 0xF120, // blizz F120 (for GAMEOBJECT_TYPE_TRANSPORT) + HIGHGUID_UNIT = 0xF130, // blizz F130 + HIGHGUID_PET = 0xF140, // blizz F140 + HIGHGUID_VEHICLE = 0xF150, // blizz F550 + HIGHGUID_DYNAMICOBJECT = 0xF100, // blizz F100 + HIGHGUID_CORPSE = 0xF101, // blizz F100 + HIGHGUID_MO_TRANSPORT = 0x1FC0, // blizz 1FC0 (for GAMEOBJECT_TYPE_MO_TRANSPORT) + HIGHGUID_GROUP = 0x1F50, +}; + +#define IS_EMPTY_GUID(Guid) (Guid == 0) + +#define IS_CREATURE_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_UNIT) +#define IS_PET_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_PET) +#define IS_VEHICLE_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_VEHICLE) +#define IS_CRE_OR_VEH_GUID(Guid) (IS_CREATURE_GUID(Guid) || IS_VEHICLE_GUID(Guid)) +#define IS_CRE_OR_VEH_OR_PET_GUID(Guid)(IS_CRE_OR_VEH_GUID(Guid) || IS_PET_GUID(Guid)) +#define IS_PLAYER_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_PLAYER && Guid != 0) +#define IS_UNIT_GUID(Guid) (IS_CRE_OR_VEH_OR_PET_GUID(Guid) || IS_PLAYER_GUID(Guid)) + // special case for empty guid need check +#define IS_ITEM_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_ITEM) +#define IS_GAMEOBJECT_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_GAMEOBJECT) +#define IS_DYNAMICOBJECT_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_DYNAMICOBJECT) +#define IS_CORPSE_GUID(Guid) (GUID_HIPART(Guid) == HIGHGUID_CORPSE) +#define IS_TRANSPORT(Guid) (GUID_HIPART(Guid) == HIGHGUID_TRANSPORT) +#define IS_MO_TRANSPORT(Guid) (GUID_HIPART(Guid) == HIGHGUID_MO_TRANSPORT) +#define IS_GROUP(Guid) (GUID_HIPART(Guid) == HIGHGUID_GROUP) + +// l - OBJECT_FIELD_GUID +// e - OBJECT_FIELD_ENTRY for GO (except GAMEOBJECT_TYPE_MO_TRANSPORT) and creatures or UNIT_FIELD_PETNUMBER for pets +// h - OBJECT_FIELD_GUID + 1 +#define MAKE_NEW_GUID(l, e, h) uint64(uint64(l) | (uint64(e) << 24) | (uint64(h) << 48)) + +#define GUID_HIPART(x) (uint32)((uint64(x) >> 48) & 0x0000FFFF) + +// We have different low and middle part size for different guid types +#define _GUID_ENPART_2(x) 0 +#define _GUID_ENPART_3(x) (uint32)((uint64(x) >> 24) & UI64LIT(0x0000000000FFFFFF)) +#define _GUID_LOPART_2(x) (uint32)(uint64(x) & UI64LIT(0x00000000FFFFFFFF)) +#define _GUID_LOPART_3(x) (uint32)(uint64(x) & UI64LIT(0x0000000000FFFFFF)) + +inline bool IsGuidHaveEnPart(uint64 const& guid) +{ + switch(GUID_HIPART(guid)) + { + case HIGHGUID_ITEM: + case HIGHGUID_PLAYER: + case HIGHGUID_DYNAMICOBJECT: + case HIGHGUID_CORPSE: + case HIGHGUID_GROUP: + return false; + case HIGHGUID_GAMEOBJECT: + case HIGHGUID_TRANSPORT: + case HIGHGUID_UNIT: + case HIGHGUID_PET: + case HIGHGUID_VEHICLE: + case HIGHGUID_MO_TRANSPORT: + default: + return true; + } +} + +#define GUID_ENPART(x) (IsGuidHaveEnPart(x) ? _GUID_ENPART_3(x) : _GUID_ENPART_2(x)) +#define GUID_LOPART(x) (IsGuidHaveEnPart(x) ? _GUID_LOPART_3(x) : _GUID_LOPART_2(x)) + +inline char const* GetLogNameForGuid(uint64 guid) +{ + switch(GUID_HIPART(guid)) + { + case HIGHGUID_ITEM: return "item"; + case HIGHGUID_PLAYER: return guid ? "player" : "none"; + case HIGHGUID_GAMEOBJECT: return "gameobject"; + case HIGHGUID_TRANSPORT: return "transport"; + case HIGHGUID_UNIT: return "creature"; + case HIGHGUID_PET: return "pet"; + case HIGHGUID_VEHICLE: return "vehicle"; + case HIGHGUID_DYNAMICOBJECT:return "dynobject"; + case HIGHGUID_CORPSE: return "corpse"; + case HIGHGUID_MO_TRANSPORT: return "mo_transport"; + case HIGHGUID_GROUP: return "group"; + default: + return "<unknown>"; + } +} +#endif + diff --git a/src/server/game/Entities/Object/ObjectMgr.cpp b/src/server/game/Entities/Object/ObjectMgr.cpp new file mode 100644 index 00000000000..ce86c2a4e77 --- /dev/null +++ b/src/server/game/Entities/Object/ObjectMgr.cpp @@ -0,0 +1,8724 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 "Common.h" +#include "Database/DatabaseEnv.h" +#include "Database/SQLStorage.h" +#include "Database/SQLStorageImpl.h" +#include "Policies/SingletonImp.h" + +#include "Log.h" +#include "MapManager.h" +#include "ObjectMgr.h" +#include "SpellMgr.h" +#include "UpdateMask.h" +#include "World.h" +#include "Group.h" +#include "Guild.h" +#include "ArenaTeam.h" +#include "Transports.h" +#include "ProgressBar.h" +#include "Language.h" +#include "GameEventMgr.h" +#include "Spell.h" +#include "Chat.h" +#include "AccountMgr.h" +#include "InstanceSaveMgr.h" +#include "SpellAuras.h" +#include "Util.h" +#include "WaypointManager.h" +#include "GossipDef.h" +#include "Vehicle.h" +#include "AchievementMgr.h" + +INSTANTIATE_SINGLETON_1(ObjectMgr); + +ScriptMapMap sQuestEndScripts; +ScriptMapMap sQuestStartScripts; +ScriptMapMap sSpellScripts; +ScriptMapMap sGameObjectScripts; +ScriptMapMap sEventScripts; +ScriptMapMap sGossipScripts; +ScriptMapMap sWaypointScripts; + +bool normalizePlayerName(std::string& name) +{ + if (name.empty()) + return false; + + wchar_t wstr_buf[MAX_INTERNAL_PLAYER_NAME+1]; + size_t wstr_len = MAX_INTERNAL_PLAYER_NAME; + + if (!Utf8toWStr(name,&wstr_buf[0],wstr_len)) + return false; + + wstr_buf[0] = wcharToUpper(wstr_buf[0]); + for (size_t i = 1; i < wstr_len; ++i) + wstr_buf[i] = wcharToLower(wstr_buf[i]); + + if (!WStrToUtf8(wstr_buf,wstr_len,name)) + return false; + + return true; +} + +LanguageDesc lang_description[LANGUAGES_COUNT] = +{ + { LANG_ADDON, 0, 0 }, + { LANG_UNIVERSAL, 0, 0 }, + { LANG_ORCISH, 669, SKILL_LANG_ORCISH }, + { LANG_DARNASSIAN, 671, SKILL_LANG_DARNASSIAN }, + { LANG_TAURAHE, 670, SKILL_LANG_TAURAHE }, + { LANG_DWARVISH, 672, SKILL_LANG_DWARVEN }, + { LANG_COMMON, 668, SKILL_LANG_COMMON }, + { LANG_DEMONIC, 815, SKILL_LANG_DEMON_TONGUE }, + { LANG_TITAN, 816, SKILL_LANG_TITAN }, + { LANG_THALASSIAN, 813, SKILL_LANG_THALASSIAN }, + { LANG_DRACONIC, 814, SKILL_LANG_DRACONIC }, + { LANG_KALIMAG, 817, SKILL_LANG_OLD_TONGUE }, + { LANG_GNOMISH, 7340, SKILL_LANG_GNOMISH }, + { LANG_TROLL, 7341, SKILL_LANG_TROLL }, + { LANG_GUTTERSPEAK, 17737, SKILL_LANG_GUTTERSPEAK }, + { LANG_DRAENEI, 29932, SKILL_LANG_DRAENEI }, + { LANG_ZOMBIE, 0, 0 }, + { LANG_GNOMISH_BINARY, 0, 0 }, + { LANG_GOBLIN_BINARY, 0, 0 } +}; + +LanguageDesc const* GetLanguageDescByID(uint32 lang) +{ + for (uint8 i = 0; i < LANGUAGES_COUNT; ++i) + { + if (uint32(lang_description[i].lang_id) == lang) + return &lang_description[i]; + } + + return NULL; +} + +bool SpellClickInfo::IsFitToRequirements(Player const* player, Creature const * clickNpc) const +{ + if (questStart) + { + // not in expected required quest state + if (!player || ((!questStartCanActive || !player->IsActiveQuest(questStart)) && !player->GetQuestRewardStatus(questStart))) + return false; + } + + if (questEnd) + { + // not in expected forbidden quest state + if (!player || player->GetQuestRewardStatus(questEnd)) + return false; + } + + if (auraRequired) + if (!player->HasAura(auraRequired)) + return false; + + if (auraForbidden) + if (player->HasAura(auraForbidden)) + return false; + + Unit const * summoner = NULL; + // Check summoners for party + if (clickNpc->isSummon()) + summoner = clickNpc->ToTempSummon()->GetSummoner(); + if (!summoner) + summoner = clickNpc; + + switch (userType) + { + case SPELL_CLICK_USER_FRIEND: + if (!player->IsFriendlyTo(summoner)) + return false; + break; + case SPELL_CLICK_USER_RAID: + if (!player->IsInRaidWith(summoner)) + return false; + break; + case SPELL_CLICK_USER_PARTY: + if (!player->IsInPartyWith(summoner)) + return false; + break; + } + + return true; +} + +ObjectMgr::ObjectMgr() +{ + m_hiCharGuid = 1; + m_hiCreatureGuid = 1; + m_hiPetGuid = 1; + m_hiVehicleGuid = 1; + m_hiItemGuid = 1; + m_hiGoGuid = 1; + m_hiDoGuid = 1; + m_hiCorpseGuid = 1; + m_hiPetNumber = 1; + m_hiGroupGuid = 1; + m_ItemTextId = 1; + m_mailid = 1; + m_equipmentSetGuid = 1; + m_guildId = 1; + m_arenaTeamId = 1; + m_auctionid = 1; +} + +ObjectMgr::~ObjectMgr() +{ + for (QuestMap::iterator i = mQuestTemplates.begin(); i != mQuestTemplates.end(); ++i) + delete i->second; + + for (PetLevelInfoMap::iterator i = petInfo.begin(); i != petInfo.end(); ++i) + delete[] i->second; + + // free only if loaded + for (int class_ = 0; class_ < MAX_CLASSES; ++class_) + delete[] playerClassInfo[class_].levelInfo; + + for (int race = 0; race < MAX_RACES; ++race) + for (int class_ = 0; class_ < MAX_CLASSES; ++class_) + delete[] playerInfo[race][class_].levelInfo; + + // free group and guild objects + for (GroupSet::iterator itr = mGroupSet.begin(); itr != mGroupSet.end(); ++itr) + delete (*itr); + + for (GuildMap::iterator itr = mGuildMap.begin(); itr != mGuildMap.end(); ++itr) + delete itr->second; + + for (ArenaTeamMap::iterator itr = mArenaTeamMap.begin(); itr != mArenaTeamMap.end(); ++itr) + delete itr->second; + + for (CacheVendorItemMap::iterator itr = m_mCacheVendorItemMap.begin(); itr != m_mCacheVendorItemMap.end(); ++itr) + itr->second.Clear(); + + for (CacheTrainerSpellMap::iterator itr = m_mCacheTrainerSpellMap.begin(); itr != m_mCacheTrainerSpellMap.end(); ++itr) + itr->second.Clear(); +} + +Group * ObjectMgr::GetGroupByGUID(const uint64 &guid) const +{ + for (GroupSet::const_iterator itr = mGroupSet.begin(); itr != mGroupSet.end(); ++itr) + if ((*itr)->GetGUID() == guid) + return *itr; + + return NULL; +} + +Guild * ObjectMgr::GetGuildById(uint32 GuildId) const +{ + GuildMap::const_iterator itr = mGuildMap.find(GuildId); + if (itr != mGuildMap.end()) + return itr->second; + + return NULL; +} + +Guild * ObjectMgr::GetGuildByName(const std::string& guildname) const +{ + std::string search = guildname; + std::transform(search.begin(), search.end(), search.begin(), ::toupper); + for (GuildMap::const_iterator itr = mGuildMap.begin(); itr != mGuildMap.end(); ++itr) + { + std::string gname = itr->second->GetName(); + std::transform(gname.begin(), gname.end(), gname.begin(), ::toupper); + if (search == gname) + return itr->second; + } + return NULL; +} + +std::string ObjectMgr::GetGuildNameById(uint32 GuildId) const +{ + GuildMap::const_iterator itr = mGuildMap.find(GuildId); + if (itr != mGuildMap.end()) + return itr->second->GetName(); + + return ""; +} + +Guild* ObjectMgr::GetGuildByLeader(const uint64 &guid) const +{ + for (GuildMap::const_iterator itr = mGuildMap.begin(); itr != mGuildMap.end(); ++itr) + if (itr->second->GetLeader() == guid) + return itr->second; + + return NULL; +} + +void ObjectMgr::AddGuild(Guild* guild) +{ + mGuildMap[guild->GetId()] = guild; +} + +void ObjectMgr::RemoveGuild(uint32 Id) +{ + mGuildMap.erase(Id); +} + +ArenaTeam* ObjectMgr::GetArenaTeamById(uint32 arenateamid) const +{ + ArenaTeamMap::const_iterator itr = mArenaTeamMap.find(arenateamid); + if (itr != mArenaTeamMap.end()) + return itr->second; + + return NULL; +} + +ArenaTeam* ObjectMgr::GetArenaTeamByName(const std::string& arenateamname) const +{ + std::string search = arenateamname; + std::transform(search.begin(), search.end(), search.begin(), ::toupper); + for (ArenaTeamMap::const_iterator itr = mArenaTeamMap.begin(); itr != mArenaTeamMap.end(); ++itr) + { + std::string teamname = itr->second->GetName(); + std::transform(teamname.begin(), teamname.end(), teamname.begin(), ::toupper); + if (search == teamname) + return itr->second; + } + return NULL; +} + +ArenaTeam* ObjectMgr::GetArenaTeamByCaptain(uint64 const& guid) const +{ + for (ArenaTeamMap::const_iterator itr = mArenaTeamMap.begin(); itr != mArenaTeamMap.end(); ++itr) + if (itr->second->GetCaptain() == guid) + return itr->second; + + return NULL; +} + +void ObjectMgr::AddArenaTeam(ArenaTeam* arenaTeam) +{ + mArenaTeamMap[arenaTeam->GetId()] = arenaTeam; +} + +void ObjectMgr::RemoveArenaTeam(uint32 Id) +{ + mArenaTeamMap.erase(Id); +} + +CreatureInfo const* ObjectMgr::GetCreatureTemplate(uint32 id) +{ + return sCreatureStorage.LookupEntry<CreatureInfo>(id); +} + +void ObjectMgr::LoadCreatureLocales() +{ + mCreatureLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,name_loc1,subname_loc1,name_loc2,subname_loc2,name_loc3,subname_loc3,name_loc4,subname_loc4,name_loc5,subname_loc5,name_loc6,subname_loc6,name_loc7,subname_loc7,name_loc8,subname_loc8 FROM locales_creature"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + CreatureLocale& data = mCreatureLocaleMap[entry]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[1+2*(i-1)].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Name.size() <= idx) + data.Name.resize(idx+1); + + data.Name[idx] = str; + } + } + str = fields[1+2*(i-1)+1].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.SubName.size() <= idx) + data.SubName.resize(idx+1); + + data.SubName[idx] = str; + } + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu creature locale strings", (unsigned long)mCreatureLocaleMap.size()); +} + +void ObjectMgr::LoadGossipMenuItemsLocales() +{ + mGossipMenuItemsLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT menu_id,id," + "option_text_loc1,box_text_loc1,option_text_loc2,box_text_loc2," + "option_text_loc3,box_text_loc3,option_text_loc4,box_text_loc4," + "option_text_loc5,box_text_loc5,option_text_loc6,box_text_loc6," + "option_text_loc7,box_text_loc7,option_text_loc8,box_text_loc8 " + "FROM locales_gossip_menu_option"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint16 menuId = fields[0].GetUInt16(); + uint16 id = fields[1].GetUInt16(); + + GossipMenuItemsLocale& data = mGossipMenuItemsLocaleMap[MAKE_PAIR32(menuId,id)]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[2+2*(i-1)].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.OptionText.size() <= idx) + data.OptionText.resize(idx+1); + + data.OptionText[idx] = str; + } + } + str = fields[2+2*(i-1)+1].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.BoxText.size() <= idx) + data.BoxText.resize(idx+1); + + data.BoxText[idx] = str; + } + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu gossip_menu_option locale strings", (unsigned long)mGossipMenuItemsLocaleMap.size()); +} + +void ObjectMgr::LoadPointOfInterestLocales() +{ + mPointOfInterestLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,icon_name_loc1,icon_name_loc2,icon_name_loc3,icon_name_loc4,icon_name_loc5,icon_name_loc6,icon_name_loc7,icon_name_loc8 FROM locales_points_of_interest"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + PointOfInterestLocale& data = mPointOfInterestLocaleMap[entry]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[i].GetCppString(); + if (str.empty()) + continue; + + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.IconName.size() <= idx) + data.IconName.resize(idx+1); + + data.IconName[idx] = str; + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu points_of_interest locale strings", (unsigned long)mPointOfInterestLocaleMap.size()); +} + +struct SQLCreatureLoader : public SQLStorageLoaderBase<SQLCreatureLoader> +{ + template<class D> + void convert_from_str(uint32 /*field_pos*/, char *src, D &dst) + { + dst = D(objmgr.GetScriptId(src)); + } +}; + +void ObjectMgr::LoadCreatureTemplates() +{ + SQLCreatureLoader loader; + loader.Load(sCreatureStorage); + + sLog.outString(">> Loaded %u creature definitions", sCreatureStorage.RecordCount); + sLog.outString(); + + // check data correctness + for (uint32 i = 1; i < sCreatureStorage.MaxEntry; ++i) + { + CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i); + CheckCreatureTemplate(cInfo); + } +} + +void ObjectMgr::CheckCreatureTemplate(CreatureInfo const* cInfo) +{ + if (!cInfo) + return; + + bool ok = true; // bool to allow continue outside this loop + for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff) + { + if (!cInfo->DifficultyEntry[diff]) + continue; + ok = false; // will be set to true at the end of this loop again + + CreatureInfo const* difficultyInfo = GetCreatureTemplate(cInfo->DifficultyEntry[diff]); + if (!difficultyInfo) + { + sLog.outErrorDb("Creature (Entry: %u) have `difficulty_entry_%u`=%u but creature entry %u not exist.", + cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff]); + continue; + } + + if (difficultyEntries[diff].find(cInfo->Entry) != difficultyEntries[diff].end()) + { + sLog.outErrorDb("Creature (Entry: %u) listed as difficulty %u but have value in `difficulty_entry_1`.", cInfo->Entry, diff + 1); + continue; + } + + bool ok2 = true; + for (uint32 diff2 = 0; diff2 < MAX_DIFFICULTY - 1 && ok2; ++diff2) + { + ok2 = false; + if (difficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != difficultyEntries[diff2].end()) + { + sLog.outErrorDb("Creature (Entry: %u) already listed as difficulty %u for another entry.", cInfo->DifficultyEntry[diff], diff2 + 1); + continue; + } + + if (hasDifficultyEntries[diff2].find(cInfo->DifficultyEntry[diff]) != hasDifficultyEntries[diff2].end()) + { + sLog.outErrorDb("Creature (Entry: %u) have `difficulty_entry_%u`=%u but creature entry %u have difficulty %u entry also.", + cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff], cInfo->DifficultyEntry[diff], diff2 + 1); + continue; + } + ok2 = true; + } + if (!ok2) + continue; + + if (cInfo->unit_class != difficultyInfo->unit_class) + { + sLog.outErrorDb("Creature (Entry: %u, class %u) has different `unit_class` in difficulty %u mode (Entry: %u, class %u).", + cInfo->Entry, cInfo->unit_class, diff + 1, cInfo->DifficultyEntry[diff], difficultyInfo->unit_class); + continue; + } + + if (cInfo->npcflag != difficultyInfo->npcflag) + { + sLog.outErrorDb("Creature (Entry: %u) has different `npcflag` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]); + continue; + } + + if (cInfo->trainer_class != difficultyInfo->trainer_class) + { + sLog.outErrorDb("Creature (Entry: %u) has different `trainer_class` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]); + continue; + } + + if (cInfo->trainer_race != difficultyInfo->trainer_race) + { + sLog.outErrorDb("Creature (Entry: %u) has different `trainer_race` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]); + continue; + } + + if (cInfo->trainer_type != difficultyInfo->trainer_type) + { + sLog.outErrorDb("Creature (Entry: %u) has different `trainer_type` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]); + continue; + } + + if (cInfo->trainer_spell != difficultyInfo->trainer_spell) + { + sLog.outErrorDb("Creature (Entry: %u) has different `trainer_spell` in difficulty %u mode (Entry: %u).", cInfo->Entry, diff + 1, cInfo->DifficultyEntry[diff]); + continue; + } + + if (difficultyInfo->AIName && *difficultyInfo->AIName) + { + sLog.outErrorDb("Difficulty %u mode creature (Entry: %u) has `AIName`, but in any case will used difficulty 0 mode creature (Entry: %u) AIName.", + diff, cInfo->DifficultyEntry[diff], cInfo->Entry); + continue; + } + + if (difficultyInfo->ScriptID) + { + sLog.outErrorDb("Difficulty %u mode creature (Entry: %u) has `ScriptName`, but in any case will used difficulty 0 mode creature (Entry: %u) ScriptName.", + diff, cInfo->DifficultyEntry[diff], cInfo->Entry); + continue; + } + + hasDifficultyEntries[diff].insert(cInfo->Entry); + difficultyEntries[diff].insert(cInfo->DifficultyEntry[diff]); + ok = true; + } + + FactionTemplateEntry const* factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction_A); + if (!factionTemplate) + sLog.outErrorDb("Creature (Entry: %u) has non-existing faction_A template (%u)", cInfo->Entry, cInfo->faction_A); + + factionTemplate = sFactionTemplateStore.LookupEntry(cInfo->faction_H); + if (!factionTemplate) + sLog.outErrorDb("Creature (Entry: %u) has non-existing faction_H template (%u)", cInfo->Entry, cInfo->faction_H); + + // used later for scale + CreatureDisplayInfoEntry const* displayScaleEntry = NULL; + + if (cInfo->Modelid1) + { + CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid1); + if (!displayEntry) + { + sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid1 id (%u), can crash client", cInfo->Entry, cInfo->Modelid1); + const_cast<CreatureInfo*>(cInfo)->Modelid1 = 0; + } + else if (!displayScaleEntry) + displayScaleEntry = displayEntry; + + CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid1); + if (!minfo) + sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid1 (%u)", cInfo->Entry, cInfo->Modelid1); + } + + if (cInfo->Modelid2) + { + CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid2); + if (!displayEntry) + { + sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid2 id (%u), can crash client", cInfo->Entry, cInfo->Modelid2); + const_cast<CreatureInfo*>(cInfo)->Modelid2 = 0; + } + else if (!displayScaleEntry) + displayScaleEntry = displayEntry; + + CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid2); + if (!minfo) + sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid2 (%u)", cInfo->Entry, cInfo->Modelid2); + } + + if (cInfo->Modelid3) + { + CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid3); + if (!displayEntry) + { + sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid3 id (%u), can crash client", cInfo->Entry, cInfo->Modelid3); + const_cast<CreatureInfo*>(cInfo)->Modelid3 = 0; + } + else if (!displayScaleEntry) + displayScaleEntry = displayEntry; + + CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid3); + if (!minfo) + sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid3 (%u)", cInfo->Entry, cInfo->Modelid3); + } + + if (cInfo->Modelid4) + { + CreatureDisplayInfoEntry const* displayEntry = sCreatureDisplayInfoStore.LookupEntry(cInfo->Modelid4); + if (!displayEntry) + { + sLog.outErrorDb("Creature (Entry: %u) has non-existing Modelid4 id (%u), can crash client", cInfo->Entry, cInfo->Modelid4); + const_cast<CreatureInfo*>(cInfo)->Modelid4 = 0; + } + else if (!displayScaleEntry) + displayScaleEntry = displayEntry; + + CreatureModelInfo const* minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(cInfo->Modelid4); + if (!minfo) + sLog.outErrorDb("Creature (Entry: %u) not has model data for Modelid4 (%u)", cInfo->Entry, cInfo->Modelid4); + } + + if (!displayScaleEntry) + sLog.outErrorDb("Creature (Entry: %u) not has any existed display id in Modelid1/Modelid2/Modelid3/Modelid4", cInfo->Entry); + + for (int k = 0; k < MAX_KILL_CREDIT; ++k) + { + if (cInfo->KillCredit[k]) + { + if (!GetCreatureTemplate(cInfo->KillCredit[k])) + { + sLog.outErrorDb("Creature (Entry: %u) has not existed creature entry in `KillCredit%d` (%u)",cInfo->Entry,k+1,cInfo->KillCredit[k]); + const_cast<CreatureInfo*>(cInfo)->KillCredit[k] = 0; + } + } + } + + if (!cInfo->unit_class || ((1 << (cInfo->unit_class-1)) & CLASSMASK_ALL_CREATURES) == 0) + { + sLog.outErrorDb("Creature (Entry: %u) has invalid unit_class(%u) for creature_template. Set to 1 (UNIT_CLASS_WARRIOR).", cInfo->Entry, cInfo->unit_class); + const_cast<CreatureInfo*>(cInfo)->unit_class = UNIT_CLASS_WARRIOR; + } + + if (cInfo->dmgschool >= MAX_SPELL_SCHOOL) + { + sLog.outErrorDb("Creature (Entry: %u) has invalid spell school value (%u) in `dmgschool`",cInfo->Entry,cInfo->dmgschool); + const_cast<CreatureInfo*>(cInfo)->dmgschool = SPELL_SCHOOL_NORMAL; + } + + if (cInfo->baseattacktime == 0) + const_cast<CreatureInfo*>(cInfo)->baseattacktime = BASE_ATTACK_TIME; + + if (cInfo->rangeattacktime == 0) + const_cast<CreatureInfo*>(cInfo)->rangeattacktime = BASE_ATTACK_TIME; + + if (cInfo->npcflag & UNIT_NPC_FLAG_SPELLCLICK) + { + sLog.outErrorDb("Creature (Entry: %u) has dynamic flag UNIT_NPC_FLAG_SPELLCLICK (%u) set, it expect to be set by code base at `npc_spellclick_spells` content.",cInfo->Entry,UNIT_NPC_FLAG_SPELLCLICK); + const_cast<CreatureInfo*>(cInfo)->npcflag &= ~UNIT_NPC_FLAG_SPELLCLICK; + } + + if ((cInfo->npcflag & UNIT_NPC_FLAG_TRAINER) && cInfo->trainer_type >= MAX_TRAINER_TYPE) + sLog.outErrorDb("Creature (Entry: %u) has wrong trainer type %u",cInfo->Entry,cInfo->trainer_type); + + if (cInfo->type && !sCreatureTypeStore.LookupEntry(cInfo->type)) + { + sLog.outErrorDb("Creature (Entry: %u) has invalid creature type (%u) in `type`",cInfo->Entry,cInfo->type); + const_cast<CreatureInfo*>(cInfo)->type = CREATURE_TYPE_HUMANOID; + } + + // must exist or used hidden but used in data horse case + if (cInfo->family && !sCreatureFamilyStore.LookupEntry(cInfo->family) && cInfo->family != CREATURE_FAMILY_HORSE_CUSTOM) + { + sLog.outErrorDb("Creature (Entry: %u) has invalid creature family (%u) in `family`",cInfo->Entry,cInfo->family); + const_cast<CreatureInfo*>(cInfo)->family = 0; + } + + if (cInfo->InhabitType <= 0 || cInfo->InhabitType > INHABIT_ANYWHERE) + { + sLog.outErrorDb("Creature (Entry: %u) has wrong value (%u) in `InhabitType`, creature will not correctly walk/swim/fly",cInfo->Entry,cInfo->InhabitType); + const_cast<CreatureInfo*>(cInfo)->InhabitType = INHABIT_ANYWHERE; + } + + if (cInfo->VehicleId) + { + VehicleEntry const* vehId = sVehicleStore.LookupEntry(cInfo->VehicleId); + if (!vehId) + sLog.outErrorDb("Creature (Entry: %u) has a non-existing VehicleId (%u). This *WILL* cause the client to freeze!", cInfo->Entry, cInfo->VehicleId); + } + + if (cInfo->PetSpellDataId) + { + CreatureSpellDataEntry const* spellDataId = sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId); + if (!spellDataId) + sLog.outErrorDb("Creature (Entry: %u) has non-existing PetSpellDataId (%u)", cInfo->Entry, cInfo->PetSpellDataId); + } + + for (uint8 j = 0; j < CREATURE_MAX_SPELLS; ++j) + { + if (cInfo->spells[j] && !sSpellStore.LookupEntry(cInfo->spells[j])) + { + sLog.outErrorDb("Creature (Entry: %u) has non-existing Spell%d (%u), set to 0", cInfo->Entry, j+1,cInfo->spells[j]); + const_cast<CreatureInfo*>(cInfo)->spells[j] = 0; + } + } + + if (cInfo->MovementType >= MAX_DB_MOTION_TYPE) + { + sLog.outErrorDb("Creature (Entry: %u) has wrong movement generator type (%u), ignore and set to IDLE.",cInfo->Entry,cInfo->MovementType); + const_cast<CreatureInfo*>(cInfo)->MovementType = IDLE_MOTION_TYPE; + } + + if (cInfo->equipmentId > 0) // 0 no equipment + { + if (!GetEquipmentInfo(cInfo->equipmentId)) + { + sLog.outErrorDb("Table `creature_template` have creature (Entry: %u) with equipment_id %u not found in table `creature_equip_template`, set to no equipment.", cInfo->Entry, cInfo->equipmentId); + const_cast<CreatureInfo*>(cInfo)->equipmentId = 0; + } + } + + /// if not set custom creature scale then load scale from CreatureDisplayInfo.dbc + if (cInfo->scale <= 0.0f) + { + if (displayScaleEntry) + const_cast<CreatureInfo*>(cInfo)->scale = displayScaleEntry->scale; + else + const_cast<CreatureInfo*>(cInfo)->scale = 1.0f; + } + + if (cInfo->expansion > (MAX_CREATURE_BASE_HP - 1)) + { + sLog.outErrorDb("Table `creature_template` have creature (Entry: %u) with expansion %u ignore and set to NULL.", cInfo->expansion); + const_cast<CreatureInfo*>(cInfo)->expansion = 0; + } + + const_cast<CreatureInfo*>(cInfo)->dmg_multiplier *= Creature::_GetDamageMod(cInfo->rank); +} + +void ObjectMgr::ConvertCreatureAddonAuras(CreatureDataAddon* addon, char const* table, char const* guidEntryStr) +{ + // Now add the auras, format "spellid effectindex spellid effectindex..." + char *p,*s; + std::map<uint32, uint32> val; + s=p=(char*)reinterpret_cast<char const*>(addon->auras); + if (p) + { + uint32 currSpellId = 0; + bool spell = true; + while (p[0] != 0) + { + ++p; + if (p[0] == ' ' || p[0] == 0) + { + if (spell) + currSpellId = atoi(s); + else + { + uint8 eff = atoi(s); + if (eff >=3) + { + sLog.outErrorDb("Creature (%s: %u) has wrong `auras` data in `%s`(too high aura effect: %d for spell: %d)",guidEntryStr,addon->guidOrEntry,table,eff,currSpellId); + } + val[currSpellId] |= 1<<eff; + } + spell = !spell; + if (p[0] == 0) + break; + s=++p; + } + } + + // free char* loaded memory + delete[] (char*)reinterpret_cast<char const*>(addon->auras); + + // wrong list + if (!spell) + { + addon->auras = NULL; + sLog.outErrorDb("Creature (%s: %u) has wrong `auras` data in `%s`.",guidEntryStr,addon->guidOrEntry,table); + return; + } + } + + // empty list + if (val.empty()) + { + addon->auras = NULL; + return; + } + + // replace by new structures array + const_cast<CreatureDataAddonAura*&>(addon->auras) = new CreatureDataAddonAura[val.size()+1]; + + uint32 i=0; + for (std::map<uint32, uint32>::iterator itr = val.begin(); itr != val.end();++itr) + { + CreatureDataAddonAura& cAura = const_cast<CreatureDataAddonAura&>(addon->auras[i]); + cAura.spell_id = itr->first; + cAura.effectMask = itr->second; + if (cAura.effectMask > 7 || !cAura.effectMask) + { + sLog.outErrorDb("Creature (%s: %u) has wrong effect for spell %u in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,cAura.spell_id,table); + continue; + } + SpellEntry const *AdditionalSpellInfo = sSpellStore.LookupEntry(cAura.spell_id); + if (!AdditionalSpellInfo) + { + sLog.outErrorDb("Creature (%s: %u) has wrong spell %u defined in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,cAura.spell_id,table); + continue; + } + for (uint8 eff = 0; eff < MAX_SPELL_EFFECTS; ++eff) + { + if ((1<<eff) & cAura.effectMask) + { + if (!AdditionalSpellInfo->Effect[eff] || !AdditionalSpellInfo->EffectApplyAuraName[eff]) + { + sLog.outErrorDb("Creature (%s: %u) has not aura effect %u of spell %u defined in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,eff,cAura.spell_id,table); + continue; + } + else if (AdditionalSpellInfo->Effect[eff] == SPELL_EFFECT_PERSISTENT_AREA_AURA) + { + sLog.outErrorDb("Creature (%s: %u) has persistent area aura effect %u of spell %u defined in `auras` field in `%s`.",guidEntryStr,addon->guidOrEntry,eff,cAura.spell_id,table); + continue; + } + } + } + + ++i; + } + + // fill terminator element (after last added) + CreatureDataAddonAura& endAura = const_cast<CreatureDataAddonAura&>(addon->auras[i]); + endAura.spell_id = 0; + endAura.effectMask = 0; +} + +void ObjectMgr::LoadCreatureAddons(SQLStorage& creatureaddons, char const* entryName, char const* comment) +{ + creatureaddons.Load(); + + sLog.outString(">> Loaded %u %s", creatureaddons.RecordCount, comment); + sLog.outString(); + + // check data correctness and convert 'auras' + for (uint32 i = 1; i < creatureaddons.MaxEntry; ++i) + { + CreatureDataAddon const* addon = creatureaddons.LookupEntry<CreatureDataAddon>(i); + if (!addon) + continue; + + if (addon->mount) + { + if (!sCreatureDisplayInfoStore.LookupEntry(addon->mount)) + { + sLog.outErrorDb("Creature (%s %u) have invalid displayInfoId for mount (%u) defined in `%s`.", entryName, addon->guidOrEntry, addon->mount, creatureaddons.GetTableName()); + const_cast<CreatureDataAddon*>(addon)->mount = 0; + } + } + + if (!sEmotesStore.LookupEntry(addon->emote)) + sLog.outErrorDb("Creature (%s %u) have invalid emote (%u) defined in `%s`.", entryName, addon->guidOrEntry, addon->emote, creatureaddons.GetTableName()); + + /*if (addon->move_flags & (MONSTER_MOVE_UNK1|MONSTER_MOVE_UNK4)) + { + sLog.outErrorDb("Creature (%s %u) movement flags mask defined in `%s` include forbidden flags (" I32FMT ") that can crash client, cleanup at load.", entryName, addon->guidOrEntry, creatureaddons.GetTableName(), (MONSTER_MOVE_UNK1|MONSTER_MOVE_UNK4)); + const_cast<CreatureDataAddon*>(addon)->move_flags &= ~(MONSTER_MOVE_UNK1|MONSTER_MOVE_UNK4); + }*/ + + ConvertCreatureAddonAuras(const_cast<CreatureDataAddon*>(addon), creatureaddons.GetTableName(), entryName); + } +} + +void ObjectMgr::LoadCreatureAddons() +{ + LoadCreatureAddons(sCreatureInfoAddonStorage,"Entry","creature template addons"); + + // check entry ids + for (uint32 i = 1; i < sCreatureInfoAddonStorage.MaxEntry; ++i) + if (CreatureDataAddon const* addon = sCreatureInfoAddonStorage.LookupEntry<CreatureDataAddon>(i)) + if (!sCreatureStorage.LookupEntry<CreatureInfo>(addon->guidOrEntry)) + sLog.outErrorDb("Creature (Entry: %u) does not exist but has a record in `%s`",addon->guidOrEntry, sCreatureInfoAddonStorage.GetTableName()); + + sLog.outString("Loading Creature Addon Data..."); + LoadCreatureAddons(sCreatureDataAddonStorage,"GUID","creature addons"); + + // check entry ids + for (uint32 i = 1; i < sCreatureDataAddonStorage.MaxEntry; ++i) + if (CreatureDataAddon const* addon = sCreatureDataAddonStorage.LookupEntry<CreatureDataAddon>(i)) + if (mCreatureDataMap.find(addon->guidOrEntry) == mCreatureDataMap.end()) + sLog.outErrorDb("Creature (GUID: %u) does not exist but has a record in `creature_addon`",addon->guidOrEntry); +} + +EquipmentInfo const* ObjectMgr::GetEquipmentInfo(uint32 entry) +{ + return sEquipmentStorage.LookupEntry<EquipmentInfo>(entry); +} + +void ObjectMgr::LoadEquipmentTemplates() +{ + sEquipmentStorage.Load(); + + for (uint32 i=0; i< sEquipmentStorage.MaxEntry; ++i) + { + EquipmentInfo const* eqInfo = sEquipmentStorage.LookupEntry<EquipmentInfo>(i); + + if (!eqInfo) + continue; + + for (uint8 j=0; j<3; j++) + { + if (!eqInfo->equipentry[j]) + continue; + + ItemEntry const *dbcitem = sItemStore.LookupEntry(eqInfo->equipentry[j]); + + if (!dbcitem) + { + sLog.outErrorDb("Unknown item (entry=%u) in creature_equip_template.equipentry%u for entry = %u, forced to 0.", eqInfo->equipentry[j], j+1, i); + const_cast<EquipmentInfo*>(eqInfo)->equipentry[j] = 0; + continue; + } + + if (dbcitem->InventoryType != INVTYPE_WEAPON && + dbcitem->InventoryType != INVTYPE_SHIELD && + dbcitem->InventoryType != INVTYPE_RANGED && + dbcitem->InventoryType != INVTYPE_2HWEAPON && + dbcitem->InventoryType != INVTYPE_WEAPONMAINHAND && + dbcitem->InventoryType != INVTYPE_WEAPONOFFHAND && + dbcitem->InventoryType != INVTYPE_HOLDABLE && + dbcitem->InventoryType != INVTYPE_THROWN && + dbcitem->InventoryType != INVTYPE_RANGEDRIGHT) + { + sLog.outErrorDb("Item (entry=%u) in creature_equip_template.equipentry%u for entry = %u is not equipable in a hand, forced to 0.", eqInfo->equipentry[j], j+1, i); + const_cast<EquipmentInfo*>(eqInfo)->equipentry[j] = 0; + } + } + } + sLog.outString(">> Loaded %u equipment template", sEquipmentStorage.RecordCount); + sLog.outString(); +} + +CreatureModelInfo const* ObjectMgr::GetCreatureModelInfo(uint32 modelid) +{ + return sCreatureModelStorage.LookupEntry<CreatureModelInfo>(modelid); +} + +uint32 ObjectMgr::ChooseDisplayId(uint32 /*team*/, const CreatureInfo *cinfo, const CreatureData *data /*= NULL*/) +{ + // Load creature model (display id) + uint32 display_id = 0; + + if (!data || data->displayid == 0) + { + display_id = cinfo->GetRandomValidModelId(); + } + else + return data->displayid; + + /*if (!team) + { + switch(cinfo->Entry) + { + case 28511: // Eye of Acherus + case 33114: // Flame Leviathan Seat (model 24914 chair) + case 33167: // Salvaged Demolisher Mechanic Seat + case 33189: // Liquid Pryite + return cinfo->Modelid1; + case 33218: // Pyrite Safety Container + return cinfo->Modelid2; + case 33143: // Overload Control Device + return cinfo->Modelid3; + default: + return cinfo->GetRandomValidModelId(); + } + }*/ + + return display_id; +} + +CreatureModelInfo const* ObjectMgr::GetCreatureModelRandomGender(uint32 display_id) +{ + CreatureModelInfo const *minfo = GetCreatureModelInfo(display_id); + if (!minfo) + return NULL; + + // If a model for another gender exists, 50% chance to use it + if (minfo->modelid_other_gender != 0 && urand(0,1) == 0) + { + CreatureModelInfo const *minfo_tmp = GetCreatureModelInfo(minfo->modelid_other_gender); + if (!minfo_tmp) + { + sLog.outErrorDb("Model (Entry: %u) has modelid_other_gender %u not found in table `creature_model_info`. ", minfo->modelid, minfo->modelid_other_gender); + return minfo; // not fatal, just use the previous one + } + else + return minfo_tmp; + } + else + return minfo; +} + +void ObjectMgr::LoadCreatureModelInfo() +{ + sCreatureModelStorage.Load(); + + // post processing + for (uint32 i = 1; i < sCreatureModelStorage.MaxEntry; ++i) + { + CreatureModelInfo const *minfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(i); + if (!minfo) + continue; + + if (!sCreatureDisplayInfoStore.LookupEntry(minfo->modelid)) + sLog.outErrorDb("Table `creature_model_info` has model for not existed display id (%u).", minfo->modelid); + + if (minfo->gender > GENDER_NONE) + { + sLog.outErrorDb("Table `creature_model_info` has wrong gender (%u) for display id (%u).", uint32(minfo->gender), minfo->modelid); + const_cast<CreatureModelInfo*>(minfo)->gender = GENDER_MALE; + } + + if (minfo->modelid_other_gender && !sCreatureDisplayInfoStore.LookupEntry(minfo->modelid_other_gender)) + { + sLog.outErrorDb("Table `creature_model_info` has not existed alt.gender model (%u) for existed display id (%u).", minfo->modelid_other_gender, minfo->modelid); + const_cast<CreatureModelInfo*>(minfo)->modelid_other_gender = 0; + } + } + + sLog.outString(">> Loaded %u creature model based info", sCreatureModelStorage.RecordCount); + sLog.outString(); + + // check if combat_reach is valid + for (uint32 i = 1; i < sCreatureModelStorage.MaxEntry; ++i) + { + CreatureModelInfo const* mInfo = sCreatureModelStorage.LookupEntry<CreatureModelInfo>(i); + if (!mInfo) + continue; + + if (mInfo->combat_reach < 0.1f) + { + //sLog.outErrorDb("Creature model (Entry: %u) has invalid combat reach (%f), setting it to 0.5", mInfo->modelid, mInfo->combat_reach); + const_cast<CreatureModelInfo*>(mInfo)->combat_reach = DEFAULT_COMBAT_REACH; + } + } +} + +bool ObjectMgr::CheckCreatureLinkedRespawn(uint32 guid, uint32 linkedGuid) const +{ + const CreatureData* const slave = GetCreatureData(guid); + const CreatureData* const master = GetCreatureData(linkedGuid); + + if (!slave || !master) // they must have a corresponding entry in db + { + sLog.outError("LinkedRespawn: Creature '%u' linking to '%u' which doesn't exist",guid,linkedGuid); + return false; + } + + const MapEntry* const map = sMapStore.LookupEntry(master->mapid); + + if (master->mapid != slave->mapid // link only to same map + && (!map || map->Instanceable())) // or to unistanced world + { + sLog.outError("LinkedRespawn: Creature '%u' linking to '%u' on an unpermitted map",guid,linkedGuid); + return false; + } + + if (!(master->spawnMask & slave->spawnMask) // they must have a possibility to meet (normal/heroic difficulty) + && (!map || map->Instanceable())) + { + sLog.outError("LinkedRespawn: Creature '%u' linking to '%u' with not corresponding spawnMask",guid,linkedGuid); + return false; + } + + return true; +} + +void ObjectMgr::LoadCreatureLinkedRespawn() +{ + mCreatureLinkedRespawnMap.clear(); + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT guid, linkedGuid FROM creature_linked_respawn ORDER BY guid ASC"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(""); + sLog.outErrorDb(">> Loaded 0 linked respawns. DB table `creature_linked_respawn` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 guid = fields[0].GetUInt32(); + uint32 linkedGuid = fields[1].GetUInt32(); + + if (CheckCreatureLinkedRespawn(guid,linkedGuid)) + mCreatureLinkedRespawnMap[guid] = linkedGuid; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u linked respawns", mCreatureLinkedRespawnMap.size()); +} + +bool ObjectMgr::SetCreatureLinkedRespawn(uint32 guid, uint32 linkedGuid) +{ + if (!guid) + return false; + + if (!linkedGuid) // we're removing the linking + { + mCreatureLinkedRespawnMap.erase(guid); + WorldDatabase.DirectPExecute("DELETE FROM creature_linked_respawn WHERE guid = '%u'",guid); + return true; + } + + if (CheckCreatureLinkedRespawn(guid,linkedGuid)) // we add/change linking + { + mCreatureLinkedRespawnMap[guid] = linkedGuid; + WorldDatabase.DirectPExecute("REPLACE INTO creature_linked_respawn (guid,linkedGuid) VALUES ('%u','%u')",guid,linkedGuid); + return true; + } + return false; +} + +void ObjectMgr::LoadCreatures() +{ + uint32 count = 0; + // 0 1 2 3 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT creature.guid, id, map, modelid," + // 4 5 6 7 8 9 10 11 + "equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, currentwaypoint," + // 12 13 14 15 16 17 18 19 + "curhealth, curmana, DeathState, MovementType, spawnMask, phaseMask, event, pool_entry " + "FROM creature LEFT OUTER JOIN game_event_creature ON creature.guid = game_event_creature.guid " + "LEFT OUTER JOIN pool_creature ON creature.guid = pool_creature.guid"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 creature. DB table `creature` is empty."); + return; + } + + // build single time for check creature data + std::set<uint32> difficultyCreatures[MAX_DIFFICULTY - 1]; + for (uint32 i = 0; i < sCreatureStorage.MaxEntry; ++i) + if (CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i)) + for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1; ++diff) + if (cInfo->DifficultyEntry[diff]) + difficultyCreatures[diff].insert(cInfo->DifficultyEntry[diff]); + + // build single time for check spawnmask + std::map<uint32,uint32> spawnMasks; + for (uint32 i = 0; i < sMapStore.GetNumRows(); ++i) + if (sMapStore.LookupEntry(i)) + for (int k = 0; k < MAX_DIFFICULTY; ++k) + if (GetMapDifficultyData(i,Difficulty(k))) + spawnMasks[i] |= (1 << k); + + //TODO: remove this + //gameeventmgr.mGameEventCreatureGuids.resize(52*2-1); + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 guid = fields[ 0].GetUInt32(); + uint32 entry = fields[ 1].GetUInt32(); + + CreatureInfo const* cInfo = GetCreatureTemplate(entry); + if (!cInfo) + { + sLog.outErrorDb("Table `creature` has creature (GUID: %u) with non existing creature entry %u, skipped.", guid, entry); + continue; + } + + CreatureData& data = mCreatureDataMap[guid]; + + data.id = entry; + data.mapid = fields[ 2].GetUInt32(); + data.displayid = fields[ 3].GetUInt32(); + data.equipmentId = fields[ 4].GetUInt32(); + data.posX = fields[ 5].GetFloat(); + data.posY = fields[ 6].GetFloat(); + data.posZ = fields[ 7].GetFloat(); + data.orientation = fields[ 8].GetFloat(); + data.spawntimesecs = fields[ 9].GetUInt32(); + data.spawndist = fields[10].GetFloat(); + data.currentwaypoint= fields[11].GetUInt32(); + data.curhealth = fields[12].GetUInt32(); + data.curmana = fields[13].GetUInt32(); + data.is_dead = fields[14].GetBool(); + data.movementType = fields[15].GetUInt8(); + data.spawnMask = fields[16].GetUInt8(); + data.phaseMask = fields[17].GetUInt16(); + int16 gameEvent = fields[18].GetInt16(); + int16 PoolId = fields[19].GetInt16(); + + MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + if (!mapEntry) + { + sLog.outErrorDb("Table `creature` have creature (GUID: %u) that spawned at not existed map (Id: %u), skipped.",guid, data.mapid); + continue; + } + + if (data.spawnMask & ~spawnMasks[data.mapid]) + sLog.outErrorDb("Table `creature` have creature (GUID: %u) that have wrong spawn mask %u including not supported difficulty modes for map (Id: %u).",guid, data.spawnMask, data.mapid); + + bool ok = true; + for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff) + { + if (difficultyCreatures[diff].find(data.id) != difficultyCreatures[diff].end()) + { + sLog.outErrorDb("Table `creature` have creature (GUID: %u) that listed as difficulty %u template (entry: %u) in `creature_template`, skipped.", + guid, diff + 1, data.id); + ok = false; + } + } + if (!ok) + continue; + + // I do not know why but in db most display id are not zero + /*if (data.displayid == 11686 || data.displayid == 24719) + { + (const_cast<CreatureInfo*>(cInfo))->flags_extra |= CREATURE_FLAG_EXTRA_TRIGGER; + } + else if (data.displayid == cInfo->DisplayID_A || data.displayid == cInfo->DisplayID_A2 + || data.displayid == cInfo->DisplayID_H || data.displayid == cInfo->DisplayID_H2) + data.displayid = 0; + */ + + if (data.equipmentId > 0) // -1 no equipment, 0 use default + { + if (!GetEquipmentInfo(data.equipmentId)) + { + sLog.outErrorDb("Table `creature` have creature (Entry: %u) with equipment_id %u not found in table `creature_equip_template`, set to no equipment.", data.id, data.equipmentId); + data.equipmentId = -1; + } + } + + if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_INSTANCE_BIND) + { + if (!mapEntry || !mapEntry->IsDungeon()) + sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `creature_template`.`flags_extra` including CREATURE_FLAG_EXTRA_INSTANCE_BIND but creature are not in instance.",guid,data.id); + } + + if (data.spawndist < 0.0f) + { + sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `spawndist`< 0, set to 0.",guid,data.id); + data.spawndist = 0.0f; + } + else if (data.movementType == RANDOM_MOTION_TYPE) + { + if (data.spawndist == 0.0f) + { + sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `MovementType`=1 (random movement) but with `spawndist`=0, replace by idle movement type (0).",guid,data.id); + data.movementType = IDLE_MOTION_TYPE; + } + else if (cInfo->flags_extra & CREATURE_FLAG_EXTRA_TRIGGER) + data.movementType = IDLE_MOTION_TYPE; + } + else if (data.movementType == IDLE_MOTION_TYPE) + { + if (data.spawndist != 0.0f) + { + sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `MovementType`=0 (idle) have `spawndist`<>0, set to 0.",guid,data.id); + data.spawndist = 0.0f; + } + } + + if (data.phaseMask == 0) + { + sLog.outErrorDb("Table `creature` have creature (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.",guid,data.id); + data.phaseMask = 1; + } + + //if (entry == 32307 || entry == 32308) + /*if (entry == 30739 || entry == 30740) + { + gameEvent = 51; + uint32 guid2 = objmgr.GenerateLowGuid(HIGHGUID_UNIT); + CreatureData& data2 = mCreatureDataMap[guid2]; + data2 = data; +// data2.id = (entry == 32307 ? 32308 : 32307); + data2.id = (entry == 30739 ? 30740 : 30739); + data2.displayid = 0; + gameeventmgr.mGameEventCreatureGuids[51+51].push_back(guid); + gameeventmgr.mGameEventCreatureGuids[51+50].push_back(guid2); + }*/ + + if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system + AddCreatureToGrid(guid, &data); + + ++count; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu creatures", (unsigned long)mCreatureDataMap.size()); +} + +void ObjectMgr::AddCreatureToGrid(uint32 guid, CreatureData const* data) +{ + uint8 mask = data->spawnMask; + for (uint8 i = 0; mask != 0; i++, mask >>= 1) + { + if (mask & 1) + { + CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY); + uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord; + + CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id]; + cell_guids.creatures.insert(guid); + } + } +} + +void ObjectMgr::RemoveCreatureFromGrid(uint32 guid, CreatureData const* data) +{ + uint8 mask = data->spawnMask; + for (uint8 i = 0; mask != 0; i++, mask >>= 1) + { + if (mask & 1) + { + CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY); + uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord; + + CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id]; + cell_guids.creatures.erase(guid); + } + } +} + +uint32 ObjectMgr::AddGOData(uint32 entry, uint32 mapId, float x, float y, float z, float o, uint32 spawntimedelay, float rotation0, float rotation1, float rotation2, float rotation3) +{ + GameObjectInfo const* goinfo = GetGameObjectInfo(entry); + if (!goinfo) + return 0; + + Map* map = const_cast<Map*>(MapManager::Instance().CreateBaseMap(mapId)); + if (!map) + return 0; + + uint32 guid = GenerateLowGuid(HIGHGUID_GAMEOBJECT); + GameObjectData& data = NewGOData(guid); + data.id = entry; + data.mapid = mapId; + data.posX = x; + data.posY = y; + data.posZ = z; + data.orientation = o; + data.rotation0 = rotation0; + data.rotation1 = rotation1; + data.rotation2 = rotation2; + data.rotation3 = rotation3; + data.spawntimesecs = spawntimedelay; + data.animprogress = 100; + data.spawnMask = 1; + data.go_state = GO_STATE_READY; + data.phaseMask = PHASEMASK_NORMAL; + data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0; + data.dbData = false; + + AddGameobjectToGrid(guid, &data); + + // Spawn if necessary (loaded grids only) + // We use spawn coords to spawn + if (!map->Instanceable() && map->IsLoaded(x, y)) + { + GameObject *go = new GameObject; + if (!go->LoadFromDB(guid, map)) + { + sLog.outError("AddGOData: cannot add gameobject entry %u to map", entry); + delete go; + return 0; + } + map->Add(go); + } + + sLog.outDebug("AddGOData: dbguid %u entry %u map %u x %f y %f z %f o %f", guid, entry, mapId, x, y, z, o); + + return guid; +} + +bool ObjectMgr::MoveCreData(uint32 guid, uint32 mapId, Position pos) +{ + CreatureData& data = NewOrExistCreatureData(guid); + if (!data.id) + return false; + + RemoveCreatureFromGrid(guid, &data); + if (data.posX == pos.GetPositionX() && data.posY == pos.GetPositionY() && data.posZ == pos.GetPositionZ()) + return true; + data.posX = pos.GetPositionX(); + data.posY = pos.GetPositionY(); + data.posZ = pos.GetPositionZ(); + data.orientation = pos.GetOrientation(); + AddCreatureToGrid(guid, &data); + + // Spawn if necessary (loaded grids only) + if (Map* map = const_cast<Map*>(MapManager::Instance().CreateBaseMap(mapId))) + { + // We use spawn coords to spawn + if (!map->Instanceable() && map->IsLoaded(data.posX, data.posY)) + { + Creature *creature = new Creature; + if (!creature->LoadFromDB(guid, map)) + { + sLog.outError("AddCreature: cannot add creature entry %u to map", guid); + delete creature; + return false; + } + map->Add(creature); + } + } + return true; +} + +uint32 ObjectMgr::AddCreData(uint32 entry, uint32 /*team*/, uint32 mapId, float x, float y, float z, float o, uint32 spawntimedelay) +{ + CreatureInfo const *cInfo = GetCreatureTemplate(entry); + if (!cInfo) + return 0; + + uint32 level = cInfo->minlevel == cInfo->maxlevel ? cInfo->minlevel : urand(cInfo->minlevel, cInfo->maxlevel); // Only used for extracting creature base stats + CreatureBaseStats const* stats = objmgr.GetCreatureBaseStats(level, cInfo->unit_class); + + uint32 guid = GenerateLowGuid(HIGHGUID_UNIT); + CreatureData& data = NewOrExistCreatureData(guid); + data.id = entry; + data.mapid = mapId; + data.displayid = 0; + data.equipmentId = cInfo->equipmentId; + data.posX = x; + data.posY = y; + data.posZ = z; + data.orientation = o; + data.spawntimesecs = spawntimedelay; + data.spawndist = 0; + data.currentwaypoint = 0; + data.curhealth = stats->GenerateHealth(cInfo); + data.curmana = stats->GenerateMana(cInfo); + data.is_dead = false; + data.movementType = cInfo->MovementType; + data.spawnMask = 1; + data.phaseMask = PHASEMASK_NORMAL; + data.dbData = false; + + AddCreatureToGrid(guid, &data); + + // Spawn if necessary (loaded grids only) + if (Map* map = const_cast<Map*>(MapManager::Instance().CreateBaseMap(mapId))) + { + // We use spawn coords to spawn + if (!map->Instanceable() && !map->IsRemovalGrid(x, y)) + { + Creature* creature = new Creature; + if (!creature->LoadFromDB(guid, map)) + { + sLog.outError("AddCreature: cannot add creature entry %u to map", entry); + delete creature; + return 0; + } + map->Add(creature); + } + } + + return guid; +} + +void ObjectMgr::LoadGameobjects() +{ + uint32 count = 0; + + // 0 1 2 3 4 5 6 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT gameobject.guid, id, map, position_x, position_y, position_z, orientation," + // 7 8 9 10 11 12 13 14 15 16 17 + "rotation0, rotation1, rotation2, rotation3, spawntimesecs, animprogress, state, spawnMask, phaseMask, event, pool_entry " + "FROM gameobject LEFT OUTER JOIN game_event_gameobject ON gameobject.guid = game_event_gameobject.guid " + "LEFT OUTER JOIN pool_gameobject ON gameobject.guid = pool_gameobject.guid"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 gameobjects. DB table `gameobject` is empty."); + return; + } + + // build single time for check spawnmask + std::map<uint32,uint32> spawnMasks; + for (uint32 i = 0; i < sMapStore.GetNumRows(); ++i) + if (sMapStore.LookupEntry(i)) + for (int k = 0; k < MAX_DIFFICULTY; ++k) + if (GetMapDifficultyData(i,Difficulty(k))) + spawnMasks[i] |= (1 << k); + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 guid = fields[ 0].GetUInt32(); + uint32 entry = fields[ 1].GetUInt32(); + + GameObjectInfo const* gInfo = GetGameObjectInfo(entry); + if (!gInfo) + { + sLog.outErrorDb("Table `gameobject` has gameobject (GUID: %u) with non existing gameobject entry %u, skipped.", guid, entry); + continue; + } + + if (!gInfo->displayId) + { + switch (gInfo->type) + { + case GAMEOBJECT_TYPE_TRAP: + case GAMEOBJECT_TYPE_SPELL_FOCUS: + break; + default: + sLog.outErrorDb("Gameobject (GUID: %u Entry %u GoType: %u) doesn't have displayId (%u), not loaded.", guid, entry, gInfo->type, gInfo->displayId); + break; + } + } + + if (gInfo->displayId && !sGameObjectDisplayInfoStore.LookupEntry(gInfo->displayId)) + { + sLog.outErrorDb("Gameobject (GUID: %u Entry %u GoType: %u) have invalid displayId (%u), not loaded.",guid, entry, gInfo->type, gInfo->displayId); + continue; + } + + GameObjectData& data = mGameObjectDataMap[guid]; + + data.id = entry; + data.mapid = fields[ 2].GetUInt32(); + data.posX = fields[ 3].GetFloat(); + data.posY = fields[ 4].GetFloat(); + data.posZ = fields[ 5].GetFloat(); + data.orientation = fields[ 6].GetFloat(); + data.rotation0 = fields[ 7].GetFloat(); + data.rotation1 = fields[ 8].GetFloat(); + data.rotation2 = fields[ 9].GetFloat(); + data.rotation3 = fields[10].GetFloat(); + data.spawntimesecs = fields[11].GetInt32(); + + MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + if (!mapEntry) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) that spawned at not existed map (Id: %u), skip", guid, data.id, data.mapid); + continue; + } + + if (data.spawntimesecs == 0 && gInfo->IsDespawnAtAction()) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with `spawntimesecs` (0) value, but gameobejct marked as despawnable at action.",guid,data.id); + } + + data.animprogress = fields[12].GetUInt32(); + data.artKit = 0; + + uint32 go_state = fields[13].GetUInt32(); + if (go_state >= MAX_GO_STATE) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid `state` (%u) value, skip",guid,data.id,go_state); + continue; + } + data.go_state = GOState(go_state); + + data.spawnMask = fields[14].GetUInt8(); + + if (data.spawnMask & ~spawnMasks[data.mapid]) + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) that have wrong spawn mask %u including not supported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.mapid); + + data.phaseMask = fields[15].GetUInt16(); + int16 gameEvent = fields[16].GetInt16(); + int16 PoolId = fields[17].GetInt16(); + + if (data.rotation2 < -1.0f || data.rotation2 > 1.0f) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid rotation2 (%f) value, skip",guid,data.id,data.rotation2); + continue; + } + + if (data.rotation3 < -1.0f || data.rotation3 > 1.0f) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid rotation3 (%f) value, skip",guid,data.id,data.rotation3); + continue; + } + + if (!MapManager::IsValidMapCoord(data.mapid,data.posX,data.posY,data.posZ,data.orientation)) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with invalid coordinates, skip",guid,data.id); + continue; + } + + if (data.phaseMask == 0) + { + sLog.outErrorDb("Table `gameobject` have gameobject (GUID: %u Entry: %u) with `phaseMask`=0 (not visible for anyone), set to 1.",guid,data.id); + data.phaseMask = 1; + } + + if (gameEvent == 0 && PoolId == 0) // if not this is to be managed by GameEvent System or Pool system + AddGameobjectToGrid(guid, &data); + ++count; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu gameobjects", (unsigned long)mGameObjectDataMap.size()); +} + +void ObjectMgr::AddGameobjectToGrid(uint32 guid, GameObjectData const* data) +{ + uint8 mask = data->spawnMask; + for (uint8 i = 0; mask != 0; i++, mask >>= 1) + { + if (mask & 1) + { + CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY); + uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord; + + CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id]; + cell_guids.gameobjects.insert(guid); + } + } +} + +void ObjectMgr::RemoveGameobjectFromGrid(uint32 guid, GameObjectData const* data) +{ + uint8 mask = data->spawnMask; + for (uint8 i = 0; mask != 0; i++, mask >>= 1) + { + if (mask & 1) + { + CellPair cell_pair = Trinity::ComputeCellPair(data->posX, data->posY); + uint32 cell_id = (cell_pair.y_coord*TOTAL_NUMBER_OF_CELLS_PER_MAP) + cell_pair.x_coord; + + CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(data->mapid,i)][cell_id]; + cell_guids.gameobjects.erase(guid); + } + } +} + +void ObjectMgr::LoadCreatureRespawnTimes() +{ + uint32 count = 0; + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT guid,respawntime,instance FROM creature_respawn"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded 0 creature respawn time."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 loguid = fields[0].GetUInt32(); + uint64 respawn_time = fields[1].GetUInt64(); + uint32 instance = fields[2].GetUInt32(); + + mCreatureRespawnTimes[MAKE_PAIR64(loguid,instance)] = time_t(respawn_time); + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu creature respawn times", (unsigned long)mCreatureRespawnTimes.size()); +} + +void ObjectMgr::LoadGameobjectRespawnTimes() +{ + // remove outdated data + WorldDatabase.DirectExecute("DELETE FROM gameobject_respawn WHERE respawntime <= UNIX_TIMESTAMP(NOW())"); + + uint32 count = 0; + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT guid,respawntime,instance FROM gameobject_respawn"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded 0 gameobject respawn time."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 loguid = fields[0].GetUInt32(); + uint64 respawn_time = fields[1].GetUInt64(); + uint32 instance = fields[2].GetUInt32(); + + mGORespawnTimes[MAKE_PAIR64(loguid,instance)] = time_t(respawn_time); + + ++count; + } while (result->NextRow()); + + sLog.outString(">> Loaded %lu gameobject respawn times", (unsigned long)mGORespawnTimes.size()); + sLog.outString(); +} + +// name must be checked to correctness (if received) before call this function +uint64 ObjectMgr::GetPlayerGUIDByName(std::string name) const +{ + uint64 guid = 0; + + CharacterDatabase.escape_string(name); + + // Player name safe to sending to DB (checked at login) and this function using + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT guid FROM characters WHERE name = '%s'", name.c_str()); + if (result) + guid = MAKE_NEW_GUID((*result)[0].GetUInt32(), 0, HIGHGUID_PLAYER); + + return guid; +} + +bool ObjectMgr::GetPlayerNameByGUID(const uint64 &guid, std::string &name) const +{ + // prevent DB access for online player + if (Player* player = GetPlayer(guid)) + { + name = player->GetName(); + return true; + } + + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT name FROM characters WHERE guid = '%u'", GUID_LOPART(guid)); + + if (result) + { + name = (*result)[0].GetCppString(); + return true; + } + + return false; +} + +uint32 ObjectMgr::GetPlayerTeamByGUID(const uint64 &guid) const +{ + // prevent DB access for online player + if (Player* player = GetPlayer(guid)) + { + return Player::TeamForRace(player->getRace()); + } + + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT race FROM characters WHERE guid = '%u'", GUID_LOPART(guid)); + + if (result) + { + uint8 race = (*result)[0].GetUInt8(); + return Player::TeamForRace(race); + } + + return 0; +} + +uint32 ObjectMgr::GetPlayerAccountIdByGUID(const uint64 &guid) const +{ + // prevent DB access for online player + if (Player* player = GetPlayer(guid)) + { + return player->GetSession()->GetAccountId(); + } + + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT account FROM characters WHERE guid = '%u'", GUID_LOPART(guid)); + if (result) + { + uint32 acc = (*result)[0].GetUInt32(); + return acc; + } + + return 0; +} + +uint32 ObjectMgr::GetPlayerAccountIdByPlayerName(const std::string& name) const +{ + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT account FROM characters WHERE name = '%s'", name.c_str()); + if (result) + { + uint32 acc = (*result)[0].GetUInt32(); + return acc; + } + + return 0; +} + +void ObjectMgr::LoadItemLocales() +{ + mItemLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,name_loc1,description_loc1,name_loc2,description_loc2,name_loc3,description_loc3,name_loc4,description_loc4,name_loc5,description_loc5,name_loc6,description_loc6,name_loc7,description_loc7,name_loc8,description_loc8 FROM locales_item"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + ItemLocale& data = mItemLocaleMap[entry]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[1+2*(i-1)].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Name.size() <= idx) + data.Name.resize(idx+1); + + data.Name[idx] = str; + } + } + + str = fields[1+2*(i-1)+1].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Description.size() <= idx) + data.Description.resize(idx+1); + + data.Description[idx] = str; + } + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu Item locale strings", (unsigned long)mItemLocaleMap.size()); +} + +struct SQLItemLoader : public SQLStorageLoaderBase<SQLItemLoader> +{ + template<class D> + void convert_from_str(uint32 /*field_pos*/, char *src, D &dst) + { + dst = D(objmgr.GetScriptId(src)); + } +}; + +void ObjectMgr::LoadItemPrototypes() +{ + SQLItemLoader loader; + loader.Load(sItemStorage); + sLog.outString(">> Loaded %u item prototypes", sItemStorage.RecordCount); + sLog.outString(); + + // check data correctness + for (uint32 i = 1; i < sItemStorage.MaxEntry; ++i) + { + ItemPrototype const* proto = sItemStorage.LookupEntry<ItemPrototype >(i); + ItemEntry const *dbcitem = sItemStore.LookupEntry(i); + if (!proto) + { + /* to many errors, and possible not all items really used in game + if (dbcitem) + sLog.outErrorDb("Item (Entry: %u) doesn't exists in DB, but must exist.",i); + */ + continue; + } + + if (dbcitem) + { + if (proto->Class != dbcitem->Class) + { + sLog.outErrorDb("Item (Entry: %u) not correct class %u, must be %u (still using DB value).",i,proto->Class,dbcitem->Class); + // It safe let use Class from DB + } + /* disabled: have some strange wrong cases for Subclass values. + for enable also uncomment Subclass field in ItemEntry structure and in Itemfmt[] + if (proto->SubClass != dbcitem->SubClass) + { + sLog.outErrorDb("Item (Entry: %u) not correct (Class: %u, Sub: %u) pair, must be (Class: %u, Sub: %u) (still using DB value).",i,proto->Class,proto->SubClass,dbcitem->Class,dbcitem->SubClass); + // It safe let use Subclass from DB + } + */ + + if (proto->Unk0 != dbcitem->Unk0) + { + sLog.outErrorDb("Item (Entry: %u) not correct %i Unk0, must be %i (still using DB value).",i,proto->Unk0,dbcitem->Unk0); + // It safe let use Unk0 from DB + } + + if (proto->Material != dbcitem->Material) + { + sLog.outErrorDb("Item (Entry: %u) not correct %i material, must be %i (still using DB value).",i,proto->Material,dbcitem->Material); + // It safe let use Material from DB + } + + if (proto->InventoryType != dbcitem->InventoryType) + { + sLog.outErrorDb("Item (Entry: %u) not correct %u inventory type, must be %u (still using DB value).",i,proto->InventoryType,dbcitem->InventoryType); + // It safe let use InventoryType from DB + } + + if (proto->DisplayInfoID != dbcitem->DisplayId) + { + sLog.outErrorDb("Item (Entry: %u) not correct %u display id, must be %u (using it).",i,proto->DisplayInfoID,dbcitem->DisplayId); + const_cast<ItemPrototype*>(proto)->DisplayInfoID = dbcitem->DisplayId; + } + if (proto->Sheath != dbcitem->Sheath) + { + sLog.outErrorDb("Item (Entry: %u) not correct %u sheath, must be %u (using it).",i,proto->Sheath,dbcitem->Sheath); + const_cast<ItemPrototype*>(proto)->Sheath = dbcitem->Sheath; + } + } + else + sLog.outErrorDb("Item (Entry: %u) not correct (not listed in list of existed items).",i); + + if (proto->Class >= MAX_ITEM_CLASS) + { + sLog.outErrorDb("Item (Entry: %u) has wrong Class value (%u)",i,proto->Class); + const_cast<ItemPrototype*>(proto)->Class = ITEM_CLASS_MISC; + } + + if (proto->SubClass >= MaxItemSubclassValues[proto->Class]) + { + sLog.outErrorDb("Item (Entry: %u) has wrong Subclass value (%u) for class %u",i,proto->SubClass,proto->Class); + const_cast<ItemPrototype*>(proto)->SubClass = 0;// exist for all item classes + } + + if (proto->Quality >= MAX_ITEM_QUALITY) + { + sLog.outErrorDb("Item (Entry: %u) has wrong Quality value (%u)",i,proto->Quality); + const_cast<ItemPrototype*>(proto)->Quality = ITEM_QUALITY_NORMAL; + } + + if (proto->BuyCount <= 0) + { + sLog.outErrorDb("Item (Entry: %u) has wrong BuyCount value (%u), set to default(1).",i,proto->BuyCount); + const_cast<ItemPrototype*>(proto)->BuyCount = 1; + } + + if (proto->InventoryType >= MAX_INVTYPE) + { + sLog.outErrorDb("Item (Entry: %u) has wrong InventoryType value (%u)",i,proto->InventoryType); + const_cast<ItemPrototype*>(proto)->InventoryType = INVTYPE_NON_EQUIP; + } + + if (proto->RequiredSkill >= MAX_SKILL_TYPE) + { + sLog.outErrorDb("Item (Entry: %u) has wrong RequiredSkill value (%u)",i,proto->RequiredSkill); + const_cast<ItemPrototype*>(proto)->RequiredSkill = 0; + } + + { + // can be used in equip slot, as page read use in inventory, or spell casting at use + bool req = proto->InventoryType != INVTYPE_NON_EQUIP || proto->PageText; + if (!req) + for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; ++j) + { + if (proto->Spells[j].SpellId) + { + req = true; + break; + } + } + + if (req) + { + if (!(proto->AllowableClass & CLASSMASK_ALL_PLAYABLE)) + sLog.outErrorDb("Item (Entry: %u) not have in `AllowableClass` any playable classes (%u) and can't be equipped or use.",i,proto->AllowableClass); + + if (!(proto->AllowableRace & RACEMASK_ALL_PLAYABLE)) + sLog.outErrorDb("Item (Entry: %u) not have in `AllowableRace` any playable races (%u) and can't be equipped or use.",i,proto->AllowableRace); + } + } + + if (proto->RequiredSpell && !sSpellStore.LookupEntry(proto->RequiredSpell)) + { + sLog.outErrorDb("Item (Entry: %u) have wrong (non-existed) spell in RequiredSpell (%u)",i,proto->RequiredSpell); + const_cast<ItemPrototype*>(proto)->RequiredSpell = 0; + } + + if (proto->RequiredReputationRank >= MAX_REPUTATION_RANK) + sLog.outErrorDb("Item (Entry: %u) has wrong reputation rank in RequiredReputationRank (%u), item can't be used.",i,proto->RequiredReputationRank); + + if (proto->RequiredReputationFaction) + { + if (!sFactionStore.LookupEntry(proto->RequiredReputationFaction)) + { + sLog.outErrorDb("Item (Entry: %u) has wrong (not existing) faction in RequiredReputationFaction (%u)",i,proto->RequiredReputationFaction); + const_cast<ItemPrototype*>(proto)->RequiredReputationFaction = 0; + } + + if (proto->RequiredReputationRank == MIN_REPUTATION_RANK) + sLog.outErrorDb("Item (Entry: %u) has min. reputation rank in RequiredReputationRank (0) but RequiredReputationFaction > 0, faction setting is useless.",i); + } + + if (proto->MaxCount < -1) + { + sLog.outErrorDb("Item (Entry: %u) has too large negative in maxcount (%i), replace by value (-1) no storing limits.",i,proto->MaxCount); + const_cast<ItemPrototype*>(proto)->MaxCount = -1; + } + + if (proto->Stackable == 0) + { + sLog.outErrorDb("Item (Entry: %u) has wrong value in stackable (%i), replace by default 1.",i,proto->Stackable); + const_cast<ItemPrototype*>(proto)->Stackable = 1; + } + else if (proto->Stackable < -1) + { + sLog.outErrorDb("Item (Entry: %u) has too large negative in stackable (%i), replace by value (-1) no stacking limits.",i,proto->Stackable); + const_cast<ItemPrototype*>(proto)->Stackable = -1; + } + + if (proto->ContainerSlots > MAX_BAG_SIZE) + { + sLog.outErrorDb("Item (Entry: %u) has too large value in ContainerSlots (%u), replace by hardcoded limit (%u).",i,proto->ContainerSlots,MAX_BAG_SIZE); + const_cast<ItemPrototype*>(proto)->ContainerSlots = MAX_BAG_SIZE; + } + + if (proto->StatsCount > MAX_ITEM_PROTO_STATS) + { + sLog.outErrorDb("Item (Entry: %u) has too large value in statscount (%u), replace by hardcoded limit (%u).",i,proto->StatsCount,MAX_ITEM_PROTO_STATS); + const_cast<ItemPrototype*>(proto)->StatsCount = MAX_ITEM_PROTO_STATS; + } + + for (uint8 j = 0; j < MAX_ITEM_PROTO_STATS; ++j) + { + // for ItemStatValue != 0 + if (proto->ItemStat[j].ItemStatValue && proto->ItemStat[j].ItemStatType >= MAX_ITEM_MOD) + { + sLog.outErrorDb("Item (Entry: %u) has wrong stat_type%d (%u)",i,j+1,proto->ItemStat[j].ItemStatType); + const_cast<ItemPrototype*>(proto)->ItemStat[j].ItemStatType = 0; + } + + switch (proto->ItemStat[j].ItemStatType) + { + case ITEM_MOD_SPELL_HEALING_DONE: + case ITEM_MOD_SPELL_DAMAGE_DONE: + sLog.outErrorDb("Item (Entry: %u) has deprecated stat_type%d (%u)",i,j+1,proto->ItemStat[j].ItemStatType); + break; + default: + break; + } + } + + for (uint8 j = 0; j < MAX_ITEM_PROTO_DAMAGES; ++j) + { + if (proto->Damage[j].DamageType >= MAX_SPELL_SCHOOL) + { + sLog.outErrorDb("Item (Entry: %u) has wrong dmg_type%d (%u)",i,j+1,proto->Damage[j].DamageType); + const_cast<ItemPrototype*>(proto)->Damage[j].DamageType = 0; + } + } + + // special format + if ((proto->Spells[0].SpellId == 483) || (proto->Spells[0].SpellId == 55884)) + { + // spell_1 + if (proto->Spells[0].SpellTrigger != ITEM_SPELLTRIGGER_ON_USE) + { + sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u) for special learning format",i,0+1,proto->Spells[0].SpellTrigger); + const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[0].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + + // spell_2 have learning spell + if (proto->Spells[1].SpellTrigger != ITEM_SPELLTRIGGER_LEARN_SPELL_ID) + { + sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u) for special learning format.",i,1+1,proto->Spells[1].SpellTrigger); + const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + else if (!proto->Spells[1].SpellId) + { + sLog.outErrorDb("Item (Entry: %u) not has expected spell in spellid_%d in special learning format.",i,1+1); + const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + else if (proto->Spells[1].SpellId != -1) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[1].SpellId); + if (!spellInfo) + { + sLog.outErrorDb("Item (Entry: %u) has wrong (not existing) spell in spellid_%d (%d)",i,1+1,proto->Spells[1].SpellId); + const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + // allowed only in special format + else if ((proto->Spells[1].SpellId == 483) || (proto->Spells[1].SpellId == 55884)) + { + sLog.outErrorDb("Item (Entry: %u) has broken spell in spellid_%d (%d)",i,1+1,proto->Spells[1].SpellId); + const_cast<ItemPrototype*>(proto)->Spells[0].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[1].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + } + + // spell_3*,spell_4*,spell_5* is empty + for (uint8 j = 2; j < MAX_ITEM_PROTO_SPELLS; ++j) + { + if (proto->Spells[j].SpellTrigger != ITEM_SPELLTRIGGER_ON_USE) + { + sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u)",i,j+1,proto->Spells[j].SpellTrigger); + const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[j].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + else if (proto->Spells[j].SpellId != 0) + { + sLog.outErrorDb("Item (Entry: %u) has wrong spell in spellid_%d (%d) for learning special format",i,j+1,proto->Spells[j].SpellId); + const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0; + } + } + } + // normal spell list + else + { + for (uint8 j = 0; j < MAX_ITEM_PROTO_SPELLS; ++j) + { + if (proto->Spells[j].SpellTrigger >= MAX_ITEM_SPELLTRIGGER || proto->Spells[j].SpellTrigger == ITEM_SPELLTRIGGER_LEARN_SPELL_ID) + { + sLog.outErrorDb("Item (Entry: %u) has wrong item spell trigger value in spelltrigger_%d (%u)",i,j+1,proto->Spells[j].SpellTrigger); + const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0; + const_cast<ItemPrototype*>(proto)->Spells[j].SpellTrigger = ITEM_SPELLTRIGGER_ON_USE; + } + + if (proto->Spells[j].SpellId && proto->Spells[j].SpellId != -1) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(proto->Spells[j].SpellId); + if (!spellInfo) + { + sLog.outErrorDb("Item (Entry: %u) has wrong (not existing) spell in spellid_%d (%d)",i,j+1,proto->Spells[j].SpellId); + const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0; + } + // allowed only in special format + else if ((proto->Spells[j].SpellId == 483) || (proto->Spells[j].SpellId == 55884)) + { + sLog.outErrorDb("Item (Entry: %u) has broken spell in spellid_%d (%d)",i,j+1,proto->Spells[j].SpellId); + const_cast<ItemPrototype*>(proto)->Spells[j].SpellId = 0; + } + } + } + } + + if (proto->Bonding >= MAX_BIND_TYPE) + sLog.outErrorDb("Item (Entry: %u) has wrong Bonding value (%u)",i,proto->Bonding); + + if (proto->PageText && !sPageTextStore.LookupEntry<PageText>(proto->PageText)) + sLog.outErrorDb("Item (Entry: %u) has non existing first page (Id:%u)", i,proto->PageText); + + if (proto->LockID && !sLockStore.LookupEntry(proto->LockID)) + sLog.outErrorDb("Item (Entry: %u) has wrong LockID (%u)",i,proto->LockID); + + if (proto->Sheath >= MAX_SHEATHETYPE) + { + sLog.outErrorDb("Item (Entry: %u) has wrong Sheath (%u)",i,proto->Sheath); + const_cast<ItemPrototype*>(proto)->Sheath = SHEATHETYPE_NONE; + } + + if (proto->RandomProperty && !sItemRandomPropertiesStore.LookupEntry(GetItemEnchantMod(proto->RandomProperty))) + { + sLog.outErrorDb("Item (Entry: %u) has unknown (wrong or not listed in `item_enchantment_template`) RandomProperty (%u)",i,proto->RandomProperty); + const_cast<ItemPrototype*>(proto)->RandomProperty = 0; + } + + if (proto->RandomSuffix && !sItemRandomSuffixStore.LookupEntry(GetItemEnchantMod(proto->RandomSuffix))) + { + sLog.outErrorDb("Item (Entry: %u) has wrong RandomSuffix (%u)",i,proto->RandomSuffix); + const_cast<ItemPrototype*>(proto)->RandomSuffix = 0; + } + + if (proto->ItemSet && !sItemSetStore.LookupEntry(proto->ItemSet)) + { + sLog.outErrorDb("Item (Entry: %u) have wrong ItemSet (%u)",i,proto->ItemSet); + const_cast<ItemPrototype*>(proto)->ItemSet = 0; + } + + if (proto->Area && !GetAreaEntryByAreaID(proto->Area)) + sLog.outErrorDb("Item (Entry: %u) has wrong Area (%u)",i,proto->Area); + + if (proto->Map && !sMapStore.LookupEntry(proto->Map)) + sLog.outErrorDb("Item (Entry: %u) has wrong Map (%u)",i,proto->Map); + + if (proto->BagFamily) + { + // check bits + for (uint32 j = 0; j < sizeof(proto->BagFamily)*8; ++j) + { + uint32 mask = 1 << j; + if ((proto->BagFamily & mask) == 0) + continue; + + ItemBagFamilyEntry const* bf = sItemBagFamilyStore.LookupEntry(j+1); + if (!bf) + { + sLog.outErrorDb("Item (Entry: %u) has bag family bit set not listed in ItemBagFamily.dbc, remove bit",i); + const_cast<ItemPrototype*>(proto)->BagFamily &= ~mask; + continue; + } + + if (BAG_FAMILY_MASK_CURRENCY_TOKENS & mask) + { + CurrencyTypesEntry const* ctEntry = sCurrencyTypesStore.LookupEntry(proto->ItemId); + if (!ctEntry) + { + sLog.outErrorDb("Item (Entry: %u) has currency bag family bit set in BagFamily but not listed in CurrencyTypes.dbc, remove bit",i); + const_cast<ItemPrototype*>(proto)->BagFamily &= ~mask; + } + } + } + } + + if (proto->TotemCategory && !sTotemCategoryStore.LookupEntry(proto->TotemCategory)) + sLog.outErrorDb("Item (Entry: %u) has wrong TotemCategory (%u)",i,proto->TotemCategory); + + for (uint8 j = 0; j < MAX_ITEM_PROTO_SOCKETS; ++j) + { + if (proto->Socket[j].Color && (proto->Socket[j].Color & SOCKET_COLOR_ALL) != proto->Socket[j].Color) + { + sLog.outErrorDb("Item (Entry: %u) has wrong socketColor_%d (%u)",i,j+1,proto->Socket[j].Color); + const_cast<ItemPrototype*>(proto)->Socket[j].Color = 0; + } + } + + if (proto->GemProperties && !sGemPropertiesStore.LookupEntry(proto->GemProperties)) + sLog.outErrorDb("Item (Entry: %u) has wrong GemProperties (%u)",i,proto->GemProperties); + + if (proto->FoodType >= MAX_PET_DIET) + { + sLog.outErrorDb("Item (Entry: %u) has wrong FoodType value (%u)",i,proto->FoodType); + const_cast<ItemPrototype*>(proto)->FoodType = 0; + } + + if (proto->ItemLimitCategory && !sItemLimitCategoryStore.LookupEntry(proto->ItemLimitCategory)) + { + sLog.outErrorDb("Item (Entry: %u) has wrong LimitCategory value (%u)",i,proto->ItemLimitCategory); + const_cast<ItemPrototype*>(proto)->ItemLimitCategory = 0; + } + + if (proto->HolidayId && !sHolidaysStore.LookupEntry(proto->HolidayId)) + { + sLog.outErrorDb("Item (Entry: %u) has wrong HolidayId value (%u)", i, proto->HolidayId); + const_cast<ItemPrototype*>(proto)->HolidayId = 0; + } + } + + // check some dbc referecned items (avoid duplicate reports) + std::set<uint32> notFoundOutfit; + for (uint32 i = 1; i < sCharStartOutfitStore.GetNumRows(); ++i) + { + CharStartOutfitEntry const* entry = sCharStartOutfitStore.LookupEntry(i); + if (!entry) + continue; + + for (int j = 0; j < MAX_OUTFIT_ITEMS; ++j) + { + if (entry->ItemId[j] <= 0) + continue; + + uint32 item_id = entry->ItemId[j]; + + if (!GetItemPrototype(item_id)) + notFoundOutfit.insert(item_id); + } + } + + for (std::set<uint32>::const_iterator itr = notFoundOutfit.begin(); itr != notFoundOutfit.end(); ++itr) + sLog.outErrorDb("Item (Entry: %u) not exist in `item_template` but referenced in `CharStartOutfit.dnc`", *itr); +} + +void ObjectMgr::LoadVehicleAccessories() +{ + m_VehicleAccessoryMap.clear(); // needed for reload case + + uint32 count = 0; + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT `entry`,`accessory_entry`,`seat_id`,`minion` FROM `vehicle_accessory`"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 LoadVehicleAccessor. DB table `vehicle_accessory` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 uiEntry = fields[0].GetUInt32(); + uint32 uiAccessory = fields[1].GetUInt32(); + int8 uiSeat = int8(fields[2].GetInt16()); + bool bMinion = fields[3].GetBool(); + + if (!sCreatureStorage.LookupEntry<CreatureInfo>(uiEntry)) + { + sLog.outErrorDb("Table `vehicle_accessory`: creature template entry %u does not exist.", uiEntry); + continue; + } + + if (!sCreatureStorage.LookupEntry<CreatureInfo>(uiAccessory)) + { + sLog.outErrorDb("Table `vehicle_accessory`: Accessory %u does not exist.", uiAccessory); + continue; + } + + m_VehicleAccessoryMap[uiEntry].push_back(VehicleAccessory(uiAccessory, uiSeat, bMinion)); + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u Vehicle Accessories", count); +} + +void ObjectMgr::LoadPetLevelInfo() +{ + // Loading levels data + { + // 0 1 2 3 4 5 6 7 8 9 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT creature_entry, level, hp, mana, str, agi, sta, inte, spi, armor FROM pet_levelstats"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u level pet stats definitions", count); + sLog.outErrorDb("Error loading `pet_levelstats` table or empty table."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 creature_id = fields[0].GetUInt32(); + if (!sCreatureStorage.LookupEntry<CreatureInfo>(creature_id)) + { + sLog.outErrorDb("Wrong creature id %u in `pet_levelstats` table, ignoring.",creature_id); + continue; + } + + uint32 current_level = fields[1].GetUInt32(); + if (current_level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum + sLog.outErrorDb("Wrong (> %u) level %u in `pet_levelstats` table, ignoring.",STRONG_MAX_LEVEL,current_level); + else + { + sLog.outDetail("Unused (> MaxPlayerLevel in Trinityd.conf) level %u in `pet_levelstats` table, ignoring.",current_level); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + else if (current_level < 1) + { + sLog.outErrorDb("Wrong (<1) level %u in `pet_levelstats` table, ignoring.",current_level); + continue; + } + + PetLevelInfo*& pInfoMapEntry = petInfo[creature_id]; + + if (pInfoMapEntry == NULL) + pInfoMapEntry = new PetLevelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)]; + + // data for level 1 stored in [0] array element, ... + PetLevelInfo* pLevelInfo = &pInfoMapEntry[current_level-1]; + + pLevelInfo->health = fields[2].GetUInt16(); + pLevelInfo->mana = fields[3].GetUInt16(); + pLevelInfo->armor = fields[9].GetUInt16(); + + for (int i = 0; i < MAX_STATS; i++) + { + pLevelInfo->stats[i] = fields[i+4].GetUInt16(); + } + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u level pet stats definitions", count); + } + + // Fill gaps and check integrity + for (PetLevelInfoMap::iterator itr = petInfo.begin(); itr != petInfo.end(); ++itr) + { + PetLevelInfo* pInfo = itr->second; + + // fatal error if no level 1 data + if (!pInfo || pInfo[0].health == 0) + { + sLog.outErrorDb("Creature %u does not have pet stats data for Level 1!",itr->first); + exit(1); + } + + // fill level gaps + for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level) + { + if (pInfo[level].health == 0) + { + sLog.outErrorDb("Creature %u has no data for Level %i pet stats data, using data of Level %i.",itr->first,level+1, level); + pInfo[level] = pInfo[level-1]; + } + } + } +} + +PetLevelInfo const* ObjectMgr::GetPetLevelInfo(uint32 creature_id, uint8 level) const +{ + if (level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + level = sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); + + PetLevelInfoMap::const_iterator itr = petInfo.find(creature_id); + if (itr == petInfo.end()) + return NULL; + + return &itr->second[level-1]; // data for level 1 stored in [0] array element, ... +} + +void ObjectMgr::LoadPlayerInfo() +{ + // Load playercreate + { + // 0 1 2 3 4 5 6 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, map, zone, position_x, position_y, position_z FROM playercreateinfo"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u player create definitions", count); + sLog.outErrorDb("Error loading `playercreateinfo` table or empty table."); + exit(1); + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_race = fields[0].GetUInt32(); + uint32 current_class = fields[1].GetUInt32(); + uint32 mapId = fields[2].GetUInt32(); + uint32 areaId = fields[3].GetUInt32(); + float positionX = fields[4].GetFloat(); + float positionY = fields[5].GetFloat(); + float positionZ = fields[6].GetFloat(); + + if (current_race >= MAX_RACES) + { + sLog.outErrorDb("Wrong race %u in `playercreateinfo` table, ignoring.",current_race); + continue; + } + + ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(current_race); + if (!rEntry) + { + sLog.outErrorDb("Wrong race %u in `playercreateinfo` table, ignoring.",current_race); + continue; + } + + if (current_class >= MAX_CLASSES) + { + sLog.outErrorDb("Wrong class %u in `playercreateinfo` table, ignoring.",current_class); + continue; + } + + if (!sChrClassesStore.LookupEntry(current_class)) + { + sLog.outErrorDb("Wrong class %u in `playercreateinfo` table, ignoring.",current_class); + continue; + } + + // accept DB data only for valid position (and non instanceable) + if (!MapManager::IsValidMapCoord(mapId,positionX,positionY,positionZ)) + { + sLog.outErrorDb("Wrong home position for class %u race %u pair in `playercreateinfo` table, ignoring.",current_class,current_race); + continue; + } + + if (sMapStore.LookupEntry(mapId)->Instanceable()) + { + sLog.outErrorDb("Home position in instanceable map for class %u race %u pair in `playercreateinfo` table, ignoring.",current_class,current_race); + continue; + } + + PlayerInfo* pInfo = &playerInfo[current_race][current_class]; + + pInfo->mapId = mapId; + pInfo->areaId = areaId; + pInfo->positionX = positionX; + pInfo->positionY = positionY; + pInfo->positionZ = positionZ; + + pInfo->displayId_m = rEntry->model_m; + pInfo->displayId_f = rEntry->model_f; + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u player create definitions", count); + } + + // Load playercreate items + sLog.outString("Loading Player Create Items Data..."); + { + // 0 1 2 3 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, itemid, amount FROM playercreateinfo_item"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u custom player create items", count); + } + else + { + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_race = fields[0].GetUInt32(); + if (current_race >= MAX_RACES) + { + sLog.outErrorDb("Wrong race %u in `playercreateinfo_item` table, ignoring.",current_race); + continue; + } + + uint32 current_class = fields[1].GetUInt32(); + if (current_class >= MAX_CLASSES) + { + sLog.outErrorDb("Wrong class %u in `playercreateinfo_item` table, ignoring.",current_class); + continue; + } + + PlayerInfo* pInfo = &playerInfo[current_race][current_class]; + + uint32 item_id = fields[2].GetUInt32(); + + if (!GetItemPrototype(item_id)) + { + sLog.outErrorDb("Item id %u (race %u class %u) in `playercreateinfo_item` table but not listed in `item_template`, ignoring.",item_id,current_race,current_class); + continue; + } + + uint32 amount = fields[3].GetUInt32(); + + if (!amount) + { + sLog.outErrorDb("Item id %u (class %u race %u) have amount == 0 in `playercreateinfo_item` table, ignoring.",item_id,current_race,current_class); + continue; + } + + pInfo->item.push_back(PlayerCreateInfoItem(item_id, amount)); + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u custom player create items", count); + } + } + + // Load playercreate spells + sLog.outString("Loading Player Create Spell Data..."); + { + + QueryResult_AutoPtr result = QueryResult_AutoPtr(NULL); + if (sWorld.getConfig(CONFIG_START_ALL_SPELLS)) + result = WorldDatabase.Query("SELECT race, class, Spell, Active FROM playercreateinfo_spell_custom"); + else + result = WorldDatabase.Query("SELECT race, class, Spell FROM playercreateinfo_spell"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u player create spells", count); + sLog.outErrorDb("Error loading player starting spells or empty table."); + } + else + { + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_race = fields[0].GetUInt32(); + if (current_race >= MAX_RACES) + { + sLog.outErrorDb("Wrong race %u in `playercreateinfo_spell` table, ignoring.",current_race); + continue; + } + + uint32 current_class = fields[1].GetUInt32(); + if (current_class >= MAX_CLASSES) + { + sLog.outErrorDb("Wrong class %u in `playercreateinfo_spell` table, ignoring.",current_class); + continue; + } + + if (!current_race || !current_class) + { + uint32 min_race = current_race ? current_race : 1; + uint32 max_race = current_race ? current_race + 1 : MAX_RACES; + uint32 min_class = current_class ? current_class : 1; + uint32 max_class = current_class ? current_class + 1 : MAX_CLASSES; + for (uint32 r = min_race; r < max_race; ++r) + for (uint32 c = min_class; c < max_class; ++c) + playerInfo[r][c].spell.push_back(fields[2].GetUInt32()); + } + else + playerInfo[current_race][current_class].spell.push_back(fields[2].GetUInt32()); + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u player create spells", count); + } + } + + // Load playercreate actions + sLog.outString("Loading Player Create Action Data..."); + { + // 0 1 2 3 4 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, button, action, type FROM playercreateinfo_action"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u player create actions", count); + sLog.outErrorDb("Error loading `playercreateinfo_action` table or empty table."); + } + else + { + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_race = fields[0].GetUInt32(); + if (current_race >= MAX_RACES) + { + sLog.outErrorDb("Wrong race %u in `playercreateinfo_action` table, ignoring.",current_race); + continue; + } + + uint32 current_class = fields[1].GetUInt32(); + if (current_class >= MAX_CLASSES) + { + sLog.outErrorDb("Wrong class %u in `playercreateinfo_action` table, ignoring.",current_class); + continue; + } + + PlayerInfo* pInfo = &playerInfo[current_race][current_class]; + pInfo->action.push_back(PlayerCreateInfoAction(fields[2].GetUInt8(),fields[3].GetUInt32(),fields[4].GetUInt8())); + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u player create actions", count); + } + } + + // Loading levels data (class only dependent) + sLog.outString("Loading Player Create Level HP/Mana Data..."); + { + // 0 1 2 3 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT class, level, basehp, basemana FROM player_classlevelstats"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u level health/mana definitions", count); + sLog.outErrorDb("Error loading `player_classlevelstats` table or empty table."); + exit(1); + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_class = fields[0].GetUInt32(); + if (current_class >= MAX_CLASSES) + { + sLog.outErrorDb("Wrong class %u in `player_classlevelstats` table, ignoring.",current_class); + continue; + } + + uint8 current_level = fields[1].GetUInt8(); + if (current_level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum + sLog.outErrorDb("Wrong (> %u) level %u in `player_classlevelstats` table, ignoring.",STRONG_MAX_LEVEL,current_level); + else + { + sLog.outDetail("Unused (> MaxPlayerLevel in Trinityd.conf) level %u in `player_classlevelstats` table, ignoring.",current_level); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + + PlayerClassInfo* pClassInfo = &playerClassInfo[current_class]; + + if (!pClassInfo->levelInfo) + pClassInfo->levelInfo = new PlayerClassLevelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)]; + + PlayerClassLevelInfo* pClassLevelInfo = &pClassInfo->levelInfo[current_level-1]; + + pClassLevelInfo->basehealth = fields[2].GetUInt16(); + pClassLevelInfo->basemana = fields[3].GetUInt16(); + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u level health/mana definitions", count); + } + + // Fill gaps and check integrity + for (int class_ = 0; class_ < MAX_CLASSES; ++class_) + { + // skip non existed classes + if (!sChrClassesStore.LookupEntry(class_)) + continue; + + PlayerClassInfo* pClassInfo = &playerClassInfo[class_]; + + // fatal error if no level 1 data + if (!pClassInfo->levelInfo || pClassInfo->levelInfo[0].basehealth == 0) + { + sLog.outErrorDb("Class %i Level 1 does not have health/mana data!",class_); + exit(1); + } + + // fill level gaps + for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level) + { + if (pClassInfo->levelInfo[level].basehealth == 0) + { + sLog.outErrorDb("Class %i Level %i does not have health/mana data. Using stats data of level %i.",class_,level+1, level); + pClassInfo->levelInfo[level] = pClassInfo->levelInfo[level-1]; + } + } + } + + // Loading levels data (class/race dependent) + sLog.outString("Loading Player Create Level Stats Data..."); + { + // 0 1 2 3 4 5 6 7 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT race, class, level, str, agi, sta, inte, spi FROM player_levelstats"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u level stats definitions", count); + sLog.outErrorDb("Error loading `player_levelstats` table or empty table."); + exit(1); + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_race = fields[0].GetUInt32(); + if (current_race >= MAX_RACES) + { + sLog.outErrorDb("Wrong race %u in `player_levelstats` table, ignoring.",current_race); + continue; + } + + uint32 current_class = fields[1].GetUInt32(); + if (current_class >= MAX_CLASSES) + { + sLog.outErrorDb("Wrong class %u in `player_levelstats` table, ignoring.",current_class); + continue; + } + + uint32 current_level = fields[2].GetUInt32(); + if (current_level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum + sLog.outErrorDb("Wrong (> %u) level %u in `player_levelstats` table, ignoring.",STRONG_MAX_LEVEL,current_level); + else + { + sLog.outDetail("Unused (> MaxPlayerLevel in Trinityd.conf) level %u in `player_levelstats` table, ignoring.",current_level); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + + PlayerInfo* pInfo = &playerInfo[current_race][current_class]; + + if (!pInfo->levelInfo) + pInfo->levelInfo = new PlayerLevelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)]; + + PlayerLevelInfo* pLevelInfo = &pInfo->levelInfo[current_level-1]; + + for (int i = 0; i < MAX_STATS; i++) + { + pLevelInfo->stats[i] = fields[i+3].GetUInt8(); + } + + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u level stats definitions", count); + } + + // Fill gaps and check integrity + for (int race = 0; race < MAX_RACES; ++race) + { + // skip non existed races + if (!sChrRacesStore.LookupEntry(race)) + continue; + + for (int class_ = 0; class_ < MAX_CLASSES; ++class_) + { + // skip non existed classes + if (!sChrClassesStore.LookupEntry(class_)) + continue; + + PlayerInfo* pInfo = &playerInfo[race][class_]; + + // skip non loaded combinations + if (!pInfo->displayId_m || !pInfo->displayId_f) + continue; + + // skip expansion races if not playing with expansion + if (sWorld.getConfig(CONFIG_EXPANSION) < 1 && (race == RACE_BLOODELF || race == RACE_DRAENEI)) + continue; + + // skip expansion classes if not playing with expansion + if (sWorld.getConfig(CONFIG_EXPANSION) < 2 && class_ == CLASS_DEATH_KNIGHT) + continue; + + // fatal error if no level 1 data + if (!pInfo->levelInfo || pInfo->levelInfo[0].stats[0] == 0) + { + sLog.outErrorDb("Race %i Class %i Level 1 does not have stats data!",race,class_); + exit(1); + } + + // fill level gaps + for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level) + { + if (pInfo->levelInfo[level].stats[0] == 0) + { + sLog.outErrorDb("Race %i Class %i Level %i does not have stats data. Using stats data of level %i.",race,class_,level+1, level); + pInfo->levelInfo[level] = pInfo->levelInfo[level-1]; + } + } + } + } + + // Loading xp per level data + sLog.outString("Loading Player Create XP Data..."); + { + mPlayerXPperLevel.resize(sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)); + for (uint8 level = 0; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level) + mPlayerXPperLevel[level] = 0; + + // 0 1 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT lvl, xp_for_next_level FROM player_xp_for_level"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + + sLog.outString(); + sLog.outString(">> Loaded %u xp for level definitions", count); + sLog.outErrorDb("Error loading `player_xp_for_level` table or empty table."); + exit(1); + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field* fields = result->Fetch(); + + uint32 current_level = fields[0].GetUInt32(); + uint32 current_xp = fields[1].GetUInt32(); + + if (current_level >= sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + { + if (current_level > STRONG_MAX_LEVEL) // hardcoded level maximum + sLog.outErrorDb("Wrong (> %u) level %u in `player_xp_for_level` table, ignoring.", STRONG_MAX_LEVEL,current_level); + else + { + sLog.outDetail("Unused (> MaxPlayerLevel in TrinityCore.conf) level %u in `player_xp_for_levels` table, ignoring.",current_level); + ++count; // make result loading percent "expected" correct in case disabled detail mode for example. + } + continue; + } + //PlayerXPperLevel + mPlayerXPperLevel[current_level] = current_xp; + bar.step(); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u xp for level definitions", count); + } + + // fill level gaps + for (uint8 level = 1; level < sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); ++level) + { + if (mPlayerXPperLevel[level] == 0) + { + sLog.outErrorDb("Level %i does not have XP for level data. Using data of level [%i] + 100.",level+1, level); + mPlayerXPperLevel[level] = mPlayerXPperLevel[level-1]+100; + } + } +} + +void ObjectMgr::GetPlayerClassLevelInfo(uint32 class_, uint8 level, PlayerClassLevelInfo* info) const +{ + if (level < 1 || class_ >= MAX_CLASSES) + return; + + PlayerClassInfo const* pInfo = &playerClassInfo[class_]; + + if (level > sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + level = sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL); + + *info = pInfo->levelInfo[level-1]; +} + +void ObjectMgr::GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, PlayerLevelInfo* info) const +{ + if (level < 1 || race >= MAX_RACES || class_ >= MAX_CLASSES) + return; + + PlayerInfo const* pInfo = &playerInfo[race][class_]; + if (pInfo->displayId_m == 0 || pInfo->displayId_f == 0) + return; + + if (level <= sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)) + *info = pInfo->levelInfo[level-1]; + else + BuildPlayerLevelInfo(race,class_,level,info); +} + +void ObjectMgr::BuildPlayerLevelInfo(uint8 race, uint8 _class, uint8 level, PlayerLevelInfo* info) const +{ + // base data (last known level) + *info = playerInfo[race][_class].levelInfo[sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)-1]; + + // if conversion from uint32 to uint8 causes unexpected behaviour, change lvl to uint32 + for (uint8 lvl = sWorld.getConfig(CONFIG_MAX_PLAYER_LEVEL)-1; lvl < level; ++lvl) + { + switch(_class) + { + case CLASS_WARRIOR: + info->stats[STAT_STRENGTH] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_STAMINA] += (lvl > 23 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_AGILITY] += (lvl > 36 ? 1: (lvl > 6 && (lvl%2) ? 1: 0)); + info->stats[STAT_INTELLECT] += (lvl > 9 && !(lvl%2) ? 1: 0); + info->stats[STAT_SPIRIT] += (lvl > 9 && !(lvl%2) ? 1: 0); + break; + case CLASS_PALADIN: + info->stats[STAT_STRENGTH] += (lvl > 3 ? 1: 0); + info->stats[STAT_STAMINA] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 7 && !(lvl%2) ? 1: 0)); + info->stats[STAT_INTELLECT] += (lvl > 6 && (lvl%2) ? 1: 0); + info->stats[STAT_SPIRIT] += (lvl > 7 ? 1: 0); + break; + case CLASS_HUNTER: + info->stats[STAT_STRENGTH] += (lvl > 4 ? 1: 0); + info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0); + info->stats[STAT_AGILITY] += (lvl > 33 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_INTELLECT] += (lvl > 8 && (lvl%2) ? 1: 0); + info->stats[STAT_SPIRIT] += (lvl > 38 ? 1: (lvl > 9 && !(lvl%2) ? 1: 0)); + break; + case CLASS_ROGUE: + info->stats[STAT_STRENGTH] += (lvl > 5 ? 1: 0); + info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0); + info->stats[STAT_AGILITY] += (lvl > 16 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_INTELLECT] += (lvl > 8 && !(lvl%2) ? 1: 0); + info->stats[STAT_SPIRIT] += (lvl > 38 ? 1: (lvl > 9 && !(lvl%2) ? 1: 0)); + break; + case CLASS_PRIEST: + info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0); + info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0); + info->stats[STAT_AGILITY] += (lvl > 38 ? 1: (lvl > 8 && (lvl%2) ? 1: 0)); + info->stats[STAT_INTELLECT] += (lvl > 22 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_SPIRIT] += (lvl > 3 ? 1: 0); + break; + case CLASS_SHAMAN: + info->stats[STAT_STRENGTH] += (lvl > 34 ? 1: (lvl > 6 && (lvl%2) ? 1: 0)); + info->stats[STAT_STAMINA] += (lvl > 4 ? 1: 0); + info->stats[STAT_AGILITY] += (lvl > 7 && !(lvl%2) ? 1: 0); + info->stats[STAT_INTELLECT] += (lvl > 5 ? 1: 0); + info->stats[STAT_SPIRIT] += (lvl > 4 ? 1: 0); + break; + case CLASS_MAGE: + info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0); + info->stats[STAT_STAMINA] += (lvl > 5 ? 1: 0); + info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0); + info->stats[STAT_INTELLECT] += (lvl > 24 ? 2: (lvl > 1 ? 1: 0)); + info->stats[STAT_SPIRIT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0)); + break; + case CLASS_WARLOCK: + info->stats[STAT_STRENGTH] += (lvl > 9 && !(lvl%2) ? 1: 0); + info->stats[STAT_STAMINA] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0)); + info->stats[STAT_AGILITY] += (lvl > 9 && !(lvl%2) ? 1: 0); + info->stats[STAT_INTELLECT] += (lvl > 33 ? 2: (lvl > 2 ? 1: 0)); + info->stats[STAT_SPIRIT] += (lvl > 38 ? 2: (lvl > 3 ? 1: 0)); + break; + case CLASS_DRUID: + info->stats[STAT_STRENGTH] += (lvl > 38 ? 2: (lvl > 6 && (lvl%2) ? 1: 0)); + info->stats[STAT_STAMINA] += (lvl > 32 ? 2: (lvl > 4 ? 1: 0)); + info->stats[STAT_AGILITY] += (lvl > 38 ? 2: (lvl > 8 && (lvl%2) ? 1: 0)); + info->stats[STAT_INTELLECT] += (lvl > 38 ? 3: (lvl > 4 ? 1: 0)); + info->stats[STAT_SPIRIT] += (lvl > 38 ? 3: (lvl > 5 ? 1: 0)); + } + } +} + +void ObjectMgr::LoadGuilds() +{ + Guild *newGuild; + uint32 count = 0; + + // 0 1 2 3 4 5 6 + QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT guild.guildid,guild.name,leaderguid,EmblemStyle,EmblemColor,BorderStyle,BorderColor," + // 7 8 9 10 11 12 + "BackgroundColor,info,motd,createdate,BankMoney,COUNT(guild_bank_tab.guildid) " + "FROM guild LEFT JOIN guild_bank_tab ON guild.guildid = guild_bank_tab.guildid GROUP BY guild.guildid ORDER BY guildid ASC"); + + if (!result) + { + + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u guild definitions", count); + return; + } + + // load guild ranks + // 0 1 2 3 4 + QueryResult_AutoPtr guildRanksResult = CharacterDatabase.Query("SELECT guildid,rid,rname,rights,BankMoneyPerDay FROM guild_rank ORDER BY guildid ASC, rid ASC"); + + // load guild members + // 0 1 2 3 4 5 6 + QueryResult_AutoPtr guildMembersResult = CharacterDatabase.Query("SELECT guildid,guild_member.guid,rank,pnote,offnote,BankResetTimeMoney,BankRemMoney," + // 7 8 9 10 11 12 + "BankResetTimeTab0,BankRemSlotsTab0,BankResetTimeTab1,BankRemSlotsTab1,BankResetTimeTab2,BankRemSlotsTab2," + // 13 14 15 16 17 18 + "BankResetTimeTab3,BankRemSlotsTab3,BankResetTimeTab4,BankRemSlotsTab4,BankResetTimeTab5,BankRemSlotsTab5," + // 19 20 21 22 23 + "characters.name, characters.level, characters.class, characters.zone, characters.logout_time " + "FROM guild_member LEFT JOIN characters ON characters.guid = guild_member.guid ORDER BY guildid ASC"); + + // load guild bank tab rights + // 0 1 2 3 4 + QueryResult_AutoPtr guildBankTabRightsResult = CharacterDatabase.Query("SELECT guildid,TabId,rid,gbright,SlotPerDay FROM guild_bank_right ORDER BY guildid ASC, TabId ASC"); + + barGoLink bar(result->GetRowCount()); + + do + { + //Field *fields = result->Fetch(); + + bar.step(); + ++count; + + newGuild = new Guild; + if (!newGuild->LoadGuildFromDB(result) || + !newGuild->LoadRanksFromDB(guildRanksResult) || + !newGuild->LoadMembersFromDB(guildMembersResult) || + !newGuild->LoadBankRightsFromDB(guildBankTabRightsResult) || + !newGuild->CheckGuildStructure() +) + { + newGuild->Disband(); + delete newGuild; + continue; + } + newGuild->LoadGuildEventLogFromDB(); + newGuild->LoadGuildBankEventLogFromDB(); + newGuild->LoadGuildBankFromDB(); + AddGuild(newGuild); + + } while (result->NextRow()); + + //delete unused LogGuid records in guild_eventlog and guild_bank_eventlog table + //you can comment these lines if you don't plan to change CONFIG_GUILD_EVENT_LOG_COUNT and CONFIG_GUILD_BANK_EVENT_LOG_COUNT + CharacterDatabase.PQuery("DELETE FROM guild_eventlog WHERE LogGuid > '%u'", sWorld.getConfig(CONFIG_GUILD_EVENT_LOG_COUNT)); + CharacterDatabase.PQuery("DELETE FROM guild_bank_eventlog WHERE LogGuid > '%u'", sWorld.getConfig(CONFIG_GUILD_BANK_EVENT_LOG_COUNT)); + + sLog.outString(); + sLog.outString(">> Loaded %u guild definitions", count); +} + +void ObjectMgr::LoadArenaTeams() +{ + uint32 count = 0; + + // 0 1 2 3 4 5 + QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT arena_team.arenateamid,name,captainguid,type,BackgroundColor,EmblemStyle," + // 6 7 8 9 10 11 12 13 14 + "EmblemColor,BorderStyle,BorderColor, rating,games,wins,played,wins2,rank " + "FROM arena_team LEFT JOIN arena_team_stats ON arena_team.arenateamid = arena_team_stats.arenateamid ORDER BY arena_team.arenateamid ASC"); + + if (!result) + { + + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u arenateam definitions", count); + return; + } + + // load arena_team members + QueryResult_AutoPtr arenaTeamMembersResult = CharacterDatabase.Query( + // 0 1 2 3 4 5 6 7 8 + "SELECT arenateamid,member.guid,played_week,wons_week,played_season,wons_season,personal_rating,name,class " + "FROM arena_team_member member LEFT JOIN characters chars on member.guid = chars.guid ORDER BY member.arenateamid ASC"); + + barGoLink bar(result->GetRowCount()); + + do + { + //Field *fields = result->Fetch(); + + bar.step(); + ++count; + + ArenaTeam *newArenaTeam = new ArenaTeam; + if (!newArenaTeam->LoadArenaTeamFromDB(result) || + !newArenaTeam->LoadMembersFromDB(arenaTeamMembersResult)) + { + newArenaTeam->Disband(NULL); + delete newArenaTeam; + continue; + } + AddArenaTeam(newArenaTeam); + }while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u arenateam definitions", count); +} + +void ObjectMgr::LoadGroups() +{ + Group *group = NULL; + Field *fields = NULL; + uint64 groupGuid = 0; + uint32 count = 0; + + // Consistency cleaning before load to avoid having to do some checks later + // Delete all members that does not exist + CharacterDatabase.PExecute("DELETE FROM group_member WHERE NOT EXISTS (SELECT guid FROM characters WHERE guid=memberGuid)"); + // Delete all groups whose leader does not exist + CharacterDatabase.PExecute("DELETE FROM groups WHERE NOT EXISTS (SELECT guid FROM characters WHERE guid=leaderGuid)"); + // Delete all groups with less than 2 members + CharacterDatabase.PExecute("DELETE FROM groups WHERE guid NOT IN (SELECT guid FROM group_member GROUP BY guid HAVING COUNT(guid) > 1)"); + // Delete all rows from group_member or group_instance with no group + CharacterDatabase.PExecute("DELETE FROM group_member WHERE guid NOT IN (SELECT guid FROM groups)"); + CharacterDatabase.PExecute("DELETE FROM group_instance WHERE guid NOT IN (SELECT guid FROM groups)"); + + // ----------------------- Load Group definitions + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT leaderGuid, lootMethod, looterGuid, lootThreshold, icon1, icon2, icon3, icon4, icon5, icon6, icon7, icon8, groupType, difficulty, raiddifficulty, guid FROM groups"); + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded 0 group definitions"); + return; + } + + barGoLink bar(result->GetRowCount()); + do + { + bar.step(); + fields = result->Fetch(); + ++count; + group = new Group; + groupGuid = MAKE_NEW_GUID(fields[15].GetUInt32(),0,HIGHGUID_GROUP); + group->LoadGroupFromDB(groupGuid, result, false); + // group load will never be false (we have run consistency sql's before loading) + AddGroup(group); + }while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u group definitions", count); + + // ----------------------- Load member + // 0 1 2 3 + result = CharacterDatabase.Query("SELECT guid, memberGuid, memberFlags, subgroup FROM group_member ORDER BY guid"); + if (!result) + { + barGoLink bar2(1); + bar2.step(); + sLog.outString(); + sLog.outString(">> Loaded 0 group members"); + return; + } + + barGoLink bar2(result->GetRowCount()); + uint32 groupLowGuid = 0; + count = 0; + do + { + bar2.step(); + fields = result->Fetch(); + + if (groupLowGuid != fields[0].GetUInt32()) + { + groupLowGuid = fields[0].GetUInt32(); + groupGuid = MAKE_NEW_GUID(groupLowGuid, 0, HIGHGUID_GROUP); + group = GetGroupByGUID(groupGuid); + // group will never be NULL (we have run consistency sql's before loading) + } + group->LoadMemberFromDB(fields[1].GetUInt32(), fields[2].GetUInt8(), fields[3].GetUInt8()); + ++count; + }while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u group members", count); + + + // ----------------------- Load instance save + // 0 1 2 3 4 5 + result = CharacterDatabase.Query("SELECT guid, map, instance, permanent, difficulty, resettime, " + // 6 + "(SELECT COUNT(1) FROM groups JOIN character_instance ON leaderGuid = groups.guid WHERE instance = group_instance.instance AND permanent = 1 LIMIT 1) " + "FROM group_instance LEFT JOIN instance ON instance = id ORDER BY guid"); + + if (!result) + { + barGoLink bar2(1); + bar2.step(); + sLog.outString(); + sLog.outString(">> Loaded 0 group-instance saves"); + return; + } + + barGoLink bar3(result->GetRowCount()); + count = 0; + do + { + bar3.step(); + fields = result->Fetch(); + groupGuid = MAKE_NEW_GUID(fields[0].GetUInt32(), 0, HIGHGUID_GROUP); + group = GetGroupByGUID(groupGuid); + // group will never be NULL (we have run consistency sql's before loading) + + MapEntry const* mapEntry = sMapStore.LookupEntry(fields[1].GetUInt32()); + if (!mapEntry || !mapEntry->IsDungeon()) + { + sLog.outErrorDb("Incorrect entry in group_instance table : no dungeon map %d", fields[1].GetUInt32()); + continue; + } + + uint32 diff = fields[4].GetUInt8(); + if (diff >= (mapEntry->IsRaid() ? MAX_RAID_DIFFICULTY : MAX_DUNGEON_DIFFICULTY)) + { + sLog.outErrorDb("Wrong dungeon difficulty use in group_instance table: %d", diff + 1); + diff = 0; // default for both difficaly types + } + + InstanceSave *save = sInstanceSaveManager.AddInstanceSave(mapEntry->MapID, fields[2].GetUInt32(), Difficulty(diff), time_t(fields[5].GetUInt64()), fields[6].GetBool(), true); + group->BindToInstance(save, fields[3].GetBool(), true); + ++count; + }while (result->NextRow()); + sLog.outString(); + sLog.outString(">> Loaded %u group-instance saves", count); +} + +void ObjectMgr::LoadQuests() +{ + // For reload case + for (QuestMap::const_iterator itr=mQuestTemplates.begin(); itr != mQuestTemplates.end(); ++itr) + delete itr->second; + mQuestTemplates.clear(); + + mExclusiveQuestGroups.clear(); + + // 0 1 2 3 4 5 6 7 8 9 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, Method, ZoneOrSort, SkillOrClassMask, MinLevel, MaxLevel, QuestLevel, Type, RequiredRaces, RequiredSkillValue," + // 10 11 12 13 14 15 16 17 18 19 + "RepObjectiveFaction, RepObjectiveValue, RepObjectiveFaction2, RepObjectiveValue2, RequiredMinRepFaction, RequiredMinRepValue, RequiredMaxRepFaction, RequiredMaxRepValue, SuggestedPlayers, LimitTime," + // 20 21 22 23 24 25 26 27 28 29 30 31 32 33 + "QuestFlags, SpecialFlags, CharTitleId, PlayersSlain, BonusTalents, RewardArenaPoints, PrevQuestId, NextQuestId, ExclusiveGroup, NextQuestInChain, RewXPId, SrcItemId, SrcItemCount, SrcSpell," + // 34 35 36 37 38 39 40 41 42 43 44 + "Title, Details, Objectives, OfferRewardText, RequestItemsText, EndText, CompletedText, ObjectiveText1, ObjectiveText2, ObjectiveText3, ObjectiveText4," + // 45 46 47 48 49 50 51 52 53 54 55 56 + "ReqItemId1, ReqItemId2, ReqItemId3, ReqItemId4, ReqItemId5, ReqItemId6, ReqItemCount1, ReqItemCount2, ReqItemCount3, ReqItemCount4, ReqItemCount5, ReqItemCount6," + // 57 58 59 60 61 62 63 64 + "ReqSourceId1, ReqSourceId2, ReqSourceId3, ReqSourceId4, ReqSourceCount1, ReqSourceCount2, ReqSourceCount3, ReqSourceCount4," + // 65 66 67 68 69 70 71 72 + "ReqCreatureOrGOId1, ReqCreatureOrGOId2, ReqCreatureOrGOId3, ReqCreatureOrGOId4, ReqCreatureOrGOCount1, ReqCreatureOrGOCount2, ReqCreatureOrGOCount3, ReqCreatureOrGOCount4," + // 73 74 75 76 + "ReqSpellCast1, ReqSpellCast2, ReqSpellCast3, ReqSpellCast4," + // 77 78 79 80 81 82 + "RewChoiceItemId1, RewChoiceItemId2, RewChoiceItemId3, RewChoiceItemId4, RewChoiceItemId5, RewChoiceItemId6," + // 83 84 85 86 87 88 + "RewChoiceItemCount1, RewChoiceItemCount2, RewChoiceItemCount3, RewChoiceItemCount4, RewChoiceItemCount5, RewChoiceItemCount6," + // 89 90 91 92 93 94 95 96 + "RewItemId1, RewItemId2, RewItemId3, RewItemId4, RewItemCount1, RewItemCount2, RewItemCount3, RewItemCount4," + // 97 98 99 100 101 102 103 104 105 106 + "RewRepFaction1, RewRepFaction2, RewRepFaction3, RewRepFaction4, RewRepFaction5, RewRepValueId1, RewRepValueId2, RewRepValueId3, RewRepValueId4, RewRepValueId5," + // 107 108 109 110 111 + "RewRepValue1, RewRepValue2, RewRepValue3, RewRepValue4, RewRepValue5," + // 112 113 114 115 116 117 118 119 120 121 122 123 + "RewHonorAddition, RewHonorMultiplier, RewOrReqMoney, RewMoneyMaxLevel, RewSpell, RewSpellCast, RewMailTemplateId, RewMailDelaySecs, PointMapId, PointX, PointY, PointOpt," + // 124 125 126 127 128 129 130 131 + "DetailsEmote1, DetailsEmote2, DetailsEmote3, DetailsEmote4, DetailsEmoteDelay1, DetailsEmoteDelay2, DetailsEmoteDelay3, DetailsEmoteDelay4," + // 132 133 134 135 136 137 + "IncompleteEmote, CompleteEmote, OfferRewardEmote1, OfferRewardEmote2, OfferRewardEmote3, OfferRewardEmote4," + // 138 139 140 141 + "OfferRewardEmoteDelay1, OfferRewardEmoteDelay2, OfferRewardEmoteDelay3, OfferRewardEmoteDelay4," + // 142 143 + "StartScript, CompleteScript" + " FROM quest_template"); + if (result == NULL) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded 0 quests definitions"); + sLog.outErrorDb("`quest_template` table is empty!"); + return; + } + + // create multimap previous quest for each existed quest + // some quests can have many previous maps set by NextQuestId in previous quest + // for example set of race quests can lead to single not race specific quest + barGoLink bar(result->GetRowCount()); + do + { + bar.step(); + Field *fields = result->Fetch(); + + Quest * newQuest = new Quest(fields); + mQuestTemplates[newQuest->GetQuestId()] = newQuest; + } while (result->NextRow()); + + std::map<uint32,uint32> usedMailTemplates; + + // Post processing + for (QuestMap::iterator iter = mQuestTemplates.begin(); iter != mQuestTemplates.end(); ++iter) + { + Quest * qinfo = iter->second; + + // additional quest integrity checks (GO, creature_template and item_template must be loaded already) + + if (qinfo->GetQuestMethod() >= 3) + { + sLog.outErrorDb("Quest %u has `Method` = %u, expected values are 0, 1 or 2.",qinfo->GetQuestId(),qinfo->GetQuestMethod()); + } + + if (qinfo->QuestFlags & ~QUEST_TRINITY_FLAGS_DB_ALLOWED) + { + sLog.outErrorDb("Quest %u has `SpecialFlags` = %u > max allowed value. Correct `SpecialFlags` to value <= %u", + qinfo->GetQuestId(),qinfo->QuestFlags >> 20, QUEST_TRINITY_FLAGS_DB_ALLOWED >> 20); + qinfo->QuestFlags &= QUEST_TRINITY_FLAGS_DB_ALLOWED; + } + + if (qinfo->QuestFlags & QUEST_FLAGS_DAILY && qinfo->QuestFlags & QUEST_FLAGS_WEEKLY) + { + sLog.outErrorDb("Weekly Quest %u is marked as daily quest in `QuestFlags`, removed daily flag.",qinfo->GetQuestId()); + qinfo->QuestFlags &= ~QUEST_FLAGS_DAILY; + } + + if (qinfo->QuestFlags & QUEST_FLAGS_DAILY) + { + if (!(qinfo->QuestFlags & QUEST_TRINITY_FLAGS_REPEATABLE)) + { + sLog.outErrorDb("Daily Quest %u not marked as repeatable in `SpecialFlags`, added.",qinfo->GetQuestId()); + qinfo->QuestFlags |= QUEST_TRINITY_FLAGS_REPEATABLE; + } + } + + if (qinfo->QuestFlags & QUEST_FLAGS_WEEKLY) + { + if (!(qinfo->QuestFlags & QUEST_TRINITY_FLAGS_REPEATABLE)) + { + sLog.outErrorDb("Weekly Quest %u not marked as repeatable in `SpecialFlags`, added.",qinfo->GetQuestId()); + qinfo->QuestFlags |= QUEST_TRINITY_FLAGS_REPEATABLE; + } + } + + if (qinfo->QuestFlags & QUEST_FLAGS_AUTO_REWARDED) + { + // at auto-reward can be rewarded only RewChoiceItemId[0] + for (int j = 1; j < QUEST_REWARD_CHOICES_COUNT; ++j ) + { + if (uint32 id = qinfo->RewChoiceItemId[j]) + { + sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = %u but item from `RewChoiceItemId%d` can't be rewarded with quest flag QUEST_FLAGS_AUTO_REWARDED.", + qinfo->GetQuestId(),j+1,id,j+1); + // no changes, quest ignore this data + } + } + } + + // client quest log visual (area case) + if (qinfo->ZoneOrSort > 0) + { + if (!GetAreaEntryByAreaID(qinfo->ZoneOrSort)) + { + sLog.outErrorDb("Quest %u has `ZoneOrSort` = %u (zone case) but zone with this id does not exist.", + qinfo->GetQuestId(),qinfo->ZoneOrSort); + // no changes, quest not dependent from this value but can have problems at client + } + } + // client quest log visual (sort case) + if (qinfo->ZoneOrSort < 0) + { + QuestSortEntry const* qSort = sQuestSortStore.LookupEntry(-int32(qinfo->ZoneOrSort)); + if (!qSort) + { + sLog.outErrorDb("Quest %u has `ZoneOrSort` = %i (sort case) but quest sort with this id does not exist.", + qinfo->GetQuestId(),qinfo->ZoneOrSort); + // no changes, quest not dependent from this value but can have problems at client (note some may be 0, we must allow this so no check) + } + //check SkillOrClass value (class case). + if (ClassByQuestSort(-int32(qinfo->ZoneOrSort))) + { + // SkillOrClass should not have class case when class case already set in ZoneOrSort. + if (qinfo->SkillOrClassMask < 0) + { + sLog.outErrorDb("Quest %u has `ZoneOrSort` = %i (class sort case) and `SkillOrClassMask` = %i (class case), redundant.", + qinfo->GetQuestId(),qinfo->ZoneOrSort,qinfo->SkillOrClassMask); + } + } + //check for proper SkillOrClass value (skill case) + if (int32 skill_id = SkillByQuestSort(-int32(qinfo->ZoneOrSort))) + { + // skill is positive value in SkillOrClass + if (qinfo->SkillOrClassMask != skill_id) + { + sLog.outErrorDb("Quest %u has `ZoneOrSort` = %i (skill sort case) but `SkillOrClassMask` does not have a corresponding value (%i).", + qinfo->GetQuestId(),qinfo->ZoneOrSort,skill_id); + //override, and force proper value here? + } + } + } + + // SkillOrClassMask (class case) + if (qinfo->SkillOrClassMask < 0) + { + if (!(-int32(qinfo->SkillOrClassMask) & CLASSMASK_ALL_PLAYABLE)) + { + sLog.outErrorDb("Quest %u has `SkillOrClassMask` = %i (class case) but classmask does not have valid class", + qinfo->GetQuestId(),qinfo->SkillOrClassMask); + } + } + // SkillOrClassMask (skill case) + if (qinfo->SkillOrClassMask > 0) + { + if (!sSkillLineStore.LookupEntry(qinfo->SkillOrClassMask)) + { + sLog.outErrorDb("Quest %u has `SkillOrClass` = %u (skill case) but skill (%i) does not exist", + qinfo->GetQuestId(),qinfo->SkillOrClassMask,qinfo->SkillOrClassMask); + } + } + + if (qinfo->RequiredSkillValue) + { + if (qinfo->RequiredSkillValue > sWorld.GetConfigMaxSkillValue()) + { + sLog.outErrorDb("Quest %u has `RequiredSkillValue` = %u but max possible skill is %u, quest can't be done.", + qinfo->GetQuestId(),qinfo->RequiredSkillValue,sWorld.GetConfigMaxSkillValue()); + // no changes, quest can't be done for this requirement + } + + if (qinfo->SkillOrClassMask <= 0) + { + sLog.outErrorDb("Quest %u has `RequiredSkillValue` = %u but `SkillOrClass` = %i (class case), value ignored.", + qinfo->GetQuestId(),qinfo->RequiredSkillValue,qinfo->SkillOrClassMask); + // no changes, quest can't be done for this requirement (fail at wrong skill id) + } + } + // else Skill quests can have 0 skill level, this is ok + + if (qinfo->RepObjectiveFaction2 && !sFactionStore.LookupEntry(qinfo->RepObjectiveFaction2)) + { + sLog.outErrorDb("Quest %u has `RepObjectiveFaction2` = %u but faction template %u does not exist, quest can't be done.", + qinfo->GetQuestId(),qinfo->RepObjectiveFaction2,qinfo->RepObjectiveFaction2); + // no changes, quest can't be done for this requirement + } + + if (qinfo->RepObjectiveFaction && !sFactionStore.LookupEntry(qinfo->RepObjectiveFaction)) + { + sLog.outErrorDb("Quest %u has `RepObjectiveFaction` = %u but faction template %u does not exist, quest can't be done.", + qinfo->GetQuestId(),qinfo->RepObjectiveFaction,qinfo->RepObjectiveFaction); + // no changes, quest can't be done for this requirement + } + + if (qinfo->RequiredMinRepFaction && !sFactionStore.LookupEntry(qinfo->RequiredMinRepFaction)) + { + sLog.outErrorDb("Quest %u has `RequiredMinRepFaction` = %u but faction template %u does not exist, quest can't be done.", + qinfo->GetQuestId(),qinfo->RequiredMinRepFaction,qinfo->RequiredMinRepFaction); + // no changes, quest can't be done for this requirement + } + + if (qinfo->RequiredMaxRepFaction && !sFactionStore.LookupEntry(qinfo->RequiredMaxRepFaction)) + { + sLog.outErrorDb("Quest %u has `RequiredMaxRepFaction` = %u but faction template %u does not exist, quest can't be done.", + qinfo->GetQuestId(),qinfo->RequiredMaxRepFaction,qinfo->RequiredMaxRepFaction); + // no changes, quest can't be done for this requirement + } + + if (qinfo->RequiredMinRepValue && qinfo->RequiredMinRepValue > ReputationMgr::Reputation_Cap) + { + sLog.outErrorDb("Quest %u has `RequiredMinRepValue` = %d but max reputation is %u, quest can't be done.", + qinfo->GetQuestId(),qinfo->RequiredMinRepValue,ReputationMgr::Reputation_Cap); + // no changes, quest can't be done for this requirement + } + + if (qinfo->RequiredMinRepValue && qinfo->RequiredMaxRepValue && qinfo->RequiredMaxRepValue <= qinfo->RequiredMinRepValue) + { + sLog.outErrorDb("Quest %u has `RequiredMaxRepValue` = %d and `RequiredMinRepValue` = %d, quest can't be done.", + qinfo->GetQuestId(),qinfo->RequiredMaxRepValue,qinfo->RequiredMinRepValue); + // no changes, quest can't be done for this requirement + } + + if (!qinfo->RepObjectiveFaction && qinfo->RepObjectiveValue > 0) + { + sLog.outErrorDb("Quest %u has `RepObjectiveValue` = %d but `RepObjectiveFaction` is 0, value has no effect", + qinfo->GetQuestId(),qinfo->RepObjectiveValue); + // warning + } + + if (!qinfo->RepObjectiveFaction2 && qinfo->RepObjectiveValue2 > 0) + { + sLog.outErrorDb("Quest %u has `RepObjectiveValue2` = %d but `RepObjectiveFaction2` is 0, value has no effect", + qinfo->GetQuestId(),qinfo->RepObjectiveValue2); + // warning + } + + if (!qinfo->RequiredMinRepFaction && qinfo->RequiredMinRepValue > 0) + { + sLog.outErrorDb("Quest %u has `RequiredMinRepValue` = %d but `RequiredMinRepFaction` is 0, value has no effect", + qinfo->GetQuestId(),qinfo->RequiredMinRepValue); + // warning + } + + if (!qinfo->RequiredMaxRepFaction && qinfo->RequiredMaxRepValue > 0) + { + sLog.outErrorDb("Quest %u has `RequiredMaxRepValue` = %d but `RequiredMaxRepFaction` is 0, value has no effect", + qinfo->GetQuestId(),qinfo->RequiredMaxRepValue); + // warning + } + + if (qinfo->CharTitleId && !sCharTitlesStore.LookupEntry(qinfo->CharTitleId)) + { + sLog.outErrorDb("Quest %u has `CharTitleId` = %u but CharTitle Id %u does not exist, quest can't be rewarded with title.", + qinfo->GetQuestId(),qinfo->GetCharTitleId(),qinfo->GetCharTitleId()); + qinfo->CharTitleId = 0; + // quest can't reward this title + } + + if (qinfo->SrcItemId) + { + if (!sItemStorage.LookupEntry<ItemPrototype>(qinfo->SrcItemId)) + { + sLog.outErrorDb("Quest %u has `SrcItemId` = %u but item with entry %u does not exist, quest can't be done.", + qinfo->GetQuestId(),qinfo->SrcItemId,qinfo->SrcItemId); + qinfo->SrcItemId = 0; // quest can't be done for this requirement + } + else if (qinfo->SrcItemCount == 0) + { + sLog.outErrorDb("Quest %u has `SrcItemId` = %u but `SrcItemCount` = 0, set to 1 but need fix in DB.", + qinfo->GetQuestId(),qinfo->SrcItemId); + qinfo->SrcItemCount = 1; // update to 1 for allow quest work for backward compatibility with DB + } + } + else if (qinfo->SrcItemCount>0) + { + sLog.outErrorDb("Quest %u has `SrcItemId` = 0 but `SrcItemCount` = %u, useless value.", + qinfo->GetQuestId(),qinfo->SrcItemCount); + qinfo->SrcItemCount=0; // no quest work changes in fact + } + + if (qinfo->SrcSpell) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(qinfo->SrcSpell); + if (!spellInfo) + { + sLog.outErrorDb("Quest %u has `SrcSpell` = %u but spell %u doesn't exist, quest can't be done.", + qinfo->GetQuestId(),qinfo->SrcSpell,qinfo->SrcSpell); + qinfo->SrcSpell = 0; // quest can't be done for this requirement + } + else if (!SpellMgr::IsSpellValid(spellInfo)) + { + sLog.outErrorDb("Quest %u has `SrcSpell` = %u but spell %u is broken, quest can't be done.", + qinfo->GetQuestId(),qinfo->SrcSpell,qinfo->SrcSpell); + qinfo->SrcSpell = 0; // quest can't be done for this requirement + } + } + + for (uint8 j = 0; j < QUEST_ITEM_OBJECTIVES_COUNT; ++j) + { + uint32 id = qinfo->ReqItemId[j]; + if (id) + { + if (qinfo->ReqItemCount[j] == 0) + { + sLog.outErrorDb("Quest %u has `ReqItemId%d` = %u but `ReqItemCount%d` = 0, quest can't be done.", + qinfo->GetQuestId(),j+1,id,j+1); + // no changes, quest can't be done for this requirement + } + + qinfo->SetFlag(QUEST_TRINITY_FLAGS_DELIVER); + + if (!sItemStorage.LookupEntry<ItemPrototype>(id)) + { + sLog.outErrorDb("Quest %u has `ReqItemId%d` = %u but item with entry %u does not exist, quest can't be done.", + qinfo->GetQuestId(),j+1,id,id); + qinfo->ReqItemCount[j] = 0; // prevent incorrect work of quest + } + } + else if (qinfo->ReqItemCount[j]>0) + { + sLog.outErrorDb("Quest %u has `ReqItemId%d` = 0 but `ReqItemCount%d` = %u, quest can't be done.", + qinfo->GetQuestId(),j+1,j+1,qinfo->ReqItemCount[j]); + qinfo->ReqItemCount[j] = 0; // prevent incorrect work of quest + } + } + + for (uint8 j = 0; j < QUEST_SOURCE_ITEM_IDS_COUNT; ++j) + { + uint32 id = qinfo->ReqSourceId[j]; + if (id) + { + if (!sItemStorage.LookupEntry<ItemPrototype>(id)) + { + sLog.outErrorDb("Quest %u has `ReqSourceId%d` = %u but item with entry %u does not exist, quest can't be done.", + qinfo->GetQuestId(),j+1,id,id); + // no changes, quest can't be done for this requirement + } + } + else + { + if (qinfo->ReqSourceCount[j]>0) + { + sLog.outErrorDb("Quest %u has `ReqSourceId%d` = 0 but `ReqSourceCount%d` = %u.", + qinfo->GetQuestId(),j+1,j+1,qinfo->ReqSourceCount[j]); + // no changes, quest ignore this data + } + } + } + + for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + { + uint32 id = qinfo->ReqSpell[j]; + if (id) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(id); + if (!spellInfo) + { + sLog.outErrorDb("Quest %u has `ReqSpellCast%d` = %u but spell %u does not exist, quest can't be done.", + qinfo->GetQuestId(),j+1,id,id); + continue; + } + + if (!qinfo->ReqCreatureOrGOId[j]) + { + bool found = false; + for (uint8 k = 0; k < 3; ++k) + { + if ((spellInfo->Effect[k] == SPELL_EFFECT_QUEST_COMPLETE && uint32(spellInfo->EffectMiscValue[k]) == qinfo->QuestId) || + spellInfo->Effect[k] == SPELL_EFFECT_SEND_EVENT) + { + found = true; + break; + } + } + + if (found) + { + if (!qinfo->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT)) + { + sLog.outErrorDb("Spell (id: %u) have SPELL_EFFECT_QUEST_COMPLETE or SPELL_EFFECT_SEND_EVENT for quest %u and ReqCreatureOrGOId%d = 0, but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT. Quest flags or ReqCreatureOrGOId%d must be fixed, quest modified to enable objective.",spellInfo->Id,qinfo->QuestId,j+1,j+1); + + // this will prevent quest completing without objective + const_cast<Quest*>(qinfo)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT); + } + } + else + { + sLog.outErrorDb("Quest %u has `ReqSpellCast%d` = %u and ReqCreatureOrGOId%d = 0 but spell %u does not have SPELL_EFFECT_QUEST_COMPLETE or SPELL_EFFECT_SEND_EVENT effect for this quest, quest can't be done.", + qinfo->GetQuestId(),j+1,id,j+1,id); + // no changes, quest can't be done for this requirement + } + } + } + } + + for (uint8 j = 0; j < QUEST_OBJECTIVES_COUNT; ++j) + { + int32 id = qinfo->ReqCreatureOrGOId[j]; + if (id < 0 && !sGOStorage.LookupEntry<GameObjectInfo>(-id)) + { + sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = %i but gameobject %u does not exist, quest can't be done.", + qinfo->GetQuestId(),j+1,id,uint32(-id)); + qinfo->ReqCreatureOrGOId[j] = 0; // quest can't be done for this requirement + } + + if (id > 0 && !sCreatureStorage.LookupEntry<CreatureInfo>(id)) + { + sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = %i but creature with entry %u does not exist, quest can't be done.", + qinfo->GetQuestId(),j+1,id,uint32(id)); + qinfo->ReqCreatureOrGOId[j] = 0; // quest can't be done for this requirement + } + + if (id) + { + // In fact SpeakTo and Kill are quite same: either you can speak to mob:SpeakTo or you can't:Kill/Cast + + qinfo->SetFlag(QUEST_TRINITY_FLAGS_KILL_OR_CAST | QUEST_TRINITY_FLAGS_SPEAKTO); + + if (!qinfo->ReqCreatureOrGOCount[j]) + { + sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = %u but `ReqCreatureOrGOCount%d` = 0, quest can't be done.", + qinfo->GetQuestId(),j+1,id,j+1); + // no changes, quest can be incorrectly done, but we already report this + } + } + else if (qinfo->ReqCreatureOrGOCount[j]>0) + { + sLog.outErrorDb("Quest %u has `ReqCreatureOrGOId%d` = 0 but `ReqCreatureOrGOCount%d` = %u.", + qinfo->GetQuestId(),j+1,j+1,qinfo->ReqCreatureOrGOCount[j]); + // no changes, quest ignore this data + } + } + + for (uint8 j = 0; j < QUEST_REWARD_CHOICES_COUNT; ++j) + { + uint32 id = qinfo->RewChoiceItemId[j]; + if (id) + { + if (!sItemStorage.LookupEntry<ItemPrototype>(id)) + { + sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = %u but item with entry %u does not exist, quest will not reward this item.", + qinfo->GetQuestId(),j+1,id,id); + qinfo->RewChoiceItemId[j] = 0; // no changes, quest will not reward this + } + + if (!qinfo->RewChoiceItemCount[j]) + { + sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = %u but `RewChoiceItemCount%d` = 0, quest can't be done.", + qinfo->GetQuestId(),j+1,id,j+1); + // no changes, quest can't be done + } + } + else if (qinfo->RewChoiceItemCount[j]>0) + { + sLog.outErrorDb("Quest %u has `RewChoiceItemId%d` = 0 but `RewChoiceItemCount%d` = %u.", + qinfo->GetQuestId(),j+1,j+1,qinfo->RewChoiceItemCount[j]); + // no changes, quest ignore this data + } + } + + for (uint8 j = 0; j < QUEST_REWARDS_COUNT; ++j) + { + uint32 id = qinfo->RewItemId[j]; + if (id) + { + if (!sItemStorage.LookupEntry<ItemPrototype>(id)) + { + sLog.outErrorDb("Quest %u has `RewItemId%d` = %u but item with entry %u does not exist, quest will not reward this item.", + qinfo->GetQuestId(),j+1,id,id); + qinfo->RewItemId[j] = 0; // no changes, quest will not reward this item + } + + if (!qinfo->RewItemCount[j]) + { + sLog.outErrorDb("Quest %u has `RewItemId%d` = %u but `RewItemCount%d` = 0, quest will not reward this item.", + qinfo->GetQuestId(),j+1,id,j+1); + // no changes + } + } + else if (qinfo->RewItemCount[j]>0) + { + sLog.outErrorDb("Quest %u has `RewItemId%d` = 0 but `RewItemCount%d` = %u.", + qinfo->GetQuestId(),j+1,j+1,qinfo->RewItemCount[j]); + // no changes, quest ignore this data + } + } + + for (uint8 j = 0; j < QUEST_REPUTATIONS_COUNT; ++j) + { + if (qinfo->RewRepFaction[j]) + { + if (abs(qinfo->RewRepValueId[j]) > 9) + { + sLog.outErrorDb("Quest %u has RewRepValueId%d = %i. That is outside the range of valid values (-9 to 9).", qinfo->GetQuestId(), j+1, qinfo->RewRepValueId[j]); + } + if (!sFactionStore.LookupEntry(qinfo->RewRepFaction[j])) + { + sLog.outErrorDb("Quest %u has `RewRepFaction%d` = %u but raw faction (faction.dbc) %u does not exist, quest will not reward reputation for this faction.", qinfo->GetQuestId(),j+1,qinfo->RewRepFaction[j] ,qinfo->RewRepFaction[j]); + qinfo->RewRepFaction[j] = 0; // quest will not reward this + } + } + + + else if (qinfo->RewRepValue[j] != 0) + { + sLog.outErrorDb("Quest %u has `RewRepFaction%d` = 0 but `RewRepValue%d` = %i.", + qinfo->GetQuestId(),j+1,j+1,qinfo->RewRepValue[j]); + // no changes, quest ignore this data + } + } + + + if (qinfo->RewSpell) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(qinfo->RewSpell); + + if (!spellInfo) + { + sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u does not exist, spell removed as display reward.", + qinfo->GetQuestId(),qinfo->RewSpell,qinfo->RewSpell); + qinfo->RewSpell = 0; // no spell reward will display for this quest + } + + else if (!SpellMgr::IsSpellValid(spellInfo)) + { + sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u is broken, quest will not have a spell reward.", + qinfo->GetQuestId(),qinfo->RewSpell,qinfo->RewSpell); + qinfo->RewSpell = 0; // no spell reward will display for this quest + } + + else if (GetTalentSpellCost(qinfo->RewSpell)) + { + sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u is talent, quest will not have a spell reward.", + qinfo->GetQuestId(),qinfo->RewSpell,qinfo->RewSpell); + qinfo->RewSpell = 0; // no spell reward will display for this quest + } + } + + if (qinfo->RewSpellCast > 0) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(qinfo->RewSpellCast); + + if (!spellInfo) + { + sLog.outErrorDb("Quest %u has `RewSpellCast` = %u but spell %u does not exist, quest will not have a spell reward.", + qinfo->GetQuestId(),qinfo->RewSpellCast,qinfo->RewSpellCast); + qinfo->RewSpellCast = 0; // no spell will be casted on player + } + + else if (!SpellMgr::IsSpellValid(spellInfo)) + { + sLog.outErrorDb("Quest %u has `RewSpellCast` = %u but spell %u is broken, quest will not have a spell reward.", + qinfo->GetQuestId(),qinfo->RewSpellCast,qinfo->RewSpellCast); + qinfo->RewSpellCast = 0; // no spell will be casted on player + } + + else if (GetTalentSpellCost(qinfo->RewSpellCast)) + { + sLog.outErrorDb("Quest %u has `RewSpell` = %u but spell %u is talent, quest will not have a spell reward.", + qinfo->GetQuestId(),qinfo->RewSpellCast,qinfo->RewSpellCast); + qinfo->RewSpellCast = 0; // no spell will be casted on player + } + } + + if (qinfo->RewMailTemplateId) + { + if (!sMailTemplateStore.LookupEntry(qinfo->RewMailTemplateId)) + { + sLog.outErrorDb("Quest %u has `RewMailTemplateId` = %u but mail template %u does not exist, quest will not have a mail reward.", + qinfo->GetQuestId(),qinfo->RewMailTemplateId,qinfo->RewMailTemplateId); + qinfo->RewMailTemplateId = 0; // no mail will send to player + qinfo->RewMailDelaySecs = 0; // no mail will send to player + } + else if (usedMailTemplates.find(qinfo->RewMailTemplateId) != usedMailTemplates.end()) + { + std::map<uint32,uint32>::const_iterator used_mt_itr = usedMailTemplates.find(qinfo->RewMailTemplateId); + sLog.outErrorDb("Quest %u has `RewMailTemplateId` = %u but mail template %u already used for quest %u, quest will not have a mail reward.", + qinfo->GetQuestId(),qinfo->RewMailTemplateId,qinfo->RewMailTemplateId,used_mt_itr->second); + qinfo->RewMailTemplateId = 0; // no mail will send to player + qinfo->RewMailDelaySecs = 0; // no mail will send to player + } + else + usedMailTemplates[qinfo->RewMailTemplateId] = qinfo->GetQuestId(); + } + + if (qinfo->NextQuestInChain) + { + QuestMap::iterator qNextItr = mQuestTemplates.find(qinfo->NextQuestInChain); + if (qNextItr == mQuestTemplates.end()) + { + sLog.outErrorDb("Quest %u has `NextQuestInChain` = %u but quest %u does not exist, quest chain will not work.", + qinfo->GetQuestId(),qinfo->NextQuestInChain ,qinfo->NextQuestInChain); + qinfo->NextQuestInChain = 0; + } + else + qNextItr->second->prevChainQuests.push_back(qinfo->GetQuestId()); + } + + // fill additional data stores + if (qinfo->PrevQuestId) + { + if (mQuestTemplates.find(abs(qinfo->GetPrevQuestId())) == mQuestTemplates.end()) + { + sLog.outErrorDb("Quest %d has PrevQuestId %i, but no such quest", qinfo->GetQuestId(), qinfo->GetPrevQuestId()); + } + else + { + qinfo->prevQuests.push_back(qinfo->PrevQuestId); + } + } + + if (qinfo->NextQuestId) + { + QuestMap::iterator qNextItr = mQuestTemplates.find(abs(qinfo->GetNextQuestId())); + if (qNextItr == mQuestTemplates.end()) + { + sLog.outErrorDb("Quest %d has NextQuestId %i, but no such quest", qinfo->GetQuestId(), qinfo->GetNextQuestId()); + } + else + { + int32 signedQuestId = qinfo->NextQuestId < 0 ? -int32(qinfo->GetQuestId()) : int32(qinfo->GetQuestId()); + qNextItr->second->prevQuests.push_back(signedQuestId); + } + } + + if (qinfo->ExclusiveGroup) + mExclusiveQuestGroups.insert(std::pair<int32, uint32>(qinfo->ExclusiveGroup, qinfo->GetQuestId())); + if (qinfo->LimitTime) + qinfo->SetFlag(QUEST_TRINITY_FLAGS_TIMED); + } + + // check QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT for spell with SPELL_EFFECT_QUEST_COMPLETE + for (uint32 i = 0; i < sSpellStore.GetNumRows(); ++i) + { + SpellEntry const *spellInfo = sSpellStore.LookupEntry(i); + if (!spellInfo) + continue; + + for (uint8 j = 0; j < 3; ++j) + { + if (spellInfo->Effect[j] != SPELL_EFFECT_QUEST_COMPLETE) + continue; + + uint32 quest_id = spellInfo->EffectMiscValue[j]; + + Quest const* quest = GetQuestTemplate(quest_id); + + // some quest referenced in spells not exist (outdated spells) + if (!quest) + continue; + + if (!quest->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT)) + { + sLog.outErrorDb("Spell (id: %u) have SPELL_EFFECT_QUEST_COMPLETE for quest %u , but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT. Quest flags must be fixed, quest modified to enable objective.",spellInfo->Id,quest_id); + + // this will prevent quest completing without objective + const_cast<Quest*>(quest)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT); + } + } + } + + sLog.outString(); + sLog.outString(">> Loaded %lu quests definitions", (unsigned long)mQuestTemplates.size()); +} + +void ObjectMgr::LoadQuestLocales() +{ + mQuestLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry," + "Title_loc1,Details_loc1,Objectives_loc1,OfferRewardText_loc1,RequestItemsText_loc1,EndText_loc1,CompletedText_loc1,ObjectiveText1_loc1,ObjectiveText2_loc1,ObjectiveText3_loc1,ObjectiveText4_loc1," + "Title_loc2,Details_loc2,Objectives_loc2,OfferRewardText_loc2,RequestItemsText_loc2,EndText_loc2,CompletedText_loc2,ObjectiveText1_loc2,ObjectiveText2_loc2,ObjectiveText3_loc2,ObjectiveText4_loc2," + "Title_loc3,Details_loc3,Objectives_loc3,OfferRewardText_loc3,RequestItemsText_loc3,EndText_loc3,CompletedText_loc3,ObjectiveText1_loc3,ObjectiveText2_loc3,ObjectiveText3_loc3,ObjectiveText4_loc3," + "Title_loc4,Details_loc4,Objectives_loc4,OfferRewardText_loc4,RequestItemsText_loc4,EndText_loc4,CompletedText_loc4,ObjectiveText1_loc4,ObjectiveText2_loc4,ObjectiveText3_loc4,ObjectiveText4_loc4," + "Title_loc5,Details_loc5,Objectives_loc5,OfferRewardText_loc5,RequestItemsText_loc5,EndText_loc5,CompletedText_loc5,ObjectiveText1_loc5,ObjectiveText2_loc5,ObjectiveText3_loc5,ObjectiveText4_loc5," + "Title_loc6,Details_loc6,Objectives_loc6,OfferRewardText_loc6,RequestItemsText_loc6,EndText_loc6,CompletedText_loc6,ObjectiveText1_loc6,ObjectiveText2_loc6,ObjectiveText3_loc6,ObjectiveText4_loc6," + "Title_loc7,Details_loc7,Objectives_loc7,OfferRewardText_loc7,RequestItemsText_loc7,EndText_loc7,CompletedText_loc7,ObjectiveText1_loc7,ObjectiveText2_loc7,ObjectiveText3_loc7,ObjectiveText4_loc7," + "Title_loc8,Details_loc8,Objectives_loc8,OfferRewardText_loc8,RequestItemsText_loc8,EndText_loc8,CompletedText_loc8,ObjectiveText1_loc8,ObjectiveText2_loc8,ObjectiveText3_loc8,ObjectiveText4_loc8" + " FROM locales_quest" +); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + QuestLocale& data = mQuestLocaleMap[entry]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[1+11*(i-1)].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Title.size() <= idx) + data.Title.resize(idx+1); + + data.Title[idx] = str; + } + } + str = fields[1+11*(i-1)+1].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Details.size() <= idx) + data.Details.resize(idx+1); + + data.Details[idx] = str; + } + } + str = fields[1+11*(i-1)+2].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Objectives.size() <= idx) + data.Objectives.resize(idx+1); + + data.Objectives[idx] = str; + } + } + str = fields[1+11*(i-1)+3].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.OfferRewardText.size() <= idx) + data.OfferRewardText.resize(idx+1); + + data.OfferRewardText[idx] = str; + } + } + str = fields[1+11*(i-1)+4].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.RequestItemsText.size() <= idx) + data.RequestItemsText.resize(idx+1); + + data.RequestItemsText[idx] = str; + } + } + str = fields[1+11*(i-1)+5].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.EndText.size() <= idx) + data.EndText.resize(idx+1); + + data.EndText[idx] = str; + } + } + str = fields[1+11*(i-1)+6].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.CompletedText.size() <= idx) + data.CompletedText.resize(idx+1); + + data.CompletedText[idx] = str; + } + } + + for (uint8 k = 0; k < 4; ++k) + { + str = fields[1+11*(i-1)+7+k].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.ObjectiveText[k].size() <= idx) + data.ObjectiveText[k].resize(idx+1); + + data.ObjectiveText[k][idx] = str; + } + } + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu Quest locale strings", (unsigned long)mQuestLocaleMap.size()); +} + +void ObjectMgr::LoadScripts(ScriptMapMap& scripts, char const* tablename) +{ + if (sWorld.IsScriptScheduled()) // function don't must be called in time scripts use. + return; + + sLog.outString("%s :", tablename); + + scripts.clear(); // need for reload support + + QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT id,delay,command,datalong,datalong2,dataint, x, y, z, o FROM %s", tablename); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u script definitions", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + ScriptInfo tmp; + tmp.id = fields[0].GetUInt32(); + tmp.delay = fields[1].GetUInt32(); + tmp.command = fields[2].GetUInt32(); + tmp.datalong = fields[3].GetUInt32(); + tmp.datalong2 = fields[4].GetUInt32(); + tmp.dataint = fields[5].GetInt32(); + tmp.x = fields[6].GetFloat(); + tmp.y = fields[7].GetFloat(); + tmp.z = fields[8].GetFloat(); + tmp.o = fields[9].GetFloat(); + + // generic command args check + switch (tmp.command) + { + case SCRIPT_COMMAND_TALK: + { + if (tmp.datalong > CHAT_TYPE_WHISPER) + { + sLog.outErrorDb("Table `%s` has invalid talk type (datalong = %u) in SCRIPT_COMMAND_TALK for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + if (tmp.dataint == 0) + { + sLog.outErrorDb("Table `%s` has invalid talk text id (dataint = %i) in SCRIPT_COMMAND_TALK for script id %u",tablename,tmp.dataint,tmp.id); + continue; + } + if (tmp.dataint < MIN_DB_SCRIPT_STRING_ID || tmp.dataint >= MAX_DB_SCRIPT_STRING_ID) + { + sLog.outErrorDb("Table `%s` has out of range text id (dataint = %i expected %u-%u) in SCRIPT_COMMAND_TALK for script id %u",tablename,tmp.dataint,MIN_DB_SCRIPT_STRING_ID,MAX_DB_SCRIPT_STRING_ID,tmp.id); + continue; + } + + break; + } + + case SCRIPT_COMMAND_EMOTE: + { + if (!sEmotesStore.LookupEntry(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` has invalid emote id (datalong = %u) in SCRIPT_COMMAND_EMOTE for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + break; + } + + case SCRIPT_COMMAND_TELEPORT_TO: + { + if (!sMapStore.LookupEntry(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` has invalid map (Id: %u) in SCRIPT_COMMAND_TELEPORT_TO for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + + if (!Trinity::IsValidMapCoord(tmp.x,tmp.y,tmp.z,tmp.o)) + { + sLog.outErrorDb("Table `%s` has invalid coordinates (X: %f Y: %f) in SCRIPT_COMMAND_TELEPORT_TO for script id %u",tablename,tmp.x,tmp.y,tmp.id); + continue; + } + break; + } + + case SCRIPT_COMMAND_KILL_CREDIT: + { + if (!GetCreatureTemplate(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` has invalid creature (Entry: %u) in SCRIPT_COMMAND_KILL_CREDIT for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + break; + } + + case SCRIPT_COMMAND_TEMP_SUMMON_CREATURE: + { + if (!Trinity::IsValidMapCoord(tmp.x,tmp.y,tmp.z,tmp.o)) + { + sLog.outErrorDb("Table `%s` has invalid coordinates (X: %f Y: %f) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id %u",tablename,tmp.x,tmp.y,tmp.id); + continue; + } + + if (!GetCreatureTemplate(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` has invalid creature (Entry: %u) in SCRIPT_COMMAND_TEMP_SUMMON_CREATURE for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + break; + } + + case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT: + { + GameObjectData const* data = GetGOData(tmp.datalong); + if (!data) + { + sLog.outErrorDb("Table `%s` has invalid gameobject (GUID: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + + GameObjectInfo const* info = GetGameObjectInfo(data->id); + if (!info) + { + sLog.outErrorDb("Table `%s` has gameobject with invalid entry (GUID: %u Entry: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",tablename,tmp.datalong,data->id,tmp.id); + continue; + } + + if (info->type == GAMEOBJECT_TYPE_FISHINGNODE || + info->type == GAMEOBJECT_TYPE_FISHINGHOLE || + info->type == GAMEOBJECT_TYPE_DOOR || + info->type == GAMEOBJECT_TYPE_BUTTON || + info->type == GAMEOBJECT_TYPE_TRAP) + { + sLog.outErrorDb("Table `%s` have gameobject type (%u) unsupported by command SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u",tablename,info->id,tmp.id); + continue; + } + break; + } + case SCRIPT_COMMAND_OPEN_DOOR: + case SCRIPT_COMMAND_CLOSE_DOOR: + { + GameObjectData const* data = GetGOData(tmp.datalong); + if (!data) + { + sLog.outErrorDb("Table `%s` has invalid gameobject (GUID: %u) in %s for script id %u",tablename,tmp.datalong,(tmp.command == SCRIPT_COMMAND_OPEN_DOOR ? "SCRIPT_COMMAND_OPEN_DOOR" : "SCRIPT_COMMAND_CLOSE_DOOR"),tmp.id); + continue; + } + + GameObjectInfo const* info = GetGameObjectInfo(data->id); + if (!info) + { + sLog.outErrorDb("Table `%s` has gameobject with invalid entry (GUID: %u Entry: %u) in %s for script id %u",tablename,tmp.datalong,data->id,(tmp.command == SCRIPT_COMMAND_OPEN_DOOR ? "SCRIPT_COMMAND_OPEN_DOOR" : "SCRIPT_COMMAND_CLOSE_DOOR"),tmp.id); + continue; + } + + if (info->type != GAMEOBJECT_TYPE_DOOR) + { + sLog.outErrorDb("Table `%s` has gameobject type (%u) non supported by command %s for script id %u",tablename,info->id,(tmp.command == SCRIPT_COMMAND_OPEN_DOOR ? "SCRIPT_COMMAND_OPEN_DOOR" : "SCRIPT_COMMAND_CLOSE_DOOR"),tmp.id); + continue; + } + + break; + } + case SCRIPT_COMMAND_QUEST_EXPLORED: + { + Quest const* quest = GetQuestTemplate(tmp.datalong); + if (!quest) + { + sLog.outErrorDb("Table `%s` has invalid quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u",tablename,tmp.datalong,tmp.id); + continue; + } + + if (!quest->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT)) + { + sLog.outErrorDb("Table `%s` has quest (ID: %u) in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT in quest flags. Script command or quest flags wrong. Quest modified to require objective.",tablename,tmp.datalong,tmp.id); + + // this will prevent quest completing without objective + const_cast<Quest*>(quest)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT); + + // continue; - quest objective requirement set and command can be allowed + } + + if (float(tmp.datalong2) > DEFAULT_VISIBILITY_DISTANCE) + { + sLog.outErrorDb("Table `%s` has too large distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u", + tablename,tmp.datalong2,tmp.id); + continue; + } + + if (tmp.datalong2 && float(tmp.datalong2) > DEFAULT_VISIBILITY_DISTANCE) + { + sLog.outErrorDb("Table `%s` has too large distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, max distance is %f or 0 for disable distance check", + tablename,tmp.datalong2,tmp.id,DEFAULT_VISIBILITY_DISTANCE); + continue; + } + + if (tmp.datalong2 && float(tmp.datalong2) < INTERACTION_DISTANCE) + { + sLog.outErrorDb("Table `%s` has too small distance (%u) for exploring objective complete in `datalong2` in SCRIPT_COMMAND_QUEST_EXPLORED in `datalong` for script id %u, min distance is %f or 0 for disable distance check", + tablename,tmp.datalong2,tmp.id,INTERACTION_DISTANCE); + continue; + } + + break; + } + + case SCRIPT_COMMAND_REMOVE_AURA: + { + if (!sSpellStore.LookupEntry(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` using non-existent spell (id: %u) in SCRIPT_COMMAND_REMOVE_AURA or SCRIPT_COMMAND_CAST_SPELL for script id %u", + tablename,tmp.datalong,tmp.id); + continue; + } + if (tmp.datalong2 & ~0x1) // 1 bits (0,1) + { + sLog.outErrorDb("Table `%s` using unknown flags in datalong2 (%u)i n SCRIPT_COMMAND_CAST_SPELL for script id %u", + tablename,tmp.datalong2,tmp.id); + continue; + } + break; + } + case SCRIPT_COMMAND_CAST_SPELL: + { + if (!sSpellStore.LookupEntry(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` using non-existent spell (id: %u) in SCRIPT_COMMAND_REMOVE_AURA or SCRIPT_COMMAND_CAST_SPELL for script id %u", + tablename,tmp.datalong,tmp.id); + continue; + } + if (tmp.datalong2 & ~0x3) // 2 bits + { + sLog.outErrorDb("Table `%s` using unknown flags in datalong2 (%u)i n SCRIPT_COMMAND_CAST_SPELL for script id %u", + tablename,tmp.datalong2,tmp.id); + continue; + } + break; + } + + case SCRIPT_COMMAND_CREATE_ITEM: + { + if (!GetItemPrototype(tmp.datalong)) + { + sLog.outErrorDb("Table `%s` has nonexistent item (entry: %u) in SCRIPT_COMMAND_CREATE_ITEM for script id %u", + tablename, tmp.datalong, tmp.id); + continue; + } + if (!tmp.datalong2) + { + sLog.outErrorDb("Table `%s` SCRIPT_COMMAND_CREATE_ITEM but amount is %u for script id %u", + tablename, tmp.datalong2, tmp.id); + continue; + } + break; + } + } + + if (scripts.find(tmp.id) == scripts.end()) + { + ScriptMap emptyMap; + scripts[tmp.id] = emptyMap; + } + scripts[tmp.id].insert(std::pair<uint32, ScriptInfo>(tmp.delay, tmp)); + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u script definitions", count); +} + +void ObjectMgr::LoadGameObjectScripts() +{ + LoadScripts(sGameObjectScripts, "gameobject_scripts"); + + // check ids + for (ScriptMapMap::const_iterator itr = sGameObjectScripts.begin(); itr != sGameObjectScripts.end(); ++itr) + { + if (!GetGOData(itr->first)) + sLog.outErrorDb("Table `gameobject_scripts` has not existing gameobject (GUID: %u) as script id",itr->first); + } +} + +void ObjectMgr::LoadQuestEndScripts() +{ + LoadScripts(sQuestEndScripts, "quest_end_scripts"); + + // check ids + for (ScriptMapMap::const_iterator itr = sQuestEndScripts.begin(); itr != sQuestEndScripts.end(); ++itr) + { + if (!GetQuestTemplate(itr->first)) + sLog.outErrorDb("Table `quest_end_scripts` has not existing quest (Id: %u) as script id",itr->first); + } +} + +void ObjectMgr::LoadQuestStartScripts() +{ + LoadScripts(sQuestStartScripts,"quest_start_scripts"); + + // check ids + for (ScriptMapMap::const_iterator itr = sQuestStartScripts.begin(); itr != sQuestStartScripts.end(); ++itr) + { + if (!GetQuestTemplate(itr->first)) + sLog.outErrorDb("Table `quest_start_scripts` has not existing quest (Id: %u) as script id",itr->first); + } +} + +void ObjectMgr::LoadSpellScripts() +{ + LoadScripts(sSpellScripts, "spell_scripts"); + + // check ids + for (ScriptMapMap::const_iterator itr = sSpellScripts.begin(); itr != sSpellScripts.end(); ++itr) + { + SpellEntry const* spellInfo = sSpellStore.LookupEntry(itr->first); + + if (!spellInfo) + { + sLog.outErrorDb("Table `spell_scripts` has not existing spell (Id: %u) as script id",itr->first); + continue; + } + + //check for correct spellEffect + bool found = false; + for (uint8 i=0; i<3; ++i) + { + // skip empty effects + if (!spellInfo->Effect[i]) + continue; + + if (spellInfo->Effect[i] == SPELL_EFFECT_SCRIPT_EFFECT) + { + found = true; + break; + } + } + + if (!found) + sLog.outErrorDb("Table `spell_scripts` has unsupported spell (Id: %u) without SPELL_EFFECT_SCRIPT_EFFECT (%u) spell effect",itr->first,SPELL_EFFECT_SCRIPT_EFFECT); + } +} + +void ObjectMgr::LoadEventScripts() +{ + LoadScripts(sEventScripts, "event_scripts"); + + std::set<uint32> evt_scripts; + // Load all possible script entries from gameobjects + for (uint32 i = 1; i < sGOStorage.MaxEntry; ++i) + { + GameObjectInfo const * goInfo = sGOStorage.LookupEntry<GameObjectInfo>(i); + if (goInfo) + { + switch(goInfo->type) + { + case GAMEOBJECT_TYPE_GOOBER: + if (goInfo->goober.eventId) + evt_scripts.insert(goInfo->goober.eventId); + break; + case GAMEOBJECT_TYPE_CHEST: + if (goInfo->chest.eventId) + evt_scripts.insert(goInfo->chest.eventId); + break; + case GAMEOBJECT_TYPE_CAMERA: + if (goInfo->camera.eventID) + evt_scripts.insert(goInfo->camera.eventID); + default: + break; + } + } + } + // Load all possible script entries from spells + for (uint32 i = 1; i < sSpellStore.GetNumRows(); ++i) + { + SpellEntry const * spell = sSpellStore.LookupEntry(i); + if (spell) + { + for (uint8 j=0; j<3; ++j) + { + if (spell->Effect[j] == SPELL_EFFECT_SEND_EVENT) + { + if (spell->EffectMiscValue[j]) + evt_scripts.insert(spell->EffectMiscValue[j]); + } + } + } + } + + // Then check if all scripts are in above list of possible script entries + for (ScriptMapMap::const_iterator itr = sEventScripts.begin(); itr != sEventScripts.end(); ++itr) + { + std::set<uint32>::const_iterator itr2 = evt_scripts.find(itr->first); + if (itr2 == evt_scripts.end()) + sLog.outErrorDb("Table `event_scripts` has script (Id: %u) not referring to any gameobject_template type 10 data2 field, type 3 data6 field, type 13 data 2 field or any spell effect %u", + itr->first, SPELL_EFFECT_SEND_EVENT); + } +} + +//Load WP Scripts +void ObjectMgr::LoadWaypointScripts() +{ + LoadScripts(sWaypointScripts, "waypoint_scripts"); + + for (ScriptMapMap::const_iterator itr = sWaypointScripts.begin(); itr != sWaypointScripts.end(); ++itr) + { + QueryResult_AutoPtr query = WorldDatabase.PQuery("SELECT * FROM waypoint_scripts WHERE id = %u", itr->first); + if (!query || !query->GetRowCount()) + sLog.outErrorDb("There is no waypoint which links to the waypoint script %u", itr->first); + } +} + +void ObjectMgr::LoadGossipScripts() +{ + LoadScripts(sGossipScripts, "gossip_scripts"); + + // checks are done in LoadGossipMenuItems +} + +void ObjectMgr::LoadPageTexts() +{ + sPageTextStore.Free(); // for reload case + + sPageTextStore.Load(); + sLog.outString(">> Loaded %u page texts", sPageTextStore.RecordCount); + sLog.outString(); + + for (uint32 i = 1; i < sPageTextStore.MaxEntry; ++i) + { + // check data correctness + PageText const* page = sPageTextStore.LookupEntry<PageText>(i); + if (!page) + continue; + + if (page->Next_Page && !sPageTextStore.LookupEntry<PageText>(page->Next_Page)) + { + sLog.outErrorDb("Page text (Id: %u) has not existing next page (Id:%u)", i,page->Next_Page); + continue; + } + + // detect circular reference + std::set<uint32> checkedPages; + for (PageText const* pageItr = page; pageItr; pageItr = sPageTextStore.LookupEntry<PageText>(pageItr->Next_Page)) + { + if (!pageItr->Next_Page) + break; + checkedPages.insert(pageItr->Page_ID); + if (checkedPages.find(pageItr->Next_Page)!= checkedPages.end()) + { + std::ostringstream ss; + ss << "The text page(s) "; + for (std::set<uint32>::iterator itr= checkedPages.begin(); itr != checkedPages.end(); ++itr) + ss << *itr << " "; + ss << "create(s) a circular reference, which can cause the server to freeze. Changing Next_Page of page " + << pageItr->Page_ID <<" to 0"; + sLog.outErrorDb(ss.str().c_str()); + const_cast<PageText*>(pageItr)->Next_Page = 0; + break; + } + } + } +} + +void ObjectMgr::LoadPageTextLocales() +{ + mPageTextLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,text_loc1,text_loc2,text_loc3,text_loc4,text_loc5,text_loc6,text_loc7,text_loc8 FROM locales_page_text"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + PageTextLocale& data = mPageTextLocaleMap[entry]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[i].GetCppString(); + if (str.empty()) + continue; + + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Text.size() <= idx) + data.Text.resize(idx+1); + + data.Text[idx] = str; + } + } + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu PageText locale strings", (unsigned long)mPageTextLocaleMap.size()); +} + +struct SQLInstanceLoader : public SQLStorageLoaderBase<SQLInstanceLoader> +{ + template<class D> + void convert_from_str(uint32 /*field_pos*/, char *src, D &dst) + { + dst = D(objmgr.GetScriptId(src)); + } +}; + +void ObjectMgr::LoadInstanceTemplate() +{ + SQLInstanceLoader loader; + loader.Load(sInstanceTemplate); + + for (uint32 i = 0; i < sInstanceTemplate.MaxEntry; i++) + { + InstanceTemplate* temp = (InstanceTemplate*)GetInstanceTemplate(i); + if (!temp) + continue; + + if (!MapManager::IsValidMAP(temp->map)) + sLog.outErrorDb("ObjectMgr::LoadInstanceTemplate: bad mapid %d for template!", temp->map); + + if (!MapManager::IsValidMapCoord(temp->parent,temp->startLocX,temp->startLocY,temp->startLocZ,temp->startLocO)) + { + sLog.outErrorDb("ObjectMgr::LoadInstanceTemplate: bad parent entrance coordinates for map id %d template!", temp->map); + temp->parent = 0; // will have wrong continent 0 parent, at least existed + } + } + + sLog.outString(">> Loaded %u Instance Template definitions", sInstanceTemplate.RecordCount); + sLog.outString(); +} + +GossipText const *ObjectMgr::GetGossipText(uint32 Text_ID) const +{ + GossipTextMap::const_iterator itr = mGossipText.find(Text_ID); + if (itr != mGossipText.end()) + return &itr->second; + return NULL; +} + +void ObjectMgr::LoadGossipText() +{ + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT * FROM npc_text"); + + int count = 0; + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u npc texts", count); + return; + } + + int cic; + + barGoLink bar(result->GetRowCount()); + + do + { + ++count; + cic = 0; + + Field *fields = result->Fetch(); + + bar.step(); + + uint32 Text_ID = fields[cic++].GetUInt32(); + if (!Text_ID) + { + sLog.outErrorDb("Table `npc_text` has record wit reserved id 0, ignore."); + continue; + } + + GossipText& gText = mGossipText[Text_ID]; + + for (int i=0; i< 8; i++) + { + gText.Options[i].Text_0 = fields[cic++].GetCppString(); + gText.Options[i].Text_1 = fields[cic++].GetCppString(); + + gText.Options[i].Language = fields[cic++].GetUInt32(); + gText.Options[i].Probability = fields[cic++].GetFloat(); + + for (uint8 j=0; j < 3; ++j) + { + gText.Options[i].Emotes[j]._Delay = fields[cic++].GetUInt32(); + gText.Options[i].Emotes[j]._Emote = fields[cic++].GetUInt32(); + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u npc texts", count); +} + +void ObjectMgr::LoadNpcTextLocales() +{ + mNpcTextLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry," + "Text0_0_loc1,Text0_1_loc1,Text1_0_loc1,Text1_1_loc1,Text2_0_loc1,Text2_1_loc1,Text3_0_loc1,Text3_1_loc1,Text4_0_loc1,Text4_1_loc1,Text5_0_loc1,Text5_1_loc1,Text6_0_loc1,Text6_1_loc1,Text7_0_loc1,Text7_1_loc1," + "Text0_0_loc2,Text0_1_loc2,Text1_0_loc2,Text1_1_loc2,Text2_0_loc2,Text2_1_loc2,Text3_0_loc2,Text3_1_loc1,Text4_0_loc2,Text4_1_loc2,Text5_0_loc2,Text5_1_loc2,Text6_0_loc2,Text6_1_loc2,Text7_0_loc2,Text7_1_loc2," + "Text0_0_loc3,Text0_1_loc3,Text1_0_loc3,Text1_1_loc3,Text2_0_loc3,Text2_1_loc3,Text3_0_loc3,Text3_1_loc1,Text4_0_loc3,Text4_1_loc3,Text5_0_loc3,Text5_1_loc3,Text6_0_loc3,Text6_1_loc3,Text7_0_loc3,Text7_1_loc3," + "Text0_0_loc4,Text0_1_loc4,Text1_0_loc4,Text1_1_loc4,Text2_0_loc4,Text2_1_loc4,Text3_0_loc4,Text3_1_loc1,Text4_0_loc4,Text4_1_loc4,Text5_0_loc4,Text5_1_loc4,Text6_0_loc4,Text6_1_loc4,Text7_0_loc4,Text7_1_loc4," + "Text0_0_loc5,Text0_1_loc5,Text1_0_loc5,Text1_1_loc5,Text2_0_loc5,Text2_1_loc5,Text3_0_loc5,Text3_1_loc1,Text4_0_loc5,Text4_1_loc5,Text5_0_loc5,Text5_1_loc5,Text6_0_loc5,Text6_1_loc5,Text7_0_loc5,Text7_1_loc5," + "Text0_0_loc6,Text0_1_loc6,Text1_0_loc6,Text1_1_loc6,Text2_0_loc6,Text2_1_loc6,Text3_0_loc6,Text3_1_loc1,Text4_0_loc6,Text4_1_loc6,Text5_0_loc6,Text5_1_loc6,Text6_0_loc6,Text6_1_loc6,Text7_0_loc6,Text7_1_loc6," + "Text0_0_loc7,Text0_1_loc7,Text1_0_loc7,Text1_1_loc7,Text2_0_loc7,Text2_1_loc7,Text3_0_loc7,Text3_1_loc1,Text4_0_loc7,Text4_1_loc7,Text5_0_loc7,Text5_1_loc7,Text6_0_loc7,Text6_1_loc7,Text7_0_loc7,Text7_1_loc7, " + "Text0_0_loc8,Text0_1_loc8,Text1_0_loc8,Text1_1_loc8,Text2_0_loc8,Text2_1_loc8,Text3_0_loc8,Text3_1_loc1,Text4_0_loc8,Text4_1_loc8,Text5_0_loc8,Text5_1_loc8,Text6_0_loc8,Text6_1_loc8,Text7_0_loc8,Text7_1_loc8 " + " FROM locales_npc_text"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + NpcTextLocale& data = mNpcTextLocaleMap[entry]; + + for (uint8 i=1; i<MAX_LOCALE; ++i) + { + for (uint8 j=0; j<8; ++j) + { + std::string str0 = fields[1+8*2*(i-1)+2*j].GetCppString(); + if (!str0.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Text_0[j].size() <= idx) + data.Text_0[j].resize(idx+1); + + data.Text_0[j][idx] = str0; + } + } + std::string str1 = fields[1+8*2*(i-1)+2*j+1].GetCppString(); + if (!str1.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Text_1[j].size() <= idx) + data.Text_1[j].resize(idx+1); + + data.Text_1[j][idx] = str1; + } + } + } + } + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu NpcText locale strings", (unsigned long)mNpcTextLocaleMap.size()); +} + +//not very fast function but it is called only once a day, or on starting-up +void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp) +{ + time_t basetime = time(NULL); + sLog.outDebug("Returning mails current time: hour: %d, minute: %d, second: %d ", localtime(&basetime)->tm_hour, localtime(&basetime)->tm_min, localtime(&basetime)->tm_sec); + //delete all old mails without item and without body immediately, if starting server + if (!serverUp) + CharacterDatabase.PExecute("DELETE FROM mail WHERE expire_time < '" UI64FMTD "' AND has_items = '0' AND body = ''", (uint64)basetime); + // 0 1 2 3 4 5 6 7 8 9 + QueryResult_AutoPtr result = CharacterDatabase.PQuery("SELECT id,messageType,sender,receiver,has_items,expire_time,cod,checked,mailTemplateId FROM mail WHERE expire_time < '" UI64FMTD "'", (uint64)basetime); + if (!result) + { + barGoLink bar(1); + bar.step(); + sLog.outString(); + sLog.outString(">> Only expired mails (need to be return or delete) or DB table `mail` is empty."); + return; // any mails need to be returned or deleted + } + + //std::ostringstream delitems, delmails; //will be here for optimization + //bool deletemail = false, deleteitem = false; + //delitems << "DELETE FROM item_instance WHERE guid IN ("; + //delmails << "DELETE FROM mail WHERE id IN (" + + barGoLink bar(result->GetRowCount()); + uint32 count = 0; + Field *fields; + + do + { + bar.step(); + + fields = result->Fetch(); + Mail *m = new Mail; + m->messageID = fields[0].GetUInt32(); + m->messageType = fields[1].GetUInt8(); + m->sender = fields[2].GetUInt32(); + m->receiver = fields[3].GetUInt32(); + bool has_items = fields[4].GetBool(); + m->expire_time = (time_t)fields[5].GetUInt64(); + m->deliver_time = 0; + m->COD = fields[6].GetUInt32(); + m->checked = fields[7].GetUInt32(); + m->mailTemplateId = fields[8].GetInt16(); + + Player *pl = 0; + if (serverUp) + pl = GetPlayer((uint64)m->receiver); + if (pl && pl->m_mailsLoaded) + { //this code will run very improbably (the time is between 4 and 5 am, in game is online a player, who has old mail + //his in mailbox and he has already listed his mails) + delete m; + continue; + } + //delete or return mail: + if (has_items) + { + QueryResult_AutoPtr resultItems = CharacterDatabase.PQuery("SELECT item_guid,item_template FROM mail_items WHERE mail_id='%u'", m->messageID); + if (resultItems) + { + do + { + Field *fields2 = resultItems->Fetch(); + + uint32 item_guid_low = fields2[0].GetUInt32(); + uint32 item_template = fields2[1].GetUInt32(); + + m->AddItem(item_guid_low, item_template); + } + while (resultItems->NextRow()); + } + //if it is mail from AH, it shouldn't be returned, but deleted + if (m->messageType != MAIL_NORMAL || m->messageType == MAIL_AUCTION || (m->checked & (MAIL_CHECK_MASK_COD_PAYMENT | MAIL_CHECK_MASK_RETURNED))) + { + // mail open and then not returned + for (std::vector<MailItemInfo>::iterator itr2 = m->items.begin(); itr2 != m->items.end(); ++itr2) + CharacterDatabase.PExecute("DELETE FROM item_instance WHERE guid = '%u'", itr2->item_guid); + } + else + { + //mail will be returned: + CharacterDatabase.PExecute("UPDATE mail SET sender = '%u', receiver = '%u', expire_time = '" UI64FMTD "', deliver_time = '" UI64FMTD "',cod = '0', checked = '%u' WHERE id = '%u'", m->receiver, m->sender, (uint64)(basetime + 30*DAY), (uint64)basetime, MAIL_CHECK_MASK_RETURNED, m->messageID); + delete m; + continue; + } + } + + //deletemail = true; + //delmails << m->messageID << ", "; + CharacterDatabase.PExecute("DELETE FROM mail WHERE id = '%u'", m->messageID); + delete m; + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u mails", count); +} + +void ObjectMgr::LoadQuestAreaTriggers() +{ + mQuestAreaTriggerMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id,quest FROM areatrigger_involvedrelation"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u quest trigger points", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + ++count; + bar.step(); + + Field *fields = result->Fetch(); + + uint32 trigger_ID = fields[0].GetUInt32(); + uint32 quest_ID = fields[1].GetUInt32(); + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(trigger_ID); + if (!atEntry) + { + sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",trigger_ID); + continue; + } + + Quest const* quest = GetQuestTemplate(quest_ID); + + if (!quest) + { + sLog.outErrorDb("Table `areatrigger_involvedrelation` has record (id: %u) for not existing quest %u",trigger_ID,quest_ID); + continue; + } + + if (!quest->HasFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT)) + { + sLog.outErrorDb("Table `areatrigger_involvedrelation` has record (id: %u) for not quest %u, but quest not have flag QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT. Trigger or quest flags must be fixed, quest modified to require objective.",trigger_ID,quest_ID); + + // this will prevent quest completing without objective + const_cast<Quest*>(quest)->SetFlag(QUEST_TRINITY_FLAGS_EXPLORATION_OR_EVENT); + + // continue; - quest modified to required objective and trigger can be allowed. + } + + mQuestAreaTriggerMap[trigger_ID] = quest_ID; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u quest trigger points", count); +} + +void ObjectMgr::LoadTavernAreaTriggers() +{ + mTavernAreaTriggerSet.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id FROM areatrigger_tavern"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u tavern triggers", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + ++count; + bar.step(); + + Field *fields = result->Fetch(); + + uint32 Trigger_ID = fields[0].GetUInt32(); + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); + if (!atEntry) + { + sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",Trigger_ID); + continue; + } + + mTavernAreaTriggerSet.insert(Trigger_ID); + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u tavern triggers", count); +} + +void ObjectMgr::LoadAreaTriggerScripts() +{ + mAreaTriggerScripts.clear(); // need for reload case + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, ScriptName FROM areatrigger_scripts"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u areatrigger scripts", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + ++count; + bar.step(); + + Field *fields = result->Fetch(); + + uint32 Trigger_ID = fields[0].GetUInt32(); + const char *scriptName = fields[1].GetString(); + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); + if (!atEntry) + { + sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",Trigger_ID); + continue; + } + mAreaTriggerScripts[Trigger_ID] = GetScriptId(scriptName); + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u areatrigger scripts", count); +} + +uint32 ObjectMgr::GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 team) +{ + bool found = false; + float dist = 10000; + uint32 id = 0; + + for (uint32 i = 1; i < sTaxiNodesStore.GetNumRows(); ++i) + { + TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(i); + + if (!node || node->map_id != mapid || !node->MountCreatureID[team == ALLIANCE ? 1 : 0] && node->MountCreatureID[0] != 32981) // dk flight + continue; + + uint8 field = (uint8)((i - 1) / 32); + uint32 submask = 1<<((i-1)%32); + + // skip not taxi network nodes + if ((sTaxiNodesMask[field] & submask) == 0) + continue; + + float dist2 = (node->x - x)*(node->x - x)+(node->y - y)*(node->y - y)+(node->z - z)*(node->z - z); + if (found) + { + if (dist2 < dist) + { + dist = dist2; + id = i; + } + } + else + { + found = true; + dist = dist2; + id = i; + } + } + + return id; +} + +void ObjectMgr::GetTaxiPath(uint32 source, uint32 destination, uint32 &path, uint32 &cost) +{ + TaxiPathSetBySource::iterator src_i = sTaxiPathSetBySource.find(source); + if (src_i == sTaxiPathSetBySource.end()) + { + path = 0; + cost = 0; + return; + } + + TaxiPathSetForSource& pathSet = src_i->second; + + TaxiPathSetForSource::iterator dest_i = pathSet.find(destination); + if (dest_i == pathSet.end()) + { + path = 0; + cost = 0; + return; + } + + cost = dest_i->second.price; + path = dest_i->second.ID; +} + +uint32 ObjectMgr::GetTaxiMountDisplayId(uint32 id, uint32 team, bool allowed_alt_team /* = false */) +{ + uint32 mount_entry = 0; + uint32 mount_id = 0; + + // select mount creature id + TaxiNodesEntry const* node = sTaxiNodesStore.LookupEntry(id); + if (node) + { + if (team == ALLIANCE) + mount_entry = node->MountCreatureID[1]; + else + mount_entry = node->MountCreatureID[0]; + + // Fix for Alliance not being able to use Acherus taxi + // only one mount type for both sides + if (mount_entry == 0 && allowed_alt_team) + { + // Simply reverse the selection. At least one team in theory should have a valid mount ID to choose. + mount_entry = team == ALLIANCE ? node->MountCreatureID[0] : node->MountCreatureID[1]; + } + + CreatureInfo const *mount_info = GetCreatureTemplate(mount_entry); + if (mount_info) + { + mount_id = mount_info->GetRandomValidModelId(); + if (!mount_id) + { + sLog.outErrorDb("No displayid found for the taxi mount with the entry %u! Can't load it!", mount_entry); + return false; + } + } + } + + CreatureModelInfo const *minfo = objmgr.GetCreatureModelRandomGender(mount_id); + if (minfo) + mount_id = minfo->modelid; + + return mount_id; +} + +void ObjectMgr::GetTaxiPathNodes(uint32 path, Path &pathnodes, std::vector<uint32>& mapIds) +{ + if (path >= sTaxiPathNodesByPath.size()) + return; + + TaxiPathNodeList& nodeList = sTaxiPathNodesByPath[path]; + + pathnodes.Resize(nodeList.size()); + mapIds.resize(nodeList.size()); + + for (size_t i = 0; i < nodeList.size(); ++i) + { + pathnodes[ i ].x = nodeList[i].x; + pathnodes[ i ].y = nodeList[i].y; + pathnodes[ i ].z = nodeList[i].z; + + mapIds[i] = nodeList[i].mapid; + } +} + +void ObjectMgr::GetTransportPathNodes(uint32 path, TransportPath &pathnodes) +{ + if (path >= sTaxiPathNodesByPath.size()) + return; + + TaxiPathNodeList& nodeList = sTaxiPathNodesByPath[path]; + + pathnodes.Resize(nodeList.size()); + + for (size_t i = 0; i < nodeList.size(); ++i) + { + pathnodes[ i ].mapid = nodeList[i].mapid; + pathnodes[ i ].x = nodeList[i].x; + pathnodes[ i ].y = nodeList[i].y; + pathnodes[ i ].z = nodeList[i].z; + pathnodes[ i ].actionFlag = nodeList[i].actionFlag; + pathnodes[ i ].delay = nodeList[i].delay; + } +} + +void ObjectMgr::LoadGraveyardZones() +{ + mGraveYardMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id,ghost_zone,faction FROM game_graveyard_zone"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u graveyard-zone links", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + ++count; + bar.step(); + + Field *fields = result->Fetch(); + + uint32 safeLocId = fields[0].GetUInt32(); + uint32 zoneId = fields[1].GetUInt32(); + uint32 team = fields[2].GetUInt32(); + + WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(safeLocId); + if (!entry) + { + sLog.outErrorDb("Table `game_graveyard_zone` has record for not existing graveyard (WorldSafeLocs.dbc id) %u, skipped.",safeLocId); + continue; + } + + AreaTableEntry const *areaEntry = GetAreaEntryByAreaID(zoneId); + if (!areaEntry) + { + sLog.outErrorDb("Table `game_graveyard_zone` has record for not existing zone id (%u), skipped.",zoneId); + continue; + } + + if (areaEntry->zone != 0) + { + sLog.outErrorDb("Table `game_graveyard_zone` has record subzone id (%u) instead of zone, skipped.",zoneId); + continue; + } + + if (team != 0 && team != HORDE && team != ALLIANCE) + { + sLog.outErrorDb("Table `game_graveyard_zone` has record for non player faction (%u), skipped.",team); + continue; + } + + if (!AddGraveYardLink(safeLocId,zoneId,team,false)) + sLog.outErrorDb("Table `game_graveyard_zone` has a duplicate record for Graveyard (ID: %u) and Zone (ID: %u), skipped.",safeLocId,zoneId); + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u graveyard-zone links", count); +} + +WorldSafeLocsEntry const *ObjectMgr::GetClosestGraveYard(float x, float y, float z, uint32 MapId, uint32 team) +{ + // search for zone associated closest graveyard + uint32 zoneId = MapManager::Instance().GetZoneId(MapId,x,y,z); + + // Simulate std. algorithm: + // found some graveyard associated to (ghost_zone,ghost_map) + // + // if mapId == graveyard.mapId (ghost in plain zone or city or battleground) and search graveyard at same map + // then check faction + // if mapId != graveyard.mapId (ghost in instance) and search any graveyard associated + // then check faction + GraveYardMap::const_iterator graveLow = mGraveYardMap.lower_bound(zoneId); + GraveYardMap::const_iterator graveUp = mGraveYardMap.upper_bound(zoneId); + MapEntry const* map = sMapStore.LookupEntry(MapId); + // not need to check validity of map object; MapId _MUST_ be valid here + + if (graveLow == graveUp && !map->IsBattleArena()) + { + //sLog.outErrorDb("Table `game_graveyard_zone` incomplete: Zone %u Team %u does not have a linked graveyard.",zoneId,team); + return NULL; + } + + // at corpse map + bool foundNear = false; + float distNear = 10000; + WorldSafeLocsEntry const* entryNear = NULL; + + // at entrance map for corpse map + bool foundEntr = false; + float distEntr = 10000; + WorldSafeLocsEntry const* entryEntr = NULL; + + // some where other + WorldSafeLocsEntry const* entryFar = NULL; + + MapEntry const* mapEntry = sMapStore.LookupEntry(MapId); + + for (GraveYardMap::const_iterator itr = graveLow; itr != graveUp; ++itr) + { + GraveYardData const& data = itr->second; + + WorldSafeLocsEntry const* entry = sWorldSafeLocsStore.LookupEntry(data.safeLocId); + if (!entry) + { + sLog.outErrorDb("Table `game_graveyard_zone` has record for not existing graveyard (WorldSafeLocs.dbc id) %u, skipped.",data.safeLocId); + continue; + } + + // skip enemy faction graveyard + // team == 0 case can be at call from .neargrave + if (data.team != 0 && team != 0 && data.team != team) + continue; + + // find now nearest graveyard at other map + if (MapId != entry->map_id) + { + // if find graveyard at different map from where entrance placed (or no entrance data), use any first + if (!mapEntry || + mapEntry->entrance_map < 0 || + uint32(mapEntry->entrance_map) != entry->map_id || + (mapEntry->entrance_x == 0 && mapEntry->entrance_y == 0)) + { + // not have any corrdinates for check distance anyway + entryFar = entry; + continue; + } + + // at entrance map calculate distance (2D); + float dist2 = (entry->x - mapEntry->entrance_x)*(entry->x - mapEntry->entrance_x) + +(entry->y - mapEntry->entrance_y)*(entry->y - mapEntry->entrance_y); + if (foundEntr) + { + if (dist2 < distEntr) + { + distEntr = dist2; + entryEntr = entry; + } + } + else + { + foundEntr = true; + distEntr = dist2; + entryEntr = entry; + } + } + // find now nearest graveyard at same map + else + { + float dist2 = (entry->x - x)*(entry->x - x)+(entry->y - y)*(entry->y - y)+(entry->z - z)*(entry->z - z); + if (foundNear) + { + if (dist2 < distNear) + { + distNear = dist2; + entryNear = entry; + } + } + else + { + foundNear = true; + distNear = dist2; + entryNear = entry; + } + } + } + + if (entryNear) + return entryNear; + + if (entryEntr) + return entryEntr; + + return entryFar; +} + +GraveYardData const* ObjectMgr::FindGraveYardData(uint32 id, uint32 zoneId) +{ + GraveYardMap::const_iterator graveLow = mGraveYardMap.lower_bound(zoneId); + GraveYardMap::const_iterator graveUp = mGraveYardMap.upper_bound(zoneId); + + for (GraveYardMap::const_iterator itr = graveLow; itr != graveUp; ++itr) + { + if (itr->second.safeLocId == id) + return &itr->second; + } + + return NULL; +} + +bool ObjectMgr::AddGraveYardLink(uint32 id, uint32 zoneId, uint32 team, bool inDB) +{ + if (FindGraveYardData(id,zoneId)) + return false; + + // add link to loaded data + GraveYardData data; + data.safeLocId = id; + data.team = team; + + mGraveYardMap.insert(GraveYardMap::value_type(zoneId,data)); + + // add link to DB + if (inDB) + { + WorldDatabase.PExecuteLog("INSERT INTO game_graveyard_zone (id,ghost_zone,faction) " + "VALUES ('%u', '%u','%u')",id,zoneId,team); + } + + return true; +} + +void ObjectMgr::RemoveGraveYardLink(uint32 id, uint32 zoneId, uint32 team, bool inDB) +{ + GraveYardMap::iterator graveLow = mGraveYardMap.lower_bound(zoneId); + GraveYardMap::iterator graveUp = mGraveYardMap.upper_bound(zoneId); + if (graveLow == graveUp) + { + //sLog.outErrorDb("Table `game_graveyard_zone` incomplete: Zone %u Team %u does not have a linked graveyard.",zoneId,team); + return; + } + + bool found = false; + + GraveYardMap::iterator itr; + + for (itr = graveLow; itr != graveUp; ++itr) + { + GraveYardData & data = itr->second; + + // skip not matching safezone id + if (data.safeLocId != id) + continue; + + // skip enemy faction graveyard at same map (normal area, city, or battleground) + // team == 0 case can be at call from .neargrave + if (data.team != 0 && team != 0 && data.team != team) + continue; + + found = true; + break; + } + + // no match, return + if (!found) + return; + + // remove from links + mGraveYardMap.erase(itr); + + // remove link from DB + if (inDB) + { + WorldDatabase.PExecute("DELETE FROM game_graveyard_zone WHERE id = '%u' AND ghost_zone = '%u' AND faction = '%u'",id,zoneId,team); + } + + return; +} + +void ObjectMgr::LoadAreaTriggerTeleports() +{ + mAreaTriggers.clear(); // need for reload case + + uint32 count = 0; + + // 0 1 2 3 4 5 6 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, access_id, target_map, target_position_x, target_position_y, target_position_z, target_orientation FROM areatrigger_teleport"); + if (!result) + { + + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u area trigger teleport definitions", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + + bar.step(); + + ++count; + + uint32 Trigger_ID = fields[0].GetUInt32(); + + AreaTrigger at; + + at.access_id = fields[1].GetUInt32(); + at.target_mapId = fields[2].GetUInt32(); + at.target_X = fields[3].GetFloat(); + at.target_Y = fields[4].GetFloat(); + at.target_Z = fields[5].GetFloat(); + at.target_Orientation = fields[6].GetFloat(); + + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(Trigger_ID); + if (!atEntry) + { + sLog.outErrorDb("Area trigger (ID:%u) does not exist in `AreaTrigger.dbc`.",Trigger_ID); + continue; + } + + MapEntry const* mapEntry = sMapStore.LookupEntry(at.target_mapId); + if (!mapEntry) + { + sLog.outErrorDb("Area trigger (ID:%u) target map (ID: %u) does not exist in `Map.dbc`.",Trigger_ID,at.target_mapId); + continue; + } + + if (at.target_X == 0 && at.target_Y == 0 && at.target_Z == 0) + { + sLog.outErrorDb("Area trigger (ID:%u) target coordinates not provided.",Trigger_ID); + continue; + } + + mAreaTriggers[Trigger_ID] = at; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u area trigger teleport definitions", count); +} + +void ObjectMgr::LoadAccessRequirements() +{ + mAccessRequirements.clear(); // need for reload case + + uint32 count = 0; + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, level_min, level_max, item, item2, heroic_key, heroic_key2, quest_done, quest_failed_text, heroic_quest_done, heroic_quest_failed_text, heroic_level_min, status FROM access_requirement"); + if (!result) + { + + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u access requirement definitions", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + + bar.step(); + + ++count; + + uint32 requiremt_ID = fields[0].GetUInt32(); + + AccessRequirement ar; + + ar.levelMin = fields[1].GetUInt8(); + ar.levelMax = fields[2].GetUInt8(); + ar.heroicLevelMin = fields[11].GetUInt8(); + ar.item = fields[3].GetUInt32(); + ar.item2 = fields[4].GetUInt32(); + ar.heroicKey = fields[5].GetUInt32(); + ar.heroicKey2 = fields[6].GetUInt32(); + ar.quest = fields[7].GetUInt32(); + ar.questFailedText = fields[8].GetCppString(); + ar.heroicQuest = fields[9].GetUInt32(); + ar.heroicQuestFailedText = fields[10].GetCppString(); + ar.status = fields[12].GetUInt8(); + + if (ar.item) + { + ItemPrototype const *pProto = GetItemPrototype(ar.item); + if (!pProto) + { + sLog.outError("Key item %u does not exist for requirement %u, removing key requirement.", ar.item, requiremt_ID); + ar.item = 0; + } + } + + if (ar.item2) + { + ItemPrototype const *pProto = GetItemPrototype(ar.item2); + if (!pProto) + { + sLog.outError("Second item %u does not exist for requirement %u, removing key requirement.", ar.item2, requiremt_ID); + ar.item2 = 0; + } + } + + if (ar.heroicKey) + { + ItemPrototype const *pProto = GetItemPrototype(ar.heroicKey); + if (!pProto) + { + sLog.outError("Heroic key %u not exist for trigger %u, remove key requirement.", ar.heroicKey, requiremt_ID); + ar.heroicKey = 0; + } + } + + if (ar.heroicKey2) + { + ItemPrototype const *pProto = GetItemPrototype(ar.heroicKey2); + if (!pProto) + { + sLog.outError("Second heroic key %u not exist for trigger %u, remove key requirement.", ar.heroicKey2, requiremt_ID); + ar.heroicKey2 = 0; + } + } + + if (ar.heroicQuest) + { + QuestMap::iterator qReqItr = mQuestTemplates.find(ar.heroicQuest); + if (qReqItr == mQuestTemplates.end()) + { + sLog.outErrorDb("Required Heroic Quest %u not exist for trigger %u, remove heroic quest done requirement.",ar.heroicQuest,requiremt_ID); + ar.heroicQuest = 0; + } + } + + if (ar.quest) + { + QuestMap::iterator qReqItr = mQuestTemplates.find(ar.quest); + if (qReqItr == mQuestTemplates.end()) + { + sLog.outErrorDb("Required Quest %u not exist for trigger %u, remove quest done requirement.",ar.quest,requiremt_ID); + ar.quest = 0; + } + } + + mAccessRequirements[requiremt_ID] = ar; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u access requirement definitions", count); +} + +/* + * Searches for the areatrigger which teleports players out of the given map with instance_template.parent field support + */ +AreaTrigger const* ObjectMgr::GetGoBackTrigger(uint32 Map) const +{ + bool useParentDbValue = false; + uint32 parentId = 0; + const MapEntry *mapEntry = sMapStore.LookupEntry(Map); + if (!mapEntry || mapEntry->entrance_map < 0) + return NULL; + + if (mapEntry->IsDungeon()) + { + const InstanceTemplate *iTemplate = objmgr.GetInstanceTemplate(Map); + + if (!iTemplate) + return NULL; + + parentId = iTemplate->parent; + useParentDbValue = true; + } + + uint32 entrance_map = uint32(mapEntry->entrance_map); + for (AreaTriggerMap::const_iterator itr = mAreaTriggers.begin(); itr != mAreaTriggers.end(); ++itr) + if ((!useParentDbValue && itr->second.target_mapId == entrance_map) || (useParentDbValue && itr->second.target_mapId == parentId)) + { + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(itr->first); + if (atEntry && atEntry->mapid == Map) + return &itr->second; + } + return NULL; +} + +/** + * Searches for the areatrigger which teleports players to the given map + */ +AreaTrigger const* ObjectMgr::GetMapEntranceTrigger(uint32 Map) const +{ + for (AreaTriggerMap::const_iterator itr = mAreaTriggers.begin(); itr != mAreaTriggers.end(); ++itr) + { + if (itr->second.target_mapId == Map) + { + AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(itr->first); + if (atEntry) + return &itr->second; + } + } + return NULL; +} + +void ObjectMgr::SetHighestGuids() +{ + QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT MAX(guid) FROM characters"); + if (result) + m_hiCharGuid = (*result)[0].GetUInt32()+1; + + result = WorldDatabase.Query("SELECT MAX(guid) FROM creature"); + if (result) + m_hiCreatureGuid = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(guid) FROM item_instance"); + if (result) + m_hiItemGuid = (*result)[0].GetUInt32()+1; + + // Cleanup other tables from not existed guids ( >= m_hiItemGuid) + CharacterDatabase.PExecute("DELETE FROM character_inventory WHERE item >= '%u'", m_hiItemGuid); + CharacterDatabase.PExecute("DELETE FROM mail_items WHERE item_guid >= '%u'", m_hiItemGuid); + CharacterDatabase.PExecute("DELETE FROM auctionhouse WHERE itemguid >= '%u'", m_hiItemGuid); + CharacterDatabase.PExecute("DELETE FROM guild_bank_item WHERE item_guid >= '%u'", m_hiItemGuid); + + result = WorldDatabase.Query("SELECT MAX(guid) FROM gameobject"); + if (result) + m_hiGoGuid = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(id) FROM auctionhouse"); + if (result) + m_auctionid = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(id) FROM mail"); + if (result) + m_mailid = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(guid) FROM corpse"); + if (result) + m_hiCorpseGuid = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(arenateamid) FROM arena_team"); + if (result) + m_arenaTeamId = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(setguid) FROM character_equipmentsets"); + if (result) + m_equipmentSetGuid = (*result)[0].GetUInt64()+1; + + result = CharacterDatabase.Query("SELECT MAX(guildid) FROM guild"); + if (result) + m_guildId = (*result)[0].GetUInt32()+1; + + result = CharacterDatabase.Query("SELECT MAX(guid) FROM groups"); + if (result) + m_hiGroupGuid = (*result)[0].GetUInt32()+1; +} + +uint32 ObjectMgr::GenerateArenaTeamId() +{ + if (m_arenaTeamId >= 0xFFFFFFFE) + { + sLog.outError("Arena team ids overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_arenaTeamId++; +} + +uint32 ObjectMgr::GenerateAuctionID() +{ + if (m_auctionid >= 0xFFFFFFFE) + { + sLog.outError("Auctions ids overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_auctionid++; +} + +uint64 ObjectMgr::GenerateEquipmentSetGuid() +{ + if (m_equipmentSetGuid >= 0xFFFFFFFFFFFFFFFEll) + { + sLog.outError("EquipmentSet guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_equipmentSetGuid++; +} + +uint32 ObjectMgr::GenerateGuildId() +{ + if (m_guildId >= 0xFFFFFFFE) + { + sLog.outError("Guild ids overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_guildId++; +} + +uint32 ObjectMgr::GenerateMailID() +{ + if (m_mailid >= 0xFFFFFFFE) + { + sLog.outError("Mail ids overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_mailid++; +} + +uint32 ObjectMgr::GenerateLowGuid(HighGuid guidhigh) +{ + switch(guidhigh) + { + case HIGHGUID_ITEM: + if (m_hiItemGuid >= 0xFFFFFFFE) + { + sLog.outError("Item guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiItemGuid++; + case HIGHGUID_UNIT: + if (m_hiCreatureGuid >= 0x00FFFFFE) + { + sLog.outError("Creature guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiCreatureGuid++; + case HIGHGUID_PET: + if (m_hiPetGuid >= 0x00FFFFFE) + { + sLog.outError("Pet guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiPetGuid++; + case HIGHGUID_VEHICLE: + if (m_hiVehicleGuid >= 0x00FFFFFF) + { + sLog.outError("Vehicle guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiVehicleGuid++; + case HIGHGUID_PLAYER: + if (m_hiCharGuid >= 0xFFFFFFFE) + { + sLog.outError("Players guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiCharGuid++; + case HIGHGUID_GAMEOBJECT: + if (m_hiGoGuid >= 0x00FFFFFE) + { + sLog.outError("Gameobject guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiGoGuid++; + case HIGHGUID_CORPSE: + if (m_hiCorpseGuid >= 0xFFFFFFFE) + { + sLog.outError("Corpse guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiCorpseGuid++; + case HIGHGUID_DYNAMICOBJECT: + if (m_hiDoGuid >= 0xFFFFFFFE) + { + sLog.outError("DynamicObject guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiDoGuid++; + case HIGHGUID_GROUP: + if (m_hiGroupGuid >= 0xFFFFFFFE) + { + sLog.outError("Group guid overflow!! Can't continue, shutting down server. "); + World::StopNow(ERROR_EXIT_CODE); + } + return m_hiGroupGuid++; + default: + ASSERT(0); + } + + ASSERT(0); + return 0; +} + +void ObjectMgr::LoadGameObjectLocales() +{ + mGameObjectLocaleMap.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry," + "name_loc1,name_loc2,name_loc3,name_loc4,name_loc5,name_loc6,name_loc7,name_loc8," + "castbarcaption_loc1,castbarcaption_loc2,castbarcaption_loc3,castbarcaption_loc4," + "castbarcaption_loc5,castbarcaption_loc6,castbarcaption_loc7,castbarcaption_loc8 FROM locales_gameobject"); + + if (!result) + return; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 entry = fields[0].GetUInt32(); + + GameObjectLocale& data = mGameObjectLocaleMap[entry]; + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[i].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.Name.size() <= idx) + data.Name.resize(idx+1); + + data.Name[idx] = str; + } + } + } + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[i+(MAX_LOCALE-1)].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + if (data.CastBarCaption.size() <= idx) + data.CastBarCaption.resize(idx+1); + + data.CastBarCaption[idx] = str; + } + } + } + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %lu gameobject locale strings", (unsigned long)mGameObjectLocaleMap.size()); +} + +struct SQLGameObjectLoader : public SQLStorageLoaderBase<SQLGameObjectLoader> +{ + template<class D> + void convert_from_str(uint32 /*field_pos*/, char *src, D &dst) + { + dst = D(objmgr.GetScriptId(src)); + } +}; + +inline void CheckGOLockId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N) +{ + if (sLockStore.LookupEntry(dataN)) + return; + + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but lock (Id: %u) not found.", + goInfo->id,goInfo->type,N,goInfo->door.lockId,goInfo->door.lockId); +} + +inline void CheckGOLinkedTrapId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N) +{ + if (GameObjectInfo const* trapInfo = sGOStorage.LookupEntry<GameObjectInfo>(dataN)) + { + if (trapInfo->type != GAMEOBJECT_TYPE_TRAP) + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but GO (Entry %u) have not GAMEOBJECT_TYPE_TRAP (%u) type.", + goInfo->id,goInfo->type,N,dataN,dataN,GAMEOBJECT_TYPE_TRAP); + } + /* disable check for while (too many error reports baout not existed in trap templates + else + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but trap GO (Entry %u) not exist in `gameobject_template`.", + goInfo->id,goInfo->type,N,dataN,dataN); + */ +} + +inline void CheckGOSpellId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N) +{ + if (sSpellStore.LookupEntry(dataN)) + return; + + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but Spell (Entry %u) not exist.", + goInfo->id,goInfo->type,N,dataN,dataN); +} + +inline void CheckAndFixGOChairHeightId(GameObjectInfo const* goInfo,uint32 const& dataN,uint32 N) +{ + if (dataN <= (UNIT_STAND_STATE_SIT_HIGH_CHAIR-UNIT_STAND_STATE_SIT_LOW_CHAIR)) + return; + + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but correct chair height in range 0..%i.", + goInfo->id,goInfo->type,N,dataN,UNIT_STAND_STATE_SIT_HIGH_CHAIR-UNIT_STAND_STATE_SIT_LOW_CHAIR); + + // prevent client and server unexpected work + const_cast<uint32&>(dataN) = 0; +} + +inline void CheckGONoDamageImmuneId(GameObjectInfo const* goInfo,uint32 dataN,uint32 N) +{ + // 0/1 correct values + if (dataN <= 1) + return; + + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but expected boolean (0/1) noDamageImmune field value.", + goInfo->id,goInfo->type,N,dataN); +} + +inline void CheckGOConsumable(GameObjectInfo const* goInfo,uint32 dataN,uint32 N) +{ + // 0/1 correct values + if (dataN <= 1) + return; + + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data%d=%u but expected boolean (0/1) consumable field value.", + goInfo->id,goInfo->type,N,dataN); +} + +void ObjectMgr::LoadGameobjectInfo() +{ + SQLGameObjectLoader loader; + loader.Load(sGOStorage); + + // some checks + for (uint32 id = 1; id < sGOStorage.MaxEntry; id++) + { + GameObjectInfo const* goInfo = sGOStorage.LookupEntry<GameObjectInfo>(id); + if (!goInfo) + continue; + + // some GO types have unused go template, check goInfo->displayId at GO spawn data loading or ignore + + switch(goInfo->type) + { + case GAMEOBJECT_TYPE_DOOR: //0 + { + if (goInfo->door.lockId) + CheckGOLockId(goInfo,goInfo->door.lockId,1); + CheckGONoDamageImmuneId(goInfo,goInfo->door.noDamageImmune,3); + break; + } + case GAMEOBJECT_TYPE_BUTTON: //1 + { + if (goInfo->button.lockId) + CheckGOLockId(goInfo,goInfo->button.lockId,1); + CheckGONoDamageImmuneId(goInfo,goInfo->button.noDamageImmune,4); + break; + } + case GAMEOBJECT_TYPE_QUESTGIVER: //2 + { + if (goInfo->questgiver.lockId) + CheckGOLockId(goInfo,goInfo->questgiver.lockId,0); + CheckGONoDamageImmuneId(goInfo,goInfo->questgiver.noDamageImmune,5); + break; + } + case GAMEOBJECT_TYPE_CHEST: //3 + { + if (goInfo->chest.lockId) + CheckGOLockId(goInfo,goInfo->chest.lockId,0); + + CheckGOConsumable(goInfo,goInfo->chest.consumable,3); + + if (goInfo->chest.linkedTrapId) // linked trap + CheckGOLinkedTrapId(goInfo,goInfo->chest.linkedTrapId,7); + break; + } + case GAMEOBJECT_TYPE_TRAP: //6 + { + if (goInfo->trap.lockId) + CheckGOLockId(goInfo,goInfo->trap.lockId,0); + /* disable check for while, too many not existed spells + if (goInfo->trap.spellId) // spell + CheckGOSpellId(goInfo,goInfo->trap.spellId,3); + */ + break; + } + case GAMEOBJECT_TYPE_CHAIR: //7 + CheckAndFixGOChairHeightId(goInfo,goInfo->chair.height,1); + break; + case GAMEOBJECT_TYPE_SPELL_FOCUS: //8 + { + if (goInfo->spellFocus.focusId) + { + if (!sSpellFocusObjectStore.LookupEntry(goInfo->spellFocus.focusId)) + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data0=%u but SpellFocus (Id: %u) not exist.", + id,goInfo->type,goInfo->spellFocus.focusId,goInfo->spellFocus.focusId); + } + + if (goInfo->spellFocus.linkedTrapId) // linked trap + CheckGOLinkedTrapId(goInfo,goInfo->spellFocus.linkedTrapId,2); + break; + } + case GAMEOBJECT_TYPE_GOOBER: //10 + { + if (goInfo->goober.lockId) + CheckGOLockId(goInfo,goInfo->goober.lockId,0); + + CheckGOConsumable(goInfo,goInfo->goober.consumable,3); + + if (goInfo->goober.pageId) // pageId + { + if (!sPageTextStore.LookupEntry<PageText>(goInfo->goober.pageId)) + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data7=%u but PageText (Entry %u) not exist.", + id,goInfo->type,goInfo->goober.pageId,goInfo->goober.pageId); + } + /* disable check for while, too many not existed spells + if (goInfo->goober.spellId) // spell + CheckGOSpellId(goInfo,goInfo->goober.spellId,10); + */ + CheckGONoDamageImmuneId(goInfo,goInfo->goober.noDamageImmune,11); + if (goInfo->goober.linkedTrapId) // linked trap + CheckGOLinkedTrapId(goInfo,goInfo->goober.linkedTrapId,12); + break; + } + case GAMEOBJECT_TYPE_AREADAMAGE: //12 + { + if (goInfo->areadamage.lockId) + CheckGOLockId(goInfo,goInfo->areadamage.lockId,0); + break; + } + case GAMEOBJECT_TYPE_CAMERA: //13 + { + if (goInfo->camera.lockId) + CheckGOLockId(goInfo,goInfo->camera.lockId,0); + break; + } + case GAMEOBJECT_TYPE_MO_TRANSPORT: //15 + { + if (goInfo->moTransport.taxiPathId) + { + if (goInfo->moTransport.taxiPathId >= sTaxiPathNodesByPath.size() || sTaxiPathNodesByPath[goInfo->moTransport.taxiPathId].empty()) + sLog.outErrorDb("Gameobject (Entry: %u GoType: %u) have data0=%u but TaxiPath (Id: %u) not exist.", + id,goInfo->type,goInfo->moTransport.taxiPathId,goInfo->moTransport.taxiPathId); + } + break; + } + case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18 + { + /* disable check for while, too many not existed spells + // always must have spell + CheckGOSpellId(goInfo,goInfo->summoningRitual.spellId,1); + */ + break; + } + case GAMEOBJECT_TYPE_SPELLCASTER: //22 + { + // always must have spell + CheckGOSpellId(goInfo,goInfo->spellcaster.spellId,0); + break; + } + case GAMEOBJECT_TYPE_FLAGSTAND: //24 + { + if (goInfo->flagstand.lockId) + CheckGOLockId(goInfo,goInfo->flagstand.lockId,0); + CheckGONoDamageImmuneId(goInfo,goInfo->flagstand.noDamageImmune,5); + break; + } + case GAMEOBJECT_TYPE_FISHINGHOLE: //25 + { + if (goInfo->fishinghole.lockId) + CheckGOLockId(goInfo,goInfo->fishinghole.lockId,4); + break; + } + case GAMEOBJECT_TYPE_FLAGDROP: //26 + { + if (goInfo->flagdrop.lockId) + CheckGOLockId(goInfo,goInfo->flagdrop.lockId,0); + CheckGONoDamageImmuneId(goInfo,goInfo->flagdrop.noDamageImmune,3); + break; + } + case GAMEOBJECT_TYPE_BARBER_CHAIR: //32 + CheckAndFixGOChairHeightId(goInfo,goInfo->barberChair.chairheight,0); + break; + } + } + + sLog.outString(">> Loaded %u game object templates", sGOStorage.RecordCount); + sLog.outString(); +} + +void ObjectMgr::LoadExplorationBaseXP() +{ + uint32 count = 0; + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT level,basexp FROM exploration_basexp"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u BaseXP definitions", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + uint8 level = fields[0].GetUInt8(); + uint32 basexp = fields[1].GetUInt32(); + mBaseXPTable[level] = basexp; + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u BaseXP definitions", count); +} + +uint32 ObjectMgr::GetBaseXP(uint8 level) +{ + return mBaseXPTable[level] ? mBaseXPTable[level] : 0; +} + +uint32 ObjectMgr::GetXPForLevel(uint8 level) +{ + if (level < mPlayerXPperLevel.size()) + return mPlayerXPperLevel[level]; + return 0; +} + +void ObjectMgr::LoadPetNames() +{ + uint32 count = 0; + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT word,entry,half FROM pet_name_generation"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u pet name parts", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + std::string word = fields[0].GetString(); + uint32 entry = fields[1].GetUInt32(); + bool half = fields[2].GetBool(); + if (half) + PetHalfName1[entry].push_back(word); + else + PetHalfName0[entry].push_back(word); + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u pet name parts", count); +} + +void ObjectMgr::LoadPetNumber() +{ + QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT MAX(id) FROM character_pet"); + if (result) + { + Field *fields = result->Fetch(); + m_hiPetNumber = fields[0].GetUInt32()+1; + } + + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded the max pet number: %d", m_hiPetNumber-1); +} + +std::string ObjectMgr::GeneratePetName(uint32 entry) +{ + std::vector<std::string> & list0 = PetHalfName0[entry]; + std::vector<std::string> & list1 = PetHalfName1[entry]; + + if (list0.empty() || list1.empty()) + { + CreatureInfo const *cinfo = GetCreatureTemplate(entry); + char* petname = GetPetName(cinfo->family, sWorld.GetDefaultDbcLocale()); + if (!petname) + petname = cinfo->Name; + return std::string(petname); + } + + return *(list0.begin()+urand(0, list0.size()-1)) + *(list1.begin()+urand(0, list1.size()-1)); +} + +uint32 ObjectMgr::GeneratePetNumber() +{ + return ++m_hiPetNumber; +} + +void ObjectMgr::LoadCorpses() +{ + uint32 count = 0; + // 0 1 2 3 4 5 6 7 8 9 10 + QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT position_x, position_y, position_z, orientation, map, data, time, corpse_type, instance, phaseMask, guid FROM corpse WHERE corpse_type <> 0"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u corpses", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + + uint32 guid = fields[result->GetFieldCount()-1].GetUInt32(); + + Corpse *corpse = new Corpse; + if (!corpse->LoadFromDB(guid,fields)) + { + delete corpse; + continue; + } + + ObjectAccessor::Instance().AddCorpse(corpse); + + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u corpses", count); +} + +void ObjectMgr::LoadReputationOnKill() +{ + uint32 count = 0; + + // 0 1 2 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT creature_id, RewOnKillRepFaction1, RewOnKillRepFaction2," + // 3 4 5 6 7 8 9 + "IsTeamAward1, MaxStanding1, RewOnKillRepValue1, IsTeamAward2, MaxStanding2, RewOnKillRepValue2, TeamDependent " + "FROM creature_onkill_reputation"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 creature_id = fields[0].GetUInt32(); + + ReputationOnKillEntry repOnKill; + repOnKill.repfaction1 = fields[1].GetUInt32(); + repOnKill.repfaction2 = fields[2].GetUInt32(); + repOnKill.is_teamaward1 = fields[3].GetBool(); + repOnKill.reputation_max_cap1 = fields[4].GetUInt32(); + repOnKill.repvalue1 = fields[5].GetInt32(); + repOnKill.is_teamaward2 = fields[6].GetBool(); + repOnKill.reputation_max_cap2 = fields[7].GetUInt32(); + repOnKill.repvalue2 = fields[8].GetInt32(); + repOnKill.team_dependent = fields[9].GetUInt8(); + + if (!GetCreatureTemplate(creature_id)) + { + sLog.outErrorDb("Table `creature_onkill_reputation` have data for not existed creature entry (%u), skipped",creature_id); + continue; + } + + if (repOnKill.repfaction1) + { + FactionEntry const *factionEntry1 = sFactionStore.LookupEntry(repOnKill.repfaction1); + if (!factionEntry1) + { + sLog.outErrorDb("Faction (faction.dbc) %u does not exist but is used in `creature_onkill_reputation`",repOnKill.repfaction1); + continue; + } + } + + if (repOnKill.repfaction2) + { + FactionEntry const *factionEntry2 = sFactionStore.LookupEntry(repOnKill.repfaction2); + if (!factionEntry2) + { + sLog.outErrorDb("Faction (faction.dbc) %u does not exist but is used in `creature_onkill_reputation`",repOnKill.repfaction2); + continue; + } + } + + mRepOnKill[creature_id] = repOnKill; + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u creature award reputation definitions", count); +} + +void ObjectMgr::LoadPointsOfInterest() +{ + uint32 count = 0; + + // 0 1 2 3 4 5 6 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, x, y, icon, flags, data, icon_name FROM points_of_interest"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 point_id = fields[0].GetUInt32(); + + PointOfInterest POI; + POI.x = fields[1].GetFloat(); + POI.y = fields[2].GetFloat(); + POI.icon = fields[3].GetUInt32(); + POI.flags = fields[4].GetUInt32(); + POI.data = fields[5].GetUInt32(); + POI.icon_name = fields[6].GetCppString(); + + if (!Trinity::IsValidMapCoord(POI.x,POI.y)) + { + sLog.outErrorDb("Table `points_of_interest` (Entry: %u) have invalid coordinates (X: %f Y: %f), ignored.",point_id,POI.x,POI.y); + continue; + } + + mPointsOfInterest[point_id] = POI; + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u Points of Interest definitions", count); +} + +void ObjectMgr::LoadQuestPOI() +{ + uint32 count = 0; + + // 0 1 2 3 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT questId, id, objIndex, mapid, WorldMapAreaId, FloorId, unk3, unk4 FROM quest_poi order by questId"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 quest POI definitions. DB table `quest_poi` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 questId = fields[0].GetUInt32(); + uint32 id = fields[1].GetUInt32(); + int32 objIndex = fields[2].GetInt32(); + uint32 mapId = fields[3].GetUInt32(); + uint32 WorldMapAreaId = fields[4].GetUInt32(); + uint32 FloorId = fields[5].GetUInt32(); + uint32 unk3 = fields[6].GetUInt32(); + uint32 unk4 = fields[7].GetUInt32(); + + QuestPOI POI(id, objIndex, mapId, WorldMapAreaId, FloorId, unk3, unk4); + + QueryResult_AutoPtr points = WorldDatabase.PQuery("SELECT x, y FROM quest_poi_points WHERE questId='%u' AND id='%i'", questId, id); + + if (points) + { + do + { + Field *pointFields = points->Fetch(); + int32 x = pointFields[0].GetInt32(); + int32 y = pointFields[1].GetInt32(); + QuestPOIPoint point(x, y); + POI.points.push_back(point); + } while (points->NextRow()); + } + + mQuestPOIMap[questId].push_back(POI); + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u quest POI definitions", count); +} + +void ObjectMgr::LoadNPCSpellClickSpells() +{ + uint32 count = 0; + + mSpellClickInfoMap.clear(); + // 0 1 2 3 4 5 6 7 8 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT npc_entry, spell_id, quest_start, quest_start_active, quest_end, cast_flags, aura_required, aura_forbidden, user_type FROM npc_spellclick_spells"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 npc_entry = fields[0].GetUInt32(); + CreatureInfo const* cInfo = GetCreatureTemplate(npc_entry); + if (!cInfo) + { + sLog.outErrorDb("Table npc_spellclick_spells references unknown creature_template %u. Skipping entry.", npc_entry); + continue; + } + + if (!(cInfo->npcflag & UNIT_NPC_FLAG_SPELLCLICK)) + const_cast<CreatureInfo*>(cInfo)->npcflag |= UNIT_NPC_FLAG_SPELLCLICK; + + uint32 spellid = fields[1].GetUInt32(); + SpellEntry const *spellinfo = sSpellStore.LookupEntry(spellid); + if (!spellinfo) + { + sLog.outErrorDb("Table npc_spellclick_spells references unknown spellid %u. Skipping entry.", spellid); + continue; + } + + uint32 auraRequired = fields[6].GetUInt32(); + if (auraRequired) + { + SpellEntry const *aurReqInfo = sSpellStore.LookupEntry(auraRequired); + if (!aurReqInfo) + { + sLog.outErrorDb("Table npc_spellclick_spells references unknown aura required %u. Skipping entry.", auraRequired); + continue; + } + } + + uint32 auraForbidden = fields[7].GetUInt32(); + if (auraForbidden) + { + SpellEntry const *aurForInfo = sSpellStore.LookupEntry(auraForbidden); + if (!aurForInfo) + { + sLog.outErrorDb("Table npc_spellclick_spells references unknown aura forbidden %u. Skipping entry.", auraForbidden); + continue; + } + } + + uint32 quest_start = fields[2].GetUInt32(); + + // quest might be 0 to enable spellclick independent of any quest + if (quest_start) + { + if (mQuestTemplates.find(quest_start) == mQuestTemplates.end()) + { + sLog.outErrorDb("Table npc_spellclick_spells references unknown start quest %u. Skipping entry.", quest_start); + continue; + } + } + + bool quest_start_active = fields[3].GetBool(); + + uint32 quest_end = fields[4].GetUInt32(); + // quest might be 0 to enable spellclick active infinity after start quest + if (quest_end) + { + if (mQuestTemplates.find(quest_end) == mQuestTemplates.end()) + { + sLog.outErrorDb("Table npc_spellclick_spells references unknown end quest %u. Skipping entry.", quest_end); + continue; + } + } + + uint8 userType = fields[8].GetUInt8(); + if (userType >= SPELL_CLICK_USER_MAX) + sLog.outErrorDb("Table npc_spellclick_spells references unknown user type %u. Skipping entry.", uint32(userType)); + + uint8 castFlags = fields[5].GetUInt8(); + SpellClickInfo info; + info.spellId = spellid; + info.questStart = quest_start; + info.questStartCanActive = quest_start_active; + info.questEnd = quest_end; + info.castFlags = castFlags; + info.auraRequired = auraRequired; + info.auraForbidden = auraForbidden; + info.userType = SpellClickUserTypes(userType); + mSpellClickInfoMap.insert(SpellClickInfoMap::value_type(npc_entry, info)); + + // mark creature template as spell clickable + const_cast<CreatureInfo*>(cInfo)->npcflag |= UNIT_NPC_FLAG_SPELLCLICK; + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u spellclick definitions", count); +} + +void ObjectMgr::LoadWeatherZoneChances() +{ + uint32 count = 0; + + // 0 1 2 3 4 5 6 7 8 9 10 11 12 + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT zone, spring_rain_chance, spring_snow_chance, spring_storm_chance, summer_rain_chance, summer_snow_chance, summer_storm_chance, fall_rain_chance, fall_snow_chance, fall_storm_chance, winter_rain_chance, winter_snow_chance, winter_storm_chance FROM game_weather"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 weather definitions. DB table `game_weather` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 zone_id = fields[0].GetUInt32(); + + WeatherZoneChances& wzc = mWeatherZoneMap[zone_id]; + + for (uint8 season = 0; season < WEATHER_SEASONS; ++season) + { + wzc.data[season].rainChance = fields[season * (MAX_WEATHER_TYPE-1) + 1].GetUInt32(); + wzc.data[season].snowChance = fields[season * (MAX_WEATHER_TYPE-1) + 2].GetUInt32(); + wzc.data[season].stormChance = fields[season * (MAX_WEATHER_TYPE-1) + 3].GetUInt32(); + + if (wzc.data[season].rainChance > 100) + { + wzc.data[season].rainChance = 25; + sLog.outErrorDb("Weather for zone %u season %u has wrong rain chance > 100%%",zone_id,season); + } + + if (wzc.data[season].snowChance > 100) + { + wzc.data[season].snowChance = 25; + sLog.outErrorDb("Weather for zone %u season %u has wrong snow chance > 100%%",zone_id,season); + } + + if (wzc.data[season].stormChance > 100) + { + wzc.data[season].stormChance = 25; + sLog.outErrorDb("Weather for zone %u season %u has wrong storm chance > 100%%",zone_id,season); + } + } + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u weather definitions", count); +} + +void ObjectMgr::SaveCreatureRespawnTime(uint32 loguid, uint32 instance, time_t t) +{ + mCreatureRespawnTimes[MAKE_PAIR64(loguid,instance)] = t; + WorldDatabase.PExecute("DELETE FROM creature_respawn WHERE guid = '%u' AND instance = '%u'", loguid, instance); + if (t) + WorldDatabase.PExecute("INSERT INTO creature_respawn VALUES ('%u', '" UI64FMTD "', '%u')", loguid, uint64(t), instance); +} + +void ObjectMgr::DeleteCreatureData(uint32 guid) +{ + // remove mapid*cellid -> guid_set map + CreatureData const* data = GetCreatureData(guid); + if (data) + RemoveCreatureFromGrid(guid, data); + + mCreatureDataMap.erase(guid); +} + +void ObjectMgr::SaveGORespawnTime(uint32 loguid, uint32 instance, time_t t) +{ + mGORespawnTimes[MAKE_PAIR64(loguid,instance)] = t; + WorldDatabase.PExecute("DELETE FROM gameobject_respawn WHERE guid = '%u' AND instance = '%u'", loguid, instance); + if (t) + WorldDatabase.PExecute("INSERT INTO gameobject_respawn VALUES ('%u', '" UI64FMTD "', '%u')", loguid, uint64(t), instance); +} + +void ObjectMgr::DeleteRespawnTimeForInstance(uint32 instance) +{ + RespawnTimes::iterator next; + + for (RespawnTimes::iterator itr = mGORespawnTimes.begin(); itr != mGORespawnTimes.end(); itr = next) + { + next = itr; + ++next; + + if (GUID_HIPART(itr->first) == instance) + mGORespawnTimes.erase(itr); + } + + for (RespawnTimes::iterator itr = mCreatureRespawnTimes.begin(); itr != mCreatureRespawnTimes.end(); itr = next) + { + next = itr; + ++next; + + if (GUID_HIPART(itr->first) == instance) + mCreatureRespawnTimes.erase(itr); + } + + WorldDatabase.PExecute("DELETE FROM creature_respawn WHERE instance = '%u'", instance); + WorldDatabase.PExecute("DELETE FROM gameobject_respawn WHERE instance = '%u'", instance); +} + +void ObjectMgr::DeleteGOData(uint32 guid) +{ + // remove mapid*cellid -> guid_set map + GameObjectData const* data = GetGOData(guid); + if (data) + RemoveGameobjectFromGrid(guid, data); + + mGameObjectDataMap.erase(guid); +} + +void ObjectMgr::AddCorpseCellData(uint32 mapid, uint32 cellid, uint32 player_guid, uint32 instance) +{ + // corpses are always added to spawn mode 0 and they are spawned by their instance id + CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(mapid,0)][cellid]; + cell_guids.corpses[player_guid] = instance; +} + +void ObjectMgr::DeleteCorpseCellData(uint32 mapid, uint32 cellid, uint32 player_guid) +{ + // corpses are always added to spawn mode 0 and they are spawned by their instance id + CellObjectGuids& cell_guids = mMapObjectGuids[MAKE_PAIR32(mapid,0)][cellid]; + cell_guids.corpses.erase(player_guid); +} + +void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map,char const* table) +{ + map.clear(); // need for reload case + + uint32 count = 0; + + QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT id,quest FROM %s",table); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded 0 quest relations from %s. DB table `%s` is empty.",table,table); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + uint32 id = fields[0].GetUInt32(); + uint32 quest = fields[1].GetUInt32(); + + if (mQuestTemplates.find(quest) == mQuestTemplates.end()) + { + sLog.outErrorDb("Table `%s: Quest %u listed for entry %u does not exist.",table,quest,id); + continue; + } + + map.insert(QuestRelations::value_type(id,quest)); + + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u quest relations from %s", count,table); +} + +void ObjectMgr::LoadGameobjectQuestRelations() +{ + LoadQuestRelationsHelper(mGOQuestRelations,"gameobject_questrelation"); + + for (QuestRelations::iterator itr = mGOQuestRelations.begin(); itr != mGOQuestRelations.end(); ++itr) + { + GameObjectInfo const* goInfo = GetGameObjectInfo(itr->first); + if (!goInfo) + sLog.outErrorDb("Table `gameobject_questrelation` have data for not existed gameobject entry (%u) and existed quest %u",itr->first,itr->second); + else if (goInfo->type != GAMEOBJECT_TYPE_QUESTGIVER) + sLog.outErrorDb("Table `gameobject_questrelation` have data gameobject entry (%u) for quest %u, but GO is not GAMEOBJECT_TYPE_QUESTGIVER",itr->first,itr->second); + } +} + +void ObjectMgr::LoadGameobjectInvolvedRelations() +{ + LoadQuestRelationsHelper(mGOQuestInvolvedRelations,"gameobject_involvedrelation"); + + for (QuestRelations::iterator itr = mGOQuestInvolvedRelations.begin(); itr != mGOQuestInvolvedRelations.end(); ++itr) + { + GameObjectInfo const* goInfo = GetGameObjectInfo(itr->first); + if (!goInfo) + sLog.outErrorDb("Table `gameobject_involvedrelation` have data for not existed gameobject entry (%u) and existed quest %u",itr->first,itr->second); + else if (goInfo->type != GAMEOBJECT_TYPE_QUESTGIVER) + sLog.outErrorDb("Table `gameobject_involvedrelation` have data gameobject entry (%u) for quest %u, but GO is not GAMEOBJECT_TYPE_QUESTGIVER",itr->first,itr->second); + } +} + +void ObjectMgr::LoadCreatureQuestRelations() +{ + LoadQuestRelationsHelper(mCreatureQuestRelations,"creature_questrelation"); + + for (QuestRelations::iterator itr = mCreatureQuestRelations.begin(); itr != mCreatureQuestRelations.end(); ++itr) + { + CreatureInfo const* cInfo = GetCreatureTemplate(itr->first); + if (!cInfo) + sLog.outErrorDb("Table `creature_questrelation` have data for not existed creature entry (%u) and existed quest %u",itr->first,itr->second); + else if (!(cInfo->npcflag & UNIT_NPC_FLAG_QUESTGIVER)) + sLog.outErrorDb("Table `creature_questrelation` has creature entry (%u) for quest %u, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER",itr->first,itr->second); + } +} + +void ObjectMgr::LoadCreatureInvolvedRelations() +{ + LoadQuestRelationsHelper(mCreatureQuestInvolvedRelations,"creature_involvedrelation"); + + for (QuestRelations::iterator itr = mCreatureQuestInvolvedRelations.begin(); itr != mCreatureQuestInvolvedRelations.end(); ++itr) + { + CreatureInfo const* cInfo = GetCreatureTemplate(itr->first); + if (!cInfo) + sLog.outErrorDb("Table `creature_involvedrelation` have data for not existed creature entry (%u) and existed quest %u",itr->first,itr->second); + else if (!(cInfo->npcflag & UNIT_NPC_FLAG_QUESTGIVER)) + sLog.outErrorDb("Table `creature_involvedrelation` has creature entry (%u) for quest %u, but npcflag does not include UNIT_NPC_FLAG_QUESTGIVER",itr->first,itr->second); + } +} + +void ObjectMgr::LoadReservedPlayersNames() +{ + m_ReservedNames.clear(); // need for reload case + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT name FROM reserved_name"); + + uint32 count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u reserved player names", count); + return; + } + + barGoLink bar(result->GetRowCount()); + + Field* fields; + do + { + bar.step(); + fields = result->Fetch(); + std::string name= fields[0].GetCppString(); + + std::wstring wstr; + if (!Utf8toWStr (name,wstr)) + { + sLog.outError("Table `reserved_name` have invalid name: %s", name.c_str()); + continue; + } + + wstrToLower(wstr); + + m_ReservedNames.insert(wstr); + ++count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u reserved player names", count); +} + +bool ObjectMgr::IsReservedName(const std::string& name) const +{ + std::wstring wstr; + if (!Utf8toWStr (name,wstr)) + return false; + + wstrToLower(wstr); + + return m_ReservedNames.find(wstr) != m_ReservedNames.end(); +} + +enum LanguageType +{ + LT_BASIC_LATIN = 0x0000, + LT_EXTENDEN_LATIN = 0x0001, + LT_CYRILLIC = 0x0002, + LT_EAST_ASIA = 0x0004, + LT_ANY = 0xFFFF +}; + +static LanguageType GetRealmLanguageType(bool create) +{ + switch(sWorld.getConfig(CONFIG_REALM_ZONE)) + { + case REALM_ZONE_UNKNOWN: // any language + case REALM_ZONE_DEVELOPMENT: + case REALM_ZONE_TEST_SERVER: + case REALM_ZONE_QA_SERVER: + return LT_ANY; + case REALM_ZONE_UNITED_STATES: // extended-Latin + case REALM_ZONE_OCEANIC: + case REALM_ZONE_LATIN_AMERICA: + case REALM_ZONE_ENGLISH: + case REALM_ZONE_GERMAN: + case REALM_ZONE_FRENCH: + case REALM_ZONE_SPANISH: + return LT_EXTENDEN_LATIN; + case REALM_ZONE_KOREA: // East-Asian + case REALM_ZONE_TAIWAN: + case REALM_ZONE_CHINA: + return LT_EAST_ASIA; + case REALM_ZONE_RUSSIAN: // Cyrillic + return LT_CYRILLIC; + default: + return create ? LT_BASIC_LATIN : LT_ANY; // basic-Latin at create, any at login + } +} + +bool isValidString(std::wstring wstr, uint32 strictMask, bool numericOrSpace, bool create = false) +{ + if (strictMask == 0) // any language, ignore realm + { + if (isExtendedLatinString(wstr,numericOrSpace)) + return true; + if (isCyrillicString(wstr,numericOrSpace)) + return true; + if (isEastAsianString(wstr,numericOrSpace)) + return true; + return false; + } + + if (strictMask & 0x2) // realm zone specific + { + LanguageType lt = GetRealmLanguageType(create); + if (lt & LT_EXTENDEN_LATIN) + if (isExtendedLatinString(wstr,numericOrSpace)) + return true; + if (lt & LT_CYRILLIC) + if (isCyrillicString(wstr,numericOrSpace)) + return true; + if (lt & LT_EAST_ASIA) + if (isEastAsianString(wstr,numericOrSpace)) + return true; + } + + if (strictMask & 0x1) // basic Latin + { + if (isBasicLatinString(wstr,numericOrSpace)) + return true; + } + + return false; +} + +uint8 ObjectMgr::CheckPlayerName(const std::string& name, bool create) +{ + std::wstring wname; + if (!Utf8toWStr(name,wname)) + return CHAR_NAME_INVALID_CHARACTER; + + if (wname.size() > MAX_PLAYER_NAME) + return CHAR_NAME_TOO_LONG; + + uint32 minName = sWorld.getConfig(CONFIG_MIN_PLAYER_NAME); + if (wname.size() < minName) + return CHAR_NAME_TOO_SHORT; + + uint32 strictMask = sWorld.getConfig(CONFIG_STRICT_PLAYER_NAMES); + if (!isValidString(wname,strictMask,false,create)) + return CHAR_NAME_MIXED_LANGUAGES; + + return CHAR_NAME_SUCCESS; +} + +bool ObjectMgr::IsValidCharterName(const std::string& name) +{ + std::wstring wname; + if (!Utf8toWStr(name,wname)) + return false; + + if (wname.size() > MAX_CHARTER_NAME) + return false; + + uint32 minName = sWorld.getConfig(CONFIG_MIN_CHARTER_NAME); + if (wname.size() < minName) + return false; + + uint32 strictMask = sWorld.getConfig(CONFIG_STRICT_CHARTER_NAMES); + + return isValidString(wname,strictMask,true); +} + +PetNameInvalidReason ObjectMgr::CheckPetName(const std::string& name) +{ + std::wstring wname; + if (!Utf8toWStr(name,wname)) + return PET_NAME_INVALID; + + if (wname.size() > MAX_PET_NAME) + return PET_NAME_TOO_LONG; + + uint32 minName = sWorld.getConfig(CONFIG_MIN_PET_NAME); + if (wname.size() < minName) + return PET_NAME_TOO_SHORT; + + uint32 strictMask = sWorld.getConfig(CONFIG_STRICT_PET_NAMES); + if (!isValidString(wname,strictMask,false)) + return PET_NAME_MIXED_LANGUAGES; + + return PET_NAME_SUCCESS; +} + +int ObjectMgr::GetIndexForLocale(LocaleConstant loc) +{ + if (loc == LOCALE_enUS) + return -1; + + for (size_t i=0; i < m_LocalForIndex.size(); ++i) + if (m_LocalForIndex[i] == loc) + return i; + + return -1; +} + +LocaleConstant ObjectMgr::GetLocaleForIndex(int i) +{ + if (i<0 || i >= m_LocalForIndex.size()) + return LOCALE_enUS; + + return m_LocalForIndex[i]; +} + +int ObjectMgr::GetOrNewIndexForLocale(LocaleConstant loc) +{ + if (loc == LOCALE_enUS) + return -1; + + for (size_t i=0; i < m_LocalForIndex.size(); ++i) + if (m_LocalForIndex[i] == loc) + return i; + + m_LocalForIndex.push_back(loc); + return m_LocalForIndex.size()-1; +} + +void ObjectMgr::LoadGameObjectForQuests() +{ + mGameObjectForQuestSet.clear(); // need for reload case + + if (!sGOStorage.MaxEntry) + { + barGoLink bar(1); + bar.step(); + sLog.outString(); + sLog.outString(">> Loaded 0 GameObjects for quests"); + return; + } + + barGoLink bar(sGOStorage.MaxEntry - 1); + uint32 count = 0; + + // collect GO entries for GO that must activated + for (uint32 go_entry = 1; go_entry < sGOStorage.MaxEntry; ++go_entry) + { + bar.step(); + GameObjectInfo const* goInfo = sGOStorage.LookupEntry<GameObjectInfo>(go_entry); + if (!goInfo) + continue; + + switch(goInfo->type) + { + // scan GO chest with loot including quest items + case GAMEOBJECT_TYPE_CHEST: + { + uint32 loot_id = goInfo->GetLootId(); + + // find quest loot for GO + if (LootTemplates_Gameobject.HaveQuestLootFor(loot_id)) + { + mGameObjectForQuestSet.insert(go_entry); + ++count; + } + break; + } + case GAMEOBJECT_TYPE_GOOBER: + { + if (goInfo->goober.questId) //quests objects + { + mGameObjectForQuestSet.insert(go_entry); + count++; + } + break; + } + default: + break; + } + } + + sLog.outString(); + sLog.outString(">> Loaded %u GameObjects for quests", count); +} + +bool ObjectMgr::LoadTrinityStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value) +{ + int32 start_value = min_value; + int32 end_value = max_value; + // some string can have negative indexes range + if (start_value < 0) + { + if (end_value >= start_value) + { + sLog.outErrorDb("Table '%s' attempt loaded with invalid range (%d - %d), strings not loaded.",table,min_value,max_value); + return false; + } + + // real range (max+1,min+1) exaple: (-10,-1000) -> -999...-10+1 + std::swap(start_value,end_value); + ++start_value; + ++end_value; + } + else + { + if (start_value >= end_value) + { + sLog.outErrorDb("Table '%s' attempt loaded with invalid range (%d - %d), strings not loaded.",table,min_value,max_value); + return false; + } + } + + // cleanup affected map part for reloading case + for (TrinityStringLocaleMap::iterator itr = mTrinityStringLocaleMap.begin(); itr != mTrinityStringLocaleMap.end();) + { + if (itr->first >= start_value && itr->first < end_value) + mTrinityStringLocaleMap.erase(itr++); + else + ++itr; + } + + QueryResult_AutoPtr result = db.PQuery("SELECT entry,content_default,content_loc1,content_loc2,content_loc3,content_loc4,content_loc5,content_loc6,content_loc7,content_loc8 FROM %s",table); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + if (min_value == MIN_TRINITY_STRING_ID) // error only in case internal strings + sLog.outErrorDb(">> Loaded 0 trinity strings. DB table `%s` is empty. Cannot continue.",table); + else + sLog.outString(">> Loaded 0 string templates. DB table `%s` is empty.",table); + return false; + } + + uint32 count = 0; + + barGoLink bar(result->GetRowCount()); + + do + { + Field *fields = result->Fetch(); + bar.step(); + + int32 entry = fields[0].GetInt32(); + + if (entry == 0) + { + sLog.outErrorDb("Table `%s` contain reserved entry 0, ignored.",table); + continue; + } + else if (entry < start_value || entry >= end_value) + { + sLog.outErrorDb("Table `%s` contain entry %i out of allowed range (%d - %d), ignored.",table,entry,min_value,max_value); + continue; + } + + TrinityStringLocale& data = mTrinityStringLocaleMap[entry]; + + if (data.Content.size() > 0) + { + sLog.outErrorDb("Table `%s` contain data for already loaded entry %i (from another table?), ignored.",table,entry); + continue; + } + + data.Content.resize(1); + ++count; + + // 0 -> default, idx in to idx+1 + data.Content[0] = fields[1].GetCppString(); + + for (uint8 i = 1; i < MAX_LOCALE; ++i) + { + std::string str = fields[i+1].GetCppString(); + if (!str.empty()) + { + int idx = GetOrNewIndexForLocale(LocaleConstant(i)); + if (idx >= 0) + { + // 0 -> default, idx in to idx+1 + if (data.Content.size() <= idx+1) + data.Content.resize(idx+2); + + data.Content[idx+1] = str; + } + } + } + } while (result->NextRow()); + + sLog.outString(); + if (min_value == MIN_TRINITY_STRING_ID) + sLog.outString(">> Loaded %u Trinity strings from table %s", count,table); + else + sLog.outString(">> Loaded %u string templates from %s", count,table); + + return true; +} + +const char *ObjectMgr::GetTrinityString(int32 entry, int locale_idx) const +{ + // locale_idx == -1 -> default, locale_idx >= 0 in to idx+1 + // Content[0] always exist if exist TrinityStringLocale + if (TrinityStringLocale const *msl = GetTrinityStringLocale(entry)) + { + if (msl->Content.size() > locale_idx+1 && !msl->Content[locale_idx+1].empty()) + return msl->Content[locale_idx+1].c_str(); + else + return msl->Content[0].c_str(); + } + + if (entry > 0) + sLog.outErrorDb("Entry %i not found in `trinity_string` table.",entry); + else + sLog.outErrorDb("Trinity string entry %i not found in DB.",entry); + return "<error>"; +} + +void ObjectMgr::LoadSpellDisabledEntrys() +{ + m_DisabledPlayerSpells.clear(); // need for reload case + m_DisabledCreatureSpells.clear(); + m_DisabledPetSpells.clear(); + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, disable_mask FROM spell_disabled"); + + uint32 total_count = 0; + + if (!result) + { + barGoLink bar(1); + bar.step(); + + sLog.outString(); + sLog.outString(">> Loaded %u disabled spells", total_count); + return; + } + + barGoLink bar(result->GetRowCount()); + + Field* fields; + do + { + bar.step(); + fields = result->Fetch(); + uint32 spellid = fields[0].GetUInt32(); + if (!sSpellStore.LookupEntry(spellid)) + { + sLog.outErrorDb("Spell entry %u from `spell_disabled` doesn't exist in dbc, ignoring.",spellid); + continue; + } + uint32 disable_mask = fields[1].GetUInt32(); + if (disable_mask & SPELL_DISABLE_PLAYER) + m_DisabledPlayerSpells.insert(spellid); + if (disable_mask & SPELL_DISABLE_CREATURE) + m_DisabledCreatureSpells.insert(spellid); + if (disable_mask & SPELL_DISABLE_PET) + m_DisabledPetSpells.insert(spellid); + ++total_count; + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u disabled spells from `spell_disabled`", total_count); +} + +void ObjectMgr::LoadFishingBaseSkillLevel() +{ + mFishingBaseForArea.clear(); // for reload case + + uint32 count = 0; + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry,skill FROM skill_fishing_base_level"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `skill_fishing_base_level`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + uint32 entry = fields[0].GetUInt32(); + int32 skill = fields[1].GetInt32(); + + AreaTableEntry const* fArea = GetAreaEntryByAreaID(entry); + if (!fArea) + { + sLog.outErrorDb("AreaId %u defined in `skill_fishing_base_level` does not exist",entry); + continue; + } + + mFishingBaseForArea[entry] = skill; + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u areas for fishing base skill level", count); +} + +bool ObjectMgr::CheckDeclinedNames(std::wstring mainpart, DeclinedName const& names) +{ + for (uint8 i =0; i < MAX_DECLINED_NAME_CASES; ++i) + { + std::wstring wname; + if (!Utf8toWStr(names.name[i],wname)) + return false; + + if (mainpart != GetMainPartOfName(wname,i+1)) + return false; + } + return true; +} + +uint32 ObjectMgr::GetAreaTriggerScriptId(uint32 trigger_id) +{ + AreaTriggerScriptMap::const_iterator i = mAreaTriggerScripts.find(trigger_id); + if (i!= mAreaTriggerScripts.end()) + return i->second; + return 0; +} + +SkillRangeType GetSkillRangeType(SkillLineEntry const *pSkill, bool racial) +{ + switch(pSkill->categoryId) + { + case SKILL_CATEGORY_LANGUAGES: return SKILL_RANGE_LANGUAGE; + case SKILL_CATEGORY_WEAPON: + if (pSkill->id != SKILL_FIST_WEAPONS) + return SKILL_RANGE_LEVEL; + else + return SKILL_RANGE_MONO; + case SKILL_CATEGORY_ARMOR: + case SKILL_CATEGORY_CLASS: + if (pSkill->id != SKILL_LOCKPICKING) + return SKILL_RANGE_MONO; + else + return SKILL_RANGE_LEVEL; + case SKILL_CATEGORY_SECONDARY: + case SKILL_CATEGORY_PROFESSION: + // not set skills for professions and racial abilities + if (IsProfessionSkill(pSkill->id)) + return SKILL_RANGE_RANK; + else if (racial) + return SKILL_RANGE_NONE; + else + return SKILL_RANGE_MONO; + default: + case SKILL_CATEGORY_ATTRIBUTES: //not found in dbc + case SKILL_CATEGORY_GENERIC: //only GENERIC(DND) + return SKILL_RANGE_NONE; + } +} + +void ObjectMgr::LoadGameTele() +{ + m_GameTeleMap.clear(); // for reload case + + uint32 count = 0; + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT id, position_x, position_y, position_z, orientation, map, name FROM game_tele"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `game_tele`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + + uint32 id = fields[0].GetUInt32(); + + GameTele gt; + + gt.position_x = fields[1].GetFloat(); + gt.position_y = fields[2].GetFloat(); + gt.position_z = fields[3].GetFloat(); + gt.orientation = fields[4].GetFloat(); + gt.mapId = fields[5].GetUInt32(); + gt.name = fields[6].GetCppString(); + + if (!MapManager::IsValidMapCoord(gt.mapId,gt.position_x,gt.position_y,gt.position_z,gt.orientation)) + { + sLog.outErrorDb("Wrong position for id %u (name: %s) in `game_tele` table, ignoring.",id,gt.name.c_str()); + continue; + } + + if (!Utf8toWStr(gt.name,gt.wnameLow)) + { + sLog.outErrorDb("Wrong UTF8 name for id %u in `game_tele` table, ignoring.",id); + continue; + } + + wstrToLower(gt.wnameLow); + + m_GameTeleMap[id] = gt; + + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u GameTeleports", count); +} + +GameTele const* ObjectMgr::GetGameTele(const std::string& name) const +{ + // explicit name case + std::wstring wname; + if (!Utf8toWStr(name,wname)) + return false; + + // converting string that we try to find to lower case + wstrToLower(wname); + + // Alternative first GameTele what contains wnameLow as substring in case no GameTele location found + const GameTele* alt = NULL; + for (GameTeleMap::const_iterator itr = m_GameTeleMap.begin(); itr != m_GameTeleMap.end(); ++itr) + { + if (itr->second.wnameLow == wname) + return &itr->second; + else if (alt == NULL && itr->second.wnameLow.find(wname) != std::wstring::npos) + alt = &itr->second; + } + + return alt; +} + +bool ObjectMgr::AddGameTele(GameTele& tele) +{ + // find max id + uint32 new_id = 0; + for (GameTeleMap::const_iterator itr = m_GameTeleMap.begin(); itr != m_GameTeleMap.end(); ++itr) + if (itr->first > new_id) + new_id = itr->first; + + // use next + ++new_id; + + if (!Utf8toWStr(tele.name,tele.wnameLow)) + return false; + + wstrToLower(tele.wnameLow); + + m_GameTeleMap[new_id] = tele; + + return WorldDatabase.PExecuteLog("INSERT INTO game_tele (id,position_x,position_y,position_z,orientation,map,name) VALUES (%u,%f,%f,%f,%f,%d,'%s')", + new_id,tele.position_x,tele.position_y,tele.position_z,tele.orientation,tele.mapId,tele.name.c_str()); +} + +bool ObjectMgr::DeleteGameTele(const std::string& name) +{ + // explicit name case + std::wstring wname; + if (!Utf8toWStr(name,wname)) + return false; + + // converting string that we try to find to lower case + wstrToLower(wname); + + for (GameTeleMap::iterator itr = m_GameTeleMap.begin(); itr != m_GameTeleMap.end(); ++itr) + { + if (itr->second.wnameLow == wname) + { + WorldDatabase.PExecuteLog("DELETE FROM game_tele WHERE name = '%s'",itr->second.name.c_str()); + m_GameTeleMap.erase(itr); + return true; + } + } + + return false; +} + +void ObjectMgr::LoadMailLevelRewards() +{ + m_mailLevelRewardMap.clear(); // for reload case + + uint32 count = 0; + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT level, raceMask, mailTemplateId, senderEntry FROM mail_level_reward"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `mail_level_reward`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + do + { + bar.step(); + + Field *fields = result->Fetch(); + + uint8 level = fields[0].GetUInt8(); + uint32 raceMask = fields[1].GetUInt32(); + uint32 mailTemplateId = fields[2].GetUInt32(); + uint32 senderEntry = fields[3].GetUInt32(); + + if (level > MAX_LEVEL) + { + sLog.outErrorDb("Table `mail_level_reward` have data for level %u that more supported by client (%u), ignoring.",level,MAX_LEVEL); + continue; + } + + if (!(raceMask & RACEMASK_ALL_PLAYABLE)) + { + sLog.outErrorDb("Table `mail_level_reward` have raceMask (%u) for level %u that not include any player races, ignoring.",raceMask,level); + continue; + } + + if (!sMailTemplateStore.LookupEntry(mailTemplateId)) + { + sLog.outErrorDb("Table `mail_level_reward` have invalid mailTemplateId (%u) for level %u that invalid not include any player races, ignoring.",mailTemplateId,level); + continue; + } + + if (!GetCreatureTemplateStore(senderEntry)) + { + sLog.outErrorDb("Table `mail_level_reward` have not existed sender creature entry (%u) for level %u that invalid not include any player races, ignoring.",senderEntry,level); + continue; + } + + m_mailLevelRewardMap[level].push_back(MailLevelReward(raceMask,mailTemplateId,senderEntry)); + + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u level dependent mail rewards,", count); +} + +bool ObjectMgr::AddSpellToTrainer(uint32 entry, uint32 spell, Field *fields, std::set<uint32> *skip_trainers, std::set<uint32> *talentIds) +{ + if (entry >= TRINITY_TRAINER_START_REF) + return false; + + CreatureInfo const* cInfo = GetCreatureTemplate(entry); + if (!cInfo) + { + sLog.outErrorDb("Table `npc_trainer` have entry for not existed creature template (Entry: %u), ignore", entry); + return false; + } + + if (!(cInfo->npcflag & UNIT_NPC_FLAG_TRAINER)) + { + if (skip_trainers->find(entry) == skip_trainers->end()) + { + sLog.outErrorDb("Table `npc_trainer` have data for not creature template (Entry: %u) without trainer flag, ignore", entry); + skip_trainers->insert(entry); + } + return false; + } + + SpellEntry const *spellinfo = sSpellStore.LookupEntry(spell); + if (!spellinfo) + { + sLog.outErrorDb("Table `npc_trainer` for Trainer (Entry: %u) has non existing spell %u, ignore", entry,spell); + return false; + } + + if (!SpellMgr::IsSpellValid(spellinfo)) + { + sLog.outErrorDb("Table `npc_trainer` for Trainer (Entry: %u) has broken learning spell %u, ignore", entry, spell); + return false; + } + + if (GetTalentSpellCost(spell)) + { + if (talentIds->count(spell) == 0) + { + sLog.outErrorDb("Table `npc_trainer` has talent as learning spell %u, ignore", spell); + talentIds->insert(spell); + } + return false; + } + + TrainerSpellData& data = m_mCacheTrainerSpellMap[entry]; + + TrainerSpell& trainerSpell = data.spellList[spell]; + trainerSpell.spell = spell; + trainerSpell.spellCost = fields[2].GetUInt32(); + trainerSpell.reqSkill = fields[3].GetUInt32(); + trainerSpell.reqSkillValue = fields[4].GetUInt32(); + trainerSpell.reqLevel = fields[5].GetUInt32(); + + if (!trainerSpell.reqLevel) + trainerSpell.reqLevel = spellinfo->spellLevel; + + // calculate learned spell for profession case when stored cast-spell + trainerSpell.learnedSpell[0] = spell; + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (spellinfo->Effect[i] != SPELL_EFFECT_LEARN_SPELL) + continue; + if (trainerSpell.learnedSpell[0] == spell) + trainerSpell.learnedSpell[0] = 0; + // player must be able to cast spell on himself + if (spellinfo->EffectImplicitTargetA[i] != 0 && spellinfo->EffectImplicitTargetA[i] != TARGET_UNIT_TARGET_ALLY + && spellinfo->EffectImplicitTargetA[i] != TARGET_UNIT_TARGET_ANY && spellinfo->EffectImplicitTargetA[i] != TARGET_UNIT_CASTER) + { + sLog.outErrorDb("Table `npc_trainer` has spell %u for trainer entry %u with learn effect which has incorrect target type, ignoring learn effect!", spell, entry); + continue; + } + + trainerSpell.learnedSpell[i] = spellinfo->EffectTriggerSpell[i]; + } + + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!trainerSpell.learnedSpell[i]) + continue; + if (SpellMgr::IsProfessionSpell(trainerSpell.learnedSpell[i])) + { + data.trainerType = 2; + break; + } + } + return true; +} +int ObjectMgr::LoadReferenceTrainer(uint32 trainer, int32 spell, std::set<uint32> *skip_trainers, std::set<uint32> *talentIds) +{ + QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT entry, spell,spellcost,reqskill,reqskillvalue,reqlevel FROM npc_trainer WHERE entry='%d'", spell); + if (!result) + return 0; + + uint32 count = 0; + do + { + + Field* fields = result->Fetch(); + + int32 spell = fields[1].GetInt32(); + if (spell < 0) + count += this->LoadReferenceTrainer(trainer, -spell, skip_trainers, talentIds); + else if (this->AddSpellToTrainer(trainer, uint32(spell), fields, skip_trainers, talentIds)) + ++count; + } while (result->NextRow()); + + return count; +} + +void ObjectMgr::LoadTrainerSpell() +{ + // For reload case + for (CacheTrainerSpellMap::iterator itr = m_mCacheTrainerSpellMap.begin(); itr != m_mCacheTrainerSpellMap.end(); ++itr) + itr->second.Clear(); + m_mCacheTrainerSpellMap.clear(); + + std::set<uint32> skip_trainers; + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, spell,spellcost,reqskill,reqskillvalue,reqlevel FROM npc_trainer"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `npc_trainer`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + std::set<uint32> talentIds; + + uint32 count = 0; + do + { + bar.step(); + + Field* fields = result->Fetch(); + + uint32 entry = fields[0].GetUInt32(); + int32 spell = fields[1].GetInt32(); + if (spell < 0) + count += this->LoadReferenceTrainer(entry, -spell, &skip_trainers, &talentIds); + else if (this->AddSpellToTrainer(entry, uint32(spell), fields, &skip_trainers, &talentIds)) + ++count; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %d Trainers", count); +} + +int ObjectMgr::LoadReferenceVendor(int32 vendor, int32 item, std::set<uint32> *skip_vendors) +{ + // find all items from the reference vendor + QueryResult_AutoPtr result = WorldDatabase.PQuery("SELECT item, maxcount, incrtime, ExtendedCost FROM npc_vendor WHERE entry='%d' ORDER BY slot ASC", item); + if (!result) + return 0; + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + + int32 item_id = fields[0].GetInt32(); + + // if item is a negative, its a reference + if (item_id < 0) + count += LoadReferenceVendor(vendor, -item_id, skip_vendors); + else + { + int32 maxcount = fields[1].GetInt32(); + uint32 incrtime = fields[2].GetUInt32(); + uint32 ExtendedCost = fields[3].GetUInt32(); + + if (!IsVendorItemValid(vendor,item_id,maxcount,incrtime,ExtendedCost,NULL,skip_vendors)) + continue; + + VendorItemData& vList = m_mCacheVendorItemMap[vendor]; + + vList.AddItem(item_id,maxcount,incrtime,ExtendedCost); + ++count; + } + + } while (result->NextRow()); + + return count; +} + +void ObjectMgr::LoadVendors() +{ + // For reload case + for (CacheVendorItemMap::iterator itr = m_mCacheVendorItemMap.begin(); itr != m_mCacheVendorItemMap.end(); ++itr) + itr->second.Clear(); + m_mCacheVendorItemMap.clear(); + + std::set<uint32> skip_vendors; + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, item, maxcount, incrtime, ExtendedCost FROM npc_vendor ORDER BY entry, slot ASC"); + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `npc_vendor`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + uint32 count = 0; + do + { + bar.step(); + Field* fields = result->Fetch(); + + uint32 entry = fields[0].GetUInt32(); + int32 item_id = fields[1].GetInt32(); + + // if item is a negative, its a reference + if (item_id < 0) + count += LoadReferenceVendor(entry, -item_id, &skip_vendors); + else + { + int32 maxcount = fields[2].GetInt32(); + uint32 incrtime = fields[3].GetUInt32(); + uint32 ExtendedCost = fields[4].GetUInt32(); + + if (!IsVendorItemValid(entry,item_id,maxcount,incrtime,ExtendedCost,NULL,&skip_vendors)) + continue; + + VendorItemData& vList = m_mCacheVendorItemMap[entry]; + + vList.AddItem(item_id,maxcount,incrtime,ExtendedCost); + ++count; + } + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %d Vendors ", count); +} + +void ObjectMgr::LoadNpcTextId() +{ + + m_mCacheNpcTextIdMap.clear(); + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT npc_guid, textid FROM npc_gossip"); + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `npc_gossip`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + uint32 count = 0; + uint32 guid,textid; + do + { + bar.step(); + + Field* fields = result->Fetch(); + + guid = fields[0].GetUInt32(); + textid = fields[1].GetUInt32(); + + if (!GetCreatureData(guid)) + { + sLog.outErrorDb("Table `npc_gossip` have not existed creature (GUID: %u) entry, ignore. ",guid); + continue; + } + if (!GetGossipText(textid)) + { + sLog.outErrorDb("Table `npc_gossip` for creature (GUID: %u) have wrong Textid (%u), ignore. ", guid, textid); + continue; + } + + m_mCacheNpcTextIdMap[guid] = textid ; + ++count; + + } while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %d NpcTextId ", count); +} + +void ObjectMgr::LoadGossipMenu() +{ + m_mGossipMenusMap.clear(); + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, text_id FROM gossip_menu"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded `gossip_menu`, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + uint32 count = 0; + + do + { + bar.step(); + + Field* fields = result->Fetch(); + + GossipMenus gMenu; + + gMenu.entry = fields[0].GetUInt32(); + gMenu.text_id = fields[1].GetUInt32(); + + if (!GetGossipText(gMenu.text_id)) + { + sLog.outErrorDb("Table gossip_menu entry %u are using non-existing text_id %u", gMenu.entry, gMenu.text_id); + continue; + } + + m_mGossipMenusMap.insert(GossipMenusMap::value_type(gMenu.entry, gMenu)); + + ++count; + } + while (result->NextRow()); + + sLog.outString(); + sLog.outString(">> Loaded %u gossip_menu entries", count); +} + +void ObjectMgr::LoadGossipMenuItems() +{ + m_mGossipMenuItemsMap.clear(); + + QueryResult_AutoPtr result = WorldDatabase.Query( + "SELECT menu_id, id, option_icon, option_text, option_id, npc_option_npcflag, " + "action_menu_id, action_poi_id, action_script_id, box_coded, box_money, box_text " + "FROM gossip_menu_option"); + + if (!result) + { + barGoLink bar(1); + + bar.step(); + + sLog.outString(); + sLog.outErrorDb(">> Loaded gossip_menu_option, table is empty!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + uint32 count = 0; + + std::set<uint32> gossipScriptSet; + + for (ScriptMapMap::const_iterator itr = sGossipScripts.begin(); itr != sGossipScripts.end(); ++itr) + gossipScriptSet.insert(itr->first); + + do + { + bar.step(); + + Field* fields = result->Fetch(); + + GossipMenuItems gMenuItem; + + gMenuItem.menu_id = fields[0].GetUInt32(); + gMenuItem.id = fields[1].GetUInt32(); + gMenuItem.option_icon = fields[2].GetUInt8(); + gMenuItem.option_text = fields[3].GetCppString(); + gMenuItem.option_id = fields[4].GetUInt32(); + gMenuItem.npc_option_npcflag = fields[5].GetUInt32(); + gMenuItem.action_menu_id = fields[6].GetUInt32(); + gMenuItem.action_poi_id = fields[7].GetUInt32(); + gMenuItem.action_script_id = fields[8].GetUInt32(); + gMenuItem.box_coded = fields[9].GetUInt8() != 0; + gMenuItem.box_money = fields[10].GetUInt32(); + gMenuItem.box_text = fields[11].GetCppString(); + + if (gMenuItem.option_icon >= GOSSIP_ICON_MAX) + { + sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u has unknown icon id %u. Replacing with GOSSIP_ICON_CHAT", gMenuItem.menu_id, gMenuItem.id, gMenuItem.option_icon); + gMenuItem.option_icon = GOSSIP_ICON_CHAT; + } + + if (gMenuItem.option_id >= GOSSIP_OPTION_MAX) + sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u has unknown option id %u. Option will not be used", gMenuItem.menu_id, gMenuItem.id, gMenuItem.option_id); + + if (gMenuItem.action_poi_id && !GetPointOfInterest(gMenuItem.action_poi_id)) + { + sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u use non-existing action_poi_id %u, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_poi_id); + gMenuItem.action_poi_id = 0; + } + + if (gMenuItem.action_script_id) + { + if (gMenuItem.option_id != GOSSIP_OPTION_GOSSIP) + { + sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u have action_script_id %u but option_id is not GOSSIP_OPTION_GOSSIP, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_script_id); + continue; + } + + if (sGossipScripts.find(gMenuItem.action_script_id) == sGossipScripts.end()) + { + sLog.outErrorDb("Table gossip_menu_option for menu %u, id %u have action_script_id %u that does not exist in `gossip_scripts`, ignoring", gMenuItem.menu_id, gMenuItem.id, gMenuItem.action_script_id); + continue; + } + + gossipScriptSet.erase(gMenuItem.action_script_id); + } + + m_mGossipMenuItemsMap.insert(GossipMenuItemsMap::value_type(gMenuItem.menu_id, gMenuItem)); + + ++count; + + } + while (result->NextRow()); + + if (!gossipScriptSet.empty()) + { + for (std::set<uint32>::const_iterator itr = gossipScriptSet.begin(); itr != gossipScriptSet.end(); ++itr) + sLog.outErrorDb("Table `gossip_scripts` contain unused script, id %u.", *itr); + } + + sLog.outString(); + sLog.outString(">> Loaded %u gossip_menu_option entries", count); +} + +void ObjectMgr::AddVendorItem(uint32 entry,uint32 item, int32 maxcount, uint32 incrtime, uint32 extendedcost, bool savetodb) +{ + VendorItemData& vList = m_mCacheVendorItemMap[entry]; + vList.AddItem(item,maxcount,incrtime,extendedcost); + + if (savetodb) WorldDatabase.PExecuteLog("INSERT INTO npc_vendor (entry,item,maxcount,incrtime,extendedcost) VALUES('%u','%u','%u','%u','%u')",entry, item, maxcount,incrtime,extendedcost); +} + +bool ObjectMgr::RemoveVendorItem(uint32 entry,uint32 item, bool savetodb) +{ + CacheVendorItemMap::iterator iter = m_mCacheVendorItemMap.find(entry); + if (iter == m_mCacheVendorItemMap.end()) + return false; + + if(!iter->second.RemoveItem(item)) + return false; + + if (savetodb) WorldDatabase.PExecuteLog("DELETE FROM npc_vendor WHERE entry='%u' AND item='%u'",entry, item); + return true; +} + +bool ObjectMgr::IsVendorItemValid(uint32 vendor_entry, uint32 item_id, int32 maxcount, uint32 incrtime, uint32 ExtendedCost, Player* pl, std::set<uint32>* skip_vendors, uint32 ORnpcflag) const +{ + CreatureInfo const* cInfo = GetCreatureTemplate(vendor_entry); + if (!cInfo) + { + if (pl) + ChatHandler(pl).SendSysMessage(LANG_COMMAND_VENDORSELECTION); + else + sLog.outErrorDb("Table `(game_event_)npc_vendor` have data for not existed creature template (Entry: %u), ignore", vendor_entry); + return false; + } + + if (!((cInfo->npcflag | ORnpcflag) & UNIT_NPC_FLAG_VENDOR)) + { + if (!skip_vendors || skip_vendors->count(vendor_entry) == 0) + { + if (pl) + ChatHandler(pl).SendSysMessage(LANG_COMMAND_VENDORSELECTION); + else + sLog.outErrorDb("Table `(game_event_)npc_vendor` have data for not creature template (Entry: %u) without vendor flag, ignore", vendor_entry); + + if (skip_vendors) + skip_vendors->insert(vendor_entry); + } + return false; + } + + if (!GetItemPrototype(item_id)) + { + if (pl) + ChatHandler(pl).PSendSysMessage(LANG_ITEM_NOT_FOUND, item_id); + else + sLog.outErrorDb("Table `(game_event_)npc_vendor` for Vendor (Entry: %u) have in item list non-existed item (%u), ignore",vendor_entry,item_id); + return false; + } + + if (ExtendedCost && !sItemExtendedCostStore.LookupEntry(ExtendedCost)) + { + if (pl) + ChatHandler(pl).PSendSysMessage(LANG_EXTENDED_COST_NOT_EXIST,ExtendedCost); + else + sLog.outErrorDb("Table `(game_event_)npc_vendor` have Item (Entry: %u) with wrong ExtendedCost (%u) for vendor (%u), ignore",item_id,ExtendedCost,vendor_entry); + return false; + } + + if (maxcount > 0 && incrtime == 0) + { + if (pl) + ChatHandler(pl).PSendSysMessage("MaxCount != 0 (%u) but IncrTime == 0", maxcount); + else + sLog.outErrorDb("Table `(game_event_)npc_vendor` has `maxcount` (%u) for item %u of vendor (Entry: %u) but `incrtime`=0, ignore", maxcount, item_id, vendor_entry); + return false; + } + else if (maxcount == 0 && incrtime > 0) + { + if (pl) + ChatHandler(pl).PSendSysMessage("MaxCount == 0 but IncrTime<>= 0"); + else + sLog.outErrorDb("Table `(game_event_)npc_vendor` has `maxcount`=0 for item %u of vendor (Entry: %u) but `incrtime`<>0, ignore", item_id, vendor_entry); + return false; + } + + VendorItemData const* vItems = GetNpcVendorItemList(vendor_entry); + if (!vItems) + return true; // later checks for non-empty lists + + if(vItems->FindItemCostPair(item_id,ExtendedCost)) + { + if (pl) + ChatHandler(pl).PSendSysMessage(LANG_ITEM_ALREADY_IN_LIST, item_id, ExtendedCost); + else + sLog.outErrorDb( "Table `npc_vendor` has duplicate items %u (with extended cost %u) for vendor (Entry: %u), ignoring", item_id, ExtendedCost, vendor_entry); + return false; + } + + if (vItems->GetItemCount() >= MAX_VENDOR_ITEMS) + { + if (pl) + ChatHandler(pl).SendSysMessage(LANG_COMMAND_ADDVENDORITEMITEMS); + else + sLog.outErrorDb("Table `npc_vendor` has too many items (%u >= %i) for vendor (Entry: %u), ignore", vItems->GetItemCount(), MAX_VENDOR_ITEMS, vendor_entry); + return false; + } + + return true; +} + +void ObjectMgr::LoadScriptNames() +{ + m_scriptNames.push_back(""); + QueryResult_AutoPtr result = WorldDatabase.Query( + "SELECT DISTINCT(ScriptName) FROM creature_template WHERE ScriptName <> '' " + "UNION " + "SELECT DISTINCT(ScriptName) FROM gameobject_template WHERE ScriptName <> '' " + "UNION " + "SELECT DISTINCT(ScriptName) FROM item_template WHERE ScriptName <> '' " + "UNION " + "SELECT DISTINCT(ScriptName) FROM areatrigger_scripts WHERE ScriptName <> '' " + "UNION " + "SELECT DISTINCT(script) FROM instance_template WHERE script <> ''"); + + if (!result) + { + barGoLink bar(1); + bar.step(); + sLog.outString(); + sLog.outErrorDb(">> Loaded empty set of Script Names!"); + return; + } + + barGoLink bar(result->GetRowCount()); + + //OnEvent Changes + m_scriptNames.push_back("scripted_on_events"); + uint32 count = 1; + + do + { + bar.step(); + m_scriptNames.push_back((*result)[0].GetString()); + ++count; + } while (result->NextRow()); + + std::sort(m_scriptNames.begin(), m_scriptNames.end()); + sLog.outString(); + sLog.outString(">> Loaded %d Script Names", count); +} + +uint32 ObjectMgr::GetScriptId(const char *name) +{ + // use binary search to find the script name in the sorted vector + // assume "" is the first element + if (!name) return 0; + ScriptNameMap::const_iterator itr = + std::lower_bound(m_scriptNames.begin(), m_scriptNames.end(), name); + if (itr == m_scriptNames.end() || *itr != name) return 0; + return itr - m_scriptNames.begin(); +} + +void ObjectMgr::CheckScripts(ScriptMapMap const& scripts,std::set<int32>& ids) +{ + for (ScriptMapMap::const_iterator itrMM = scripts.begin(); itrMM != scripts.end(); ++itrMM) + { + for (ScriptMap::const_iterator itrM = itrMM->second.begin(); itrM != itrMM->second.end(); ++itrM) + { + switch(itrM->second.command) + { + case SCRIPT_COMMAND_TALK: + { + if (!GetTrinityStringLocale (itrM->second.dataint)) + sLog.outErrorDb("Table `db_script_string` not has string id %u used db script (ID: %u)", itrM->second.dataint, itrMM->first); + + if (ids.find(itrM->second.dataint) != ids.end()) + ids.erase(itrM->second.dataint); + } + } + } + } +} + +void ObjectMgr::LoadDbScriptStrings() +{ + LoadTrinityStrings(WorldDatabase,"db_script_string",MIN_DB_SCRIPT_STRING_ID,MAX_DB_SCRIPT_STRING_ID); + + std::set<int32> ids; + + for (int32 i = MIN_DB_SCRIPT_STRING_ID; i < MAX_DB_SCRIPT_STRING_ID; ++i) + if (GetTrinityStringLocale(i)) + ids.insert(i); + + CheckScripts(sQuestEndScripts,ids); + CheckScripts(sQuestStartScripts,ids); + CheckScripts(sSpellScripts,ids); + CheckScripts(sGameObjectScripts,ids); + CheckScripts(sEventScripts,ids); + + CheckScripts(sWaypointScripts,ids); + + for (std::set<int32>::const_iterator itr = ids.begin(); itr != ids.end(); ++itr) + sLog.outErrorDb("Table `db_script_string` has unused string id %u", *itr); +} + +// Functions for scripting access +uint32 GetAreaTriggerScriptId(uint32 trigger_id) +{ + return objmgr.GetAreaTriggerScriptId(trigger_id); +} + +bool LoadTrinityStrings(DatabaseType& db, char const* table,int32 start_value, int32 end_value) +{ + // MAX_DB_SCRIPT_STRING_ID is max allowed negative value for scripts (scrpts can use only more deep negative values + // start/end reversed for negative values + if (start_value > MAX_DB_SCRIPT_STRING_ID || end_value >= start_value) + { + sLog.outErrorDb("Table '%s' load attempted with range (%d - %d) reserved by Trinity, strings not loaded.",table,start_value,end_value+1); + return false; + } + + return objmgr.LoadTrinityStrings(db,table,start_value,end_value); +} + +uint32 GetScriptId(const char *name) +{ + return objmgr.GetScriptId(name); +} + +ObjectMgr::ScriptNameMap & GetScriptNames() +{ + return objmgr.GetScriptNames(); +} + +GameObjectInfo const *GetGameObjectInfo(uint32 id) +{ + return objmgr.GetGameObjectInfo(id); +} + +CreatureInfo const *GetCreatureInfo(uint32 id) +{ + return objmgr.GetCreatureTemplate(id); +} + +void ObjectMgr::LoadTransportEvents() +{ + + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT entry, waypoint_id, event_id FROM transport_events"); + + if (!result) + { + barGoLink bar1(1); + bar1.step(); + sLog.outString("\n>> Transport events table is empty \n"); + return; + } + + barGoLink bar1(result->GetRowCount()); + + do + { + bar1.step(); + + Field *fields = result->Fetch(); + + //Load event values + uint32 entry = fields[0].GetUInt32(); + uint32 waypoint_id = fields[1].GetUInt32(); + uint32 event_id = fields[2].GetUInt32(); + + uint32 event_count = (entry*100)+waypoint_id; + TransportEventMap[event_count] = event_id; + } + while (result->NextRow()); + + sLog.outString("\n>> Loaded %u transport events \n", result->GetRowCount()); +} + +CreatureInfo const* GetCreatureTemplateStore(uint32 entry) +{ + return sCreatureStorage.LookupEntry<CreatureInfo>(entry); +} + +Quest const* GetQuestTemplateStore(uint32 entry) +{ + return objmgr.GetQuestTemplate(entry); +} + +uint64 ObjectMgr::GenerateGMTicketId() +{ + return ++m_GMticketid; +} + +void ObjectMgr::LoadGMTickets() +{ + if (!m_GMTicketList.empty()) + { + for (GmTicketList::const_iterator itr = m_GMTicketList.begin(); itr != m_GMTicketList.end(); ++itr) + delete *itr; + } + m_GMTicketList.clear(); + m_GMticketid = 0; + + QueryResult_AutoPtr result = CharacterDatabase.Query("SELECT guid, playerGuid, name, message, createtime, map, posX, posY, posZ, timestamp, closed, assignedto, comment FROM gm_tickets"); + + if (!result) + { + barGoLink bar(1); + bar.step(); + sLog.outString(); + sLog.outString(">> GM Tickets table is empty, no tickets were loaded."); + return; + } + + uint16 count = 0; + barGoLink bar ((*result).GetRowCount()); + GM_Ticket *ticket; + do + { + Field *fields = result->Fetch(); + ticket = new GM_Ticket; + ticket->guid = fields[0].GetUInt64(); + ticket->playerGuid = fields[1].GetUInt64(); + ticket->name = fields[2].GetCppString(); + ticket->message = fields[3].GetCppString(); + ticket->createtime = fields[4].GetUInt64(); + ticket->map = fields[5].GetUInt32(); + ticket->pos_x = fields[6].GetFloat(); + ticket->pos_y = fields[7].GetFloat(); + ticket->pos_z = fields[8].GetFloat(); + ticket->timestamp = fields[9].GetUInt64(); + ticket->closed = fields[10].GetUInt64(); + ticket->assignedToGM = fields[11].GetUInt64(); + ticket->comment = fields[12].GetCppString(); + ++count; + bar.step(); + + m_GMTicketList.push_back(ticket); + + } while (result->NextRow()); + + result = CharacterDatabase.Query("SELECT MAX(guid) from gm_tickets"); + + if (result) + { + Field *fields = result->Fetch(); + m_GMticketid = fields[0].GetUInt64(); + } + + sLog.outString(">> Loaded %u GM Tickets from the database.", count); +} + +void ObjectMgr::AddOrUpdateGMTicket(GM_Ticket &ticket, bool create) +{ + if (create) + m_GMTicketList.push_back(&ticket); + + _AddOrUpdateGMTicket(ticket); +} + +void ObjectMgr::_AddOrUpdateGMTicket(GM_Ticket &ticket) +{ + std::string msg(ticket.message), name(ticket.name), comment(ticket.comment); + CharacterDatabase.escape_string(msg); + CharacterDatabase.escape_string(name); + CharacterDatabase.escape_string(comment); + std::ostringstream ss; + ss << "REPLACE INTO gm_tickets (guid, playerGuid, name, message, createtime, map, posX, posY, posZ, timestamp, closed, assignedto, comment) VALUES('"; + ss << ticket.guid << "', '"; + ss << ticket.playerGuid << "', '"; + ss << name << "', '"; + ss << msg << "', '" ; + ss << ticket.createtime << "', '"; + ss << ticket.map << "', '"; + ss << ticket.pos_x << "', '"; + ss << ticket.pos_y << "', '"; + ss << ticket.pos_z << "', '"; + ss << ticket.timestamp << "', '"; + ss << ticket.closed << "', '"; + ss << ticket.assignedToGM << "', '"; + ss << comment << "');"; + CharacterDatabase.BeginTransaction(); + CharacterDatabase.Execute(ss.str().c_str()); + CharacterDatabase.CommitTransaction(); +} + +void ObjectMgr::RemoveGMTicket(GM_Ticket *ticket, int64 source, bool permanently) +{ + for (GmTicketList::iterator i = m_GMTicketList.begin(); i != m_GMTicketList.end(); ++i) + if ((*i)->guid == ticket->guid) + { + if (permanently) + { + CharacterDatabase.PExecute("DELETE FROM gm_tickets WHERE guid = '%u'", ticket->guid); + i = m_GMTicketList.erase(i); + ticket = NULL; + return; + } + (*i)->closed = source; + _AddOrUpdateGMTicket(*(*i)); + } +} + +void ObjectMgr::RemoveGMTicket(uint64 ticketGuid, int64 source, bool permanently) +{ + GM_Ticket *ticket = GetGMTicket(ticketGuid); + assert(ticket); + RemoveGMTicket(ticket, source, permanently); +} + +CreatureBaseStats const* ObjectMgr::GetCreatureBaseStats(uint8 level, uint8 unitClass) +{ + CreatureBaseStatsMap::const_iterator it = m_creatureBaseStatsMap.find(MAKE_PAIR16(level,unitClass)); + + if (it != m_creatureBaseStatsMap.end()) + return &(it->second); + + struct DefaultCreatureBaseStats : public CreatureBaseStats + { + DefaultCreatureBaseStats() + { + BaseArmor = 1; + for (uint8 j = 0; j < MAX_CREATURE_BASE_HP; ++j) + BaseHealth[j] = 1; + BaseMana = 0; + } + }; + static const DefaultCreatureBaseStats def_stats; + return &def_stats; +} + +void ObjectMgr::LoadCreatureClassLevelStats() +{ + QueryResult_AutoPtr result = WorldDatabase.Query("SELECT level, class, basehp0, basehp1, basehp2, basemana, basearmor FROM creature_classlevelstats"); + + if (!result) + { + barGoLink bar(1); + bar.step(); + sLog.outString(); + sLog.outString(">> Loaded 0 creature base stats. DB table `creature_classlevelstats` is empty."); + return; + } + + barGoLink bar(result->GetRowCount()); + uint32 counter = 0; + + do + { + Field *fields = result->Fetch(); + + uint8 Level = fields[0].GetUInt32(); + uint8 Class = fields[1].GetUInt8(); + + CreatureBaseStats stats; + for (uint8 i = 0; i < MAX_CREATURE_BASE_HP; ++i) + stats.BaseHealth[i] = fields[i + 2].GetUInt32(); + stats.BaseMana = fields[5].GetUInt32(); + stats.BaseArmor = fields[6].GetUInt32(); + + if (Level > STRONG_MAX_LEVEL) + { + sLog.outErrorDb("Creature base stats for class %u has invalid level %u (max is %u) - set to %u", + Class, Level, STRONG_MAX_LEVEL, STRONG_MAX_LEVEL); + Level = STRONG_MAX_LEVEL; + } + + if (!Class || ((1 << (Class - 1)) & CLASSMASK_ALL_CREATURES) == 0) + sLog.outErrorDb("Creature base stats for level %u has invalid class %u", + Level, Class); + + for (uint8 i = 0; i < MAX_CREATURE_BASE_HP; ++i) + { + if (stats.BaseHealth[i] < 1) + { + sLog.outErrorDb("Creature base stats for class %u, level %u has invalid zero base HP[%u] - set to 1", + Class, Level, i); + stats.BaseHealth[i] = 1; + } + } + + m_creatureBaseStatsMap[MAKE_PAIR16(Level, Class)] = stats; + + bar.step(); + ++counter; + } + while (result->NextRow()); + + for (uint32 i = 0; i < sCreatureStorage.MaxEntry; ++i) + { + CreatureInfo const* info = sCreatureStorage.LookupEntry<CreatureInfo>(i); + if (!info) + continue; + + for (uint16 lvl = info->minlevel; lvl <= info->maxlevel; ++lvl) + { + if (m_creatureBaseStatsMap.find(MAKE_PAIR16(lvl, info->unit_class)) == m_creatureBaseStatsMap.end()) + sLog.outErrorDb("Missing base stats for creature class %u level %u", info->unit_class, lvl); + } + } + + sLog.outString(); + sLog.outString(">> Loaded %u creature base stats.", counter); +} diff --git a/src/server/game/Entities/Object/ObjectMgr.h b/src/server/game/Entities/Object/ObjectMgr.h new file mode 100644 index 00000000000..79b6ffdd0eb --- /dev/null +++ b/src/server/game/Entities/Object/ObjectMgr.h @@ -0,0 +1,1085 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef _OBJECTMGR_H +#define _OBJECTMGR_H + +#include "Log.h" +#include "Object.h" +#include "Bag.h" +#include "Creature.h" +#include "Player.h" +#include "DynamicObject.h" +#include "GameObject.h" +#include "Corpse.h" +#include "QuestDef.h" +#include "Path.h" +#include "ItemPrototype.h" +#include "NPCHandler.h" +#include "Database/DatabaseEnv.h" +#include "Mail.h" +#include "Map.h" +#include "ObjectAccessor.h" +#include "ObjectDefines.h" +#include "Policies/Singleton.h" +#include "Database/SQLStorage.h" +#include "Vehicle.h" +#include "ObjectMgr.h" +#include <string> +#include <map> +#include <limits> +#include "ConditionMgr.h" + +extern SQLStorage sCreatureStorage; +extern SQLStorage sCreatureDataAddonStorage; +extern SQLStorage sCreatureInfoAddonStorage; +extern SQLStorage sCreatureModelStorage; +extern SQLStorage sEquipmentStorage; +extern SQLStorage sGOStorage; +extern SQLStorage sPageTextStore; +extern SQLStorage sItemStorage; +extern SQLStorage sInstanceTemplate; + +class Group; +class Guild; +class ArenaTeam; +class Path; +class TransportPath; +class Item; + +struct GameTele +{ + float position_x; + float position_y; + float position_z; + float orientation; + uint32 mapId; + std::string name; + std::wstring wnameLow; +}; + +typedef UNORDERED_MAP<uint32, GameTele > GameTeleMap; + +struct ScriptInfo +{ + uint32 id; + uint32 delay; + uint32 command; + uint32 datalong; + uint32 datalong2; + int32 dataint; + float x; + float y; + float z; + float o; +}; + +typedef std::multimap<uint32, ScriptInfo> ScriptMap; +typedef std::map<uint32, ScriptMap > ScriptMapMap; +extern ScriptMapMap sQuestEndScripts; +extern ScriptMapMap sQuestStartScripts; +extern ScriptMapMap sSpellScripts; +extern ScriptMapMap sGameObjectScripts; +extern ScriptMapMap sEventScripts; +extern ScriptMapMap sGossipScripts; +extern ScriptMapMap sWaypointScripts; + +struct SpellClickInfo +{ + uint32 spellId; + uint32 questStart; // quest start (quest must be active or rewarded for spell apply) + uint32 questEnd; // quest end (quest don't must be rewarded for spell apply) + bool questStartCanActive; // if true then quest start can be active (not only rewarded) + uint8 castFlags; + uint32 auraRequired; + uint32 auraForbidden; + SpellClickUserTypes userType; + + // helpers + bool IsFitToRequirements(Player const* player, Creature const * clickNpc) const; +}; + +typedef std::multimap<uint32, SpellClickInfo> SpellClickInfoMap; +typedef std::pair<SpellClickInfoMap::const_iterator,SpellClickInfoMap::const_iterator> SpellClickInfoMapBounds; + +struct AreaTrigger +{ + uint32 access_id; + uint32 target_mapId; + float target_X; + float target_Y; + float target_Z; + float target_Orientation; +}; + +typedef std::set<uint32> CellGuidSet; +typedef std::map<uint32/*player guid*/,uint32/*instance*/> CellCorpseSet; +struct CellObjectGuids +{ + CellGuidSet creatures; + CellGuidSet gameobjects; + CellCorpseSet corpses; +}; +typedef UNORDERED_MAP<uint32/*cell_id*/,CellObjectGuids> CellObjectGuidsMap; +typedef UNORDERED_MAP<uint32/*(mapid,spawnMode) pair*/,CellObjectGuidsMap> MapObjectGuids; + +typedef UNORDERED_MAP<uint64/*(instance,guid) pair*/,time_t> RespawnTimes; + +// Trinity string ranges +#define MIN_TRINITY_STRING_ID 1 // 'trinity_string' +#define MAX_TRINITY_STRING_ID 2000000000 +#define MIN_DB_SCRIPT_STRING_ID MAX_TRINITY_STRING_ID // 'db_script_string' +#define MAX_DB_SCRIPT_STRING_ID 2000010000 +#define MIN_CREATURE_AI_TEXT_STRING_ID (-1) // 'creature_ai_texts' +#define MAX_CREATURE_AI_TEXT_STRING_ID (-1000000) + +// Trinity Trainer Reference start range +#define TRINITY_TRAINER_START_REF 200000 + +struct TrinityStringLocale +{ + std::vector<std::string> Content; // 0 -> default, i -> i-1 locale index +}; + +typedef std::map<uint32,uint32> CreatureLinkedRespawnMap; +typedef UNORDERED_MAP<uint32,CreatureData> CreatureDataMap; +typedef UNORDERED_MAP<uint32,GameObjectData> GameObjectDataMap; +typedef UNORDERED_MAP<uint32,CreatureLocale> CreatureLocaleMap; +typedef UNORDERED_MAP<uint32,GameObjectLocale> GameObjectLocaleMap; +typedef UNORDERED_MAP<uint32,ItemLocale> ItemLocaleMap; +typedef UNORDERED_MAP<uint32,QuestLocale> QuestLocaleMap; +typedef UNORDERED_MAP<uint32,NpcTextLocale> NpcTextLocaleMap; +typedef UNORDERED_MAP<uint32,PageTextLocale> PageTextLocaleMap; +typedef UNORDERED_MAP<int32,TrinityStringLocale> TrinityStringLocaleMap; +typedef UNORDERED_MAP<uint32,GossipMenuItemsLocale> GossipMenuItemsLocaleMap; +typedef UNORDERED_MAP<uint32,PointOfInterestLocale> PointOfInterestLocaleMap; + +typedef std::multimap<uint32,uint32> QuestRelations; +typedef std::multimap<uint32,ItemRequiredTarget> ItemRequiredTargetMap; +typedef std::pair<ItemRequiredTargetMap::const_iterator, ItemRequiredTargetMap::const_iterator> ItemRequiredTargetMapBounds; + +struct PetLevelInfo +{ + PetLevelInfo() : health(0), mana(0) { for (uint8 i=0; i < MAX_STATS; ++i) stats[i] = 0; } + + uint16 stats[MAX_STATS]; + uint16 health; + uint16 mana; + uint16 armor; +}; + +struct MailLevelReward +{ + MailLevelReward() : raceMask(0), mailTemplateId(0), senderEntry(0) {} + MailLevelReward(uint32 _raceMask, uint32 _mailTemplateId, uint32 _senderEntry) : raceMask(_raceMask), mailTemplateId(_mailTemplateId), senderEntry(_senderEntry) {} + + uint32 raceMask; + uint32 mailTemplateId; + uint32 senderEntry; +}; + +typedef std::list<MailLevelReward> MailLevelRewardList; +typedef UNORDERED_MAP<uint8,MailLevelRewardList> MailLevelRewardMap; + +struct ReputationOnKillEntry +{ + uint32 repfaction1; + uint32 repfaction2; + bool is_teamaward1; + uint32 reputation_max_cap1; + int32 repvalue1; + bool is_teamaward2; + uint32 reputation_max_cap2; + int32 repvalue2; + bool team_dependent; +}; + +struct PointOfInterest +{ + uint32 entry; + float x; + float y; + uint32 icon; + uint32 flags; + uint32 data; + std::string icon_name; +}; + +struct GossipMenuItems +{ + uint32 menu_id; + uint32 id; + uint8 option_icon; + std::string option_text; + uint32 option_id; + uint32 npc_option_npcflag; + uint32 action_menu_id; + uint32 action_poi_id; + uint32 action_script_id; + bool box_coded; + uint32 box_money; + std::string box_text; + ConditionList conditions; +}; + +struct GossipMenus +{ + uint32 entry; + uint32 text_id; + ConditionList conditions; +}; + +typedef std::multimap<uint32,GossipMenus> GossipMenusMap; +typedef std::pair<GossipMenusMap::const_iterator, GossipMenusMap::const_iterator> GossipMenusMapBounds; +typedef std::pair<GossipMenusMap::iterator, GossipMenusMap::iterator> GossipMenusMapBoundsNonConst; +typedef std::multimap<uint32,GossipMenuItems> GossipMenuItemsMap; +typedef std::pair<GossipMenuItemsMap::const_iterator, GossipMenuItemsMap::const_iterator> GossipMenuItemsMapBounds; +typedef std::pair<GossipMenuItemsMap::iterator, GossipMenuItemsMap::iterator> GossipMenuItemsMapBoundsNonConst; + +struct QuestPOIPoint +{ + int32 x; + int32 y; + + QuestPOIPoint() : x(0), y(0) {} + QuestPOIPoint(int32 _x, int32 _y) : x(_x), y(_y) {} +}; + +struct QuestPOI +{ + uint32 Id; + int32 ObjectiveIndex; + uint32 MapId; + uint32 AreaId; + uint32 Unk2; + uint32 Unk3; + uint32 Unk4; + std::vector<QuestPOIPoint> points; + + QuestPOI() : Id(0), ObjectiveIndex(0), MapId(0), AreaId(0), Unk2(0), Unk3(0), Unk4(0) {} + QuestPOI(uint32 id, int32 objIndex, uint32 mapId, uint32 areaId, uint32 unk2, uint32 unk3, uint32 unk4) : Id(id), ObjectiveIndex(objIndex), MapId(mapId), AreaId(areaId), Unk2(unk2), Unk3(unk3), Unk4(unk4) {} +}; + +typedef std::vector<QuestPOI> QuestPOIVector; +typedef UNORDERED_MAP<uint32, QuestPOIVector> QuestPOIMap; + +#define WEATHER_SEASONS 4 +struct WeatherSeasonChances +{ + uint32 rainChance; + uint32 snowChance; + uint32 stormChance; +}; + +struct WeatherZoneChances +{ + WeatherSeasonChances data[WEATHER_SEASONS]; +}; + +struct GraveYardData +{ + uint32 safeLocId; + uint32 team; +}; +typedef std::multimap<uint32,GraveYardData> GraveYardMap; + +// NPC gossip text id +typedef UNORDERED_MAP<uint32, uint32> CacheNpcTextIdMap; + +typedef UNORDERED_MAP<uint32, VendorItemData> CacheVendorItemMap; +typedef UNORDERED_MAP<uint32, TrainerSpellData> CacheTrainerSpellMap; + +enum SkillRangeType +{ + SKILL_RANGE_LANGUAGE, // 300..300 + SKILL_RANGE_LEVEL, // 1..max skill for level + SKILL_RANGE_MONO, // 1..1, grey monolite bar + SKILL_RANGE_RANK, // 1..skill for known rank + SKILL_RANGE_NONE, // 0..0 always +}; + +struct GM_Ticket +{ + uint64 guid; + uint64 playerGuid; + std::string name; + float pos_x; + float pos_y; + float pos_z; + uint32 map; + std::string message; + uint64 createtime; + uint64 timestamp; + int64 closed; // 0 = Open, -1 = Console, playerGuid = player abandoned ticket, other = GM who closed it. + uint64 assignedToGM; + std::string comment; +}; +typedef std::list<GM_Ticket*> GmTicketList; +SkillRangeType GetSkillRangeType(SkillLineEntry const *pSkill, bool racial); + +#define MAX_PLAYER_NAME 12 // max allowed by client name length +#define MAX_INTERNAL_PLAYER_NAME 15 // max server internal player name length (> MAX_PLAYER_NAME for support declined names) +#define MAX_PET_NAME 12 // max allowed by client name length +#define MAX_CHARTER_NAME 24 // max allowed by client name length + +bool normalizePlayerName(std::string& name); + +struct LanguageDesc +{ + Language lang_id; + uint32 spell_id; + uint32 skill_id; +}; + +extern LanguageDesc lang_description[LANGUAGES_COUNT]; + LanguageDesc const* GetLanguageDescByID(uint32 lang); + +class PlayerDumpReader; + +class ObjectMgr +{ + friend class PlayerDumpReader; + + public: + ObjectMgr(); + ~ObjectMgr(); + + typedef UNORDERED_MAP<uint32, Item*> ItemMap; + + typedef std::set< Group * > GroupSet; + + typedef UNORDERED_MAP<uint32, Guild *> GuildMap; + + typedef UNORDERED_MAP<uint32, ArenaTeam*> ArenaTeamMap; + + typedef UNORDERED_MAP<uint32, Quest*> QuestMap; + + typedef UNORDERED_MAP<uint32, AreaTrigger> AreaTriggerMap; + + typedef UNORDERED_MAP<uint32, uint32> AreaTriggerScriptMap; + + typedef UNORDERED_MAP<uint32, AccessRequirement> AccessRequirementMap; + + typedef UNORDERED_MAP<uint32, ReputationOnKillEntry> RepOnKillMap; + typedef UNORDERED_MAP<uint32, PointOfInterest> PointOfInterestMap; + + typedef UNORDERED_MAP<uint32, WeatherZoneChances> WeatherZoneMap; + + typedef std::vector<std::string> ScriptNameMap; + + UNORDERED_MAP<uint32, uint32> TransportEventMap; + + Player* GetPlayer(const char* name) const { return ObjectAccessor::Instance().FindPlayerByName(name);} + Player* GetPlayer(uint64 guid) const { return ObjectAccessor::FindPlayer(guid); } + + static GameObjectInfo const *GetGameObjectInfo(uint32 id) { return sGOStorage.LookupEntry<GameObjectInfo>(id); } + int LoadReferenceVendor(int32 vendor, int32 item_id, std::set<uint32> *skip_vendors); + + void LoadGameobjectInfo(); + void AddGameobjectInfo(GameObjectInfo *goinfo); + + Group * GetGroupByGUID(const uint64 &guid) const; + void AddGroup(Group* group) { mGroupSet.insert(group); } + void RemoveGroup(Group* group) { mGroupSet.erase(group); } + + Guild* GetGuildByLeader(uint64 const&guid) const; + Guild* GetGuildById(uint32 GuildId) const; + Guild* GetGuildByName(const std::string& guildname) const; + std::string GetGuildNameById(uint32 GuildId) const; + void AddGuild(Guild* guild); + void RemoveGuild(uint32 Id); + + ArenaTeam* GetArenaTeamById(uint32 arenateamid) const; + ArenaTeam* GetArenaTeamByName(const std::string& arenateamname) const; + ArenaTeam* GetArenaTeamByCaptain(uint64 const& guid) const; + void AddArenaTeam(ArenaTeam* arenaTeam); + void RemoveArenaTeam(uint32 Id); + ArenaTeamMap::iterator GetArenaTeamMapBegin() { return mArenaTeamMap.begin(); } + ArenaTeamMap::iterator GetArenaTeamMapEnd() { return mArenaTeamMap.end(); } + + static CreatureInfo const *GetCreatureTemplate(uint32 id); + CreatureModelInfo const *GetCreatureModelInfo(uint32 modelid); + CreatureModelInfo const* GetCreatureModelRandomGender(uint32 display_id); + uint32 ChooseDisplayId(uint32 team, const CreatureInfo *cinfo, const CreatureData *data = NULL); + EquipmentInfo const *GetEquipmentInfo(uint32 entry); + static CreatureDataAddon const *GetCreatureAddon(uint32 lowguid) + { + return sCreatureDataAddonStorage.LookupEntry<CreatureDataAddon>(lowguid); + } + + static CreatureDataAddon const *GetCreatureTemplateAddon(uint32 entry) + { + return sCreatureInfoAddonStorage.LookupEntry<CreatureDataAddon>(entry); + } + + static ItemPrototype const* GetItemPrototype(uint32 id) { return sItemStorage.LookupEntry<ItemPrototype>(id); } + + static InstanceTemplate const* GetInstanceTemplate(uint32 map) + { + return sInstanceTemplate.LookupEntry<InstanceTemplate>(map); + } + + PetLevelInfo const* GetPetLevelInfo(uint32 creature_id, uint8 level) const; + + PlayerClassInfo const* GetPlayerClassInfo(uint32 class_) const + { + if (class_ >= MAX_CLASSES) return NULL; + return &playerClassInfo[class_]; + } + void GetPlayerClassLevelInfo(uint32 class_,uint8 level, PlayerClassLevelInfo* info) const; + + PlayerInfo const* GetPlayerInfo(uint32 race, uint32 class_) const + { + if (race >= MAX_RACES) return NULL; + if (class_ >= MAX_CLASSES) return NULL; + PlayerInfo const* info = &playerInfo[race][class_]; + if (info->displayId_m == 0 || info->displayId_f == 0) return NULL; + return info; + } + void GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, PlayerLevelInfo* info) const; + + uint64 GetPlayerGUIDByName(std::string name) const; + bool GetPlayerNameByGUID(const uint64 &guid, std::string &name) const; + uint32 GetPlayerTeamByGUID(const uint64 &guid) const; + uint32 GetPlayerAccountIdByGUID(const uint64 &guid) const; + uint32 GetPlayerAccountIdByPlayerName(const std::string& name) const; + + uint32 GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 team); + void GetTaxiPath(uint32 source, uint32 destination, uint32 &path, uint32 &cost); + uint32 GetTaxiMountDisplayId(uint32 id, uint32 team, bool allowed_alt_team = false); + void GetTaxiPathNodes(uint32 path, Path &pathnodes, std::vector<uint32>& mapIds); + void GetTransportPathNodes(uint32 path, TransportPath &pathnodes); + + Quest const* GetQuestTemplate(uint32 quest_id) const + { + QuestMap::const_iterator itr = mQuestTemplates.find(quest_id); + return itr != mQuestTemplates.end() ? itr->second : NULL; + } + QuestMap const& GetQuestTemplates() const { return mQuestTemplates; } + + uint32 GetQuestForAreaTrigger(uint32 Trigger_ID) const + { + QuestAreaTriggerMap::const_iterator itr = mQuestAreaTriggerMap.find(Trigger_ID); + if (itr != mQuestAreaTriggerMap.end()) + return itr->second; + return 0; + } + bool IsTavernAreaTrigger(uint32 Trigger_ID) const + { + return mTavernAreaTriggerSet.find(Trigger_ID) != mTavernAreaTriggerSet.end(); + } + + bool IsGameObjectForQuests(uint32 entry) const + { + return mGameObjectForQuestSet.find(entry) != mGameObjectForQuestSet.end(); + } + + GossipText const* GetGossipText(uint32 Text_ID) const; + + WorldSafeLocsEntry const *GetClosestGraveYard(float x, float y, float z, uint32 MapId, uint32 team); + bool AddGraveYardLink(uint32 id, uint32 zone, uint32 team, bool inDB = true); + void RemoveGraveYardLink(uint32 id, uint32 zone, uint32 team, bool inDB = false); + void LoadGraveyardZones(); + GraveYardData const* FindGraveYardData(uint32 id, uint32 zone); + + AreaTrigger const* GetAreaTrigger(uint32 trigger) const + { + AreaTriggerMap::const_iterator itr = mAreaTriggers.find(trigger); + if (itr != mAreaTriggers.end()) + return &itr->second; + return NULL; + } + + AccessRequirement const* GetAccessRequirement(uint32 requirement) const + { + AccessRequirementMap::const_iterator itr = mAccessRequirements.find(requirement); + if (itr != mAccessRequirements.end()) + return &itr->second; + return NULL; + } + + AreaTrigger const* GetGoBackTrigger(uint32 Map) const; + AreaTrigger const* GetMapEntranceTrigger(uint32 Map) const; + + uint32 GetAreaTriggerScriptId(uint32 trigger_id); + + ReputationOnKillEntry const* GetReputationOnKilEntry(uint32 id) const + { + RepOnKillMap::const_iterator itr = mRepOnKill.find(id); + if (itr != mRepOnKill.end()) + return &itr->second; + return NULL; + } + + PointOfInterest const* GetPointOfInterest(uint32 id) const + { + PointOfInterestMap::const_iterator itr = mPointsOfInterest.find(id); + if (itr != mPointsOfInterest.end()) + return &itr->second; + return NULL; + } + + QuestPOIVector const* GetQuestPOIVector(uint32 questId) + { + QuestPOIMap::const_iterator itr = mQuestPOIMap.find(questId); + if (itr != mQuestPOIMap.end()) + return &itr->second; + return NULL; + } + + VehicleAccessoryList const* GetVehicleAccessoryList(uint32 uiEntry) const + { + VehicleAccessoryMap::const_iterator itr = m_VehicleAccessoryMap.find(uiEntry); + if (itr != m_VehicleAccessoryMap.end()) + return &itr->second; + return NULL; + } + + void LoadGuilds(); + void LoadArenaTeams(); + void LoadGroups(); + void LoadQuests(); + void LoadQuestRelations() + { + sLog.outString("Loading GO Start Quest Data..."); + LoadGameobjectQuestRelations(); + sLog.outString("Loading GO End Quest Data..."); + LoadGameobjectInvolvedRelations(); + sLog.outString("Loading Creature Start Quest Data..."); + LoadCreatureQuestRelations(); + sLog.outString("Loading Creature End Quest Data..."); + LoadCreatureInvolvedRelations(); + } + void LoadGameobjectQuestRelations(); + void LoadGameobjectInvolvedRelations(); + void LoadCreatureQuestRelations(); + void LoadCreatureInvolvedRelations(); + + QuestRelations mGOQuestRelations; + QuestRelations mGOQuestInvolvedRelations; + QuestRelations mCreatureQuestRelations; + QuestRelations mCreatureQuestInvolvedRelations; + + void LoadGameObjectScripts(); + void LoadQuestEndScripts(); + void LoadQuestStartScripts(); + void LoadEventScripts(); + void LoadSpellScripts(); + void LoadGossipScripts(); + void LoadWaypointScripts(); + + void LoadTransportEvents(); + + bool LoadTrinityStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value); + bool LoadTrinityStrings() { return LoadTrinityStrings(WorldDatabase,"trinity_string",MIN_TRINITY_STRING_ID,MAX_TRINITY_STRING_ID); } + void LoadDbScriptStrings(); + void LoadCreatureClassLevelStats(); + void LoadCreatureLocales(); + void LoadCreatureTemplates(); + void CheckCreatureTemplate(CreatureInfo const* cInfo); + void LoadCreatures(); + void LoadCreatureLinkedRespawn(); + bool CheckCreatureLinkedRespawn(uint32 guid, uint32 linkedGuid) const; + bool SetCreatureLinkedRespawn(uint32 guid, uint32 linkedGuid); + void LoadCreatureRespawnTimes(); + void LoadCreatureAddons(); + void LoadCreatureModelInfo(); + void LoadEquipmentTemplates(); + void LoadGameObjectLocales(); + void LoadGameobjects(); + void LoadGameobjectRespawnTimes(); + void LoadItemPrototypes(); + void LoadItemLocales(); + void LoadQuestLocales(); + void LoadNpcTextLocales(); + void LoadPageTextLocales(); + void LoadGossipMenuItemsLocales(); + void LoadPointOfInterestLocales(); + void LoadInstanceTemplate(); + void LoadMailLevelRewards(); + void LoadVehicleAccessories(); + + void LoadGossipText(); + + void LoadAreaTriggerTeleports(); + void LoadAccessRequirements(); + void LoadQuestAreaTriggers(); + void LoadAreaTriggerScripts(); + void LoadTavernAreaTriggers(); + void LoadGameObjectForQuests(); + + void LoadPageTexts(); + + void LoadPlayerInfo(); + void LoadPetLevelInfo(); + void LoadExplorationBaseXP(); + void LoadPetNames(); + void LoadPetNumber(); + void LoadCorpses(); + void LoadFishingBaseSkillLevel(); + + void LoadReputationOnKill(); + void LoadPointsOfInterest(); + void LoadQuestPOI(); + + void LoadNPCSpellClickSpells(); + + void LoadWeatherZoneChances(); + void LoadGameTele(); + + void LoadNpcTextId(); + + void LoadGossipMenu(); + void LoadGossipMenuItems(); + + void LoadVendors(); + void LoadTrainerSpell(); + bool AddSpellToTrainer(uint32 entry, uint32 spell, Field *fields, std::set<uint32> *skip_trainers, std::set<uint32> *talentIds); + int LoadReferenceTrainer(uint32 trainer, int32 spell, std::set<uint32> *skip_trainers, std::set<uint32> *talentIds); + void LoadGMTickets(); + + std::string GeneratePetName(uint32 entry); + uint32 GetBaseXP(uint8 level); + uint32 GetXPForLevel(uint8 level); + + int32 GetFishingBaseSkillLevel(uint32 entry) const + { + FishingBaseSkillMap::const_iterator itr = mFishingBaseForArea.find(entry); + return itr != mFishingBaseForArea.end() ? itr->second : 0; + } + + void ReturnOrDeleteOldMails(bool serverUp); + + CreatureBaseStats const* GetCreatureBaseStats(uint8 level, uint8 unitClass); + + void SetHighestGuids(); + uint32 GenerateLowGuid(HighGuid guidhigh); + uint32 GenerateArenaTeamId(); + uint32 GenerateAuctionID(); + uint64 GenerateEquipmentSetGuid(); + uint32 GenerateGuildId(); + uint32 GenerateMailID(); + uint32 GeneratePetNumber(); + + typedef std::multimap<int32, uint32> ExclusiveQuestGroups; + ExclusiveQuestGroups mExclusiveQuestGroups; + + MailLevelReward const* GetMailLevelReward(uint32 level,uint32 raceMask) + { + MailLevelRewardMap::const_iterator map_itr = m_mailLevelRewardMap.find(level); + if (map_itr == m_mailLevelRewardMap.end()) + return NULL; + + for (MailLevelRewardList::const_iterator set_itr = map_itr->second.begin(); set_itr != map_itr->second.end(); ++set_itr) + if (set_itr->raceMask & raceMask) + return &*set_itr; + + return NULL; + } + + WeatherZoneChances const* GetWeatherChances(uint32 zone_id) const + { + WeatherZoneMap::const_iterator itr = mWeatherZoneMap.find(zone_id); + if (itr != mWeatherZoneMap.end()) + return &itr->second; + else + return NULL; + } + + CellObjectGuids const& GetCellObjectGuids(uint16 mapid, uint8 spawnMode, uint32 cell_id) + { + return mMapObjectGuids[MAKE_PAIR32(mapid,spawnMode)][cell_id]; + } + + CreatureData const* GetCreatureData(uint32 guid) const + { + CreatureDataMap::const_iterator itr = mCreatureDataMap.find(guid); + if (itr == mCreatureDataMap.end()) return NULL; + return &itr->second; + } + CreatureData& NewOrExistCreatureData(uint32 guid) { return mCreatureDataMap[guid]; } + void DeleteCreatureData(uint32 guid); + uint32 GetLinkedRespawnGuid(uint32 guid) const + { + CreatureLinkedRespawnMap::const_iterator itr = mCreatureLinkedRespawnMap.find(guid); + if (itr == mCreatureLinkedRespawnMap.end()) return 0; + return itr->second; + } + CreatureLocale const* GetCreatureLocale(uint32 entry) const + { + CreatureLocaleMap::const_iterator itr = mCreatureLocaleMap.find(entry); + if (itr == mCreatureLocaleMap.end()) return NULL; + return &itr->second; + } + GameObjectLocale const* GetGameObjectLocale(uint32 entry) const + { + GameObjectLocaleMap::const_iterator itr = mGameObjectLocaleMap.find(entry); + if (itr == mGameObjectLocaleMap.end()) return NULL; + return &itr->second; + } + ItemLocale const* GetItemLocale(uint32 entry) const + { + ItemLocaleMap::const_iterator itr = mItemLocaleMap.find(entry); + if (itr == mItemLocaleMap.end()) return NULL; + return &itr->second; + } + QuestLocale const* GetQuestLocale(uint32 entry) const + { + QuestLocaleMap::const_iterator itr = mQuestLocaleMap.find(entry); + if (itr == mQuestLocaleMap.end()) return NULL; + return &itr->second; + } + NpcTextLocale const* GetNpcTextLocale(uint32 entry) const + { + NpcTextLocaleMap::const_iterator itr = mNpcTextLocaleMap.find(entry); + if (itr == mNpcTextLocaleMap.end()) return NULL; + return &itr->second; + } + PageTextLocale const* GetPageTextLocale(uint32 entry) const + { + PageTextLocaleMap::const_iterator itr = mPageTextLocaleMap.find(entry); + if (itr == mPageTextLocaleMap.end()) return NULL; + return &itr->second; + } + GossipMenuItemsLocale const* GetGossipMenuItemsLocale(uint32 entry) const + { + GossipMenuItemsLocaleMap::const_iterator itr = mGossipMenuItemsLocaleMap.find(entry); + if (itr == mGossipMenuItemsLocaleMap.end()) return NULL; + return &itr->second; + } + PointOfInterestLocale const* GetPointOfInterestLocale(uint32 poi_id) const + { + PointOfInterestLocaleMap::const_iterator itr = mPointOfInterestLocaleMap.find(poi_id); + if (itr == mPointOfInterestLocaleMap.end()) return NULL; + return &itr->second; + } + + bool IsGoOfSpecificEntrySpawned(uint32 entry) const + { + for (GameObjectDataMap::const_iterator it = mGameObjectDataMap.begin(); it != mGameObjectDataMap.end(); ++it) + if (it->second.id == entry) + return true; + + return false; + } + + GameObjectData const* GetGOData(uint32 guid) const + { + GameObjectDataMap::const_iterator itr = mGameObjectDataMap.find(guid); + if (itr == mGameObjectDataMap.end()) return NULL; + return &itr->second; + } + GameObjectData& NewGOData(uint32 guid) { return mGameObjectDataMap[guid]; } + void DeleteGOData(uint32 guid); + + TrinityStringLocale const* GetTrinityStringLocale(int32 entry) const + { + TrinityStringLocaleMap::const_iterator itr = mTrinityStringLocaleMap.find(entry); + if (itr == mTrinityStringLocaleMap.end()) return NULL; + return &itr->second; + } + const char *GetTrinityString(int32 entry, int locale_idx) const; + const char *GetTrinityStringForDBCLocale(int32 entry) const { return GetTrinityString(entry,DBCLocaleIndex); } + int32 GetDBCLocaleIndex() const { return DBCLocaleIndex; } + void SetDBCLocaleIndex(uint32 lang) { DBCLocaleIndex = GetIndexForLocale(LocaleConstant(lang)); } + + void AddCorpseCellData(uint32 mapid, uint32 cellid, uint32 player_guid, uint32 instance); + void DeleteCorpseCellData(uint32 mapid, uint32 cellid, uint32 player_guid); + + time_t GetCreatureRespawnTime(uint32 loguid, uint32 instance) { return mCreatureRespawnTimes[MAKE_PAIR64(loguid,instance)]; } + void SaveCreatureRespawnTime(uint32 loguid, uint32 instance, time_t t); + time_t GetGORespawnTime(uint32 loguid, uint32 instance) { return mGORespawnTimes[MAKE_PAIR64(loguid,instance)]; } + void SaveGORespawnTime(uint32 loguid, uint32 instance, time_t t); + void DeleteRespawnTimeForInstance(uint32 instance); + + // grid objects + void AddCreatureToGrid(uint32 guid, CreatureData const* data); + void RemoveCreatureFromGrid(uint32 guid, CreatureData const* data); + void AddGameobjectToGrid(uint32 guid, GameObjectData const* data); + void RemoveGameobjectFromGrid(uint32 guid, GameObjectData const* data); + uint32 AddGOData(uint32 entry, uint32 map, float x, float y, float z, float o, uint32 spawntimedelay = 0, float rotation0 = 0, float rotation1 = 0, float rotation2 = 0, float rotation3 = 0); + uint32 AddCreData(uint32 entry, uint32 team, uint32 map, float x, float y, float z, float o, uint32 spawntimedelay = 0); + bool MoveCreData(uint32 guid, uint32 map, Position pos); + + // reserved names + void LoadReservedPlayersNames(); + bool IsReservedName(const std::string& name) const; + + // name with valid structure and symbols + static uint8 CheckPlayerName(const std::string& name, bool create = false); + static PetNameInvalidReason CheckPetName(const std::string& name); + static bool IsValidCharterName(const std::string& name); + + static bool CheckDeclinedNames(std::wstring mainpart, DeclinedName const& names); + + void LoadSpellDisabledEntrys(); + bool IsPlayerSpellDisabled(uint32 spellid) { return (m_DisabledPlayerSpells.count(spellid) != 0); } + bool IsCreatureSpellDisabled(uint32 spellid) { return (m_DisabledCreatureSpells.count(spellid) != 0); } + bool IsPetSpellDisabled(uint32 spellid) { return (m_DisabledPetSpells.count(spellid) != 0); } + + int GetIndexForLocale(LocaleConstant loc); + LocaleConstant GetLocaleForIndex(int i); + + GameTele const* GetGameTele(uint32 id) const + { + GameTeleMap::const_iterator itr = m_GameTeleMap.find(id); + if (itr == m_GameTeleMap.end()) return NULL; + return &itr->second; + } + GameTele const* GetGameTele(const std::string& name) const; + GameTeleMap const& GetGameTeleMap() const { return m_GameTeleMap; } + bool AddGameTele(GameTele& data); + bool DeleteGameTele(const std::string& name); + + uint32 GetNpcGossip(uint32 entry) const + { + CacheNpcTextIdMap::const_iterator iter = m_mCacheNpcTextIdMap.find(entry); + if (iter == m_mCacheNpcTextIdMap.end()) + return 0; + + return iter->second; + } + + TrainerSpellData const* GetNpcTrainerSpells(uint32 entry) const + { + CacheTrainerSpellMap::const_iterator iter = m_mCacheTrainerSpellMap.find(entry); + if (iter == m_mCacheTrainerSpellMap.end()) + return NULL; + + return &iter->second; + } + + VendorItemData const* GetNpcVendorItemList(uint32 entry) const + { + CacheVendorItemMap::const_iterator iter = m_mCacheVendorItemMap.find(entry); + if (iter == m_mCacheVendorItemMap.end()) + return NULL; + + return &iter->second; + } + void AddVendorItem(uint32 entry,uint32 item, int32 maxcount, uint32 incrtime, uint32 ExtendedCost, bool savetodb = true); // for event + bool RemoveVendorItem(uint32 entry,uint32 item, bool savetodb = true); // for event + bool IsVendorItemValid(uint32 vendor_entry, uint32 item, int32 maxcount, uint32 ptime, uint32 ExtendedCost, Player* pl = NULL, std::set<uint32>* skip_vendors = NULL, uint32 ORnpcflag = 0) const; + + void LoadScriptNames(); + ScriptNameMap &GetScriptNames() { return m_scriptNames; } + const char * GetScriptName(uint32 id) { return id < m_scriptNames.size() ? m_scriptNames[id].c_str() : ""; } + uint32 GetScriptId(const char *name); + + int GetOrNewIndexForLocale(LocaleConstant loc); + + SpellClickInfoMapBounds GetSpellClickInfoMapBounds(uint32 creature_id) const + { + return SpellClickInfoMapBounds(mSpellClickInfoMap.lower_bound(creature_id),mSpellClickInfoMap.upper_bound(creature_id)); + } + + GM_Ticket *GetGMTicket(uint64 ticketGuid) + { + for (GmTicketList::const_iterator i = m_GMTicketList.begin(); i != m_GMTicketList.end(); ++i) + if ((*i) && (*i)->guid == ticketGuid) + return (*i); + + return NULL; + } + GM_Ticket *GetGMTicketByPlayer(uint64 playerGuid) + { + for (GmTicketList::const_iterator i = m_GMTicketList.begin(); i != m_GMTicketList.end(); ++i) + if ((*i) && (*i)->playerGuid == playerGuid && (*i)->closed == 0) + return (*i); + + return NULL; + } + + GossipMenusMapBounds GetGossipMenusMapBounds(uint32 uiMenuId) const + { + return GossipMenusMapBounds(m_mGossipMenusMap.lower_bound(uiMenuId),m_mGossipMenusMap.upper_bound(uiMenuId)); + } + + GossipMenusMapBoundsNonConst GetGossipMenusMapBoundsNonConst(uint32 uiMenuId) + { + return GossipMenusMapBoundsNonConst(m_mGossipMenusMap.lower_bound(uiMenuId),m_mGossipMenusMap.upper_bound(uiMenuId)); + } + + GossipMenuItemsMapBounds GetGossipMenuItemsMapBounds(uint32 uiMenuId) const + { + return GossipMenuItemsMapBounds(m_mGossipMenuItemsMap.lower_bound(uiMenuId),m_mGossipMenuItemsMap.upper_bound(uiMenuId)); + } + GossipMenuItemsMapBoundsNonConst GetGossipMenuItemsMapBoundsNonConst(uint32 uiMenuId) + { + return GossipMenuItemsMapBoundsNonConst(m_mGossipMenuItemsMap.lower_bound(uiMenuId),m_mGossipMenuItemsMap.upper_bound(uiMenuId)); + } + + void AddOrUpdateGMTicket(GM_Ticket &ticket, bool create = false); + void _AddOrUpdateGMTicket(GM_Ticket &ticket); + void RemoveGMTicket(uint64 ticketGuid, int64 source = -1, bool permanently = false); + void RemoveGMTicket(GM_Ticket *ticket, int64 source = -1, bool permanently = false); + GmTicketList m_GMTicketList; + uint64 GenerateGMTicketId(); + + // for wintergrasp only + GraveYardMap mGraveYardMap; + protected: + + // first free id for selected id type + uint32 m_arenaTeamId; + uint32 m_auctionid; + uint64 m_equipmentSetGuid; + uint32 m_guildId; + uint32 m_ItemTextId; + uint32 m_mailid; + uint32 m_hiPetNumber; + uint64 m_GMticketid; + + // first free low guid for seelcted guid type + uint32 m_hiCharGuid; + uint32 m_hiCreatureGuid; + uint32 m_hiPetGuid; + uint32 m_hiVehicleGuid; + uint32 m_hiItemGuid; + uint32 m_hiGoGuid; + uint32 m_hiDoGuid; + uint32 m_hiCorpseGuid; + uint32 m_hiGroupGuid; + + QuestMap mQuestTemplates; + + typedef UNORDERED_MAP<uint32, GossipText> GossipTextMap; + typedef UNORDERED_MAP<uint32, uint32> QuestAreaTriggerMap; + typedef std::set<uint32> TavernAreaTriggerSet; + typedef std::set<uint32> GameObjectForQuestSet; + + GroupSet mGroupSet; + GuildMap mGuildMap; + ArenaTeamMap mArenaTeamMap; + + QuestAreaTriggerMap mQuestAreaTriggerMap; + TavernAreaTriggerSet mTavernAreaTriggerSet; + GameObjectForQuestSet mGameObjectForQuestSet; + GossipTextMap mGossipText; + AreaTriggerMap mAreaTriggers; + AreaTriggerScriptMap mAreaTriggerScripts; + AccessRequirementMap mAccessRequirements; + + RepOnKillMap mRepOnKill; + + GossipMenusMap m_mGossipMenusMap; + GossipMenuItemsMap m_mGossipMenuItemsMap; + PointOfInterestMap mPointsOfInterest; + + QuestPOIMap mQuestPOIMap; + + WeatherZoneMap mWeatherZoneMap; + + //character reserved names + typedef std::set<std::wstring> ReservedNamesMap; + ReservedNamesMap m_ReservedNames; + + std::set<uint32> m_DisabledPlayerSpells; + std::set<uint32> m_DisabledCreatureSpells; + std::set<uint32> m_DisabledPetSpells; + +// GraveYardMap mGraveYardMap; + + GameTeleMap m_GameTeleMap; + + ScriptNameMap m_scriptNames; + + SpellClickInfoMap mSpellClickInfoMap; + + ItemRequiredTargetMap m_ItemRequiredTarget; + + VehicleAccessoryMap m_VehicleAccessoryMap; + + typedef std::vector<LocaleConstant> LocalForIndex; + LocalForIndex m_LocalForIndex; + + int DBCLocaleIndex; + + private: + void LoadScripts(ScriptMapMap& scripts, char const* tablename); + void CheckScripts(ScriptMapMap const& scripts,std::set<int32>& ids); + void LoadCreatureAddons(SQLStorage& creatureaddons, char const* entryName, char const* comment); + void ConvertCreatureAddonAuras(CreatureDataAddon* addon, char const* table, char const* guidEntryStr); + void LoadQuestRelationsHelper(QuestRelations& map,char const* table); + + MailLevelRewardMap m_mailLevelRewardMap; + + CreatureBaseStatsMap m_creatureBaseStatsMap; + + typedef std::map<uint32,PetLevelInfo*> PetLevelInfoMap; + // PetLevelInfoMap[creature_id][level] + PetLevelInfoMap petInfo; // [creature_id][level] + + PlayerClassInfo playerClassInfo[MAX_CLASSES]; + + void BuildPlayerLevelInfo(uint8 race, uint8 class_, uint8 level, PlayerLevelInfo* plinfo) const; + PlayerInfo playerInfo[MAX_RACES][MAX_CLASSES]; + + typedef std::vector<uint32> PlayerXPperLevel; // [level] + PlayerXPperLevel mPlayerXPperLevel; + + typedef std::map<uint32,uint32> BaseXPMap; // [area level][base xp] + BaseXPMap mBaseXPTable; + + typedef std::map<uint32,int32> FishingBaseSkillMap; // [areaId][base skill level] + FishingBaseSkillMap mFishingBaseForArea; + + typedef std::map<uint32,std::vector<std::string> > HalfNameMap; + HalfNameMap PetHalfName0; + HalfNameMap PetHalfName1; + + MapObjectGuids mMapObjectGuids; + CreatureDataMap mCreatureDataMap; + CreatureLinkedRespawnMap mCreatureLinkedRespawnMap; + CreatureLocaleMap mCreatureLocaleMap; + GameObjectDataMap mGameObjectDataMap; + GameObjectLocaleMap mGameObjectLocaleMap; + ItemLocaleMap mItemLocaleMap; + QuestLocaleMap mQuestLocaleMap; + NpcTextLocaleMap mNpcTextLocaleMap; + PageTextLocaleMap mPageTextLocaleMap; + TrinityStringLocaleMap mTrinityStringLocaleMap; + GossipMenuItemsLocaleMap mGossipMenuItemsLocaleMap; + PointOfInterestLocaleMap mPointOfInterestLocaleMap; + RespawnTimes mCreatureRespawnTimes; + RespawnTimes mGORespawnTimes; + + CacheNpcTextIdMap m_mCacheNpcTextIdMap; + CacheVendorItemMap m_mCacheVendorItemMap; + CacheTrainerSpellMap m_mCacheTrainerSpellMap; + + std::set<uint32> difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate + std::set<uint32> hasDifficultyEntries[MAX_DIFFICULTY - 1]; // already loaded creatures with difficulty 1 values, used in CheckCreatureTemplate + +}; + +#define objmgr Trinity::Singleton<ObjectMgr>::Instance() + +// scripting access functions + bool LoadTrinityStrings(DatabaseType& db, char const* table,int32 start_value = MAX_CREATURE_AI_TEXT_STRING_ID, int32 end_value = std::numeric_limits<int32>::min()); + uint32 GetAreaTriggerScriptId(uint32 trigger_id); + uint32 GetScriptId(const char *name); + ObjectMgr::ScriptNameMap& GetScriptNames(); + GameObjectInfo const *GetGameObjectInfo(uint32 id); + CreatureInfo const *GetCreatureInfo(uint32 id); + CreatureInfo const* GetCreatureTemplateStore(uint32 entry); + Quest const* GetQuestTemplateStore(uint32 entry); + +#endif diff --git a/src/server/game/Entities/Object/UpdateData.cpp b/src/server/game/Entities/Object/UpdateData.cpp new file mode 100644 index 00000000000..68f0501f304 --- /dev/null +++ b/src/server/game/Entities/Object/UpdateData.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 "Common.h" +#include "ByteBuffer.h" +#include "WorldPacket.h" +#include "UpdateData.h" +#include "Log.h" +#include "Opcodes.h" +#include "World.h" +#include <zlib/zlib.h> + +UpdateData::UpdateData() : m_blockCount(0) +{ +} + +void UpdateData::AddOutOfRangeGUID(std::set<uint64>& guids) +{ + m_outOfRangeGUIDs.insert(guids.begin(),guids.end()); +} + +void UpdateData::AddOutOfRangeGUID(const uint64 &guid) +{ + m_outOfRangeGUIDs.insert(guid); +} + +void UpdateData::AddUpdateBlock(const ByteBuffer &block) +{ + m_data.append(block); + ++m_blockCount; +} + +void UpdateData::Compress(void* dst, uint32 *dst_size, void* src, int src_size) +{ + z_stream c_stream; + + c_stream.zalloc = (alloc_func)0; + c_stream.zfree = (free_func)0; + c_stream.opaque = (voidpf)0; + + // default Z_BEST_SPEED (1) + int z_res = deflateInit(&c_stream, sWorld.getConfig(CONFIG_COMPRESSION)); + if (z_res != Z_OK) + { + sLog.outError("Can't compress update packet (zlib: deflateInit) Error code: %i (%s)",z_res,zError(z_res)); + *dst_size = 0; + return; + } + + c_stream.next_out = (Bytef*)dst; + c_stream.avail_out = *dst_size; + c_stream.next_in = (Bytef*)src; + c_stream.avail_in = (uInt)src_size; + + z_res = deflate(&c_stream, Z_NO_FLUSH); + if (z_res != Z_OK) + { + sLog.outError("Can't compress update packet (zlib: deflate) Error code: %i (%s)",z_res,zError(z_res)); + *dst_size = 0; + return; + } + + if (c_stream.avail_in != 0) + { + sLog.outError("Can't compress update packet (zlib: deflate not greedy)"); + *dst_size = 0; + return; + } + + z_res = deflate(&c_stream, Z_FINISH); + if (z_res != Z_STREAM_END) + { + sLog.outError("Can't compress update packet (zlib: deflate should report Z_STREAM_END instead %i (%s)",z_res,zError(z_res)); + *dst_size = 0; + return; + } + + z_res = deflateEnd(&c_stream); + if (z_res != Z_OK) + { + sLog.outError("Can't compress update packet (zlib: deflateEnd) Error code: %i (%s)",z_res,zError(z_res)); + *dst_size = 0; + return; + } + + *dst_size = c_stream.total_out; +} + +bool UpdateData::BuildPacket(WorldPacket *packet) +{ + ASSERT(packet->empty()); // shouldn't happen + + ByteBuffer buf(4 + (m_outOfRangeGUIDs.empty() ? 0 : 1 + 4 + 9 * m_outOfRangeGUIDs.size()) + m_data.wpos()); + + buf << (uint32) (!m_outOfRangeGUIDs.empty() ? m_blockCount + 1 : m_blockCount); + + if (!m_outOfRangeGUIDs.empty()) + { + buf << (uint8) UPDATETYPE_OUT_OF_RANGE_OBJECTS; + buf << (uint32) m_outOfRangeGUIDs.size(); + + for (std::set<uint64>::const_iterator i = m_outOfRangeGUIDs.begin(); i != m_outOfRangeGUIDs.end(); ++i) + { + buf.appendPackGUID(*i); + } + } + + buf.append(m_data); + + size_t pSize = buf.wpos(); // use real used data size + + if (pSize > 100) // compress large packets + { + uint32 destsize = compressBound(pSize); + packet->resize(destsize + sizeof(uint32)); + + packet->put<uint32>(0, pSize); + Compress(const_cast<uint8*>(packet->contents()) + sizeof(uint32), &destsize, (void*)buf.contents(), pSize); + if (destsize == 0) + return false; + + packet->resize(destsize + sizeof(uint32)); + packet->SetOpcode(SMSG_COMPRESSED_UPDATE_OBJECT); + } + else // send small packets without compression + { + packet->append(buf); + packet->SetOpcode(SMSG_UPDATE_OBJECT); + } + + return true; +} + +void UpdateData::Clear() +{ + m_data.clear(); + m_outOfRangeGUIDs.clear(); + m_blockCount = 0; +} + diff --git a/src/server/game/Entities/Object/UpdateData.h b/src/server/game/Entities/Object/UpdateData.h new file mode 100644 index 00000000000..e560db842f5 --- /dev/null +++ b/src/server/game/Entities/Object/UpdateData.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef __UPDATEDATA_H +#define __UPDATEDATA_H + +#include "ByteBuffer.h" +class WorldPacket; + +enum OBJECT_UPDATE_TYPE +{ + UPDATETYPE_VALUES = 0, + UPDATETYPE_MOVEMENT = 1, + UPDATETYPE_CREATE_OBJECT = 2, + UPDATETYPE_CREATE_OBJECT2 = 3, + UPDATETYPE_OUT_OF_RANGE_OBJECTS = 4, + UPDATETYPE_NEAR_OBJECTS = 5 +}; + +enum OBJECT_UPDATE_FLAGS +{ + UPDATEFLAG_NONE = 0x0000, + UPDATEFLAG_SELF = 0x0001, + UPDATEFLAG_TRANSPORT = 0x0002, + UPDATEFLAG_HAS_TARGET = 0x0004, + UPDATEFLAG_LOWGUID = 0x0008, + UPDATEFLAG_HIGHGUID = 0x0010, + UPDATEFLAG_LIVING = 0x0020, + UPDATEFLAG_HAS_POSITION = 0x0040, + UPDATEFLAG_VEHICLE = 0x0080, + UPDATEFLAG_POSITION = 0x0100, + UPDATEFLAG_ROTATION = 0x0200 +}; + +class UpdateData +{ + public: + UpdateData(); + + void AddOutOfRangeGUID(std::set<uint64>& guids); + void AddOutOfRangeGUID(const uint64 &guid); + void AddUpdateBlock(const ByteBuffer &block); + bool BuildPacket(WorldPacket *packet); + bool HasData() { return m_blockCount > 0 || !m_outOfRangeGUIDs.empty(); } + void Clear(); + + std::set<uint64> const& GetOutOfRangeGUIDs() const { return m_outOfRangeGUIDs; } + + protected: + uint32 m_blockCount; + std::set<uint64> m_outOfRangeGUIDs; + ByteBuffer m_data; + + void Compress(void* dst, uint32 *dst_size, void* src, int src_size); +}; +#endif + diff --git a/src/server/game/Entities/Object/UpdateFields.h b/src/server/game/Entities/Object/UpdateFields.h new file mode 100644 index 00000000000..5f819375677 --- /dev/null +++ b/src/server/game/Entities/Object/UpdateFields.h @@ -0,0 +1,435 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef _UPDATEFIELDS_AUTO_H +#define _UPDATEFIELDS_AUTO_H + +// Auto generated for version 3, 3, 3, 11723 + +enum EObjectFields +{ + OBJECT_FIELD_GUID = 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + OBJECT_FIELD_TYPE = 0x0002, // Size: 1, Type: INT, Flags: PUBLIC + OBJECT_FIELD_ENTRY = 0x0003, // Size: 1, Type: INT, Flags: PUBLIC + OBJECT_FIELD_SCALE_X = 0x0004, // Size: 1, Type: FLOAT, Flags: PUBLIC + OBJECT_FIELD_PADDING = 0x0005, // Size: 1, Type: INT, Flags: NONE + OBJECT_END = 0x0006, +}; + +enum EItemFields +{ + ITEM_FIELD_OWNER = OBJECT_END + 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + ITEM_FIELD_CONTAINED = OBJECT_END + 0x0002, // Size: 2, Type: LONG, Flags: PUBLIC + ITEM_FIELD_CREATOR = OBJECT_END + 0x0004, // Size: 2, Type: LONG, Flags: PUBLIC + ITEM_FIELD_GIFTCREATOR = OBJECT_END + 0x0006, // Size: 2, Type: LONG, Flags: PUBLIC + ITEM_FIELD_STACK_COUNT = OBJECT_END + 0x0008, // Size: 1, Type: INT, Flags: OWNER, ITEM_OWNER + ITEM_FIELD_DURATION = OBJECT_END + 0x0009, // Size: 1, Type: INT, Flags: OWNER, ITEM_OWNER + ITEM_FIELD_SPELL_CHARGES = OBJECT_END + 0x000A, // Size: 5, Type: INT, Flags: OWNER, ITEM_OWNER + ITEM_FIELD_FLAGS = OBJECT_END + 0x000F, // Size: 1, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_1_1 = OBJECT_END + 0x0010, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_1_3 = OBJECT_END + 0x0012, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_2_1 = OBJECT_END + 0x0013, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_2_3 = OBJECT_END + 0x0015, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_3_1 = OBJECT_END + 0x0016, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_3_3 = OBJECT_END + 0x0018, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_4_1 = OBJECT_END + 0x0019, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_4_3 = OBJECT_END + 0x001B, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_5_1 = OBJECT_END + 0x001C, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_5_3 = OBJECT_END + 0x001E, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_6_1 = OBJECT_END + 0x001F, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_6_3 = OBJECT_END + 0x0021, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_7_1 = OBJECT_END + 0x0022, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_7_3 = OBJECT_END + 0x0024, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_8_1 = OBJECT_END + 0x0025, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_8_3 = OBJECT_END + 0x0027, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_9_1 = OBJECT_END + 0x0028, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_9_3 = OBJECT_END + 0x002A, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_10_1 = OBJECT_END + 0x002B, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_10_3 = OBJECT_END + 0x002D, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_11_1 = OBJECT_END + 0x002E, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_11_3 = OBJECT_END + 0x0030, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_12_1 = OBJECT_END + 0x0031, // Size: 2, Type: INT, Flags: PUBLIC + ITEM_FIELD_ENCHANTMENT_12_3 = OBJECT_END + 0x0033, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + ITEM_FIELD_PROPERTY_SEED = OBJECT_END + 0x0034, // Size: 1, Type: INT, Flags: PUBLIC + ITEM_FIELD_RANDOM_PROPERTIES_ID = OBJECT_END + 0x0035, // Size: 1, Type: INT, Flags: PUBLIC + ITEM_FIELD_DURABILITY = OBJECT_END + 0x0036, // Size: 1, Type: INT, Flags: OWNER, ITEM_OWNER + ITEM_FIELD_MAXDURABILITY = OBJECT_END + 0x0037, // Size: 1, Type: INT, Flags: OWNER, ITEM_OWNER + ITEM_FIELD_CREATE_PLAYED_TIME = OBJECT_END + 0x0038, // Size: 1, Type: INT, Flags: PUBLIC + ITEM_FIELD_PAD = OBJECT_END + 0x0039, // Size: 1, Type: INT, Flags: NONE + ITEM_END = OBJECT_END + 0x003A, +}; + +enum EContainerFields +{ + CONTAINER_FIELD_NUM_SLOTS = ITEM_END + 0x0000, // Size: 1, Type: INT, Flags: PUBLIC + CONTAINER_ALIGN_PAD = ITEM_END + 0x0001, // Size: 1, Type: BYTES, Flags: NONE + CONTAINER_FIELD_SLOT_1 = ITEM_END + 0x0002, // Size: 72, Type: LONG, Flags: PUBLIC + CONTAINER_END = ITEM_END + 0x004A, +}; + +enum EUnitFields +{ + UNIT_FIELD_CHARM = OBJECT_END + 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_FIELD_SUMMON = OBJECT_END + 0x0002, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_FIELD_CRITTER = OBJECT_END + 0x0004, // Size: 2, Type: LONG, Flags: PRIVATE + UNIT_FIELD_CHARMEDBY = OBJECT_END + 0x0006, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_FIELD_SUMMONEDBY = OBJECT_END + 0x0008, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_FIELD_CREATEDBY = OBJECT_END + 0x000A, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_FIELD_TARGET = OBJECT_END + 0x000C, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_FIELD_CHANNEL_OBJECT = OBJECT_END + 0x000E, // Size: 2, Type: LONG, Flags: PUBLIC + UNIT_CHANNEL_SPELL = OBJECT_END + 0x0010, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_BYTES_0 = OBJECT_END + 0x0011, // Size: 1, Type: BYTES, Flags: PUBLIC + UNIT_FIELD_HEALTH = OBJECT_END + 0x0012, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER1 = OBJECT_END + 0x0013, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER2 = OBJECT_END + 0x0014, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER3 = OBJECT_END + 0x0015, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER4 = OBJECT_END + 0x0016, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER5 = OBJECT_END + 0x0017, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER6 = OBJECT_END + 0x0018, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER7 = OBJECT_END + 0x0019, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXHEALTH = OBJECT_END + 0x001A, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER1 = OBJECT_END + 0x001B, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER2 = OBJECT_END + 0x001C, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER3 = OBJECT_END + 0x001D, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER4 = OBJECT_END + 0x001E, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER5 = OBJECT_END + 0x001F, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER6 = OBJECT_END + 0x0020, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MAXPOWER7 = OBJECT_END + 0x0021, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_POWER_REGEN_FLAT_MODIFIER = OBJECT_END + 0x0022, // Size: 7, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_POWER_REGEN_INTERRUPTED_FLAT_MODIFIER = OBJECT_END + 0x0029, // Size: 7, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_LEVEL = OBJECT_END + 0x0030, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_FACTIONTEMPLATE = OBJECT_END + 0x0031, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_VIRTUAL_ITEM_SLOT_ID = OBJECT_END + 0x0032, // Size: 3, Type: INT, Flags: PUBLIC + UNIT_FIELD_FLAGS = OBJECT_END + 0x0035, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_FLAGS_2 = OBJECT_END + 0x0036, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_AURASTATE = OBJECT_END + 0x0037, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_BASEATTACKTIME = OBJECT_END + 0x0038, // Size: 2, Type: INT, Flags: PUBLIC + UNIT_FIELD_RANGEDATTACKTIME = OBJECT_END + 0x003A, // Size: 1, Type: INT, Flags: PRIVATE + UNIT_FIELD_BOUNDINGRADIUS = OBJECT_END + 0x003B, // Size: 1, Type: FLOAT, Flags: PUBLIC + UNIT_FIELD_COMBATREACH = OBJECT_END + 0x003C, // Size: 1, Type: FLOAT, Flags: PUBLIC + UNIT_FIELD_DISPLAYID = OBJECT_END + 0x003D, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_NATIVEDISPLAYID = OBJECT_END + 0x003E, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MOUNTDISPLAYID = OBJECT_END + 0x003F, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_MINDAMAGE = OBJECT_END + 0x0040, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER, PARTY_LEADER + UNIT_FIELD_MAXDAMAGE = OBJECT_END + 0x0041, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER, PARTY_LEADER + UNIT_FIELD_MINOFFHANDDAMAGE = OBJECT_END + 0x0042, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER, PARTY_LEADER + UNIT_FIELD_MAXOFFHANDDAMAGE = OBJECT_END + 0x0043, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER, PARTY_LEADER + UNIT_FIELD_BYTES_1 = OBJECT_END + 0x0044, // Size: 1, Type: BYTES, Flags: PUBLIC + UNIT_FIELD_PETNUMBER = OBJECT_END + 0x0045, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_PET_NAME_TIMESTAMP = OBJECT_END + 0x0046, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_PETEXPERIENCE = OBJECT_END + 0x0047, // Size: 1, Type: INT, Flags: OWNER + UNIT_FIELD_PETNEXTLEVELEXP = OBJECT_END + 0x0048, // Size: 1, Type: INT, Flags: OWNER + UNIT_DYNAMIC_FLAGS = OBJECT_END + 0x0049, // Size: 1, Type: INT, Flags: DYNAMIC + UNIT_MOD_CAST_SPEED = OBJECT_END + 0x004A, // Size: 1, Type: FLOAT, Flags: PUBLIC + UNIT_CREATED_BY_SPELL = OBJECT_END + 0x004B, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_NPC_FLAGS = OBJECT_END + 0x004C, // Size: 1, Type: INT, Flags: DYNAMIC + UNIT_NPC_EMOTESTATE = OBJECT_END + 0x004D, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_STAT0 = OBJECT_END + 0x004E, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_STAT1 = OBJECT_END + 0x004F, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_STAT2 = OBJECT_END + 0x0050, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_STAT3 = OBJECT_END + 0x0051, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_STAT4 = OBJECT_END + 0x0052, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_POSSTAT0 = OBJECT_END + 0x0053, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_POSSTAT1 = OBJECT_END + 0x0054, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_POSSTAT2 = OBJECT_END + 0x0055, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_POSSTAT3 = OBJECT_END + 0x0056, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_POSSTAT4 = OBJECT_END + 0x0057, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_NEGSTAT0 = OBJECT_END + 0x0058, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_NEGSTAT1 = OBJECT_END + 0x0059, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_NEGSTAT2 = OBJECT_END + 0x005A, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_NEGSTAT3 = OBJECT_END + 0x005B, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_NEGSTAT4 = OBJECT_END + 0x005C, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_RESISTANCES = OBJECT_END + 0x005D, // Size: 7, Type: INT, Flags: PRIVATE, OWNER, PARTY_LEADER + UNIT_FIELD_RESISTANCEBUFFMODSPOSITIVE = OBJECT_END + 0x0064, // Size: 7, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE = OBJECT_END + 0x006B, // Size: 7, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_BASE_MANA = OBJECT_END + 0x0072, // Size: 1, Type: INT, Flags: PUBLIC + UNIT_FIELD_BASE_HEALTH = OBJECT_END + 0x0073, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_BYTES_2 = OBJECT_END + 0x0074, // Size: 1, Type: BYTES, Flags: PUBLIC + UNIT_FIELD_ATTACK_POWER = OBJECT_END + 0x0075, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_ATTACK_POWER_MODS = OBJECT_END + 0x0076, // Size: 1, Type: TWO_SHORT, Flags: PRIVATE, OWNER + UNIT_FIELD_ATTACK_POWER_MULTIPLIER = OBJECT_END + 0x0077, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_RANGED_ATTACK_POWER = OBJECT_END + 0x0078, // Size: 1, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_RANGED_ATTACK_POWER_MODS = OBJECT_END + 0x0079, // Size: 1, Type: TWO_SHORT, Flags: PRIVATE, OWNER + UNIT_FIELD_RANGED_ATTACK_POWER_MULTIPLIER = OBJECT_END + 0x007A, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_MINRANGEDDAMAGE = OBJECT_END + 0x007B, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_MAXRANGEDDAMAGE = OBJECT_END + 0x007C, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_POWER_COST_MODIFIER = OBJECT_END + 0x007D, // Size: 7, Type: INT, Flags: PRIVATE, OWNER + UNIT_FIELD_POWER_COST_MULTIPLIER = OBJECT_END + 0x0084, // Size: 7, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_MAXHEALTHMODIFIER = OBJECT_END + 0x008B, // Size: 1, Type: FLOAT, Flags: PRIVATE, OWNER + UNIT_FIELD_HOVERHEIGHT = OBJECT_END + 0x008C, // Size: 1, Type: FLOAT, Flags: PUBLIC + UNIT_FIELD_PADDING = OBJECT_END + 0x008D, // Size: 1, Type: INT, Flags: NONE + UNIT_END = OBJECT_END + 0x008E, + + PLAYER_DUEL_ARBITER = UNIT_END + 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + PLAYER_FLAGS = UNIT_END + 0x0002, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_GUILDID = UNIT_END + 0x0003, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_GUILDRANK = UNIT_END + 0x0004, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_BYTES = UNIT_END + 0x0005, // Size: 1, Type: BYTES, Flags: PUBLIC + PLAYER_BYTES_2 = UNIT_END + 0x0006, // Size: 1, Type: BYTES, Flags: PUBLIC + PLAYER_BYTES_3 = UNIT_END + 0x0007, // Size: 1, Type: BYTES, Flags: PUBLIC + PLAYER_DUEL_TEAM = UNIT_END + 0x0008, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_GUILD_TIMESTAMP = UNIT_END + 0x0009, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_QUEST_LOG_1_1 = UNIT_END + 0x000A, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_1_2 = UNIT_END + 0x000B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_1_3 = UNIT_END + 0x000C, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_1_4 = UNIT_END + 0x000E, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_2_1 = UNIT_END + 0x000F, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_2_2 = UNIT_END + 0x0010, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_2_3 = UNIT_END + 0x0011, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_2_5 = UNIT_END + 0x0013, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_3_1 = UNIT_END + 0x0014, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_3_2 = UNIT_END + 0x0015, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_3_3 = UNIT_END + 0x0016, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_3_5 = UNIT_END + 0x0018, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_4_1 = UNIT_END + 0x0019, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_4_2 = UNIT_END + 0x001A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_4_3 = UNIT_END + 0x001B, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_4_5 = UNIT_END + 0x001D, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_5_1 = UNIT_END + 0x001E, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_5_2 = UNIT_END + 0x001F, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_5_3 = UNIT_END + 0x0020, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_5_5 = UNIT_END + 0x0022, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_6_1 = UNIT_END + 0x0023, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_6_2 = UNIT_END + 0x0024, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_6_3 = UNIT_END + 0x0025, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_6_5 = UNIT_END + 0x0027, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_7_1 = UNIT_END + 0x0028, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_7_2 = UNIT_END + 0x0029, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_7_3 = UNIT_END + 0x002A, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_7_5 = UNIT_END + 0x002C, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_8_1 = UNIT_END + 0x002D, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_8_2 = UNIT_END + 0x002E, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_8_3 = UNIT_END + 0x002F, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_8_5 = UNIT_END + 0x0031, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_9_1 = UNIT_END + 0x0032, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_9_2 = UNIT_END + 0x0033, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_9_3 = UNIT_END + 0x0034, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_9_5 = UNIT_END + 0x0036, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_10_1 = UNIT_END + 0x0037, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_10_2 = UNIT_END + 0x0038, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_10_3 = UNIT_END + 0x0039, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_10_5 = UNIT_END + 0x003B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_11_1 = UNIT_END + 0x003C, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_11_2 = UNIT_END + 0x003D, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_11_3 = UNIT_END + 0x003E, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_11_5 = UNIT_END + 0x0040, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_12_1 = UNIT_END + 0x0041, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_12_2 = UNIT_END + 0x0042, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_12_3 = UNIT_END + 0x0043, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_12_5 = UNIT_END + 0x0045, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_13_1 = UNIT_END + 0x0046, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_13_2 = UNIT_END + 0x0047, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_13_3 = UNIT_END + 0x0048, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_13_5 = UNIT_END + 0x004A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_14_1 = UNIT_END + 0x004B, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_14_2 = UNIT_END + 0x004C, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_14_3 = UNIT_END + 0x004D, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_14_5 = UNIT_END + 0x004F, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_15_1 = UNIT_END + 0x0050, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_15_2 = UNIT_END + 0x0051, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_15_3 = UNIT_END + 0x0052, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_15_5 = UNIT_END + 0x0054, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_16_1 = UNIT_END + 0x0055, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_16_2 = UNIT_END + 0x0056, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_16_3 = UNIT_END + 0x0057, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_16_5 = UNIT_END + 0x0059, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_17_1 = UNIT_END + 0x005A, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_17_2 = UNIT_END + 0x005B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_17_3 = UNIT_END + 0x005C, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_17_5 = UNIT_END + 0x005E, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_18_1 = UNIT_END + 0x005F, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_18_2 = UNIT_END + 0x0060, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_18_3 = UNIT_END + 0x0061, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_18_5 = UNIT_END + 0x0063, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_19_1 = UNIT_END + 0x0064, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_19_2 = UNIT_END + 0x0065, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_19_3 = UNIT_END + 0x0066, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_19_5 = UNIT_END + 0x0068, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_20_1 = UNIT_END + 0x0069, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_20_2 = UNIT_END + 0x006A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_20_3 = UNIT_END + 0x006B, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_20_5 = UNIT_END + 0x006D, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_21_1 = UNIT_END + 0x006E, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_21_2 = UNIT_END + 0x006F, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_21_3 = UNIT_END + 0x0070, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_21_5 = UNIT_END + 0x0072, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_22_1 = UNIT_END + 0x0073, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_22_2 = UNIT_END + 0x0074, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_22_3 = UNIT_END + 0x0075, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_22_5 = UNIT_END + 0x0077, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_23_1 = UNIT_END + 0x0078, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_23_2 = UNIT_END + 0x0079, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_23_3 = UNIT_END + 0x007A, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_23_5 = UNIT_END + 0x007C, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_24_1 = UNIT_END + 0x007D, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_24_2 = UNIT_END + 0x007E, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_24_3 = UNIT_END + 0x007F, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_24_5 = UNIT_END + 0x0081, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_25_1 = UNIT_END + 0x0082, // Size: 1, Type: INT, Flags: PARTY_MEMBER + PLAYER_QUEST_LOG_25_2 = UNIT_END + 0x0083, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_QUEST_LOG_25_3 = UNIT_END + 0x0084, // Size: 2, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_QUEST_LOG_25_5 = UNIT_END + 0x0086, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_VISIBLE_ITEM_1_ENTRYID = UNIT_END + 0x0087, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_1_ENCHANTMENT = UNIT_END + 0x0088, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_2_ENTRYID = UNIT_END + 0x0089, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_2_ENCHANTMENT = UNIT_END + 0x008A, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_3_ENTRYID = UNIT_END + 0x008B, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_3_ENCHANTMENT = UNIT_END + 0x008C, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_4_ENTRYID = UNIT_END + 0x008D, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_4_ENCHANTMENT = UNIT_END + 0x008E, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_5_ENTRYID = UNIT_END + 0x008F, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_5_ENCHANTMENT = UNIT_END + 0x0090, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_6_ENTRYID = UNIT_END + 0x0091, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_6_ENCHANTMENT = UNIT_END + 0x0092, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_7_ENTRYID = UNIT_END + 0x0093, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_7_ENCHANTMENT = UNIT_END + 0x0094, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_8_ENTRYID = UNIT_END + 0x0095, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_8_ENCHANTMENT = UNIT_END + 0x0096, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_9_ENTRYID = UNIT_END + 0x0097, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_9_ENCHANTMENT = UNIT_END + 0x0098, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_10_ENTRYID = UNIT_END + 0x0099, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_10_ENCHANTMENT = UNIT_END + 0x009A, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_11_ENTRYID = UNIT_END + 0x009B, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_11_ENCHANTMENT = UNIT_END + 0x009C, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_12_ENTRYID = UNIT_END + 0x009D, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_12_ENCHANTMENT = UNIT_END + 0x009E, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_13_ENTRYID = UNIT_END + 0x009F, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_13_ENCHANTMENT = UNIT_END + 0x00A0, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_14_ENTRYID = UNIT_END + 0x00A1, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_14_ENCHANTMENT = UNIT_END + 0x00A2, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_15_ENTRYID = UNIT_END + 0x00A3, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_15_ENCHANTMENT = UNIT_END + 0x00A4, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_16_ENTRYID = UNIT_END + 0x00A5, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_16_ENCHANTMENT = UNIT_END + 0x00A6, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_17_ENTRYID = UNIT_END + 0x00A7, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_17_ENCHANTMENT = UNIT_END + 0x00A8, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_18_ENTRYID = UNIT_END + 0x00A9, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_18_ENCHANTMENT = UNIT_END + 0x00AA, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_19_ENTRYID = UNIT_END + 0x00AB, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_VISIBLE_ITEM_19_ENCHANTMENT = UNIT_END + 0x00AC, // Size: 1, Type: TWO_SHORT, Flags: PUBLIC + PLAYER_CHOSEN_TITLE = UNIT_END + 0x00AD, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_FAKE_INEBRIATION = UNIT_END + 0x00AE, // Size: 1, Type: INT, Flags: PUBLIC + PLAYER_FIELD_PAD_0 = UNIT_END + 0x00AF, // Size: 1, Type: INT, Flags: NONE + PLAYER_FIELD_INV_SLOT_HEAD = UNIT_END + 0x00B0, // Size: 46, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_PACK_SLOT_1 = UNIT_END + 0x00DE, // Size: 32, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_BANK_SLOT_1 = UNIT_END + 0x00FE, // Size: 56, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_BANKBAG_SLOT_1 = UNIT_END + 0x0136, // Size: 14, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_VENDORBUYBACK_SLOT_1 = UNIT_END + 0x0144, // Size: 24, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_KEYRING_SLOT_1 = UNIT_END + 0x015C, // Size: 64, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_CURRENCYTOKEN_SLOT_1 = UNIT_END + 0x019C, // Size: 64, Type: LONG, Flags: PRIVATE + PLAYER_FARSIGHT = UNIT_END + 0x01DC, // Size: 2, Type: LONG, Flags: PRIVATE + PLAYER__FIELD_KNOWN_TITLES = UNIT_END + 0x01DE, // Size: 2, Type: LONG, Flags: PRIVATE + PLAYER__FIELD_KNOWN_TITLES1 = UNIT_END + 0x01E0, // Size: 2, Type: LONG, Flags: PRIVATE + PLAYER__FIELD_KNOWN_TITLES2 = UNIT_END + 0x01E2, // Size: 2, Type: LONG, Flags: PRIVATE + PLAYER_FIELD_KNOWN_CURRENCIES = UNIT_END + 0x01E4, // Size: 2, Type: LONG, Flags: PRIVATE + PLAYER_XP = UNIT_END + 0x01E6, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_NEXT_LEVEL_XP = UNIT_END + 0x01E7, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_SKILL_INFO_1_1 = UNIT_END + 0x01E8, // Size: 384, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_CHARACTER_POINTS1 = UNIT_END + 0x0368, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_CHARACTER_POINTS2 = UNIT_END + 0x0369, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_TRACK_CREATURES = UNIT_END + 0x036A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_TRACK_RESOURCES = UNIT_END + 0x036B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_BLOCK_PERCENTAGE = UNIT_END + 0x036C, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_DODGE_PERCENTAGE = UNIT_END + 0x036D, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_PARRY_PERCENTAGE = UNIT_END + 0x036E, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_EXPERTISE = UNIT_END + 0x036F, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_OFFHAND_EXPERTISE = UNIT_END + 0x0370, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_CRIT_PERCENTAGE = UNIT_END + 0x0371, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_RANGED_CRIT_PERCENTAGE = UNIT_END + 0x0372, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_OFFHAND_CRIT_PERCENTAGE = UNIT_END + 0x0373, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_SPELL_CRIT_PERCENTAGE1 = UNIT_END + 0x0374, // Size: 7, Type: FLOAT, Flags: PRIVATE + PLAYER_SHIELD_BLOCK = UNIT_END + 0x037B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_SHIELD_BLOCK_CRIT_PERCENTAGE = UNIT_END + 0x037C, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_EXPLORED_ZONES_1 = UNIT_END + 0x037D, // Size: 128, Type: BYTES, Flags: PRIVATE + PLAYER_REST_STATE_EXPERIENCE = UNIT_END + 0x03FD, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_COINAGE = UNIT_END + 0x03FE, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MOD_DAMAGE_DONE_POS = UNIT_END + 0x03FF, // Size: 7, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MOD_DAMAGE_DONE_NEG = UNIT_END + 0x0406, // Size: 7, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MOD_DAMAGE_DONE_PCT = UNIT_END + 0x040D, // Size: 7, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MOD_HEALING_DONE_POS = UNIT_END + 0x0414, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MOD_HEALING_PCT = UNIT_END + 0x0415, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_FIELD_MOD_HEALING_DONE_PCT = UNIT_END + 0x0416, // Size: 1, Type: FLOAT, Flags: PRIVATE + PLAYER_FIELD_MOD_TARGET_RESISTANCE = UNIT_END + 0x0417, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MOD_TARGET_PHYSICAL_RESISTANCE = UNIT_END + 0x0418, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_BYTES = UNIT_END + 0x0419, // Size: 1, Type: BYTES, Flags: PRIVATE + PLAYER_AMMO_ID = UNIT_END + 0x041A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_SELF_RES_SPELL = UNIT_END + 0x041B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_PVP_MEDALS = UNIT_END + 0x041C, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_BUYBACK_PRICE_1 = UNIT_END + 0x041D, // Size: 12, Type: INT, Flags: PRIVATE + PLAYER_FIELD_BUYBACK_TIMESTAMP_1 = UNIT_END + 0x0429, // Size: 12, Type: INT, Flags: PRIVATE + PLAYER_FIELD_KILLS = UNIT_END + 0x0435, // Size: 1, Type: TWO_SHORT, Flags: PRIVATE + PLAYER_FIELD_TODAY_CONTRIBUTION = UNIT_END + 0x0436, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_YESTERDAY_CONTRIBUTION = UNIT_END + 0x0437, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_LIFETIME_HONORABLE_KILLS = UNIT_END + 0x0438, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_BYTES2 = UNIT_END + 0x0439, // Size: 1, Type: 6, Flags: PRIVATE + PLAYER_FIELD_WATCHED_FACTION_INDEX = UNIT_END + 0x043A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_COMBAT_RATING_1 = UNIT_END + 0x043B, // Size: 25, Type: INT, Flags: PRIVATE + PLAYER_FIELD_ARENA_TEAM_INFO_1_1 = UNIT_END + 0x0454, // Size: 21, Type: INT, Flags: PRIVATE + PLAYER_FIELD_HONOR_CURRENCY = UNIT_END + 0x0469, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_ARENA_CURRENCY = UNIT_END + 0x046A, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_MAX_LEVEL = UNIT_END + 0x046B, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_FIELD_DAILY_QUESTS_1 = UNIT_END + 0x046C, // Size: 25, Type: INT, Flags: PRIVATE + PLAYER_RUNE_REGEN_1 = UNIT_END + 0x0485, // Size: 4, Type: FLOAT, Flags: PRIVATE + PLAYER_NO_REAGENT_COST_1 = UNIT_END + 0x0489, // Size: 3, Type: INT, Flags: PRIVATE + PLAYER_FIELD_GLYPH_SLOTS_1 = UNIT_END + 0x048C, // Size: 6, Type: INT, Flags: PRIVATE + PLAYER_FIELD_GLYPHS_1 = UNIT_END + 0x0492, // Size: 6, Type: INT, Flags: PRIVATE + PLAYER_GLYPHS_ENABLED = UNIT_END + 0x0498, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_PET_SPELL_POWER = UNIT_END + 0x0499, // Size: 1, Type: INT, Flags: PRIVATE + PLAYER_END = UNIT_END + 0x049A, +}; + +enum EGameObjectFields +{ + OBJECT_FIELD_CREATED_BY = OBJECT_END + 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + GAMEOBJECT_DISPLAYID = OBJECT_END + 0x0002, // Size: 1, Type: INT, Flags: PUBLIC + GAMEOBJECT_FLAGS = OBJECT_END + 0x0003, // Size: 1, Type: INT, Flags: PUBLIC + GAMEOBJECT_PARENTROTATION = OBJECT_END + 0x0004, // Size: 4, Type: FLOAT, Flags: PUBLIC + GAMEOBJECT_DYNAMIC = OBJECT_END + 0x0008, // Size: 1, Type: TWO_SHORT, Flags: DYNAMIC + GAMEOBJECT_FACTION = OBJECT_END + 0x0009, // Size: 1, Type: INT, Flags: PUBLIC + GAMEOBJECT_LEVEL = OBJECT_END + 0x000A, // Size: 1, Type: INT, Flags: PUBLIC + GAMEOBJECT_BYTES_1 = OBJECT_END + 0x000B, // Size: 1, Type: BYTES, Flags: PUBLIC + GAMEOBJECT_END = OBJECT_END + 0x000C, +}; + +enum EDynamicObjectFields +{ + DYNAMICOBJECT_CASTER = OBJECT_END + 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + DYNAMICOBJECT_BYTES = OBJECT_END + 0x0002, // Size: 1, Type: BYTES, Flags: PUBLIC + DYNAMICOBJECT_SPELLID = OBJECT_END + 0x0003, // Size: 1, Type: INT, Flags: PUBLIC + DYNAMICOBJECT_RADIUS = OBJECT_END + 0x0004, // Size: 1, Type: FLOAT, Flags: PUBLIC + DYNAMICOBJECT_CASTTIME = OBJECT_END + 0x0005, // Size: 1, Type: INT, Flags: PUBLIC + DYNAMICOBJECT_END = OBJECT_END + 0x0006, +}; + +enum ECorpseFields +{ + CORPSE_FIELD_OWNER = OBJECT_END + 0x0000, // Size: 2, Type: LONG, Flags: PUBLIC + CORPSE_FIELD_PARTY = OBJECT_END + 0x0002, // Size: 2, Type: LONG, Flags: PUBLIC + CORPSE_FIELD_DISPLAY_ID = OBJECT_END + 0x0004, // Size: 1, Type: INT, Flags: PUBLIC + CORPSE_FIELD_ITEM = OBJECT_END + 0x0005, // Size: 19, Type: INT, Flags: PUBLIC + CORPSE_FIELD_BYTES_1 = OBJECT_END + 0x0018, // Size: 1, Type: BYTES, Flags: PUBLIC + CORPSE_FIELD_BYTES_2 = OBJECT_END + 0x0019, // Size: 1, Type: BYTES, Flags: PUBLIC + CORPSE_FIELD_GUILD = OBJECT_END + 0x001A, // Size: 1, Type: INT, Flags: PUBLIC + CORPSE_FIELD_FLAGS = OBJECT_END + 0x001B, // Size: 1, Type: INT, Flags: PUBLIC + CORPSE_FIELD_DYNAMIC_FLAGS = OBJECT_END + 0x001C, // Size: 1, Type: INT, Flags: DYNAMIC + CORPSE_FIELD_PAD = OBJECT_END + 0x001D, // Size: 1, Type: INT, Flags: NONE + CORPSE_END = OBJECT_END + 0x001E, +}; +#endif diff --git a/src/server/game/Entities/Object/UpdateMask.h b/src/server/game/Entities/Object/UpdateMask.h new file mode 100644 index 00000000000..527bec42aa7 --- /dev/null +++ b/src/server/game/Entities/Object/UpdateMask.h @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * Copyright (C) 2008-2010 Trinity <http://www.trinitycore.org/> + * + * 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 + */ + +#ifndef __UPDATEMASK_H +#define __UPDATEMASK_H + +#include "UpdateFields.h" +#include "Errors.h" + +class UpdateMask +{ + public: + UpdateMask() : mCount(0), mBlocks(0), mUpdateMask(0) { } + UpdateMask(const UpdateMask& mask) : mUpdateMask(0) { *this = mask; } + + ~UpdateMask() + { + if (mUpdateMask) + delete [] mUpdateMask; + } + + void SetBit (uint32 index) + { + ((uint8 *)mUpdateMask)[ index >> 3 ] |= 1 << (index & 0x7); + } + + void UnsetBit (uint32 index) + { + ((uint8 *)mUpdateMask)[ index >> 3 ] &= (0xff ^ (1 << (index & 0x7))); + } + + bool GetBit (uint32 index) const + { + return (((uint8 *)mUpdateMask)[ index >> 3 ] & (1 << (index & 0x7))) != 0; + } + + uint32 GetBlockCount() const { return mBlocks; } + uint32 GetLength() const { return mBlocks << 2; } + uint32 GetCount() const { return mCount; } + uint8* GetMask() { return (uint8*)mUpdateMask; } + + void SetCount (uint32 valuesCount) + { + if (mUpdateMask) + delete [] mUpdateMask; + + mCount = valuesCount; + mBlocks = (valuesCount + 31) / 32; + + mUpdateMask = new uint32[mBlocks]; + memset(mUpdateMask, 0, mBlocks << 2); + } + + void Clear() + { + if (mUpdateMask) + memset(mUpdateMask, 0, mBlocks << 2); + } + + UpdateMask& operator = (const UpdateMask& mask) + { + SetCount(mask.mCount); + memcpy(mUpdateMask, mask.mUpdateMask, mBlocks << 2); + + return *this; + } + + void operator &= (const UpdateMask& mask) + { + ASSERT(mask.mCount <= mCount); + for (uint32 i = 0; i < mBlocks; ++i) + mUpdateMask[i] &= mask.mUpdateMask[i]; + } + + void operator |= (const UpdateMask& mask) + { + ASSERT(mask.mCount <= mCount); + for (uint32 i = 0; i < mBlocks; ++i) + mUpdateMask[i] |= mask.mUpdateMask[i]; + } + + UpdateMask operator & (const UpdateMask& mask) const + { + ASSERT(mask.mCount <= mCount); + + UpdateMask newmask; + newmask = *this; + newmask &= mask; + + return newmask; + } + + UpdateMask operator | (const UpdateMask& mask) const + { + ASSERT(mask.mCount <= mCount); + + UpdateMask newmask; + newmask = *this; + newmask |= mask; + + return newmask; + } + + private: + uint32 mCount; + uint32 mBlocks; + uint32 *mUpdateMask; +}; +#endif + |
