Merge branch '6.x' of https://github.com/TrinityCore/TrinityCore into legion
This commit is contained in:
@@ -179,7 +179,7 @@ namespace MMAP
|
||||
dtTileRef tileRef = 0;
|
||||
|
||||
// memory allocated for data is now managed by detour, and will be deallocated when the tile is removed
|
||||
if (dtStatusSucceed(mmap->navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef)))
|
||||
if (dtStatusSucceed(mmap->navMesh->addTile(data, fileHeader.size, 0, 0, &tileRef)))
|
||||
{
|
||||
mmap->loadedTileRefs.insert(std::pair<uint32, dtTileRef>(packedGridPos, tileRef));
|
||||
++loadedTiles;
|
||||
@@ -187,7 +187,10 @@ namespace MMAP
|
||||
|
||||
PhaseChildMapContainer::const_iterator phasedMaps = phaseMapData.find(mapId);
|
||||
if (phasedMaps != phaseMapData.end())
|
||||
{
|
||||
mmap->AddBaseTile(packedGridPos, data, fileHeader, fileHeader.size);
|
||||
LoadPhaseTiles(phasedMaps, x, y);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -279,7 +282,7 @@ namespace MMAP
|
||||
if (dataItr != phasedTileItr->second.end())
|
||||
{
|
||||
TC_LOG_DEBUG("phase", "MMAP:UnloadPhaseTile: Unloaded phased %04u%02i%02i.mmtile for root phase map %u", phaseMapId, x, y, phasedMapData->first);
|
||||
delete dataItr->second->data;
|
||||
dtFree(dataItr->second->data);
|
||||
delete dataItr->second;
|
||||
phasedTileItr->second.erase(dataItr);
|
||||
}
|
||||
@@ -311,7 +314,8 @@ namespace MMAP
|
||||
dtTileRef tileRef = mmap->loadedTileRefs[packedGridPos];
|
||||
|
||||
// unload, and mark as non loaded
|
||||
if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, NULL, NULL)))
|
||||
unsigned char* data = NULL;
|
||||
if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, &data, NULL)))
|
||||
{
|
||||
// this is technically a memory leak
|
||||
// if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used
|
||||
@@ -327,7 +331,12 @@ namespace MMAP
|
||||
|
||||
PhaseChildMapContainer::const_iterator phasedMaps = phaseMapData.find(mapId);
|
||||
if (phasedMaps != phaseMapData.end())
|
||||
{
|
||||
mmap->DeleteBaseTile(packedGridPos);
|
||||
UnloadPhaseTile(phasedMaps, x, y);
|
||||
}
|
||||
else
|
||||
dtFree(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -350,13 +359,19 @@ namespace MMAP
|
||||
{
|
||||
uint32 x = (i->first >> 16);
|
||||
uint32 y = (i->first & 0x0000FFFF);
|
||||
if (dtStatusFailed(mmap->navMesh->removeTile(i->second, NULL, NULL)))
|
||||
unsigned char* data = NULL;
|
||||
if (dtStatusFailed(mmap->navMesh->removeTile(i->second, &data, NULL)))
|
||||
TC_LOG_ERROR("maps", "MMAP:unloadMap: Could not unload %04u%02i%02i.mmtile from navmesh", mapId, x, y);
|
||||
else
|
||||
{
|
||||
PhaseChildMapContainer::const_iterator phasedMaps = phaseMapData.find(mapId);
|
||||
if (phasedMaps != phaseMapData.end())
|
||||
{
|
||||
mmap->DeleteBaseTile(i->first);
|
||||
UnloadPhaseTile(phasedMaps, x, y);
|
||||
}
|
||||
else
|
||||
dtFree(data);
|
||||
--loadedTiles;
|
||||
TC_LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile %04i[%02i, %02i] from %04i", mapId, x, y, mapId);
|
||||
}
|
||||
@@ -443,12 +458,6 @@ namespace MMAP
|
||||
dtFreeNavMeshQuery(i->second);
|
||||
|
||||
dtFreeNavMesh(navMesh);
|
||||
|
||||
for (PhaseTileContainer::iterator i = _baseTiles.begin(); i != _baseTiles.end(); ++i)
|
||||
{
|
||||
delete (*i).second->data;
|
||||
delete (*i).second;
|
||||
}
|
||||
}
|
||||
|
||||
void MMapData::RemoveSwap(PhasedTile* ptile, uint32 swap, uint32 packedXY)
|
||||
@@ -472,9 +481,7 @@ namespace MMAP
|
||||
|
||||
// restore base tile
|
||||
if (dtStatusSucceed(navMesh->addTile(_baseTiles[packedXY]->data, _baseTiles[packedXY]->dataSize, 0, 0, &loadedTileRefs[packedXY])))
|
||||
{
|
||||
TC_LOG_DEBUG("phase", "MMapData::RemoveSwap: Loaded base mmtile %04u[%02i, %02i] into %04i[%02i, %02i]", _mapId, x, y, _mapId, header->x, header->y);
|
||||
}
|
||||
else
|
||||
TC_LOG_ERROR("phase", "MMapData::RemoveSwap: Could not load base %04u%02i%02i.mmtile to navmesh", _mapId, x, y);
|
||||
}
|
||||
@@ -490,7 +497,6 @@ namespace MMAP
|
||||
|
||||
void MMapData::AddSwap(PhasedTile* ptile, uint32 swap, uint32 packedXY)
|
||||
{
|
||||
|
||||
uint32 x = (packedXY >> 16);
|
||||
uint32 y = (packedXY & 0x0000FFFF);
|
||||
|
||||
@@ -505,7 +511,6 @@ namespace MMAP
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dtMeshHeader* header = (dtMeshHeader*)ptile->data;
|
||||
|
||||
const dtMeshTile* oldTile = navMesh->getTileByRef(loadedTileRefs[packedXY]);
|
||||
@@ -520,30 +525,19 @@ namespace MMAP
|
||||
header->x = oldTile->header->x;
|
||||
header->y = oldTile->header->y;
|
||||
|
||||
// the removed tile's data
|
||||
PhasedTile* pt = new PhasedTile();
|
||||
// remove old tile
|
||||
if (dtStatusFailed(navMesh->removeTile(loadedTileRefs[packedXY], &pt->data, &pt->dataSize)))
|
||||
{
|
||||
if (dtStatusFailed(navMesh->removeTile(loadedTileRefs[packedXY], NULL, NULL)))
|
||||
TC_LOG_ERROR("phase", "MMapData::AddSwap: Could not unload %04u%02i%02i.mmtile from navmesh", _mapId, x, y);
|
||||
delete pt;
|
||||
}
|
||||
else
|
||||
{
|
||||
TC_LOG_DEBUG("phase", "MMapData::AddSwap: Unloaded %04u%02i%02i.mmtile from navmesh", _mapId, x, y);
|
||||
|
||||
// store the removed data first time, this is the origonal, non-phased tile
|
||||
if (_baseTiles.find(packedXY) == _baseTiles.end())
|
||||
_baseTiles[packedXY] = pt;
|
||||
|
||||
_activeSwaps.insert(swap);
|
||||
loadedPhasedTiles[swap].insert(packedXY);
|
||||
|
||||
// add new swapped tile
|
||||
if (dtStatusSucceed(navMesh->addTile(ptile->data, ptile->fileHeader.size, 0, 0, &loadedTileRefs[packedXY])))
|
||||
{
|
||||
TC_LOG_DEBUG("phase", "MMapData::AddSwap: Loaded phased mmtile %04u[%02i, %02i] into %04i[%02i, %02i]", swap, x, y, _mapId, header->x, header->y);
|
||||
}
|
||||
else
|
||||
TC_LOG_ERROR("phase", "MMapData::AddSwap: Could not load %04u%02i%02i.mmtile to navmesh", swap, x, y);
|
||||
}
|
||||
@@ -576,4 +570,28 @@ namespace MMAP
|
||||
|
||||
return navMesh;
|
||||
}
|
||||
|
||||
void MMapData::AddBaseTile(uint32 packedGridPos, unsigned char* data, MmapTileHeader const& fileHeader, int32 dataSize)
|
||||
{
|
||||
auto itr = _baseTiles.find(packedGridPos);
|
||||
if (itr == _baseTiles.end())
|
||||
{
|
||||
PhasedTile* pt = new PhasedTile();
|
||||
pt->data = data;
|
||||
pt->fileHeader = fileHeader;
|
||||
pt->dataSize = dataSize;
|
||||
_baseTiles[packedGridPos] = pt;
|
||||
}
|
||||
}
|
||||
|
||||
void MMapData::DeleteBaseTile(uint32 packedGridPos)
|
||||
{
|
||||
auto itr = _baseTiles.find(packedGridPos);
|
||||
if (itr != _baseTiles.end())
|
||||
{
|
||||
dtFree(itr->second->data);
|
||||
delete itr->second;
|
||||
_baseTiles.erase(itr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ namespace MMAP
|
||||
|
||||
dtNavMesh* GetNavMesh(TerrainSet swaps);
|
||||
|
||||
void AddBaseTile(uint32 packedGridPos, unsigned char* data, MmapTileHeader const& fileHeader, int32 dataSize);
|
||||
void DeleteBaseTile(uint32 packedGridPos);
|
||||
|
||||
// we have to use single dtNavMeshQuery for every instance, since those are not thread safe
|
||||
NavMeshQuerySet navMeshQueries; // instanceId to query
|
||||
|
||||
|
||||
@@ -243,28 +243,64 @@ bool SpellTargetSelector::operator()(Unit const* target) const
|
||||
return false;
|
||||
|
||||
// copypasta from Spell::CheckRange
|
||||
uint32 range_type = _spellInfo->RangeEntry ? _spellInfo->RangeEntry->Flags : 0;
|
||||
float max_range = _caster->GetSpellMaxRangeForTarget(target, _spellInfo);
|
||||
float min_range = _caster->GetSpellMinRangeForTarget(target, _spellInfo);
|
||||
float minRange = 0.0f;
|
||||
float maxRange = 0.0f;
|
||||
float rangeMod = 0.0f;
|
||||
if (_spellInfo->RangeEntry)
|
||||
{
|
||||
if (_spellInfo->RangeEntry->Flags & SPELL_RANGE_MELEE)
|
||||
{
|
||||
rangeMod = _caster->GetCombatReach() + 4.0f / 3.0f;
|
||||
if (target)
|
||||
rangeMod += target->GetCombatReach();
|
||||
else
|
||||
rangeMod += _caster->GetCombatReach();
|
||||
|
||||
rangeMod = std::max(rangeMod, NOMINAL_MELEE_RANGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
float meleeRange = 0.0f;
|
||||
if (_spellInfo->RangeEntry->Flags & SPELL_RANGE_RANGED)
|
||||
{
|
||||
meleeRange = _caster->GetCombatReach() + 4.0f / 3.0f;
|
||||
if (target)
|
||||
meleeRange += target->GetCombatReach();
|
||||
else
|
||||
meleeRange += _caster->GetCombatReach();
|
||||
|
||||
meleeRange = std::max(meleeRange, NOMINAL_MELEE_RANGE);
|
||||
}
|
||||
|
||||
minRange = _caster->GetSpellMinRangeForTarget(target, _spellInfo) + meleeRange;
|
||||
maxRange = _caster->GetSpellMaxRangeForTarget(target, _spellInfo);
|
||||
|
||||
if (target)
|
||||
{
|
||||
rangeMod = _caster->GetCombatReach();
|
||||
rangeMod += target->GetCombatReach();
|
||||
|
||||
if (minRange > 0.0f && !(_spellInfo->RangeEntry->Flags & SPELL_RANGE_RANGED))
|
||||
minRange += rangeMod;
|
||||
}
|
||||
}
|
||||
|
||||
if (target && _caster->isMoving() && target->isMoving() && !_caster->IsWalking() && !target->IsWalking() &&
|
||||
(_spellInfo->RangeEntry->Flags & SPELL_RANGE_MELEE || target->GetTypeId() == TYPEID_PLAYER))
|
||||
rangeMod += 5.0f / 3.0f;
|
||||
}
|
||||
|
||||
maxRange += rangeMod;
|
||||
|
||||
minRange *= minRange;
|
||||
maxRange *= maxRange;
|
||||
|
||||
if (target && target != _caster)
|
||||
{
|
||||
if (range_type == SPELL_RANGE_MELEE)
|
||||
{
|
||||
// Because of lag, we can not check too strictly here.
|
||||
if (!_caster->IsWithinMeleeRange(target, max_range))
|
||||
return false;
|
||||
}
|
||||
else if (!_caster->IsWithinCombatRange(target, max_range))
|
||||
if (_caster->GetExactDistSq(target) > maxRange)
|
||||
return false;
|
||||
|
||||
if (range_type == SPELL_RANGE_RANGED)
|
||||
{
|
||||
if (_caster->IsWithinMeleeRange(target))
|
||||
return false;
|
||||
}
|
||||
else if (min_range && _caster->IsWithinCombatRange(target, min_range)) // skip this check if min_range = 0
|
||||
if (minRange > 0.0f && _caster->GetExactDistSq(target) < minRange)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "Common.h"
|
||||
#include "Corpse.h"
|
||||
#include "Player.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
#include "Common.h"
|
||||
#include "Unit.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "ItemTemplate.h"
|
||||
#include "LootMgr.h"
|
||||
#include "DatabaseEnv.h"
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
#include "Common.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "Opcodes.h"
|
||||
#include "World.h"
|
||||
#include "ObjectAccessor.h"
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "UpdateFieldFlags.h"
|
||||
#include "World.h"
|
||||
#include "Transport.h"
|
||||
#include <boost/dynamic_bitset.hpp>
|
||||
|
||||
GameObject::GameObject() : WorldObject(false), MapObject(),
|
||||
m_model(NULL), m_goValue(), m_AI(NULL), _animKitId(0)
|
||||
@@ -2302,23 +2303,24 @@ void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* t
|
||||
bool forcedFlags = GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.usegrouplootrules && HasLootRecipient();
|
||||
bool targetIsGM = target->IsGameMaster();
|
||||
|
||||
ByteBuffer fieldBuffer;
|
||||
|
||||
UpdateMask updateMask;
|
||||
updateMask.SetCount(m_valuesCount);
|
||||
boost::dynamic_bitset<uint32> updateMask(m_valuesCount);
|
||||
|
||||
uint32* flags = GameObjectUpdateFieldFlags;
|
||||
uint32 visibleFlag = UF_FLAG_PUBLIC;
|
||||
if (GetOwnerGUID() == target->GetGUID())
|
||||
visibleFlag |= UF_FLAG_OWNER;
|
||||
|
||||
*data << uint8(updateMask.num_blocks());
|
||||
std::size_t maskPos = data->wpos();
|
||||
data->resize(data->size() + updateMask.num_blocks() * sizeof(uint32));
|
||||
|
||||
for (uint16 index = 0; index < m_valuesCount; ++index)
|
||||
{
|
||||
if (_fieldNotifyFlags & flags[index] ||
|
||||
((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
|
||||
((updateType == UPDATETYPE_VALUES ? _changesMask[index] : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
|
||||
(index == GAMEOBJECT_FLAGS && forcedFlags))
|
||||
{
|
||||
updateMask.SetBit(index);
|
||||
updateMask.set(index);
|
||||
|
||||
if (index == OBJECT_DYNAMIC_FLAGS)
|
||||
{
|
||||
@@ -2355,8 +2357,8 @@ void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* t
|
||||
break;
|
||||
}
|
||||
|
||||
fieldBuffer << uint16(dynFlags);
|
||||
fieldBuffer << int16(pathProgress);
|
||||
*data << uint16(dynFlags);
|
||||
*data << int16(pathProgress);
|
||||
}
|
||||
else if (index == GAMEOBJECT_FLAGS)
|
||||
{
|
||||
@@ -2365,14 +2367,14 @@ void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* t
|
||||
if (GetGOInfo()->chest.usegrouplootrules && !IsLootAllowedFor(target))
|
||||
goFlags |= GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE;
|
||||
|
||||
fieldBuffer << goFlags;
|
||||
*data << goFlags;
|
||||
}
|
||||
else if (index == GAMEOBJECT_LEVEL)
|
||||
{
|
||||
if (isStoppableTransport)
|
||||
fieldBuffer << uint32(m_goValue.Transport.PathProgress);
|
||||
*data << uint32(m_goValue.Transport.PathProgress);
|
||||
else
|
||||
fieldBuffer << m_uint32Values[index];
|
||||
*data << m_uint32Values[index];
|
||||
}
|
||||
else if (index == GAMEOBJECT_BYTES_1)
|
||||
{
|
||||
@@ -2386,16 +2388,14 @@ void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* t
|
||||
}
|
||||
}
|
||||
|
||||
fieldBuffer << bytes1;
|
||||
*data << bytes1;
|
||||
}
|
||||
else
|
||||
fieldBuffer << m_uint32Values[index]; // other cases
|
||||
*data << m_uint32Values[index]; // other cases
|
||||
}
|
||||
}
|
||||
|
||||
*data << uint8(updateMask.GetBlockCount());
|
||||
updateMask.AppendToPacket(data);
|
||||
data->append(fieldBuffer);
|
||||
boost::to_block_range(updateMask, reinterpret_cast<uint32*>(data->contents() + maskPos));
|
||||
}
|
||||
|
||||
void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = NULL*/) const
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "TradeData.h"
|
||||
#include "GameTables.h"
|
||||
#include "CollectionMgr.h"
|
||||
#include <boost/dynamic_bitset.hpp>
|
||||
|
||||
void AddItemsSetItem(Player* player, Item* item)
|
||||
{
|
||||
@@ -283,7 +284,6 @@ Item::Item()
|
||||
m_paidMoney = 0;
|
||||
m_paidExtendedCost = 0;
|
||||
|
||||
memset(_modifiers, 0, sizeof(_modifiers));
|
||||
memset(&_bonusData, 0, sizeof(_bonusData));
|
||||
}
|
||||
|
||||
@@ -1199,59 +1199,58 @@ void Item::BuildDynamicValuesUpdate(uint8 updateType, ByteBuffer* data, Player*
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
ByteBuffer fieldBuffer;
|
||||
UpdateMask updateMask;
|
||||
updateMask.SetCount(_dynamicValuesCount);
|
||||
boost::dynamic_bitset<uint32> updateMask(_dynamicValuesCount);
|
||||
|
||||
uint32* flags = nullptr;
|
||||
uint32 visibleFlag = GetDynamicUpdateFieldData(target, flags);
|
||||
|
||||
*data << uint8(updateMask.num_blocks());
|
||||
std::size_t maskPos = data->wpos();
|
||||
data->resize(data->size() + updateMask.num_blocks() * sizeof(uint32));
|
||||
|
||||
for (uint16 index = 0; index < _dynamicValuesCount; ++index)
|
||||
{
|
||||
ByteBuffer buffer;
|
||||
std::vector<uint32> const& values = _dynamicValues[index];
|
||||
if (_fieldNotifyFlags & flags[index] ||
|
||||
((updateType == UPDATETYPE_VALUES ? _dynamicChangesMask.GetBit(index) : !values.empty()) && (flags[index] & visibleFlag)) ||
|
||||
(index == ITEM_DYNAMIC_FIELD_MODIFIERS && (updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(ITEM_FIELD_MODIFIERS_MASK) : GetUInt32Value(ITEM_FIELD_MODIFIERS_MASK) != 0)))
|
||||
((updateType == UPDATETYPE_VALUES ? _dynamicChangesMask[index] : !values.empty()) && (flags[index] & visibleFlag)))
|
||||
{
|
||||
updateMask.SetBit(index);
|
||||
updateMask.set(index);
|
||||
|
||||
UpdateMask arrayMask;
|
||||
boost::dynamic_bitset<uint32> arrayMask(values.size());
|
||||
*data << uint16(arrayMask.num_blocks());
|
||||
std::size_t fieldMaskPos = data->wpos();
|
||||
data->resize(data->size() + arrayMask.num_blocks() * sizeof(uint32));
|
||||
if (index != ITEM_DYNAMIC_FIELD_MODIFIERS)
|
||||
{
|
||||
arrayMask.SetCount(values.size());
|
||||
for (std::size_t v = 0; v < values.size(); ++v)
|
||||
{
|
||||
if (updateType == UPDATETYPE_VALUES ? _dynamicChangesArrayMask[index].GetBit(v) : values[v])
|
||||
if (updateType == UPDATETYPE_VALUES ? _dynamicChangesArrayMask[index][v] : values[v])
|
||||
{
|
||||
arrayMask.SetBit(v);
|
||||
buffer << uint32(values[v]);
|
||||
arrayMask.set(v);
|
||||
*data << uint32(values[v]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32 count = 0;
|
||||
arrayMask.SetCount(MAX_ITEM_MODIFIERS);
|
||||
for (uint32 v = 0; v < MAX_ITEM_MODIFIERS; ++v)
|
||||
// in case of ITEM_DYNAMIC_FIELD_MODIFIERS it is ITEM_FIELD_MODIFIERS_MASK that controls index of each value, not updatemask
|
||||
// so we just have to write this starting from 0 index
|
||||
for (std::size_t v = 0, m = 0; v < values.size(); ++v)
|
||||
{
|
||||
if (uint32 modifier = _modifiers[v])
|
||||
if (values[v] || _dynamicChangesArrayMask[index][v])
|
||||
{
|
||||
arrayMask.SetBit(count++);
|
||||
buffer << uint32(modifier);
|
||||
arrayMask.set(m++);
|
||||
*data << uint32(values[v]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fieldBuffer << uint16(arrayMask.GetBlockCount());
|
||||
arrayMask.AppendToPacket(&fieldBuffer);
|
||||
fieldBuffer.append(buffer);
|
||||
boost::to_block_range(arrayMask, reinterpret_cast<uint32*>(data->contents() + fieldMaskPos));
|
||||
}
|
||||
}
|
||||
|
||||
*data << uint8(updateMask.GetBlockCount());
|
||||
updateMask.AppendToPacket(data);
|
||||
data->append(fieldBuffer);
|
||||
boost::to_block_range(updateMask, reinterpret_cast<uint32*>(data->contents() + maskPos));
|
||||
}
|
||||
|
||||
void Item::AddToObjectUpdate()
|
||||
@@ -1954,16 +1953,15 @@ ItemModifiedAppearanceEntry const* Item::GetItemModifiedAppearance() const
|
||||
return sDB2Manager.GetItemModifiedAppearance(GetEntry(), _bonusData.AppearanceModID);
|
||||
}
|
||||
|
||||
uint32 Item::GetModifier(ItemModifier modifier) const
|
||||
{
|
||||
return GetDynamicValues(ITEM_DYNAMIC_FIELD_MODIFIERS)[modifier];
|
||||
}
|
||||
|
||||
void Item::SetModifier(ItemModifier modifier, uint32 value)
|
||||
{
|
||||
if (_modifiers[modifier] != value)
|
||||
{
|
||||
_dynamicChangesMask.SetBit(ITEM_DYNAMIC_FIELD_MODIFIERS);
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
|
||||
_modifiers[modifier] = value;
|
||||
ApplyModFlag(ITEM_FIELD_MODIFIERS_MASK, 1 << modifier, value != 0);
|
||||
SetDynamicValue(ITEM_DYNAMIC_FIELD_MODIFIERS, modifier, value);
|
||||
}
|
||||
|
||||
uint32 Item::GetVisibleEntry(Player const* owner) const
|
||||
|
||||
@@ -461,7 +461,7 @@ class TC_GAME_API Item : public Object
|
||||
|
||||
static uint32 GetSellPrice(ItemTemplate const* proto, bool& success);
|
||||
|
||||
uint32 GetModifier(ItemModifier modifier) const { return _modifiers[modifier]; }
|
||||
uint32 GetModifier(ItemModifier modifier) const;
|
||||
void SetModifier(ItemModifier modifier, uint32 value);
|
||||
|
||||
protected:
|
||||
@@ -479,6 +479,5 @@ class TC_GAME_API Item : public Object
|
||||
uint32 m_paidMoney;
|
||||
uint32 m_paidExtendedCost;
|
||||
GuidSet allowedGUIDs;
|
||||
uint32 _modifiers[MAX_ITEM_MODIFIERS];
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -212,19 +212,20 @@ void ItemTemplate::GetDamage(uint32 itemLevel, float& minDamage, float& maxDamag
|
||||
maxDamage = floor(float(avgDamage * (GetStatScalingFactor() * 0.5f + 1.0f) + 0.5f));
|
||||
}
|
||||
|
||||
bool ItemTemplate::CanWinForPlayer(Player const* player) const
|
||||
bool ItemTemplate::IsUsableBySpecialization(Player const* player) const
|
||||
{
|
||||
std::unordered_set<uint32> const& specs = Specializations[player->getLevel() > 40];
|
||||
if (specs.empty())
|
||||
return true;
|
||||
|
||||
uint32 spec = player->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID);
|
||||
if (!spec)
|
||||
spec = player->GetDefaultSpecId();
|
||||
|
||||
if (!spec)
|
||||
ChrSpecializationEntry const* chrSpecialization = sChrSpecializationStore.LookupEntry(spec);
|
||||
if (!chrSpecialization)
|
||||
return false;
|
||||
|
||||
auto itr = specs.find(spec);
|
||||
return itr != specs.end();
|
||||
return Specializations[player->getLevel() > 40].test(CalculateItemSpecBit(chrSpecialization));
|
||||
}
|
||||
|
||||
std::size_t ItemTemplate::CalculateItemSpecBit(ChrSpecializationEntry const* spec)
|
||||
{
|
||||
return (spec->ClassID - 1) * MAX_SPECIALIZATIONS + spec->OrderIndex;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include "DB2Structure.h"
|
||||
#include "SharedDefines.h"
|
||||
#include <bitset>
|
||||
|
||||
enum ItemModType
|
||||
{
|
||||
@@ -696,6 +697,7 @@ const uint32 MaxItemSubclassValues[MAX_ITEM_CLASS] =
|
||||
#define MAX_ITEM_LEVEL 1000
|
||||
|
||||
class Player;
|
||||
struct ChrSpecializationEntry;
|
||||
|
||||
struct TC_GAME_API ItemTemplate
|
||||
{
|
||||
@@ -769,7 +771,7 @@ struct TC_GAME_API ItemTemplate
|
||||
uint32 MaxMoneyLoot;
|
||||
uint32 FlagsCu;
|
||||
float SpellPPMRate;
|
||||
std::unordered_set<uint32> Specializations[2]; // one set for 1-40 level range and another for 41-100
|
||||
std::bitset<MAX_CLASSES * MAX_SPECIALIZATIONS> Specializations[2]; // one set for 1-40 level range and another for 41-100
|
||||
uint32 ItemSpecClassMask;
|
||||
|
||||
// helpers
|
||||
@@ -799,7 +801,8 @@ struct TC_GAME_API ItemTemplate
|
||||
char const* GetDefaultLocaleName() const;
|
||||
uint32 GetArmor(uint32 itemLevel) const;
|
||||
void GetDamage(uint32 itemLevel, float& minDamage, float& maxDamage) const;
|
||||
bool CanWinForPlayer(Player const* player) const;
|
||||
bool IsUsableBySpecialization(Player const* player) const;
|
||||
static std::size_t CalculateItemSpecBit(ChrSpecializationEntry const* spec);
|
||||
};
|
||||
|
||||
// Benchmarked: Faster than std::map (insert/find)
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "Vehicle.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "UpdateData.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "Util.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Transport.h"
|
||||
@@ -46,6 +45,7 @@
|
||||
#include "BattlefieldMgr.h"
|
||||
#include "GameObjectPackets.h"
|
||||
#include "MiscPackets.h"
|
||||
#include <boost/dynamic_bitset.hpp>
|
||||
|
||||
Object::Object()
|
||||
{
|
||||
@@ -108,15 +108,14 @@ Object::~Object()
|
||||
void Object::_InitValues()
|
||||
{
|
||||
m_uint32Values = new uint32[m_valuesCount];
|
||||
memset(m_uint32Values, 0, m_valuesCount*sizeof(uint32));
|
||||
memset(m_uint32Values, 0, m_valuesCount * sizeof(uint32));
|
||||
|
||||
_changesMask.SetCount(m_valuesCount);
|
||||
|
||||
_dynamicChangesMask.SetCount(_dynamicValuesCount);
|
||||
_changesMask.resize(m_valuesCount);
|
||||
_dynamicChangesMask.resize(_dynamicValuesCount);
|
||||
if (_dynamicValuesCount)
|
||||
{
|
||||
_dynamicValues = new std::vector<uint32>[_dynamicValuesCount];
|
||||
_dynamicChangesArrayMask = new UpdateMask[_dynamicValuesCount];
|
||||
_dynamicChangesArrayMask = new std::vector<uint8>[_dynamicValuesCount];
|
||||
}
|
||||
|
||||
m_objectUpdated = false;
|
||||
@@ -765,27 +764,27 @@ void Object::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* targe
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
ByteBuffer fieldBuffer;
|
||||
UpdateMask updateMask;
|
||||
updateMask.SetCount(m_valuesCount);
|
||||
boost::dynamic_bitset<uint32> updateMask(m_valuesCount);
|
||||
|
||||
uint32* flags = NULL;
|
||||
uint32 visibleFlag = GetUpdateFieldData(target, flags);
|
||||
ASSERT(flags);
|
||||
|
||||
*data << uint8(updateMask.num_blocks());
|
||||
std::size_t maskPos = data->wpos();
|
||||
data->resize(data->size() + updateMask.num_blocks() * sizeof(uint32));
|
||||
|
||||
for (uint16 index = 0; index < m_valuesCount; ++index)
|
||||
{
|
||||
if (_fieldNotifyFlags & flags[index] ||
|
||||
((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag)))
|
||||
((updateType == UPDATETYPE_VALUES ? _changesMask[index] : m_uint32Values[index]) && (flags[index] & visibleFlag)))
|
||||
{
|
||||
updateMask.SetBit(index);
|
||||
fieldBuffer << m_uint32Values[index];
|
||||
updateMask.set(index);
|
||||
*data << m_uint32Values[index];
|
||||
}
|
||||
}
|
||||
|
||||
*data << uint8(updateMask.GetBlockCount());
|
||||
updateMask.AppendToPacket(data);
|
||||
data->append(fieldBuffer);
|
||||
boost::to_block_range(updateMask, reinterpret_cast<uint32*>(data->contents() + maskPos));
|
||||
}
|
||||
|
||||
void Object::BuildDynamicValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const
|
||||
@@ -793,42 +792,41 @@ void Object::BuildDynamicValuesUpdate(uint8 updateType, ByteBuffer* data, Player
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
ByteBuffer fieldBuffer;
|
||||
UpdateMask updateMask;
|
||||
updateMask.SetCount(_dynamicValuesCount);
|
||||
boost::dynamic_bitset<uint32> updateMask(_dynamicValuesCount);
|
||||
|
||||
uint32* flags = nullptr;
|
||||
uint32 visibleFlag = GetDynamicUpdateFieldData(target, flags);
|
||||
|
||||
*data << uint8(updateMask.num_blocks());
|
||||
std::size_t maskPos = data->wpos();
|
||||
data->resize(data->size() + updateMask.num_blocks() * sizeof(uint32));
|
||||
|
||||
for (uint16 index = 0; index < _dynamicValuesCount; ++index)
|
||||
{
|
||||
ByteBuffer buffer;
|
||||
std::vector<uint32> const& values = _dynamicValues[index];
|
||||
if (_fieldNotifyFlags & flags[index] ||
|
||||
((updateType == UPDATETYPE_VALUES ? _dynamicChangesMask.GetBit(index) : !values.empty()) && (flags[index] & visibleFlag)))
|
||||
((updateType == UPDATETYPE_VALUES ? _dynamicChangesMask[index] : !values.empty()) && (flags[index] & visibleFlag)))
|
||||
{
|
||||
updateMask.SetBit(index);
|
||||
updateMask.set(index);
|
||||
|
||||
UpdateMask arrayMask;
|
||||
arrayMask.SetCount(values.size());
|
||||
boost::dynamic_bitset<uint32> arrayMask(values.size());
|
||||
*data << uint16(arrayMask.num_blocks());
|
||||
std::size_t fieldMaskPos = data->wpos();
|
||||
data->resize(data->size() + arrayMask.num_blocks() * sizeof(uint32));
|
||||
for (std::size_t v = 0; v < values.size(); ++v)
|
||||
{
|
||||
if (updateType == UPDATETYPE_VALUES ? _dynamicChangesArrayMask[index].GetBit(v) : values[v])
|
||||
if (updateType == UPDATETYPE_VALUES ? _dynamicChangesArrayMask[index][v] : values[v])
|
||||
{
|
||||
arrayMask.SetBit(v);
|
||||
buffer << uint32(values[v]);
|
||||
arrayMask.set(v);
|
||||
*data << uint32(values[v]);
|
||||
}
|
||||
}
|
||||
|
||||
fieldBuffer << uint16(arrayMask.GetBlockCount());
|
||||
arrayMask.AppendToPacket(&fieldBuffer);
|
||||
fieldBuffer.append(buffer);
|
||||
boost::to_block_range(arrayMask, reinterpret_cast<uint32*>(data->contents() + fieldMaskPos));
|
||||
}
|
||||
}
|
||||
|
||||
*data << uint8(updateMask.GetBlockCount());
|
||||
updateMask.AppendToPacket(data);
|
||||
data->append(fieldBuffer);
|
||||
boost::to_block_range(updateMask, reinterpret_cast<uint32*>(data->contents() + maskPos));
|
||||
}
|
||||
|
||||
void Object::AddToObjectUpdateIfNeeded()
|
||||
@@ -842,10 +840,10 @@ void Object::AddToObjectUpdateIfNeeded()
|
||||
|
||||
void Object::ClearUpdateMask(bool remove)
|
||||
{
|
||||
_changesMask.Clear();
|
||||
_dynamicChangesMask.Clear();
|
||||
memset(_changesMask.data(), 0, _changesMask.size());
|
||||
memset(_dynamicChangesMask.data(), 0, _dynamicChangesMask.size());
|
||||
for (uint32 i = 0; i < _dynamicValuesCount; ++i)
|
||||
_dynamicChangesArrayMask[i].Clear();
|
||||
memset(_dynamicChangesArrayMask[i].data(), 0, _dynamicChangesArrayMask[i].size());
|
||||
|
||||
if (m_objectUpdated)
|
||||
{
|
||||
@@ -990,7 +988,7 @@ void Object::_LoadIntoDataField(std::string const& data, uint32 startOffset, uin
|
||||
for (uint32 index = 0; index < count; ++index)
|
||||
{
|
||||
m_uint32Values[startOffset + index] = atoul(tokens[index]);
|
||||
_changesMask.SetBit(startOffset + index);
|
||||
_changesMask[startOffset + index] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1001,7 +999,7 @@ void Object::SetInt32Value(uint16 index, int32 value)
|
||||
if (m_int32Values[index] != value)
|
||||
{
|
||||
m_int32Values[index] = value;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1014,7 +1012,7 @@ void Object::SetUInt32Value(uint16 index, uint32 value)
|
||||
if (m_uint32Values[index] != value)
|
||||
{
|
||||
m_uint32Values[index] = value;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1025,7 +1023,7 @@ void Object::UpdateUInt32Value(uint16 index, uint32 value)
|
||||
ASSERT(index < m_valuesCount || PrintIndexError(index, true));
|
||||
|
||||
m_uint32Values[index] = value;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
}
|
||||
|
||||
void Object::SetUInt64Value(uint16 index, uint64 value)
|
||||
@@ -1035,8 +1033,8 @@ void Object::SetUInt64Value(uint16 index, uint64 value)
|
||||
{
|
||||
m_uint32Values[index] = PAIR64_LOPART(value);
|
||||
m_uint32Values[index + 1] = PAIR64_HIPART(value);
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask.SetBit(index + 1);
|
||||
_changesMask[index] = 1;
|
||||
_changesMask[index + 1] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1048,10 +1046,10 @@ bool Object::AddGuidValue(uint16 index, ObjectGuid const& value)
|
||||
if (!value.IsEmpty() && ((ObjectGuid*)&(m_uint32Values[index]))->IsEmpty())
|
||||
{
|
||||
*((ObjectGuid*)&(m_uint32Values[index])) = value;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask.SetBit(index + 1);
|
||||
_changesMask.SetBit(index + 2);
|
||||
_changesMask.SetBit(index + 3);
|
||||
_changesMask[index] = 1;
|
||||
_changesMask[index + 1] = 1;
|
||||
_changesMask[index + 2] = 1;
|
||||
_changesMask[index + 3] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
return true;
|
||||
@@ -1066,10 +1064,10 @@ bool Object::RemoveGuidValue(uint16 index, ObjectGuid const& value)
|
||||
if (!value.IsEmpty() && *((ObjectGuid*)&(m_uint32Values[index])) == value)
|
||||
{
|
||||
((ObjectGuid*)&(m_uint32Values[index]))->Clear();
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask.SetBit(index + 1);
|
||||
_changesMask.SetBit(index + 2);
|
||||
_changesMask.SetBit(index + 3);
|
||||
_changesMask[index] = 1;
|
||||
_changesMask[index + 1] = 1;
|
||||
_changesMask[index + 2] = 1;
|
||||
_changesMask[index + 3] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
return true;
|
||||
@@ -1085,7 +1083,7 @@ void Object::SetFloatValue(uint16 index, float value)
|
||||
if (m_floatValues[index] != value)
|
||||
{
|
||||
m_floatValues[index] = value;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1105,7 +1103,7 @@ void Object::SetByteValue(uint16 index, uint8 offset, uint8 value)
|
||||
{
|
||||
m_uint32Values[index] &= ~uint32(uint32(0xFF) << (offset * 8));
|
||||
m_uint32Values[index] |= uint32(uint32(value) << (offset * 8));
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1125,7 +1123,7 @@ void Object::SetUInt16Value(uint16 index, uint8 offset, uint16 value)
|
||||
{
|
||||
m_uint32Values[index] &= ~uint32(uint32(0xFFFF) << (offset * 16));
|
||||
m_uint32Values[index] |= uint32(uint32(value) << (offset * 16));
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1137,10 +1135,10 @@ void Object::SetGuidValue(uint16 index, ObjectGuid const& value)
|
||||
if (*((ObjectGuid*)&(m_uint32Values[index])) != value)
|
||||
{
|
||||
*((ObjectGuid*)&(m_uint32Values[index])) = value;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask.SetBit(index + 1);
|
||||
_changesMask.SetBit(index + 2);
|
||||
_changesMask.SetBit(index + 3);
|
||||
_changesMask[index] = 1;
|
||||
_changesMask[index + 1] = 1;
|
||||
_changesMask[index + 2] = 1;
|
||||
_changesMask[index + 3] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1219,7 +1217,7 @@ void Object::SetFlag(uint16 index, uint32 newFlag)
|
||||
if (oldval != newval)
|
||||
{
|
||||
m_uint32Values[index] = newval;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1236,7 +1234,7 @@ void Object::RemoveFlag(uint16 index, uint32 oldFlag)
|
||||
if (oldval != newval)
|
||||
{
|
||||
m_uint32Values[index] = newval;
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1276,7 +1274,7 @@ void Object::SetByteFlag(uint16 index, uint8 offset, uint8 newFlag)
|
||||
if (!(uint8(m_uint32Values[index] >> (offset * 8)) & newFlag))
|
||||
{
|
||||
m_uint32Values[index] |= uint32(uint32(newFlag) << (offset * 8));
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1295,7 +1293,7 @@ void Object::RemoveByteFlag(uint16 index, uint8 offset, uint8 oldFlag)
|
||||
if (uint8(m_uint32Values[index] >> (offset * 8)) & oldFlag)
|
||||
{
|
||||
m_uint32Values[index] &= ~uint32(uint32(oldFlag) << (offset * 8));
|
||||
_changesMask.SetBit(index);
|
||||
_changesMask[index] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1365,21 +1363,7 @@ uint32 Object::GetDynamicValue(uint16 index, uint16 offset) const
|
||||
void Object::AddDynamicValue(uint16 index, uint32 value)
|
||||
{
|
||||
ASSERT(index < _dynamicValuesCount || PrintIndexError(index, false));
|
||||
|
||||
std::vector<uint32>& values = _dynamicValues[index];
|
||||
UpdateMask& mask = _dynamicChangesArrayMask[index];
|
||||
|
||||
_dynamicChangesMask.SetBit(index);
|
||||
if (values.size() >= values.capacity())
|
||||
values.reserve(values.capacity() + 32);
|
||||
|
||||
values.push_back(value);
|
||||
if (mask.GetCount() < values.size())
|
||||
mask.AddBlock();
|
||||
|
||||
mask.SetBit(values.size() - 1);
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
SetDynamicValue(index, _dynamicValues[index].size(), value);
|
||||
}
|
||||
|
||||
void Object::RemoveDynamicValue(uint16 index, uint32 value)
|
||||
@@ -1393,8 +1377,8 @@ void Object::RemoveDynamicValue(uint16 index, uint32 value)
|
||||
if (values[i] == value)
|
||||
{
|
||||
values[i] = 0;
|
||||
_dynamicChangesMask.SetBit(index);
|
||||
_dynamicChangesArrayMask[index].SetBit(i);
|
||||
_dynamicChangesMask[index] = 1;
|
||||
_dynamicChangesArrayMask[index][i] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1408,8 +1392,8 @@ void Object::ClearDynamicValue(uint16 index)
|
||||
if (!_dynamicValues[index].empty())
|
||||
{
|
||||
_dynamicValues[index].clear();
|
||||
_dynamicChangesMask.SetBit(index);
|
||||
_dynamicChangesArrayMask[index].SetCount(0);
|
||||
_dynamicChangesMask[index] = 1;
|
||||
_dynamicChangesArrayMask[index].clear();
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -1420,14 +1404,17 @@ void Object::SetDynamicValue(uint16 index, uint16 offset, uint32 value)
|
||||
ASSERT(index < _dynamicValuesCount || PrintIndexError(index, false));
|
||||
|
||||
std::vector<uint32>& values = _dynamicValues[index];
|
||||
if (values.size() <= offset)
|
||||
values.resize(offset + 1);
|
||||
|
||||
ASSERT(offset < values.size());
|
||||
if (_dynamicChangesArrayMask[index].size() <= offset)
|
||||
_dynamicChangesArrayMask[index].resize((offset / 32 + 1) * 32);
|
||||
|
||||
if (values[offset] != value)
|
||||
{
|
||||
values[offset] = value;
|
||||
_dynamicChangesMask.SetBit(index);
|
||||
_dynamicChangesArrayMask[index].SetBit(offset);
|
||||
_dynamicChangesMask[index] = 1;
|
||||
_dynamicChangesArrayMask[index][offset] = 1;
|
||||
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
@@ -2171,7 +2158,7 @@ bool WorldObject::CanDetectStealthOf(WorldObject const* obj, bool checkAlert) co
|
||||
|
||||
void Object::ForceValuesUpdateAtIndex(uint32 i)
|
||||
{
|
||||
_changesMask.SetBit(i);
|
||||
_changesMask[i] = 1;
|
||||
AddToObjectUpdateIfNeeded();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
#include "Common.h"
|
||||
#include "Position.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "GridReference.h"
|
||||
#include "ObjectDefines.h"
|
||||
#include "Map.h"
|
||||
#include "UpdateFields.h"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
@@ -239,9 +239,9 @@ class TC_GAME_API Object
|
||||
|
||||
std::vector<uint32>* _dynamicValues;
|
||||
|
||||
UpdateMask _changesMask;
|
||||
UpdateMask _dynamicChangesMask;
|
||||
UpdateMask* _dynamicChangesArrayMask;
|
||||
std::vector<uint8> _changesMask;
|
||||
std::vector<uint8> _dynamicChangesMask;
|
||||
std::vector<uint8>* _dynamicChangesArrayMask;
|
||||
|
||||
uint16 m_valuesCount;
|
||||
uint16 _dynamicValuesCount;
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2016 TrinityCore <http://www.trinitycore.org/>
|
||||
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __UPDATEMASK_H
|
||||
#define __UPDATEMASK_H
|
||||
|
||||
#include "UpdateFields.h"
|
||||
#include "Errors.h"
|
||||
#include "ByteBuffer.h"
|
||||
|
||||
class UpdateMask
|
||||
{
|
||||
public:
|
||||
/// Type representing how client reads update mask
|
||||
typedef uint32 ClientUpdateMaskType;
|
||||
|
||||
enum UpdateMaskCount
|
||||
{
|
||||
CLIENT_UPDATE_MASK_BITS = sizeof(ClientUpdateMaskType) * 8,
|
||||
};
|
||||
|
||||
UpdateMask() : _fieldCount(0), _blockCount(0), _bits(NULL) { }
|
||||
|
||||
UpdateMask(UpdateMask const& right) : _bits(NULL)
|
||||
{
|
||||
SetCount(right.GetCount());
|
||||
memcpy(_bits, right._bits, sizeof(uint8) * _blockCount * 32);
|
||||
}
|
||||
|
||||
~UpdateMask() { delete[] _bits; }
|
||||
|
||||
void SetBit(uint32 index) { _bits[index] = 1; }
|
||||
void UnsetBit(uint32 index) { _bits[index] = 0; }
|
||||
bool GetBit(uint32 index) const { return _bits[index] != 0; }
|
||||
|
||||
void AppendToPacket(ByteBuffer* data)
|
||||
{
|
||||
for (uint32 i = 0; i < GetBlockCount(); ++i)
|
||||
{
|
||||
ClientUpdateMaskType maskPart = 0;
|
||||
for (uint32 j = 0; j < CLIENT_UPDATE_MASK_BITS; ++j)
|
||||
if (_bits[CLIENT_UPDATE_MASK_BITS * i + j])
|
||||
maskPart |= 1 << j;
|
||||
|
||||
*data << maskPart;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 GetBlockCount() const { return _blockCount; }
|
||||
uint32 GetCount() const { return _fieldCount; }
|
||||
|
||||
void SetCount(uint32 valuesCount)
|
||||
{
|
||||
delete[] _bits;
|
||||
|
||||
_fieldCount = valuesCount;
|
||||
_blockCount = (valuesCount + CLIENT_UPDATE_MASK_BITS - 1) / CLIENT_UPDATE_MASK_BITS;
|
||||
|
||||
if (!valuesCount)
|
||||
{
|
||||
_bits = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
_bits = new uint8[_blockCount * CLIENT_UPDATE_MASK_BITS];
|
||||
memset(_bits, 0, sizeof(uint8) * _blockCount * CLIENT_UPDATE_MASK_BITS);
|
||||
}
|
||||
|
||||
void AddBlock()
|
||||
{
|
||||
uint8* curr = _bits;
|
||||
_fieldCount += CLIENT_UPDATE_MASK_BITS;
|
||||
++_blockCount;
|
||||
|
||||
_bits = new uint8[_blockCount * CLIENT_UPDATE_MASK_BITS];
|
||||
memset(&_bits[(_blockCount - 1) * CLIENT_UPDATE_MASK_BITS], 0, CLIENT_UPDATE_MASK_BITS);
|
||||
if (curr)
|
||||
{
|
||||
memcpy(_bits, curr, sizeof(uint8) * (_blockCount - 1) * CLIENT_UPDATE_MASK_BITS);
|
||||
delete[] curr;
|
||||
}
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
if (_bits)
|
||||
memset(_bits, 0, sizeof(uint8) * _blockCount * CLIENT_UPDATE_MASK_BITS);
|
||||
}
|
||||
|
||||
UpdateMask& operator=(UpdateMask const& right)
|
||||
{
|
||||
if (this == &right)
|
||||
return *this;
|
||||
|
||||
SetCount(right.GetCount());
|
||||
memcpy(_bits, right._bits, sizeof(uint8) * _blockCount * CLIENT_UPDATE_MASK_BITS);
|
||||
return *this;
|
||||
}
|
||||
|
||||
UpdateMask& operator&=(UpdateMask const& right)
|
||||
{
|
||||
ASSERT(right.GetCount() <= GetCount());
|
||||
for (uint32 i = 0; i < _fieldCount; ++i)
|
||||
_bits[i] &= right._bits[i];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
UpdateMask& operator|=(UpdateMask const& right)
|
||||
{
|
||||
ASSERT(right.GetCount() <= GetCount());
|
||||
for (uint32 i = 0; i < _fieldCount; ++i)
|
||||
_bits[i] |= right._bits[i];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
UpdateMask operator|(UpdateMask const& right)
|
||||
{
|
||||
UpdateMask ret(*this);
|
||||
ret |= right;
|
||||
return ret;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _fieldCount;
|
||||
uint32 _blockCount;
|
||||
uint8* _bits;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -93,7 +93,6 @@
|
||||
#include "Transport.h"
|
||||
#include "UpdateData.h"
|
||||
#include "UpdateFieldFlags.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "Util.h"
|
||||
#include "VehiclePackets.h"
|
||||
#include "Weather.h"
|
||||
@@ -131,7 +130,6 @@ Player::Player(WorldSession* session) : Unit(true)
|
||||
m_ExtraFlags = 0;
|
||||
|
||||
m_spellModTakingSpell = nullptr;
|
||||
//m_pad = 0;
|
||||
|
||||
// players always accept
|
||||
if (!GetSession()->HasPermission(rbac::RBAC_PERM_CAN_FILTER_WHISPERS))
|
||||
@@ -1768,6 +1766,15 @@ void Player::RemoveFromWorld()
|
||||
}
|
||||
}
|
||||
|
||||
void Player::SetObjectScale(float scale)
|
||||
{
|
||||
Unit::SetObjectScale(scale);
|
||||
SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, scale * DEFAULT_WORLD_OBJECT_SIZE);
|
||||
SetFloatValue(UNIT_FIELD_COMBATREACH, scale * DEFAULT_COMBAT_REACH);
|
||||
if (IsInWorld())
|
||||
SendMovementSetCollisionHeight(scale * GetCollisionHeight(IsMounted()));
|
||||
}
|
||||
|
||||
bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const
|
||||
{
|
||||
SpellEffectInfo const* effect = spellInfo->GetEffect(GetMap()->GetDifficultyID(), index);
|
||||
@@ -14100,7 +14107,7 @@ bool Player::CanRewardQuest(Quest const* quest, uint32 reward, bool msg)
|
||||
|
||||
if (ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID))
|
||||
{
|
||||
if (rewardProto->CanWinForPlayer(this))
|
||||
if (rewardProto->IsUsableBySpecialization(this))
|
||||
{
|
||||
InventoryResult res = CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemCount);
|
||||
if (res != EQUIP_ERR_OK)
|
||||
@@ -14321,7 +14328,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver,
|
||||
|
||||
if (ItemTemplate const* rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID))
|
||||
{
|
||||
if (rewardProto->CanWinForPlayer(this))
|
||||
if (rewardProto->IsUsableBySpecialization(this))
|
||||
{
|
||||
ItemPosCountVec dest;
|
||||
if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, questPackageItem->ItemID, questPackageItem->ItemCount) == EQUIP_ERR_OK)
|
||||
|
||||
@@ -1196,12 +1196,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
void AddToWorld() override;
|
||||
void RemoveFromWorld() override;
|
||||
|
||||
void SetObjectScale(float scale) override
|
||||
{
|
||||
Unit::SetObjectScale(scale);
|
||||
SetFloatValue(UNIT_FIELD_BOUNDINGRADIUS, scale * DEFAULT_WORLD_OBJECT_SIZE);
|
||||
SetFloatValue(UNIT_FIELD_COMBATREACH, scale * DEFAULT_COMBAT_REACH);
|
||||
}
|
||||
void SetObjectScale(float scale) override;
|
||||
|
||||
bool TeleportTo(uint32 mapid, float x, float y, float z, float orientation, uint32 options = 0);
|
||||
bool TeleportTo(WorldLocation const &loc, uint32 options = 0);
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
#include "VehiclePackets.h"
|
||||
#include "LootPackets.h"
|
||||
#include "PartyPackets.h"
|
||||
|
||||
#include <boost/dynamic_bitset.hpp>
|
||||
#include <cmath>
|
||||
|
||||
float baseMoveSpeed[MAX_MOVE_TYPE] =
|
||||
@@ -500,7 +500,7 @@ bool Unit::IsWithinCombatRange(const Unit* obj, float dist2compare) const
|
||||
return distsq < maxdist * maxdist;
|
||||
}
|
||||
|
||||
bool Unit::IsWithinMeleeRange(const Unit* obj, float dist) const
|
||||
bool Unit::IsWithinMeleeRange(Unit const* obj) const
|
||||
{
|
||||
if (!obj || !IsInMap(obj) || !IsInPhase(obj))
|
||||
return false;
|
||||
@@ -510,10 +510,9 @@ bool Unit::IsWithinMeleeRange(const Unit* obj, float dist) const
|
||||
float dz = GetPositionZMinusOffset() - obj->GetPositionZMinusOffset();
|
||||
float distsq = dx*dx + dy*dy + dz*dz;
|
||||
|
||||
float sizefactor = GetCombatReach() + obj->GetCombatReach() + 4.0f / 3.0f;
|
||||
float maxdist = dist + sizefactor;
|
||||
float maxdist = GetCombatReach() + obj->GetCombatReach() + 4.0f / 3.0f;
|
||||
|
||||
return distsq < maxdist * maxdist;
|
||||
return distsq <= maxdist * maxdist;
|
||||
}
|
||||
|
||||
void Unit::GetRandomContactPoint(const Unit* obj, float &x, float &y, float &z, float distance2dMin, float distance2dMax) const
|
||||
@@ -15670,11 +15669,7 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
ByteBuffer fieldBuffer;
|
||||
UpdateMask updateMask;
|
||||
|
||||
uint32 valCount = m_valuesCount;
|
||||
|
||||
uint32* flags = UnitUpdateFieldFlags;
|
||||
uint32 visibleFlag = UF_FLAG_PUBLIC;
|
||||
|
||||
@@ -15683,7 +15678,7 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
else if (GetTypeId() == TYPEID_PLAYER)
|
||||
valCount = PLAYER_FIELD_END_NOT_SELF;
|
||||
|
||||
updateMask.SetCount(valCount);
|
||||
boost::dynamic_bitset<uint32> updateMask(valCount);
|
||||
|
||||
Player* plr = GetCharmerOrOwnerPlayerOrPlayerItself();
|
||||
if (GetOwnerGUID() == target->GetGUID())
|
||||
@@ -15697,14 +15692,19 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
visibleFlag |= UF_FLAG_PARTY_MEMBER;
|
||||
|
||||
Creature const* creature = ToCreature();
|
||||
|
||||
*data << uint8(updateMask.num_blocks());
|
||||
std::size_t maskPos = data->wpos();
|
||||
data->resize(data->size() + updateMask.num_blocks() * sizeof(uint32));
|
||||
|
||||
for (uint16 index = 0; index < valCount; ++index)
|
||||
{
|
||||
if (_fieldNotifyFlags & flags[index] ||
|
||||
((flags[index] & visibleFlag) & UF_FLAG_SPECIAL_INFO) ||
|
||||
((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
|
||||
((updateType == UPDATETYPE_VALUES ? _changesMask[index] : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
|
||||
(index == UNIT_FIELD_AURASTATE && HasFlag(UNIT_FIELD_AURASTATE, PER_CASTER_AURA_STATE_MASK)))
|
||||
{
|
||||
updateMask.SetBit(index);
|
||||
updateMask.set(index);
|
||||
|
||||
if (index == UNIT_NPC_FLAGS)
|
||||
{
|
||||
@@ -15714,18 +15714,18 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
if (!target->CanSeeSpellClickOn(creature))
|
||||
appendValue &= ~UNIT_NPC_FLAG_SPELLCLICK;
|
||||
|
||||
fieldBuffer << uint32(appendValue);
|
||||
*data << uint32(appendValue);
|
||||
}
|
||||
else if (index == UNIT_FIELD_AURASTATE)
|
||||
{
|
||||
// Check per caster aura states to not enable using a spell in client if specified aura is not by target
|
||||
fieldBuffer << BuildAuraStateUpdateForTarget(target);
|
||||
*data << 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
|
||||
fieldBuffer << uint32(m_floatValues[index] < 0 ? 0 : m_floatValues[index]);
|
||||
*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_NEGSTAT && index < UNIT_FIELD_NEGSTAT + MAX_STATS) ||
|
||||
@@ -15733,7 +15733,7 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
(index >= UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE && index < (UNIT_FIELD_RESISTANCEBUFFMODSNEGATIVE + MAX_SPELL_SCHOOL)) ||
|
||||
(index >= UNIT_FIELD_POSSTAT && index < UNIT_FIELD_POSSTAT + MAX_STATS))
|
||||
{
|
||||
fieldBuffer << uint32(m_floatValues[index]);
|
||||
*data << uint32(m_floatValues[index]);
|
||||
}
|
||||
// Gamemasters should be always able to select units - remove not selectable flag
|
||||
else if (index == UNIT_FIELD_FLAGS)
|
||||
@@ -15742,7 +15742,7 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
if (target->IsGameMaster())
|
||||
appendValue &= ~UNIT_FLAG_NOT_SELECTABLE;
|
||||
|
||||
fieldBuffer << uint32(appendValue);
|
||||
*data << uint32(appendValue);
|
||||
}
|
||||
// use modelid_a if not gm, _h if gm for CREATURE_FLAG_EXTRA_TRIGGER creatures
|
||||
else if (index == UNIT_FIELD_DISPLAYID)
|
||||
@@ -15767,7 +15767,7 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
displayId = cinfo->GetFirstVisibleModel();
|
||||
}
|
||||
|
||||
fieldBuffer << uint32(displayId);
|
||||
*data << uint32(displayId);
|
||||
}
|
||||
// hide lootable animation for unallowed players
|
||||
else if (index == OBJECT_DYNAMIC_FLAGS)
|
||||
@@ -15788,7 +15788,7 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
if (!HasAuraTypeWithCaster(SPELL_AURA_MOD_STALKED, target->GetGUID()))
|
||||
dynamicFlags &= ~UNIT_DYNFLAG_TRACK_UNIT;
|
||||
|
||||
fieldBuffer << dynamicFlags;
|
||||
*data << dynamicFlags;
|
||||
}
|
||||
// FG: pretend that OTHER players in own group are friendly ("blue")
|
||||
else if (index == UNIT_FIELD_BYTES_2 || index == UNIT_FIELD_FACTIONTEMPLATE)
|
||||
@@ -15801,28 +15801,26 @@ void Unit::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target)
|
||||
{
|
||||
if (index == UNIT_FIELD_BYTES_2)
|
||||
// Allow targetting opposite faction in party when enabled in config
|
||||
fieldBuffer << (m_uint32Values[UNIT_FIELD_BYTES_2] & ((UNIT_BYTE2_FLAG_SANCTUARY /*| UNIT_BYTE2_FLAG_AURAS | UNIT_BYTE2_FLAG_UNK5*/) << 8)); // this flag is at uint8 offset 1 !!
|
||||
*data << (m_uint32Values[UNIT_FIELD_BYTES_2] & ((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)
|
||||
fieldBuffer << uint32(target->getFaction());
|
||||
*data << uint32(target->getFaction());
|
||||
}
|
||||
else
|
||||
fieldBuffer << m_uint32Values[index];
|
||||
*data << m_uint32Values[index];
|
||||
}
|
||||
else
|
||||
fieldBuffer << m_uint32Values[index];
|
||||
*data << m_uint32Values[index];
|
||||
}
|
||||
else
|
||||
{
|
||||
// send in current format (float as float, uint32 as uint32)
|
||||
fieldBuffer << m_uint32Values[index];
|
||||
*data << m_uint32Values[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*data << uint8(updateMask.GetBlockCount());
|
||||
updateMask.AppendToPacket(data);
|
||||
data->append(fieldBuffer);
|
||||
boost::to_block_range(updateMask, reinterpret_cast<uint32*>(data->contents() + maskPos));
|
||||
}
|
||||
|
||||
void Unit::DestroyForPlayer(Player* target) const
|
||||
@@ -16019,16 +16017,15 @@ void Unit::Whisper(uint32 textId, Player* target, bool isBossWhisper /*= false*/
|
||||
SpellInfo const* Unit::GetCastSpellInfo(SpellInfo const* spellInfo) const
|
||||
{
|
||||
Unit::AuraEffectList swaps = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS);
|
||||
Unit::AuraEffectList const& swaps2 = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_2);
|
||||
Unit::AuraEffectList const& swaps2 = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED);
|
||||
if (!swaps2.empty())
|
||||
swaps.insert(swaps.end(), swaps2.begin(), swaps2.end());
|
||||
|
||||
for (AuraEffect const* auraEffect : swaps)
|
||||
{
|
||||
if ((!auraEffect->GetSpellEffectInfo()->SpellClassMask && uint32(auraEffect->GetMiscValue()) == spellInfo->Id) ||
|
||||
(auraEffect->GetSpellEffectInfo()->SpellClassMask && auraEffect->IsAffectingSpell(spellInfo)))
|
||||
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount()))
|
||||
return newInfo;
|
||||
if (uint32(auraEffect->GetMiscValue()) == spellInfo->Id || auraEffect->IsAffectingSpell(spellInfo))
|
||||
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount()))
|
||||
return newInfo;
|
||||
}
|
||||
|
||||
return spellInfo;
|
||||
|
||||
@@ -1375,7 +1375,7 @@ class TC_GAME_API Unit : public WorldObject
|
||||
virtual void SetCanDualWield(bool value) { m_canDualWield = value; }
|
||||
float GetCombatReach() const { return m_floatValues[UNIT_FIELD_COMBATREACH]; }
|
||||
bool IsWithinCombatRange(const Unit* obj, float dist2compare) const;
|
||||
bool IsWithinMeleeRange(const Unit* obj, float dist = MELEE_RANGE) const;
|
||||
bool IsWithinMeleeRange(Unit const* obj) const;
|
||||
void GetRandomContactPoint(const Unit* target, float &x, float &y, float &z, float distance2dMin, float distance2dMax) const;
|
||||
uint32 m_extraAttacks;
|
||||
bool m_canDualWield;
|
||||
@@ -2131,7 +2131,7 @@ class TC_GAME_API Unit : public WorldObject
|
||||
|
||||
void AddExtraUnitMovementFlag(uint16 f) { m_movementInfo.AddExtraMovementFlag(f); }
|
||||
void RemoveExtraUnitMovementFlag(uint16 f) { m_movementInfo.RemoveExtraMovementFlag(f); }
|
||||
uint16 HasExtraUnitMovementFlag(uint16 f) const { return m_movementInfo.HasExtraMovementFlag(f); }
|
||||
bool HasExtraUnitMovementFlag(uint16 f) const { return m_movementInfo.HasExtraMovementFlag(f); }
|
||||
uint16 GetExtraUnitMovementFlags() const { return m_movementInfo.GetExtraMovementFlags(); }
|
||||
void SetExtraUnitMovementFlags(uint16 f) { m_movementInfo.SetExtraMovementFlags(f); }
|
||||
bool IsSplineEnabled() const;
|
||||
|
||||
@@ -43,7 +43,6 @@
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellScript.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "Util.h"
|
||||
#include "Vehicle.h"
|
||||
#include "World.h"
|
||||
@@ -2671,9 +2670,10 @@ void ObjectMgr::LoadItemTemplates()
|
||||
if (std::vector<ItemSpecOverrideEntry const*> const* itemSpecOverrides = sDB2Manager.GetItemSpecOverrides(sparse->ID))
|
||||
{
|
||||
for (ItemSpecOverrideEntry const* itemSpecOverride : *itemSpecOverrides)
|
||||
itemTemplate.Specializations[0].insert(itemSpecOverride->SpecID);
|
||||
if (ChrSpecializationEntry const* specialization = sChrSpecializationStore.LookupEntry(itemSpecOverride->SpecID))
|
||||
itemTemplate.Specializations[0].set(ItemTemplate::CalculateItemSpecBit(specialization));
|
||||
|
||||
itemTemplate.Specializations[1] = itemTemplate.Specializations[0];
|
||||
itemTemplate.Specializations[1] |= itemTemplate.Specializations[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2704,7 +2704,7 @@ void ObjectMgr::LoadItemTemplates()
|
||||
if ((1 << (specialization->ClassID - 1)) & sparse->AllowableClass)
|
||||
{
|
||||
itemTemplate.ItemSpecClassMask |= 1 << (specialization->ClassID - 1);
|
||||
itemTemplate.Specializations[itemSpec->MaxLevel > 40].insert(itemSpec->SpecID);
|
||||
itemTemplate.Specializations[itemSpec->MaxLevel > 40].set(ItemTemplate::CalculateItemSpecBit(specialization));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "AuctionHouseMgr.h"
|
||||
#include "Log.h"
|
||||
#include "Language.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "Util.h"
|
||||
#include "AccountMgr.h"
|
||||
#include "AuctionHousePackets.h"
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
#include "SharedDefines.h"
|
||||
#include "SocialMgr.h"
|
||||
#include "SystemPackets.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
#include "SpellMgr.h"
|
||||
#include "Player.h"
|
||||
#include "GossipDef.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "ObjectAccessor.h"
|
||||
#include "Creature.h"
|
||||
#include "Pet.h"
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "World.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "NPCHandler.h"
|
||||
#include "MapManager.h"
|
||||
#include "QueryPackets.h"
|
||||
|
||||
@@ -285,7 +285,7 @@ void WorldSession::HandleQuestgiverChooseRewardOpcode(WorldPackets::Quest::Quest
|
||||
rewardProto = sObjectMgr->GetItemTemplate(questPackageItem->ItemID);
|
||||
if (rewardProto)
|
||||
{
|
||||
if (rewardProto->CanWinForPlayer(_player))
|
||||
if (rewardProto->IsUsableBySpecialization(_player))
|
||||
{
|
||||
itemValid = true;
|
||||
break;
|
||||
|
||||
@@ -664,6 +664,14 @@ void InstanceScript::SendEncounterEnd()
|
||||
instance->SendToPlayers(encounterEndMessage.Write());
|
||||
}
|
||||
|
||||
void InstanceScript::SendBossKillCredit(uint32 encounterId)
|
||||
{
|
||||
WorldPackets::Instance::BossKillCredit bossKillCreditMessage;
|
||||
bossKillCreditMessage.DungeonEncounterID = encounterId;
|
||||
|
||||
instance->SendToPlayers(bossKillCreditMessage.Write());
|
||||
}
|
||||
|
||||
void InstanceScript::UpdateEncounterState(EncounterCreditType type, uint32 creditEntry, Unit* /*source*/)
|
||||
{
|
||||
DungeonEncounterList const* encounters = sObjectMgr->GetDungeonEncounterList(instance->GetId(), instance->GetDifficultyID());
|
||||
|
||||
@@ -250,6 +250,8 @@ class TC_GAME_API InstanceScript : public ZoneScript
|
||||
void SendEncounterStart(uint32 inCombatResCount = 0, uint32 maxInCombatResCount = 0, uint32 inCombatResChargeRecovery = 0, uint32 nextCombatResChargeTime = 0);
|
||||
void SendEncounterEnd();
|
||||
|
||||
void SendBossKillCredit(uint32 encounterId);
|
||||
|
||||
virtual void FillInitialWorldStates(WorldPackets::WorldState::InitWorldStates& /*packet*/) { }
|
||||
|
||||
// ReCheck PhaseTemplate related conditions
|
||||
|
||||
@@ -18,6 +18,32 @@
|
||||
#include "AuthenticationPackets.h"
|
||||
#include "HmacHash.h"
|
||||
|
||||
bool WorldPackets::Auth::EarlyProcessClientPacket::ReadNoThrow()
|
||||
{
|
||||
try
|
||||
{
|
||||
Read();
|
||||
return true;
|
||||
}
|
||||
catch (ByteBufferPositionException const& ex)
|
||||
{
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void WorldPackets::Auth::Ping::Read()
|
||||
{
|
||||
_worldPacket >> Serial;
|
||||
_worldPacket >> Latency;
|
||||
}
|
||||
|
||||
const WorldPacket* WorldPackets::Auth::Pong::Write()
|
||||
{
|
||||
_worldPacket << uint32(Serial);
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* WorldPackets::Auth::AuthChallenge::Write()
|
||||
{
|
||||
_worldPacket.append(DosChallenge, 8);
|
||||
|
||||
@@ -31,6 +31,36 @@ namespace WorldPackets
|
||||
{
|
||||
namespace Auth
|
||||
{
|
||||
class EarlyProcessClientPacket : public ClientPacket
|
||||
{
|
||||
public:
|
||||
EarlyProcessClientPacket(OpcodeClient opcode, WorldPacket&& packet) : ClientPacket(opcode, std::move(packet)) { }
|
||||
|
||||
bool ReadNoThrow();
|
||||
};
|
||||
|
||||
class Ping final : public EarlyProcessClientPacket
|
||||
{
|
||||
public:
|
||||
Ping(WorldPacket&& packet) : EarlyProcessClientPacket(CMSG_PING, std::move(packet)) { }
|
||||
|
||||
uint32 Serial = 0;
|
||||
uint32 Latency = 0;
|
||||
|
||||
private:
|
||||
void Read();
|
||||
};
|
||||
|
||||
class Pong final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
Pong(uint32 serial) : ServerPacket(SMSG_PONG, 4), Serial(serial) { }
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
|
||||
uint32 Serial = 0;
|
||||
};
|
||||
|
||||
class AuthChallenge final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
@@ -43,19 +73,17 @@ namespace WorldPackets
|
||||
uint8 DosZeroBits = 0;
|
||||
};
|
||||
|
||||
class AuthSession final : public ClientPacket
|
||||
class AuthSession final : public EarlyProcessClientPacket
|
||||
{
|
||||
public:
|
||||
static uint32 const DigestLength = 24;
|
||||
|
||||
AuthSession(WorldPacket&& packet) : ClientPacket(CMSG_AUTH_SESSION, std::move(packet))
|
||||
AuthSession(WorldPacket&& packet) : EarlyProcessClientPacket(CMSG_AUTH_SESSION, std::move(packet))
|
||||
{
|
||||
LocalChallenge.fill(0);
|
||||
Digest.fill(0);
|
||||
}
|
||||
|
||||
void Read() override;
|
||||
|
||||
uint16 Build = 0;
|
||||
int8 BuildType = 0;
|
||||
uint32 RegionID = 0;
|
||||
@@ -66,6 +94,9 @@ namespace WorldPackets
|
||||
uint64 DosResponse = 0;
|
||||
std::string RealmJoinTicket;
|
||||
bool UseIPv6 = false;
|
||||
|
||||
private:
|
||||
void Read() override;
|
||||
};
|
||||
|
||||
class AuthResponse final : public ServerPacket
|
||||
@@ -172,23 +203,24 @@ namespace WorldPackets
|
||||
BigNumber iqmp;
|
||||
};
|
||||
|
||||
class AuthContinuedSession final : public ClientPacket
|
||||
class AuthContinuedSession final : public EarlyProcessClientPacket
|
||||
{
|
||||
public:
|
||||
static uint32 const DigestLength = 24;
|
||||
|
||||
AuthContinuedSession(WorldPacket&& packet) : ClientPacket(CMSG_AUTH_CONTINUED_SESSION, std::move(packet))
|
||||
AuthContinuedSession(WorldPacket&& packet) : EarlyProcessClientPacket(CMSG_AUTH_CONTINUED_SESSION, std::move(packet))
|
||||
{
|
||||
LocalChallenge.fill(0);
|
||||
Digest.fill(0);
|
||||
}
|
||||
|
||||
void Read() override;
|
||||
|
||||
uint64 DosResponse = 0;
|
||||
uint64 Key = 0;
|
||||
std::array<uint8, 16> LocalChallenge;
|
||||
std::array<uint8, DigestLength> Digest;
|
||||
|
||||
private:
|
||||
void Read() override;
|
||||
};
|
||||
|
||||
class ResumeComms final : public ServerPacket
|
||||
@@ -199,15 +231,16 @@ namespace WorldPackets
|
||||
WorldPacket const* Write() override { return &_worldPacket; }
|
||||
};
|
||||
|
||||
class ConnectToFailed final : public ClientPacket
|
||||
class ConnectToFailed final : public EarlyProcessClientPacket
|
||||
{
|
||||
public:
|
||||
ConnectToFailed(WorldPacket&& packet) : ClientPacket(CMSG_CONNECT_TO_FAILED, std::move(packet)) { }
|
||||
|
||||
void Read() override;
|
||||
ConnectToFailed(WorldPacket&& packet) : EarlyProcessClientPacket(CMSG_CONNECT_TO_FAILED, std::move(packet)) { }
|
||||
|
||||
ConnectToSerial Serial = ConnectToSerial::None;
|
||||
uint8 Con = 0;
|
||||
|
||||
private:
|
||||
void Read() override;
|
||||
};
|
||||
|
||||
class EnableEncryption final : public ServerPacket
|
||||
|
||||
@@ -157,3 +157,10 @@ WorldPacket const* WorldPackets::Instance::InstanceEncounterGainCombatResurrecti
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
WorldPacket const* WorldPackets::Instance::BossKillCredit::Write()
|
||||
{
|
||||
_worldPacket << uint32(DungeonEncounterID);
|
||||
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
@@ -235,6 +235,15 @@ namespace WorldPackets
|
||||
int32 InCombatResCount = 0;
|
||||
uint32 CombatResChargeRecovery = 0;
|
||||
};
|
||||
|
||||
class BossKillCredit final : public ServerPacket
|
||||
{
|
||||
public:
|
||||
BossKillCredit() : ServerPacket(SMSG_BOSS_KILL_CREDIT, 4) { }
|
||||
|
||||
WorldPacket const* Write() override;
|
||||
uint32 DungeonEncounterID = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -939,7 +939,7 @@ void OpcodeTable::Initialize()
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BLACK_MARKET_REQUEST_ITEMS_RESULT, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BLACK_MARKET_WON, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BONUS_ROLL_EMPTY, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BOSS_KILL_CREDIT, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BOSS_KILL_CREDIT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BREAK_TARGET, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BUY_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
DEFINE_SERVER_OPCODE_HANDLER(SMSG_BUY_SUCCEEDED, STATUS_NEVER, CONNECTION_TYPE_REALM);
|
||||
|
||||
@@ -368,8 +368,18 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
|
||||
switch (opcode)
|
||||
{
|
||||
case CMSG_PING:
|
||||
{
|
||||
LogOpcodeText(opcode, sessionGuard);
|
||||
return HandlePing(packet) ? ReadDataHandlerResult::Ok : ReadDataHandlerResult::Error;
|
||||
WorldPackets::Auth::Ping ping(std::move(packet));
|
||||
if (!ping.ReadNoThrow())
|
||||
{
|
||||
TC_LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_PING", GetRemoteIpAddress().to_string().c_str());
|
||||
return ReadDataHandlerResult::Error;
|
||||
}
|
||||
if (!HandlePing(ping))
|
||||
return ReadDataHandlerResult::Error;
|
||||
break;
|
||||
}
|
||||
case CMSG_AUTH_SESSION:
|
||||
{
|
||||
LogOpcodeText(opcode, sessionGuard);
|
||||
@@ -382,7 +392,11 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
|
||||
}
|
||||
|
||||
std::shared_ptr<WorldPackets::Auth::AuthSession> authSession = std::make_shared<WorldPackets::Auth::AuthSession>(std::move(packet));
|
||||
authSession->Read();
|
||||
if (!authSession->ReadNoThrow())
|
||||
{
|
||||
TC_LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_AUTH_SESSION", GetRemoteIpAddress().to_string().c_str());
|
||||
return ReadDataHandlerResult::Error;
|
||||
}
|
||||
HandleAuthSession(authSession);
|
||||
return ReadDataHandlerResult::WaitingForQuery;
|
||||
}
|
||||
@@ -398,7 +412,11 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
|
||||
}
|
||||
|
||||
std::shared_ptr<WorldPackets::Auth::AuthContinuedSession> authSession = std::make_shared<WorldPackets::Auth::AuthContinuedSession>(std::move(packet));
|
||||
authSession->Read();
|
||||
if (!authSession->ReadNoThrow())
|
||||
{
|
||||
TC_LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_AUTH_CONTINUED_SESSION", GetRemoteIpAddress().to_string().c_str());
|
||||
return ReadDataHandlerResult::Error;
|
||||
}
|
||||
HandleAuthContinuedSession(authSession);
|
||||
return ReadDataHandlerResult::WaitingForQuery;
|
||||
}
|
||||
@@ -419,7 +437,11 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
|
||||
|
||||
LogOpcodeText(opcode, sessionGuard);
|
||||
WorldPackets::Auth::ConnectToFailed connectToFailed(std::move(packet));
|
||||
connectToFailed.Read();
|
||||
if (!connectToFailed.ReadNoThrow())
|
||||
{
|
||||
TC_LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_CONNECT_TO_FAILED", GetRemoteIpAddress().to_string().c_str());
|
||||
return ReadDataHandlerResult::Error;
|
||||
}
|
||||
HandleConnectToFailed(connectToFailed);
|
||||
break;
|
||||
}
|
||||
@@ -924,17 +946,10 @@ void WorldSocket::SendAuthResponseError(uint32 code)
|
||||
SendPacketAndLogOpcode(*response.Write());
|
||||
}
|
||||
|
||||
bool WorldSocket::HandlePing(WorldPacket& recvPacket)
|
||||
bool WorldSocket::HandlePing(WorldPackets::Auth::Ping& ping)
|
||||
{
|
||||
using namespace std::chrono;
|
||||
|
||||
uint32 ping;
|
||||
uint32 latency;
|
||||
|
||||
// Get the ping packet content
|
||||
recvPacket >> ping;
|
||||
recvPacket >> latency;
|
||||
|
||||
if (_LastPingTime == steady_clock::time_point())
|
||||
{
|
||||
_LastPingTime = steady_clock::now();
|
||||
@@ -975,7 +990,7 @@ bool WorldSocket::HandlePing(WorldPacket& recvPacket)
|
||||
|
||||
if (_worldSession)
|
||||
{
|
||||
_worldSession->SetLatency(latency);
|
||||
_worldSession->SetLatency(ping.Latency);
|
||||
_worldSession->ResetClientTimeDelay();
|
||||
}
|
||||
else
|
||||
@@ -985,8 +1000,6 @@ bool WorldSocket::HandlePing(WorldPacket& recvPacket)
|
||||
}
|
||||
}
|
||||
|
||||
WorldPacket packet(SMSG_PONG, 4);
|
||||
packet << ping;
|
||||
SendPacketAndLogOpcode(packet);
|
||||
SendPacketAndLogOpcode(*WorldPackets::Auth::Pong(ping.Serial).Write());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ namespace WorldPackets
|
||||
class AuthSession;
|
||||
class AuthContinuedSession;
|
||||
class ConnectToFailed;
|
||||
class Ping;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,10 +119,9 @@ private:
|
||||
void HandleAuthContinuedSessionCallback(std::shared_ptr<WorldPackets::Auth::AuthContinuedSession> authSession, PreparedQueryResult result);
|
||||
void LoadSessionPermissionsCallback(PreparedQueryResult result);
|
||||
void HandleConnectToFailed(WorldPackets::Auth::ConnectToFailed& connectToFailed);
|
||||
bool HandlePing(WorldPackets::Auth::Ping& ping);
|
||||
void HandleEnableEncryptionAck();
|
||||
|
||||
bool HandlePing(WorldPacket& recvPacket);
|
||||
|
||||
ConnectionType _type;
|
||||
uint64 _key;
|
||||
|
||||
|
||||
@@ -390,7 +390,7 @@ enum AuraType
|
||||
SPELL_AURA_CAST_WHILE_WALKING = 330,
|
||||
SPELL_AURA_FORCE_WEATHER = 331,
|
||||
SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS = 332,
|
||||
SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_2 = 333,
|
||||
SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED = 333, // Spells cast with this override have no cast time or power cost
|
||||
SPELL_AURA_MOD_BLIND = 334, // NYI
|
||||
SPELL_AURA_335 = 335,
|
||||
SPELL_AURA_MOD_FLYING_RESTRICTIONS = 336, // NYI
|
||||
|
||||
@@ -391,8 +391,8 @@ pAuraEffectHandler AuraEffectHandler[TOTAL_AURAS]=
|
||||
&AuraEffect::HandleNULL, //329 SPELL_AURA_MOD_RUNE_REGEN_SPEED
|
||||
&AuraEffect::HandleNoImmediateEffect, //330 SPELL_AURA_CAST_WHILE_WALKING
|
||||
&AuraEffect::HandleAuraForceWeather, //331 SPELL_AURA_FORCE_WEATHER
|
||||
&AuraEffect::HandleNoImmediateEffect, //332 SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS implemented in WorldSession::HandleCastSpellOpcode
|
||||
&AuraEffect::HandleNoImmediateEffect, //333 SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_2 implemented in WorldSession::HandleCastSpellOpcode
|
||||
&AuraEffect::HandleNoImmediateEffect, //332 SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS implemented in Unit::GetCastSpellInfo
|
||||
&AuraEffect::HandleNoImmediateEffect, //333 SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_TRIGGERED implemented in Unit::GetCastSpellInfo
|
||||
&AuraEffect::HandleNULL, //334 SPELL_AURA_MOD_BLIND
|
||||
&AuraEffect::HandleNULL, //335 SPELL_AURA_335
|
||||
&AuraEffect::HandleNULL, //336 SPELL_AURA_MOD_FLYING_RESTRICTIONS
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Log.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "World.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "WorldPacket.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Log.h"
|
||||
#include "UpdateMask.h"
|
||||
#include "World.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "SpellMgr.h"
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "Common.h"
|
||||
#include "PlayerDump.h"
|
||||
#include "DatabaseEnv.h"
|
||||
#include "UpdateFields.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "Player.h"
|
||||
#include "AccountMgr.h"
|
||||
|
||||
@@ -1361,7 +1361,7 @@ public:
|
||||
DoCastVictim(SPELL_SR_SHOOT, false);
|
||||
uiTimer[2] = urand(4000, 6000);
|
||||
}
|
||||
if (me->IsWithinMeleeRange(me->GetVictim(), 6))
|
||||
if (me->IsWithinMeleeRange(me->GetVictim()))
|
||||
{
|
||||
if (uiTimer[0] <= diff)
|
||||
{
|
||||
|
||||
@@ -103,7 +103,7 @@ class npc_warp_splinter_treant : public CreatureScript
|
||||
{
|
||||
if (Unit* Warp = ObjectAccessor::GetUnit(*me, WarpGuid))
|
||||
{
|
||||
if (me->IsWithinMeleeRange(Warp, 2.5f))
|
||||
if (me->IsWithinMeleeRange(Warp))
|
||||
{
|
||||
int32 CurrentHP_Treant = (int32)me->GetHealth();
|
||||
Warp->CastCustomSpell(Warp, SPELL_HEAL_FATHER, &CurrentHP_Treant, 0, 0, true, 0, 0, me->GetGUID());
|
||||
|
||||
Reference in New Issue
Block a user