Merge fix

This commit is contained in:
kadeshar
2026-03-28 09:54:24 +01:00
393 changed files with 19287 additions and 15957 deletions
+2 -18
View File
@@ -31,6 +31,8 @@ This is the minimum interface to the VMapMamager.
namespace VMAP
{
class StaticMapTree;
enum VMAP_LOAD_RESULT
{
VMAP_LOAD_RESULT_ERROR,
@@ -88,20 +90,8 @@ namespace VMAP
virtual ~IVMapMgr() = default;
virtual int loadMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
virtual LoadResult existsMap(const char* pBasePath, unsigned int pMapId, int x, int y) = 0;
virtual void unloadMap(unsigned int pMapId, int x, int y) = 0;
virtual void unloadMap(unsigned int pMapId) = 0;
virtual bool isInLineOfSight(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) = 0;
virtual float getHeight(unsigned int pMapId, float x, float y, float z, float maxSearchDist) = 0;
/**
test if we hit an object. return true if we hit one. rx, ry, rz will hold the hit position or the dest position, if no intersection was found
return a position, that is pReduceDist closer to the origin
*/
virtual bool GetObjectHitPos(unsigned int pMapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float pModifyDist) = 0;
/**
send debug commands
*/
@@ -123,12 +113,6 @@ namespace VMAP
[[nodiscard]] bool isMapLoadingEnabled() const { return (iEnableLineOfSightCalc || iEnableHeightCalc ); }
[[nodiscard]] virtual std::string getDirFileName(unsigned int pMapId, int x, int y) const = 0;
/**
Query world model area info.
\param z gets adjusted to the ground height for which this are info is valid
*/
virtual bool GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional<uint8> reqLiquidType, AreaAndLiquidData& data) const = 0;
};
}
@@ -1,56 +0,0 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MMapFactory.h"
#include <cstring>
namespace MMAP
{
// ######################## MMapFactory ########################
// our global singleton copy
MMapMgr* g_MMapMgr = nullptr;
bool MMapFactory::forbiddenMaps[1000] = {0};
MMapMgr* MMapFactory::createOrGetMMapMgr()
{
if (!g_MMapMgr)
{
g_MMapMgr = new MMapMgr();
}
return g_MMapMgr;
}
void MMapFactory::InitializeDisabledMaps()
{
memset(&forbiddenMaps, 0, sizeof(forbiddenMaps));
int32 f[] = {616 /*EoE*/, 649 /*ToC25*/, 650 /*ToC5*/, -1};
uint32 i = 0;
while (f[i] >= 0)
{
forbiddenMaps[f[i++]] = true;
}
}
void MMapFactory::clear()
{
if (g_MMapMgr)
{
delete g_MMapMgr;
g_MMapMgr = nullptr;
}
}
}
@@ -1,45 +0,0 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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 _MMAP_FACTORY_H
#define _MMAP_FACTORY_H
#include "MMapMgr.h"
namespace MMAP
{
enum MMAP_LOAD_RESULT
{
MMAP_LOAD_RESULT_ERROR,
MMAP_LOAD_RESULT_OK,
MMAP_LOAD_RESULT_IGNORED,
};
// static class
// holds all mmap global data
// access point to MMapMgr singleton
class MMapFactory
{
public:
static MMapMgr* createOrGetMMapMgr();
static void clear();
static void InitializeDisabledMaps();
static bool forbiddenMaps[1000];
};
}
#endif
+17 -224
View File
@@ -24,63 +24,8 @@
namespace MMAP
{
// ######################## MMapMgr ########################
MMapMgr::~MMapMgr()
std::shared_ptr<dtNavMesh> MMapMgr::LoadNavMesh(uint32 mapId)
{
for (MMapDataSet::iterator i = loadedMMaps.begin(); i != loadedMMaps.end(); ++i)
{
delete i->second;
}
// by now we should not have maps loaded
// if we had, tiles in MMapData->mmapLoadedTiles, their actual data is lost!
}
void MMapMgr::InitializeThreadUnsafe(const std::vector<uint32>& mapIds)
{
// the caller must pass the list of all mapIds that will be used in the VMapMgr2 lifetime
for (const uint32& mapId : mapIds)
{
loadedMMaps.emplace(mapId, nullptr);
}
thread_safe_environment = false;
}
MMapDataSet::const_iterator MMapMgr::GetMMapData(uint32 mapId) const
{
// return the iterator if found or end() if not found/NULL
MMapDataSet::const_iterator itr = loadedMMaps.find(mapId);
if (itr != loadedMMaps.cend() && !itr->second)
{
itr = loadedMMaps.cend();
}
return itr;
}
bool MMapMgr::loadMapData(uint32 mapId)
{
// we already have this map loaded?
MMapDataSet::iterator itr = loadedMMaps.find(mapId);
if (itr != loadedMMaps.end())
{
if (itr->second)
{
return true;
}
}
else
{
if (thread_safe_environment)
{
itr = loadedMMaps.insert(MMapDataSet::value_type(mapId, nullptr)).first;
}
else
{
ABORT("Invalid mapId {} passed to MMapMgr after startup in thread unsafe environment", mapId);
}
}
// load and init dtNavMesh - read parameters from file
std::string fileName = Acore::StringFormat(MAP_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", "."), mapId);
@@ -88,7 +33,7 @@ namespace MMAP
if (!file)
{
LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not open mmap file '{}'", fileName);
return false;
return nullptr;
}
dtNavMeshParams params;
@@ -97,7 +42,7 @@ namespace MMAP
if (count != 1)
{
LOG_DEBUG("maps", "MMAP:loadMapData: Error: Could not read params from file '{}'", fileName);
return false;
return nullptr;
}
dtNavMesh* mesh = dtAllocNavMesh();
@@ -106,15 +51,13 @@ namespace MMAP
{
dtFreeNavMesh(mesh);
LOG_ERROR("maps", "MMAP:loadMapData: Failed to initialize dtNavMesh for mmap {:03} from file {}", mapId, fileName);
return false;
return nullptr;
}
LOG_DEBUG("maps", "MMAP:loadMapData: Loaded {:03}.mmap", mapId);
// store inside our map list
MMapData* mmap_data = new MMapData(mesh);
itr->second = mmap_data;
return true;
std::shared_ptr<dtNavMesh> navMesh = std::shared_ptr<dtNavMesh>(mesh, NavMeshDeleter());
return navMesh;
}
uint32 MMapMgr::packTileID(int32 x, int32 y)
@@ -122,26 +65,8 @@ namespace MMAP
return uint32(x << 16 | y);
}
bool MMapMgr::loadMap(uint32 mapId, int32 x, int32 y)
bool MMapMgr::LoadTile(dtNavMesh* navMesh, uint32 mapId, int32 x, int32 y)
{
// make sure the mmap is loaded and ready to load tiles
if (!loadMapData(mapId))
{
return false;
}
// get this mmap data
MMapData* mmap = loadedMMaps[mapId];
ASSERT(mmap->navMesh);
// check if we already have this tile loaded
uint32 packedGridPos = packTileID(x, y);
if (mmap->loadedTileRefs.find(packedGridPos) != mmap->loadedTileRefs.end())
{
LOG_ERROR("maps", "MMAP:loadMap: Asked to load already loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y);
return false;
}
// load this tile :: mmaps/MMMXXYY.mmtile
std::string fileName = Acore::StringFormat(TILE_FILE_NAME_FORMAT, sConfigMgr->GetOption<std::string>("DataDir", "."), mapId, x, y);
FILE* file = fopen(fileName.c_str(), "rb");
@@ -184,10 +109,8 @@ 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(navMesh->addTile(data, fileHeader.size, DT_TILE_FREE_DATA, 0, &tileRef)))
{
mmap->loadedTileRefs.insert(std::pair<uint32, dtTileRef>(packedGridPos, tileRef));
++loadedTiles;
dtMeshHeader* header = (dtMeshHeader*)data;
LOG_DEBUG("maps", "MMAP:loadMap: Loaded mmtile {:03}[{:02},{:02}] into {:03}[{:02},{:02}]", mapId, x, y, mapId, header->x, header->y);
return true;
@@ -198,149 +121,19 @@ namespace MMAP
return false;
}
bool MMapMgr::unloadMap(uint32 mapId, int32 x, int32 y)
ManagedNavMeshQuery MMapMgr::CreateNavMeshQuery(dtNavMesh* navMesh)
{
// check if we have this map loaded
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map. {:03}{:02}{:02}.mmtile", mapId, x, y);
return false;
}
MMapData* mmap = itr->second;
// check if we have this tile loaded
uint32 packedGridPos = packTileID(x, y);
if (mmap->loadedTileRefs.find(packedGridPos) == mmap->loadedTileRefs.end())
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh tile. {:03}{:02}{:02}.mmtile", mapId, x, y);
return false;
}
dtTileRef tileRef = mmap->loadedTileRefs[packedGridPos];
// unload, and mark as non loaded
if (dtStatusFailed(mmap->navMesh->removeTile(tileRef, nullptr, nullptr)))
{
// this is technically a memory leak
// if the grid is later reloaded, dtNavMesh::addTile will return error but no extra memory is used
// we cannot recover from this error - assert out
LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:03}{:02}{:02}.mmtile from navmesh", mapId, x, y);
ABORT();
}
mmap->loadedTileRefs.erase(packedGridPos);
--loadedTiles;
LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:03}[{:02},{:02}] from {:03}", mapId, x, y, mapId);
return true;
}
bool MMapMgr::unloadMap(uint32 mapId)
{
MMapDataSet::iterator itr = loadedMMaps.find(mapId);
if (itr == loadedMMaps.end() || !itr->second)
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMap: Asked to unload not loaded navmesh map {:03}", mapId);
return false;
}
// unload all tiles from given map
MMapData* mmap = itr->second;
for (auto& i : mmap->loadedTileRefs)
{
uint32 x = (i.first >> 16);
uint32 y = (i.first & 0x0000FFFF);
if (dtStatusFailed(mmap->navMesh->removeTile(i.second, nullptr, nullptr)))
{
LOG_ERROR("maps", "MMAP:unloadMap: Could not unload {:03}{:02}{:02}.mmtile from navmesh", mapId, x, y);
}
else
{
--loadedTiles;
LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded mmtile {:03}[{:02},{:02}] from {:03}", mapId, x, y, mapId);
}
}
delete mmap;
itr->second = nullptr;
LOG_DEBUG("maps", "MMAP:unloadMap: Unloaded {:03}.mmap", mapId);
return true;
}
bool MMapMgr::unloadMapInstance(uint32 mapId, uint32 instanceId)
{
// check if we have this map loaded
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
// file may not exist, therefore not loaded
LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded navmesh map {:03}", mapId);
return false;
}
MMapData* mmap = itr->second;
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
{
LOG_DEBUG("maps", "MMAP:unloadMapInstance: Asked to unload not loaded dtNavMeshQuery mapId {:03} instanceId {}", mapId, instanceId);
return false;
}
dtNavMeshQuery* query = mmap->navMeshQueries[instanceId];
dtFreeNavMeshQuery(query);
mmap->navMeshQueries.erase(instanceId);
LOG_DEBUG("maps", "MMAP:unloadMapInstance: Unloaded mapId {:03} instanceId {}", mapId, instanceId);
return true;
}
dtNavMesh const* MMapMgr::GetNavMesh(uint32 mapId)
{
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
// allocate mesh query
dtNavMeshQuery* query = dtAllocNavMeshQuery();
ASSERT(query);
if (dtStatusFailed(query->init(navMesh, 1024)))
{
dtFreeNavMeshQuery(query);
return nullptr;
}
return itr->second->navMesh;
}
dtNavMeshQuery const* MMapMgr::GetNavMeshQuery(uint32 mapId, uint32 instanceId)
{
MMapDataSet::const_iterator itr = GetMMapData(mapId);
if (itr == loadedMMaps.end())
{
return nullptr;
}
MMapData* mmap = itr->second;
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
{
// check again after acquiring mutex
if (mmap->navMeshQueries.find(instanceId) == mmap->navMeshQueries.end())
{
// allocate mesh query
dtNavMeshQuery* query = dtAllocNavMeshQuery();
ASSERT(query);
if (dtStatusFailed(query->init(mmap->navMesh, 1024)))
{
dtFreeNavMeshQuery(query);
LOG_ERROR("maps", "MMAP:GetNavMeshQuery: Failed to initialize dtNavMeshQuery for mapId {:03} instanceId {}", mapId, instanceId);
return nullptr;
}
LOG_DEBUG("maps", "MMAP:GetNavMeshQuery: created dtNavMeshQuery for mapId {:03} instanceId {}", mapId, instanceId);
mmap->navMeshQueries.insert(std::pair<uint32, dtNavMeshQuery*>(instanceId, query));
}
}
return mmap->navMeshQueries[instanceId];
ManagedNavMeshQuery navMeshQuery = ManagedNavMeshQuery(query);
return navMeshQuery;
}
}
+22 -50
View File
@@ -22,8 +22,7 @@
#include "DetourAlloc.h"
#include "DetourExtended.h"
#include "DetourNavMesh.h"
#include <unordered_map>
#include <vector>
#include <memory>
// memory management
inline void* dtCustomAlloc(std::size_t size, dtAllocHint /*hint*/)
@@ -39,67 +38,40 @@ inline void dtCustomFree(void* ptr)
// move map related classes
namespace MMAP
{
enum MMAP_LOAD_RESULT
{
MMAP_LOAD_RESULT_ERROR,
MMAP_LOAD_RESULT_OK,
MMAP_LOAD_RESULT_IGNORED,
};
static char const* const MAP_FILE_NAME_FORMAT = "{}/mmaps/{:03}.mmap";
static char const* const TILE_FILE_NAME_FORMAT = "{}/mmaps/{:03}{:02}{:02}.mmtile";
typedef std::unordered_map<uint32, dtTileRef> MMapTileSet;
typedef std::unordered_map<uint32, dtNavMeshQuery*> NavMeshQuerySet;
// dummy struct to hold map's mmap data
struct MMapData
struct NavMeshDeleter
{
MMapData(dtNavMesh* mesh) : navMesh(mesh) { }
~MMapData()
{
for (auto& navMeshQuerie : navMeshQueries)
{
dtFreeNavMeshQuery(navMeshQuerie.second);
}
if (navMesh)
{
dtFreeNavMesh(navMesh);
}
}
// we have to use single dtNavMeshQuery for every instance, since those are not thread safe
NavMeshQuerySet navMeshQueries; // instanceId to query
dtNavMesh* navMesh;
MMapTileSet loadedTileRefs; // maps [map grid coords] to [dtTile]
void operator()(dtNavMesh* navMesh) noexcept { dtFreeNavMesh(navMesh); }
};
typedef std::unordered_map<uint32, MMapData*> MMapDataSet;
struct NavMeshQueryDeleter
{
void operator()(dtNavMeshQuery* query) noexcept { dtFreeNavMeshQuery(query); }
};
using ManagedNavMeshQuery = std::unique_ptr<dtNavMeshQuery, NavMeshQueryDeleter>;
// singleton class
// holds all all access to mmap loading unloading and meshes
class MMapMgr
{
public:
MMapMgr() = default;
~MMapMgr();
MMapMgr() = default;
~MMapMgr() = default;
void InitializeThreadUnsafe(const std::vector<uint32>& mapIds);
bool loadMap(uint32 mapId, int32 x, int32 y);
bool unloadMap(uint32 mapId, int32 x, int32 y);
bool unloadMap(uint32 mapId);
bool unloadMapInstance(uint32 mapId, uint32 instanceId);
// the returned [dtNavMeshQuery const*] is NOT threadsafe
dtNavMeshQuery const* GetNavMeshQuery(uint32 mapId, uint32 instanceId);
dtNavMesh const* GetNavMesh(uint32 mapId);
[[nodiscard]] uint32 getLoadedTilesCount() const { return loadedTiles; }
[[nodiscard]] uint32 getLoadedMapsCount() const { return loadedMMaps.size(); }
static std::shared_ptr<dtNavMesh> LoadNavMesh(uint32 mapId);
static bool LoadTile(dtNavMesh* navMesh, uint32 mapId, int32 x, int32 y);
static ManagedNavMeshQuery CreateNavMeshQuery(dtNavMesh* navMesh);
private:
bool loadMapData(uint32 mapId);
uint32 packTileID(int32 x, int32 y);
[[nodiscard]] MMapDataSet::const_iterator GetMMapData(uint32 mapId) const;
MMapDataSet loadedMMaps;
uint32 loadedTiles{0};
bool thread_safe_environment{true};
static uint32 packTileID(int32 x, int32 y);
};
}
+1 -273
View File
@@ -17,11 +17,8 @@
#include "VMapMgr2.h"
#include "Errors.h"
#include "Log.h"
#include "MapDefines.h"
#include "MapTree.h"
#include "ModelInstance.h"
#include "WorldModel.h"
#include <G3D/Vector3.h>
#include <iomanip>
#include <sstream>
@@ -35,34 +32,13 @@ namespace VMAP
{
GetLiquidFlagsPtr = &GetLiquidFlagsDummy;
IsVMAPDisabledForPtr = &IsVMAPDisabledForDummy;
thread_safe_environment = true;
}
VMapMgr2::~VMapMgr2()
{
for (InstanceTreeMap::iterator i = iInstanceMapTrees.begin(); i != iInstanceMapTrees.end(); ++i)
{
delete i->second;
}
for (ModelFileMap::iterator i = iLoadedModelFiles.begin(); i != iLoadedModelFiles.end(); ++i)
{
delete i->second.getModel();
}
}
void VMapMgr2::InitializeThreadUnsafe(const std::vector<uint32>& mapIds)
{
// the caller must pass the list of all mapIds that will be used in the VMapMgr2 lifetime
for (const uint32& mapId : mapIds)
{
iInstanceMapTrees.emplace(mapId, nullptr);
}
thread_safe_environment = false;
}
Vector3 VMapMgr2::convertPositionToInternalRep(float x, float y, float z) const
Vector3 VMapMgr2::convertPositionToInternalRep(float x, float y, float z)
{
Vector3 pos;
const float mid = 0.5f * MAX_NUMBER_OF_GRIDS * SIZE_OF_GRIDS;
@@ -73,18 +49,6 @@ namespace VMAP
return pos;
}
InstanceTreeMap::const_iterator VMapMgr2::GetMapTree(uint32 mapId) const
{
// return the iterator if found or end() if not found/NULL
InstanceTreeMap::const_iterator itr = iInstanceMapTrees.find(mapId);
if (itr != iInstanceMapTrees.cend() && !itr->second)
{
itr = iInstanceMapTrees.cend();
}
return itr;
}
// move to MapTree too?
std::string VMapMgr2::getMapFileName(unsigned int mapId)
{
@@ -95,245 +59,9 @@ namespace VMAP
return fname.str();
}
int VMapMgr2::loadMap(const char* basePath, unsigned int mapId, int x, int y)
{
int result = VMAP_LOAD_RESULT_IGNORED;
if (isMapLoadingEnabled())
{
if (_loadMap(mapId, basePath, x, y))
{
result = VMAP_LOAD_RESULT_OK;
}
else
{
result = VMAP_LOAD_RESULT_ERROR;
}
}
return result;
}
// load one tile (internal use only)
bool VMapMgr2::_loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY)
{
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
if (instanceTree == iInstanceMapTrees.end())
{
if (thread_safe_environment)
{
instanceTree = iInstanceMapTrees.insert(InstanceTreeMap::value_type(mapId, nullptr)).first;
}
else
ABORT("Invalid mapId {} tile [{}, {}] passed to VMapMgr2 after startup in thread unsafe environment",
mapId, tileX, tileY);
}
if (!instanceTree->second)
{
std::string mapFileName = getMapFileName(mapId);
StaticMapTree* newTree = new StaticMapTree(mapId, basePath);
if (!newTree->InitMap(mapFileName, this))
{
delete newTree;
return false;
}
instanceTree->second = newTree;
}
return instanceTree->second->LoadMapTile(tileX, tileY, this);
}
void VMapMgr2::unloadMap(unsigned int mapId)
{
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
if (instanceTree != iInstanceMapTrees.end() && instanceTree->second)
{
instanceTree->second->UnloadMap(this);
if (instanceTree->second->numLoadedTiles() == 0)
{
delete instanceTree->second;
instanceTree->second = nullptr;
}
}
}
void VMapMgr2::unloadMap(unsigned int mapId, int x, int y)
{
InstanceTreeMap::iterator instanceTree = iInstanceMapTrees.find(mapId);
if (instanceTree != iInstanceMapTrees.end() && instanceTree->second)
{
instanceTree->second->UnloadMapTile(x, y, this);
if (instanceTree->second->numLoadedTiles() == 0)
{
delete instanceTree->second;
instanceTree->second = nullptr;
}
}
}
bool VMapMgr2::isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags)
{
#if defined(ENABLE_VMAP_CHECKS)
if (!isLineOfSightCalcEnabled() || IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS))
{
return true;
}
#endif
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
if (pos1 != pos2)
{
return instanceTree->second->isInLineOfSight(pos1, pos2, ignoreFlags);
}
}
return true;
}
/**
get the hit position and return true if we hit something
otherwise the result pos will be the dest pos
*/
bool VMapMgr2::GetObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist)
{
#if defined(ENABLE_VMAP_CHECKS)
if (isLineOfSightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LOS))
#endif
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos1 = convertPositionToInternalRep(x1, y1, z1);
Vector3 pos2 = convertPositionToInternalRep(x2, y2, z2);
Vector3 resultPos;
bool result = instanceTree->second->GetObjectHitPos(pos1, pos2, resultPos, modifyDist);
resultPos = convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z);
rx = resultPos.x;
ry = resultPos.y;
rz = resultPos.z;
return result;
}
}
rx = x2;
ry = y2;
rz = z2;
return false;
}
/**
get height or INVALID_HEIGHT if no height available
*/
float VMapMgr2::getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist)
{
#if defined(ENABLE_VMAP_CHECKS)
if (isHeightCalcEnabled() && !IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_HEIGHT))
#endif
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
Vector3 pos = convertPositionToInternalRep(x, y, z);
float height = instanceTree->second->getHeight(pos, maxSearchDist);
if (height >= G3D::finf())
{
return height = VMAP_INVALID_HEIGHT_VALUE; // No height
}
return height;
}
}
return VMAP_INVALID_HEIGHT_VALUE;
}
bool VMapMgr2::GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional<uint8> reqLiquidType, AreaAndLiquidData& data) const
{
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
if (instanceTree != iInstanceMapTrees.end())
{
LocationInfo info;
Vector3 pos = convertPositionToInternalRep(x, y, z);
if (instanceTree->second->GetLocationInfo(pos, info))
{
data.floorZ = info.ground_Z;
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS))
{
uint32 liquidType = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc
float liquidLevel;
if (!reqLiquidType || (GetLiquidFlagsPtr(liquidType) & *reqLiquidType))
if (info.hitInstance->GetLiquidLevel(pos, info, liquidLevel))
data.liquidInfo.emplace(liquidType, liquidLevel);
}
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG))
data.areaInfo.emplace(info.hitModel->GetWmoID(), info.hitInstance->adtId, info.rootId, info.hitModel->GetMogpFlags(), info.hitInstance->ID);
return true;
}
}
return false;
}
WorldModel* VMapMgr2::acquireModelInstance(const std::string& basepath, const std::string& filename, uint32 flags/* Only used when creating the model */)
{
//! Critical section, thread safe access to iLoadedModelFiles
std::lock_guard<std::mutex> lock(LoadedModelFilesLock);
ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
if (model == iLoadedModelFiles.end())
{
WorldModel* worldmodel = new WorldModel();
if (!worldmodel->readFile(basepath + filename + ".vmo"))
{
LOG_ERROR("maps", "VMapMgr2: could not load '{}{}.vmo'", basepath, filename);
delete worldmodel;
return nullptr;
}
LOG_DEBUG("maps", "VMapMgr2: loading file '{}{}'", basepath, filename);
worldmodel->Flags = flags;
model = iLoadedModelFiles.insert(std::pair<std::string, ManagedModel>(filename, ManagedModel())).first;
model->second.setModel(worldmodel);
}
return model->second.getModel();
}
void VMapMgr2::releaseModelInstance(const std::string& filename)
{
//! Critical section, thread safe access to iLoadedModelFiles
std::lock_guard<std::mutex> lock(LoadedModelFilesLock);
ModelFileMap::iterator model = iLoadedModelFiles.find(filename);
if (model == iLoadedModelFiles.end())
{
LOG_ERROR("maps", "VMapMgr2: trying to unload non-loaded file '{}'", filename);
return;
}
if (model->second.decRefCount() == 0)
{
LOG_DEBUG("maps", "VMapMgr2: unloading file '{}'", filename);
delete model->second.getModel();
iLoadedModelFiles.erase(model);
}
}
LoadResult VMapMgr2::existsMap(const char* basePath, unsigned int mapId, int x, int y)
{
return StaticMapTree::CanLoadMap(std::string(basePath), mapId, x, y);
}
void VMapMgr2::GetInstanceMapTree(InstanceTreeMap& instanceMapTree)
{
instanceMapTree = iInstanceMapTrees;
}
} // namespace VMAP
+1 -55
View File
@@ -19,9 +19,6 @@
#define _VMAPMANAGER2_H
#include "IVMapMgr.h"
#include <mutex>
#include <unordered_map>
#include <vector>
//===========================================================
@@ -46,24 +43,6 @@ namespace G3D
namespace VMAP
{
class StaticMapTree;
class WorldModel;
class ManagedModel
{
public:
ManagedModel() { }
void setModel(WorldModel* model) { iModel = model; }
WorldModel* getModel() { return iModel; }
int decRefCount() { return --iRefCount; }
protected:
WorldModel* iModel{nullptr};
int iRefCount{0};
};
typedef std::unordered_map<uint32, StaticMapTree*> InstanceTreeMap;
typedef std::unordered_map<std::string, ManagedModel> ModelFileMap;
enum DisableTypes
{
VMAP_DISABLE_AREAFLAG = 0x1,
@@ -75,58 +54,25 @@ namespace VMAP
class VMapMgr2 : public IVMapMgr
{
protected:
// Tree to check collision
ModelFileMap iLoadedModelFiles;
InstanceTreeMap iInstanceMapTrees;
bool thread_safe_environment;
// Mutex for iLoadedModelFiles
std::mutex LoadedModelFilesLock;
bool _loadMap(uint32 mapId, const std::string& basePath, uint32 tileX, uint32 tileY);
/* void _unloadMap(uint32 pMapId, uint32 x, uint32 y); */
static uint32 GetLiquidFlagsDummy(uint32) { return 0; }
static bool IsVMAPDisabledForDummy(uint32 /*entry*/, uint8 /*flags*/) { return false; }
InstanceTreeMap::const_iterator GetMapTree(uint32 mapId) const;
public:
// public for debug
[[nodiscard]] G3D::Vector3 convertPositionToInternalRep(float x, float y, float z) const;
static G3D::Vector3 convertPositionToInternalRep(float x, float y, float z);
static std::string getMapFileName(unsigned int mapId);
VMapMgr2();
~VMapMgr2() override;
void InitializeThreadUnsafe(const std::vector<uint32>& mapIds);
int loadMap(const char* pBasePath, unsigned int mapId, int x, int y) override;
void unloadMap(unsigned int mapId, int x, int y) override;
void unloadMap(unsigned int mapId) override;
bool isInLineOfSight(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, ModelIgnoreFlags ignoreFlags) override ;
/**
fill the hit pos and return true, if an object was hit
*/
bool GetObjectHitPos(unsigned int mapId, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) override;
float getHeight(unsigned int mapId, float x, float y, float z, float maxSearchDist) override;
bool processCommand(char* /*command*/) override { return false; } // for debug and extensions
bool GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional<uint8> reqLiquidType, AreaAndLiquidData& data) const override;
WorldModel* acquireModelInstance(const std::string& basepath, const std::string& filename, uint32 flags);
void releaseModelInstance(const std::string& filename);
// what's the use of this? o.O
[[nodiscard]] std::string getDirFileName(unsigned int mapId, int /*x*/, int /*y*/) const override
{
return getMapFileName(mapId);
}
LoadResult existsMap(const char* basePath, unsigned int mapId, int x, int y) override;
void GetInstanceMapTree(InstanceTreeMap& instanceMapTree);
typedef uint32(*GetLiquidFlagsFn)(uint32 liquidType);
GetLiquidFlagsFn GetLiquidFlagsPtr;
@@ -0,0 +1,43 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Log.h"
#include "WorldModelStore.h"
std::shared_ptr<VMAP::WorldModel> WorldModelStore::AcquireModelInstance(std::string const& basepath, std::string const& filename, uint32 flags/* Only used when creating the model */)
{
//! Critical section, thread safe access
std::lock_guard<std::mutex> lock(_lock);
ModelFileMap::iterator model = _loadedModels.find(filename);
if (model == _loadedModels.end())
{
std::shared_ptr<VMAP::WorldModel> worldmodel = std::make_shared<VMAP::WorldModel>();
LOG_DEBUG("maps", "WorldModelStore: loading file '{}{}'", basepath, filename);
if (!worldmodel->readFile(basepath + filename + ".vmo"))
{
LOG_ERROR("maps", "WorldModelStore: could not load '{}{}.vmo'", basepath, filename);
return nullptr;
}
worldmodel->Flags = flags;
model = _loadedModels.insert(std::pair<std::string, std::shared_ptr<VMAP::WorldModel>>(filename, worldmodel)).first;
}
return model->second;
}
@@ -15,32 +15,32 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _IMMAPMANAGER_H
#define _IMMAPMANAGER_H
#ifndef _WORLDMODELSTORE_H
#define _WORLDMODELSTORE_H
// Interface for IMMapManger
namespace MMAP
#include "WorldModel.h"
#include <memory>
#include <mutex>
#include <unordered_map>
class WorldModelStore
{
enum MMAP_LOAD_RESULT
public:
static WorldModelStore* instance()
{
MMAP_LOAD_RESULT_ERROR,
MMAP_LOAD_RESULT_OK,
MMAP_LOAD_RESULT_IGNORED,
};
static WorldModelStore instance;
return &instance;
}
class IMMapMgr
{
private:
bool iEnablePathFinding;
std::shared_ptr<VMAP::WorldModel> AcquireModelInstance(std::string const& basepath, std::string const& filename, uint32 flags);
public:
IMMapMgr() : iEnablePathFinding(true) {}
virtual ~IMMapMgr(void) {}
private:
typedef std::unordered_map<std::string, std::shared_ptr<VMAP::WorldModel>> ModelFileMap;
ModelFileMap _loadedModels;
//Enabled/Disabled Pathfinding
void setEnablePathFinding(bool value) { iEnablePathFinding = value; }
bool isEnablePathFinding() const { return (iEnablePathFinding); }
};
}
std::mutex _lock;
};
#define sWorldModelStore WorldModelStore::instance()
#endif
+21 -80
View File
@@ -22,6 +22,7 @@
#include "ModelInstance.h"
#include "VMapDefinitions.h"
#include "VMapMgr2.h"
#include "WorldModelStore.h"
#include <iomanip>
#include <limits>
#include <sstream>
@@ -259,7 +260,7 @@ namespace VMAP
//=========================================================
bool StaticMapTree::InitMap(const std::string& fname, VMapMgr2* vm)
bool StaticMapTree::InitMap(const std::string& fname)
{
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : initializing StaticMapTree '{}'", fname);
bool success = false;
@@ -291,13 +292,12 @@ namespace VMAP
#endif
if (!iIsTiled && ModelSpawn::readFromFile(rf, spawn))
{
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags);
std::shared_ptr<WorldModel> model = sWorldModelStore->AcquireModelInstance(iBasePath, spawn.name, spawn.flags);
//VMAP_DEBUG_LOG(LOG_FILTER_MAPS, "StaticMapTree::InitMap() : loading {}", spawn.name);
if (model)
{
// assume that global model always is the first and only tree value (could be improved...)
iTreeValues[0] = ModelInstance(spawn, model);
iLoadedSpawns[0] = 1;
}
else
{
@@ -312,23 +312,14 @@ namespace VMAP
//=========================================================
void StaticMapTree::UnloadMap(VMapMgr2* vm)
void StaticMapTree::UnloadMap()
{
for (loadedSpawnMap::iterator i = iLoadedSpawns.begin(); i != iLoadedSpawns.end(); ++i)
{
iTreeValues[i->first].setUnloaded();
for (uint32 refCount = 0; refCount < i->second; ++refCount)
{
vm->releaseModelInstance(iTreeValues[i->first].name);
}
}
iLoadedSpawns.clear();
iLoadedTiles.clear();
}
//=========================================================
bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm)
bool StaticMapTree::LoadMapTile(uint32 tileX, uint32 tileY)
{
if (!iIsTiled)
{
@@ -367,10 +358,11 @@ namespace VMAP
if (result)
{
// acquire model instance
WorldModel* model = vm->acquireModelInstance(iBasePath, spawn.name, spawn.flags);
std::shared_ptr<WorldModel> model = sWorldModelStore->AcquireModelInstance(iBasePath, spawn.name, spawn.flags);
if (!model)
{
LOG_ERROR("maps", "StaticMapTree::LoadMapTile() : could not acquire WorldModel pointer [{}, {}]", tileX, tileY);
// why do we continue to try to load if the model was unsuccessful here?
}
// update tree
@@ -378,22 +370,22 @@ namespace VMAP
if (fread(&referencedVal, sizeof(uint32), 1, tf) == 1)
{
if (!iLoadedSpawns.count(referencedVal))
if (referencedVal >= iNTreeValues)
{
#if defined(VMAP_DEBUG)
if (referencedVal > iNTreeValues)
{
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : invalid tree element ({}/{})", referencedVal, iNTreeValues);
continue;
}
#endif
iTreeValues[referencedVal] = ModelInstance(spawn, model);
iLoadedSpawns[referencedVal] = 1;
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : invalid tree element ({}/{})", referencedVal, iNTreeValues);
continue;
}
// This looks odd and is confusing, took some research to figure it out:
// the first WorldModel will create a "groupmodel" of all other same-models in the tile
// we don't actually care about anything else
if (!iTreeValues[referencedVal].getWorldModel())
{
iTreeValues[referencedVal] = ModelInstance(spawn, model);
}
#if defined(VMAP_DEBUG)
else
{
++iLoadedSpawns[referencedVal];
#if defined(VMAP_DEBUG)
if (iTreeValues[referencedVal].ID != spawn.ID)
{
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : trying to load wrong spawn in node");
@@ -402,8 +394,8 @@ namespace VMAP
{
LOG_DEBUG("maps", "StaticMapTree::LoadMapTile() : name collision on GUID={}", spawn.ID);
}
#endif
}
#endif
}
else
{
@@ -427,7 +419,7 @@ namespace VMAP
//=========================================================
void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm)
void StaticMapTree::UnloadMapTile(uint32 tileX, uint32 tileY)
{
uint32 tileID = packTileID(tileX, tileY);
loadedTileMap::iterator tile = iLoadedTiles.find(tileID);
@@ -436,57 +428,6 @@ namespace VMAP
LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-loaded tile - Map:{} X:{} Y:{}", iMapID, tileX, tileY);
return;
}
if (tile->second) // file associated with tile
{
std::string tilefile = iBasePath + getTileFileName(iMapID, tileX, tileY);
FILE* tf = fopen(tilefile.c_str(), "rb");
if (tf)
{
bool result = true;
char chunk[8];
if (!readChunk(tf, chunk, VMAP_MAGIC, 8))
{
result = false;
}
uint32 numSpawns;
if (fread(&numSpawns, sizeof(uint32), 1, tf) != 1)
{
result = false;
}
for (uint32 i = 0; i < numSpawns && result; ++i)
{
// read model spawns
ModelSpawn spawn;
result = ModelSpawn::readFromFile(tf, spawn);
if (result)
{
// release model instance
vm->releaseModelInstance(spawn.name);
// update tree
uint32 referencedNode;
if (fread(&referencedNode, sizeof(uint32), 1, tf) != 1)
{
result = false;
}
else
{
if (!iLoadedSpawns.count(referencedNode))
{
LOG_ERROR("maps", "StaticMapTree::UnloadMapTile() : trying to unload non-referenced model '{}' (ID:{})", spawn.name, spawn.ID);
}
else if (--iLoadedSpawns[referencedNode] == 0)
{
iTreeValues[referencedNode].setUnloaded();
iLoadedSpawns.erase(referencedNode);
}
}
}
}
fclose(tf);
}
}
iLoadedTiles.erase(tile);
METRIC_EVENT("map_events", "UnloadMapTile",
+4 -6
View File
@@ -60,8 +60,6 @@ namespace VMAP
// some maps are not splitted into tiles and we have to make sure, not removing the map before all tiles are removed
// empty tiles have no tile file, hence map with bool instead of just a set (consistency check)
loadedTileMap iLoadedTiles;
// stores <tree_index, reference_count> to invalidate tree values, unload map, and to be able to report errors
loadedSpawnMap iLoadedSpawns;
std::string iBasePath;
private:
@@ -81,10 +79,10 @@ namespace VMAP
[[nodiscard]] float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
bool GetLocationInfo(const G3D::Vector3& pos, LocationInfo& info) const;
bool InitMap(const std::string& fname, VMapMgr2* vm);
void UnloadMap(VMapMgr2* vm);
bool LoadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm);
void UnloadMapTile(uint32 tileX, uint32 tileY, VMapMgr2* vm);
bool InitMap(const std::string& fname);
void UnloadMap();
bool LoadMapTile(uint32 tileX, uint32 tileY);
void UnloadMapTile(uint32 tileX, uint32 tileY);
[[nodiscard]] bool isTiled() const { return iIsTiled; }
[[nodiscard]] uint32 numLoadedTiles() const { return iLoadedTiles.size(); }
void GetModelInstances(ModelInstance*& models, uint32& count);
@@ -24,6 +24,7 @@
#include "VMapFactory.h"
#include "VMapMgr2.h"
#include "WorldModel.h"
#include "WorldModelStore.h"
using G3D::Vector3;
using G3D::Ray;
@@ -101,14 +102,6 @@ void LoadGameObjectModelList(std::string const& dataPath)
LOG_INFO("server.loading", " ");
}
GameObjectModel::~GameObjectModel()
{
if (iModel)
{
VMAP::VMapFactory::createOrGetVMapMgr()->releaseModelInstance(name);
}
}
bool GameObjectModel::initialize(std::unique_ptr<GameObjectModelOwnerBase> modelOwner, std::string const& dataPath)
{
ModelList::const_iterator it = model_list.find(modelOwner->GetDisplayId());
@@ -125,7 +118,7 @@ bool GameObjectModel::initialize(std::unique_ptr<GameObjectModelOwnerBase> model
return false;
}
iModel = VMAP::VMapFactory::createOrGetVMapMgr()->acquireModelInstance(dataPath + "vmaps/", it->second.name,
iModel = sWorldModelStore->AcquireModelInstance(dataPath + "vmaps/", it->second.name,
it->second.isWmo ? VMAP::ModelFlags::MOD_WORLDSPAWN : VMAP::ModelFlags::MOD_M2);
if (!iModel)
@@ -23,6 +23,7 @@
#include <G3D/Matrix3.h>
#include <G3D/Ray.h>
#include <G3D/Vector3.h>
#include <memory>
namespace VMAP
{
@@ -58,7 +59,7 @@ public:
[[nodiscard]] const G3D::AABox& GetBounds() const { return iBound; }
~GameObjectModel();
~GameObjectModel() = default;
[[nodiscard]] const G3D::Vector3& GetPosition() const { return iPos; }
@@ -86,7 +87,7 @@ private:
G3D::Vector3 iPos;
float iInvScale{0};
float iScale{0};
VMAP::WorldModel* iModel{nullptr};
std::shared_ptr<VMAP::WorldModel> iModel;
std::unique_ptr<GameObjectModelOwnerBase> owner;
bool isWmo{false};
};
@@ -24,7 +24,7 @@ using G3D::Ray;
namespace VMAP
{
ModelInstance::ModelInstance(const ModelSpawn& spawn, WorldModel* model): ModelSpawn(spawn), iModel(model)
ModelInstance::ModelInstance(const ModelSpawn& spawn, std::shared_ptr<WorldModel> model): ModelSpawn(spawn), iModel(model)
{
iInvRot = G3D::Matrix3::fromEulerAnglesZYX(G3D::pi() * iRot.y / 180.f, G3D::pi() * iRot.x / 180.f, G3D::pi() * iRot.z / 180.f).inverse();
iInvScale = 1.f / iScale;
+4 -4
View File
@@ -23,6 +23,7 @@
#include <G3D/Matrix3.h>
#include <G3D/Ray.h>
#include <G3D/Vector3.h>
#include <memory>
namespace VMAP
{
@@ -63,16 +64,15 @@ namespace VMAP
{
public:
ModelInstance() { }
ModelInstance(const ModelSpawn& spawn, WorldModel* model);
void setUnloaded() { iModel = nullptr; }
ModelInstance(const ModelSpawn& spawn, std::shared_ptr<WorldModel> model);
bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
bool GetLocationInfo(const G3D::Vector3& p, LocationInfo& info) const;
bool GetLiquidLevel(const G3D::Vector3& p, LocationInfo& info, float& liqHeight) const;
WorldModel* getWorldModel() { return iModel; }
WorldModel* getWorldModel() { return iModel.get(); }
protected:
G3D::Matrix3 iInvRot;
float iInvScale{0.0f};
WorldModel* iModel{nullptr};
std::shared_ptr<WorldModel> iModel;
};
} // namespace VMAP
@@ -3629,6 +3629,15 @@ Wintergrasp.NoBattleTimer = 150
Wintergrasp.CrashRestartTimer = 10
#
# Wintergrasp.SkipBattleSessionCount
# Description: When the peace timer expires, if the number of active sessions
# exceeds this value, a new war will not start.
# Set to 0 to disable this check.
# Default: 3500
Wintergrasp.SkipBattleSessionCount = 3500
#
###################################################################################################
@@ -119,6 +119,13 @@ void LoginDatabaseConnection::DoPrepareStatements()
PrepareStatement(LOGIN_DEL_ACCOUNT, "DELETE FROM account WHERE id = ?", CONNECTION_ASYNC);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST, "SELECT id, weight, text FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALIZED, "SELECT id, locale, text FROM autobroadcast_locale WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_AUTOBROADCAST, "INSERT INTO autobroadcast (realmid, weight, text) VALUES (?, ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_AUTOBROADCAST, "DELETE FROM autobroadcast WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_INS_AUTOBROADCAST_LOCALE, "REPLACE INTO autobroadcast_locale (realmid, id, locale, text) VALUES (?, ?, ?, ?)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_DEL_AUTOBROADCAST_LOCALE, "DELETE FROM autobroadcast_locale WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_BY_ID, "SELECT 1 FROM autobroadcast WHERE id = ? AND (realmid = ? OR realmid = -1)", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_LOCALE_BY_ID, "SELECT locale, text FROM autobroadcast_locale WHERE (realmid = ? OR realmid = -1) AND id = ?", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_AUTOBROADCAST_MAX_ID, "SELECT MAX(id) FROM autobroadcast WHERE realmid = ? OR realmid = -1", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_MOTD, "SELECT text FROM motd WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_SEL_MOTD_LOCALE, "SELECT locale, text FROM motd_localized WHERE realmid = ? OR realmid = -1 ORDER BY realmid DESC", CONNECTION_SYNCH);
PrepareStatement(LOGIN_REP_MOTD, "REPLACE INTO motd (realmid, text) VALUES (?, ?)", CONNECTION_ASYNC);
@@ -101,6 +101,13 @@ enum LoginDatabaseStatements : uint32
LOGIN_DEL_ACCOUNT,
LOGIN_SEL_AUTOBROADCAST,
LOGIN_SEL_AUTOBROADCAST_LOCALIZED,
LOGIN_INS_AUTOBROADCAST,
LOGIN_DEL_AUTOBROADCAST,
LOGIN_INS_AUTOBROADCAST_LOCALE,
LOGIN_DEL_AUTOBROADCAST_LOCALE,
LOGIN_SEL_AUTOBROADCAST_BY_ID,
LOGIN_SEL_AUTOBROADCAST_LOCALE_BY_ID,
LOGIN_SEL_AUTOBROADCAST_MAX_ID,
LOGIN_SEL_MOTD,
LOGIN_SEL_MOTD_LOCALE,
LOGIN_REP_MOTD,
@@ -52,7 +52,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_UPD_WAYPOINT_DATA_WPGUID, "UPDATE waypoint_data SET wpguid = ? WHERE id = ? and point = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_MAX_ID, "SELECT MAX(id) FROM waypoint_data", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_MAX_POINT, "SELECT MAX(point) FROM waypoint_data WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID, "SELECT point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data WHERE id = ? ORDER BY point", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID, "SELECT point, position_x, position_y, position_z, orientation, velocity, delay, smoothTransition, move_type, action, action_chance FROM waypoint_data WHERE id = ? ORDER BY point", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_BY_ID, "SELECT point, position_x, position_y, position_z FROM waypoint_data WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_FIRST_BY_ID, "SELECT position_x, position_y, position_z FROM waypoint_data WHERE point = 1 AND id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_DATA_POS_LAST_BY_ID, "SELECT position_x, position_y, position_z, orientation FROM waypoint_data WHERE id = ? ORDER BY point DESC LIMIT 1", CONNECTION_SYNCH);
@@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH);
+6 -1
View File
@@ -20,7 +20,12 @@
PassiveAI::PassiveAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
PossessedAI::PossessedAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c) { me->SetReactState(REACT_PASSIVE); }
NullCreatureAI::NullCreatureAI(Creature* c) : CreatureAI(c)
{
me->SetReactState(REACT_PASSIVE);
// TODO: Remove once WorldObject casting is ported (triggers won't create combat refs from spell casts)
me->SetIsCombatDisallowed(true);
}
int32 NullCreatureAI::Permissible(Creature const* creature)
{
+22 -7
View File
@@ -198,8 +198,12 @@ void CreatureAI::OnOwnerCombatInteraction(Unit* target)
if (!target || !me->IsAlive())
return;
// Prevent guardian from disengaging from current target
if (me->GetVictim() && me->GetVictim()->IsAlive())
return;
if (!me->HasReactState(REACT_PASSIVE) && me->CanStartAttack(target, true))
me->EngageWithTarget(target);
AttackStart(target);
}
// Distract creature, if player gets too close while stealthed/prowling
@@ -211,6 +215,9 @@ void CreatureAI::TriggerAlert(Unit const* who) const
// If this unit isn't an NPC, is already distracted, is in combat, is confused, stunned or fleeing, do nothing
if (!me->IsCreature() || me->IsEngaged() || me->HasUnitState(UNIT_STATE_CONFUSED | UNIT_STATE_STUNNED | UNIT_STATE_FLEEING | UNIT_STATE_DISTRACTED))
return;
// If the creature is immune to players, it should not be alerted by them
if (me->IsImmuneToPC())
return;
// Only alert for hostiles!
if (me->IsCivilian() || me->HasReactState(REACT_PASSIVE) || !me->IsHostileTo(who) || !me->_IsTargetAcceptable(who))
return;
@@ -236,14 +243,19 @@ void CreatureAI::EnterEvadeMode(EvadeReason why)
{
if (Unit* owner = me->GetCharmerOrOwner())
{
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE);
// Owned creatures (pets/guardians) follow their owner — clear evade state
// so they can re-enter combat immediately via CanBeginCombat
me->ClearUnitState(UNIT_STATE_EVADE);
if (!me->IsVehicle()) // vehicles should not follow their owner (passenger)
{
me->GetMotionMaster()->Clear(false);
me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, me->GetFollowAngle(), MOTION_SLOT_ACTIVE);
}
}
else
{
// Required to prevent attacking creatures that are evading and cause them to reenter combat
// Does not apply to MoveFollow
me->AddUnitState(UNIT_STATE_EVADE);
// Does not apply to MoveFollow — UNIT_STATE_EVADE is already set from _EnterEvadeMode
me->GetMotionMaster()->MoveTargetedHome();
}
}
@@ -378,8 +390,11 @@ bool CreatureAI::_EnterEvadeMode(EvadeReason /*why*/)
return false;
}
// Set evade state early to prevent recursion when CombatStop triggers JustExitedCombat
// This will be cleared in MoveTargetedHome for pets/guardians following their owner
// Set evade state early to prevent recursion: CombatStop below purges combat
// refs, which triggers JustExitedCombat -> EnterEvadeMode -> _EnterEvadeMode.
// The IsInEvadeMode() check above will catch it.
// EnterEvadeMode will clear this for owned creatures (pets/guardians) that
// use MoveFollow instead of MoveTargetedHome.
me->AddUnitState(UNIT_STATE_EVADE);
// don't remove vehicle auras, passengers aren't supposed to drop off the vehicle
+7
View File
@@ -186,6 +186,13 @@ public:
// Called at MovePath End
virtual void PathEndReached(uint32 /*pathId*/) {}
/// == Waypoints system =============================
virtual void WaypointPathStarted(uint32 /*pathId*/) { }
virtual void WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/) { }
virtual void WaypointReached(uint32 /*nodeId*/, uint32 /*pathId*/) { }
virtual void WaypointPathEnded(uint32 /*nodeId*/, uint32 /*pathId*/) { }
void OnCharmed(bool apply) override;
// Called at reaching home after evade
@@ -472,7 +472,7 @@ void ScriptedAI::DoResetThreat(Unit* unit)
void ScriptedAI::DoResetThreatList()
{
if (!me->CanHaveThreatList() || me->GetThreatMgr().IsThreatListEmpty())
if (!me->CanHaveThreatList() || me->GetThreatMgr().IsThreatListEmpty(true))
{
LOG_ERROR("entities.unit.ai", "DoResetThreatList called for creature that either cannot have threat list or has empty threat list (me entry = {})", me->GetEntry());
return;
@@ -87,6 +87,7 @@ public:
void GenerateWaypointArray(Movement::PointsArray* points);
using CreatureAI::WaypointReached;
virtual void WaypointReached(uint32 pointId) = 0;
virtual void WaypointStart(uint32 /*pointId*/) {}
+97 -50
View File
@@ -109,27 +109,67 @@ void SmartAI::UpdateDespawn(const uint32 diff)
mDespawnTime -= diff;
}
WaypointData const* SmartAI::GetNextWayPoint()
void SmartAI::UpdateFollow(const uint32 diff)
{
if (!mWayPoints || mWayPoints->empty())
if (!mFollowGuid)
return;
if (mFollowArrivedTimer < diff)
{
if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, mFollowArrivedAlive))
StopFollow(true);
else
mFollowArrivedTimer = 1000;
}
else
mFollowArrivedTimer -= diff;
if (mFollowGuid.IsPlayer())
{
if (_followCheckTimer < diff)
{
bool shouldDespawn = false;
if (Player* player = ObjectAccessor::FindPlayer(mFollowGuid))
{
float checkDist = me->GetInstanceScript() ? SMART_ESCORT_MAX_PLAYER_DIST * 2 : SMART_ESCORT_MAX_PLAYER_DIST;
if (!me->IsWithinDistInMap(player, checkDist))
shouldDespawn = true;
}
else
shouldDespawn = true;
if (shouldDespawn)
me->DespawnOrUnsummon();
_followCheckTimer = 1000;
}
else
_followCheckTimer -= diff;
}
}
WaypointNode const* SmartAI::GetNextWayPoint()
{
if (!mWayPoints || mWayPoints->Nodes.empty())
return nullptr;
mCurrentWPID++;
auto itr = mWayPoints->find(mCurrentWPID);
if (itr != mWayPoints->end())
{
mLastWP = &(*itr).second;
if (mLastWP->id != mCurrentWPID)
LOG_ERROR("scripts.ai.sai", "SmartAI::GetNextWayPoint: Got not expected waypoint id {}, expected {}", mLastWP->id, mCurrentWPID);
// mCurrentWPID is 1-based for SmartAI escort paths
if (mCurrentWPID > mWayPoints->Nodes.size())
return nullptr;
return &(*itr).second;
}
return nullptr;
// Nodes are 0-indexed, mCurrentWPID is 1-based
WaypointNode const& node = mWayPoints->Nodes[mCurrentWPID - 1];
mLastWP = &node;
if (mLastWP->Id != mCurrentWPID)
LOG_ERROR("scripts.ai.sai", "SmartAI::GetNextWayPoint: Got not expected waypoint id {}, expected {}", mLastWP->Id, mCurrentWPID);
return mLastWP;
}
void SmartAI::GenerateWayPointArray(Movement::PointsArray* points)
{
if (!mWayPoints || mWayPoints->empty())
if (!mWayPoints || mWayPoints->Nodes.empty())
return;
// Flying unit, just fill array
@@ -138,16 +178,12 @@ void SmartAI::GenerateWayPointArray(Movement::PointsArray* points)
// xinef: first point in vector is unit real position
points->clear();
points->push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
uint32 wpCounter = mCurrentWPID;
auto itr = mWayPoints->find(wpCounter++);
do
// mCurrentWPID is 1-based
for (uint32 i = mCurrentWPID > 0 ? mCurrentWPID - 1 : 0; i < mWayPoints->Nodes.size(); ++i)
{
WaypointData const& wp = (*itr).second;
points->push_back(G3D::Vector3(wp.x, wp.y, wp.z));
itr = mWayPoints->find(wpCounter++);
WaypointNode const& wp = mWayPoints->Nodes[i];
points->push_back(G3D::Vector3(wp.X, wp.Y, wp.Z));
}
while (itr != mWayPoints->end());
}
else
{
@@ -156,15 +192,15 @@ void SmartAI::GenerateWayPointArray(Movement::PointsArray* points)
std::vector<G3D::Vector3> pVector;
// xinef: first point in vector is unit real position
pVector.push_back(G3D::Vector3(me->GetPositionX(), me->GetPositionY(), me->GetPositionZ()));
uint32 wpCounter = mCurrentWPID;
uint32 length = (mWayPoints->size() - mCurrentWPID) * size;
uint32 startIdx = mCurrentWPID > 0 ? mCurrentWPID - 1 : 0;
uint32 length = (uint32)((mWayPoints->Nodes.size() - startIdx) * size);
uint32 cnt = 0;
for (auto itr = mWayPoints->find(wpCounter); itr != mWayPoints->end() && cnt++ <= length; ++itr)
for (uint32 i = startIdx; i < mWayPoints->Nodes.size() && cnt++ <= length; ++i)
{
WaypointData const& wp = (*itr).second;
pVector.push_back(G3D::Vector3(wp.x, wp.y, wp.z));
WaypointNode const& wp = mWayPoints->Nodes[i];
pVector.push_back(G3D::Vector3(wp.X, wp.Y, wp.Z));
}
if (pVector.size() > 2) // more than source + dest
@@ -204,10 +240,10 @@ void SmartAI::StartPath(ForcedMovement forcedMovement, uint32 path, bool repeat,
return;
}
if (!mWayPoints || mWayPoints->empty())
if (!mWayPoints || mWayPoints->Nodes.empty())
return;
if (WaypointData const* wp = GetNextWayPoint())
if (WaypointNode const* wp = GetNextWayPoint())
{
AddEscortState(SMART_ESCORT_ESCORTING);
mCanRepeatPath = repeat;
@@ -223,7 +259,7 @@ void SmartAI::StartPath(ForcedMovement forcedMovement, uint32 path, bool repeat,
GenerateWayPointArray(&pathPoints);
me->GetMotionMaster()->MoveSplinePath(&pathPoints, mForcedMovement);
GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_START, nullptr, wp->id, GetScript()->GetPathId());
GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_START, nullptr, wp->Id, GetScript()->GetPathId());
}
}
@@ -282,10 +318,11 @@ void SmartAI::PausePath(uint32 delay, bool forced)
me->StopMoving();
me->GetMotionMaster()->MoveIdle();//force stop
auto waypoint = mWayPoints->find(mCurrentWPID);
if (waypoint->second.orientation.has_value())
if (mCurrentWPID > 0 && mCurrentWPID <= mWayPoints->Nodes.size())
{
me->SetFacingTo(*waypoint->second.orientation);
WaypointNode const& waypoint = mWayPoints->Nodes[mCurrentWPID - 1];
if (waypoint.Orientation.has_value())
me->SetFacingTo(*waypoint.Orientation);
}
}
GetScript()->ProcessEventsFor(SMART_EVENT_ESCORT_PAUSED, nullptr, mCurrentWPID, GetScript()->GetPathId());
@@ -536,23 +573,7 @@ void SmartAI::UpdateAI(uint32 diff)
GetScript()->OnUpdate(diff);
UpdatePath(diff);
UpdateDespawn(diff);
//TODO move to void
if (mFollowGuid)
{
if (mFollowArrivedTimer < diff)
{
if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, mFollowArrivedAlive))
{
StopFollow(true);
return;
}
mFollowArrivedTimer = 1000;
}
else
mFollowArrivedTimer -= diff;
}
UpdateFollow(diff);
if (!IsAIControlled())
{
@@ -632,7 +653,7 @@ void SmartAI::MovepointReached(uint32 id)
if (mLastWP)
{
me->SetPosition(mLastWP->x, mLastWP->y, mLastWP->z, me->GetOrientation());
me->SetPosition(mLastWP->X, mLastWP->Y, mLastWP->Z, me->GetOrientation());
me->SetHomePosition(me->GetPosition());
}
@@ -755,7 +776,7 @@ void SmartAI::MoveInLineOfSight(Unit* who)
bool SmartAI::CanAIAttack(Unit const* /*who*/) const
{
return !(me->GetReactState() == REACT_PASSIVE);
return true;
}
bool SmartAI::AssistPlayerInCombatAgainst(Unit* who)
@@ -1255,6 +1276,32 @@ void SmartAI::PathEndReached(uint32 /*pathId*/)
me->LoadPath(0);
}
void SmartAI::WaypointPathStarted(uint32 /*pathId*/)
{
}
void SmartAI::WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/)
{
}
void SmartAI::WaypointReached(uint32 nodeId, uint32 pathId)
{
if (!HasEscortState(SMART_ESCORT_ESCORTING))
{
GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, nodeId, pathId);
return;
}
}
void SmartAI::WaypointPathEnded(uint32 nodeId, uint32 pathId)
{
if (!HasEscortState(SMART_ESCORT_ESCORTING))
{
GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, nodeId, pathId);
return;
}
}
void SmartAI::DistancingEnded()
{
SetCurrentRangeMode(true, _pendingDistancing);
+10 -3
View File
@@ -58,7 +58,7 @@ public:
void StopPath(uint32 DespawnTime = 0, uint32 quest = 0, bool fail = false);
void EndPath(bool fail = false);
void ResumePath();
WaypointData const* GetNextWayPoint();
WaypointNode const* GetNextWayPoint();
void GenerateWayPointArray(Movement::PointsArray* points);
bool HasEscortState(uint32 uiEscortState) { return (mEscortState & uiEscortState); }
void AddEscortState(uint32 uiEscortState) { mEscortState |= uiEscortState; }
@@ -206,6 +206,11 @@ public:
void PathEndReached(uint32 pathId) override;
void WaypointPathStarted(uint32 pathId) override;
void WaypointStarted(uint32 nodeId, uint32 pathId) override;
void WaypointReached(uint32 nodeId, uint32 pathId) override;
void WaypointPathEnded(uint32 nodeId, uint32 pathId) override;
bool CanRespawn() override { return mcanSpawn; };
void SetCanRespawn(bool canSpawn) { mcanSpawn = canSpawn; }
@@ -222,6 +227,7 @@ private:
bool mIsCharmed;
uint32 mFollowCreditType;
uint32 mFollowArrivedTimer;
uint32 _followCheckTimer;
uint32 mFollowCredit;
uint32 mFollowArrivedEntry;
bool mFollowArrivedAlive;
@@ -238,9 +244,9 @@ private:
bool mWPReached;
bool mOOCReached;
uint32 mWPPauseTimer;
WaypointData const* mLastWP;
WaypointNode const* mLastWP;
uint32 mEscortNPCFlags;
uint32 GetWPCount() { return mWayPoints ? mWayPoints->size() : 0; }
uint32 GetWPCount() { return mWayPoints ? mWayPoints->Nodes.size() : 0; }
bool mCanRepeatPath;
bool mEvadeDisabled;
bool mCanAutoAttack;
@@ -253,6 +259,7 @@ private:
uint32 mDespawnTime;
uint32 mDespawnState;
void UpdateDespawn(const uint32 diff);
void UpdateFollow(const uint32 diff);
uint32 mEscortInvokerCheckTimer;
bool mJustReset;
@@ -2533,14 +2533,12 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
break;
}
if (!path || path->empty())
if (!path || path->Nodes.empty())
continue;
auto itrWp = path->find(1);
if (itrWp != path->end())
{
WaypointData const& wpData = itrWp->second;
float distToThisPath = creature->GetExactDistSq(wpData.x, wpData.y, wpData.z);
WaypointNode const& wpData = path->Nodes[0];
float distToThisPath = creature->GetExactDistSq(wpData.X, wpData.Y, wpData.Z);
if (distToThisPath < distanceToClosest)
{
@@ -2957,7 +2955,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (!IsPlayer(target))
continue;
target->ToPlayer()->SendCinematicStart(e.action.cinematic.entry);
target->ToPlayer()->GetCinematicMgr().StartCinematic(e.action.cinematic.entry);
}
break;
}
@@ -48,11 +48,6 @@ void SmartWaypointMgr::LoadFromDB()
{
uint32 oldMSTime = getMSTime();
for (auto itr : waypoint_map)
{
delete itr.second;
}
waypoint_map.clear();
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_SMARTAI_WP);
@@ -78,14 +73,15 @@ void SmartWaypointMgr::LoadFromDB()
float x = fields[2].Get<float>();
float y = fields[3].Get<float>();
float z = fields[4].Get<float>();
Optional<float> o;
std::optional<float> o;
if (!fields[5].IsNull())
o = fields[5].Get<float>();
uint32 delay = fields[6].Get<uint32>();
if (last_entry != entry)
{
waypoint_map[entry] = new WaypointPath();
waypoint_map[entry] = WaypointPath();
waypoint_map[entry].Id = entry;
last_id = 1;
count++;
}
@@ -94,15 +90,15 @@ void SmartWaypointMgr::LoadFromDB()
LOG_ERROR("sql.sql", "SmartWaypointMgr::LoadFromDB: Path entry {}, unexpected point id {}, expected {}.", entry, id, last_id);
last_id++;
WaypointData data;
data.id = id;
data.x = x;
data.y = y;
data.z = z;
data.orientation = o;
data.delay = delay;
data.move_type = WAYPOINT_MOVE_TYPE_MAX;
(*waypoint_map[entry]).emplace(id, data);
WaypointNode node;
node.Id = id;
node.X = x;
node.Y = y;
node.Z = z;
node.Orientation = o;
node.Delay = delay;
node.MoveType = WAYPOINT_MOVE_TYPE_MAX;
waypoint_map[entry].Nodes.push_back(std::move(node));
last_entry = entry;
total++;
@@ -112,14 +108,6 @@ void SmartWaypointMgr::LoadFromDB()
LOG_INFO("server.loading", " ");
}
SmartWaypointMgr::~SmartWaypointMgr()
{
for (auto itr : waypoint_map)
{
delete itr.second;
}
}
SmartAIMgr* SmartAIMgr::instance()
{
static SmartAIMgr instance;
@@ -2064,21 +2064,22 @@ class SmartWaypointMgr
{
SmartWaypointMgr() {}
public:
~SmartWaypointMgr();
~SmartWaypointMgr() = default;
static SmartWaypointMgr* instance();
void LoadFromDB();
WaypointPath* GetPath(uint32 id)
WaypointPath const* GetPath(uint32 id) const
{
if (waypoint_map.find(id) != waypoint_map.end())
return waypoint_map[id];
else return 0;
auto itr = waypoint_map.find(id);
if (itr != waypoint_map.end())
return &itr->second;
return nullptr;
}
private:
std::unordered_map<uint32, WaypointPath*> waypoint_map;
std::unordered_map<uint32, WaypointPath> waypoint_map;
};
// all events for a single entry
+41 -45
View File
@@ -31,6 +31,7 @@
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Transport.h"
#include "World.h"
#include "WorldPacket.h"
#include "WorldSessionMgr.h"
@@ -57,9 +58,6 @@ Battlefield::Battlefield() :
NoWarBattleTime(0),
RestartAfterCrash(0),
TimeForAcceptInvite(20),
KickDontAcceptTimer(1000),
KickAfkPlayersTimer(1000),
LastResurrectTimer(RESURRECTION_INTERVAL),
StartGroupingTimer(0),
StartGrouping(false)
{
@@ -132,6 +130,7 @@ void Battlefield::HandlePlayerLeaveZone(Player* player, uint32 /*zone*/)
cp->HandlePlayerLeave(player);
InvitedPlayers[player->GetTeamId()].erase(player->GetGUID());
PlayersInQueue[player->GetTeamId()].erase(player->GetGUID());
PlayersWillBeKick[player->GetTeamId()].erase(player->GetGUID());
Players[player->GetTeamId()].erase(player->GetGUID());
SendRemoveWorldStates(player);
@@ -147,7 +146,11 @@ bool Battlefield::Update(uint32 diff)
{
if (Timer <= diff)
{
if (!IsEnabled() || (!IsWarTime() && sWorldSessionMgr->GetActiveSessionCount() > 3500)) // if WG is disabled or there is more than 3500 connections, switch automatically
uint32 sessionLimit = sWorld->getIntConfig(CONFIG_WINTERGRASP_SKIP_BATTLE_SESSION_COUNT);
bool tooManySessions = sessionLimit && !IsWarTime()
&& sWorldSessionMgr->GetActiveSessionCount() > sessionLimit;
if (!IsEnabled() || tooManySessions)
{
Active = true;
EndBattle(false);
@@ -174,52 +177,16 @@ bool Battlefield::Update(uint32 diff)
SendUpdateWorldStates();
}
_scheduler.Update(diff);
bool objectiveChanged = false;
if (IsWarTime())
{
if (KickAfkPlayersTimer <= diff)
{
KickAfkPlayersTimer = 20000;
KickAfkPlayers();
}
else
KickAfkPlayersTimer -= diff;
// Kick players who chose not to accept invitation to the battle
if (KickDontAcceptTimer <= diff)
{
time_t now = GameTime::GetGameTime().count();
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
for (PlayerTimerMap::value_type const& pair : InvitedPlayers[team])
if (pair.second <= now)
KickPlayerFromBattlefield(pair.first);
InvitePlayersInZoneToWar();
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
for (PlayerTimerMap::value_type const& pair : PlayersWillBeKick[team])
if (pair.second <= now)
KickPlayerFromBattlefield(pair.first);
KickDontAcceptTimer = 5000;
}
else
KickDontAcceptTimer -= diff;
for (BfCapturePoint* cp : CapturePoints)
if (cp->Update(diff))
objectiveChanged = true;
}
if (LastResurrectTimer <= diff)
{
for (BfGraveyard* gy : GraveyardList)
if (gy)
gy->Resurrect();
LastResurrectTimer = RESURRECTION_INTERVAL;
}
else
LastResurrectTimer -= diff;
return objectiveChanged;
}
@@ -324,7 +291,8 @@ void Battlefield::KickAfkPlayers()
void Battlefield::KickPlayerFromBattlefield(ObjectGuid guid)
{
if (Player* player = ObjectAccessor::FindPlayer(guid))
if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster())
if (player->GetZoneId() == GetZoneId() && !player->IsGameMaster()
&& !PlayersInWar[player->GetTeamId()].count(guid))
player->TeleportTo(KickPosition);
}
@@ -342,6 +310,30 @@ void Battlefield::StartBattle()
Timer = BattleTime;
Active = true;
// Schedule war-only periodic timers
_scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context)
{
KickAfkPlayers();
context.Repeat(20s);
});
_scheduler.Schedule(1s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context)
{
time_t now = GameTime::GetGameTime().count();
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
for (PlayerTimerMap::value_type const& pair : InvitedPlayers[team])
if (pair.second <= now)
KickPlayerFromBattlefield(pair.first);
InvitePlayersInZoneToWar();
for (uint8 team = 0; team < PVP_TEAMS_COUNT; ++team)
for (PlayerTimerMap::value_type const& pair : PlayersWillBeKick[team])
if (pair.second <= now)
KickPlayerFromBattlefield(pair.first);
context.Repeat(5s);
});
InvitePlayersInZoneToWar();
InvitePlayersInQueueToWar();
@@ -359,6 +351,8 @@ void Battlefield::EndBattle(bool endByTimer)
Active = false;
_scheduler.CancelGroup(BATTLEFIELD_TIMER_GROUP_WAR);
StartGrouping = false;
if (!endByTimer)
@@ -424,7 +418,7 @@ void Battlefield::PlayerAcceptInviteToWar(Player* player)
{
player->GetSession()->SendBfEntered(BattleId);
PlayersInWar[player->GetTeamId()].insert(player->GetGUID());
InvitedPlayers[player->GetTeamId(true)].erase(player->GetGUID());
InvitedPlayers[player->GetTeamId()].erase(player->GetGUID());
if (player->isAFK())
player->ToggleAFK();
@@ -626,7 +620,9 @@ void Battlefield::RemovePlayerFromResurrectQueue(ObjectGuid playerGuid)
void Battlefield::SendAreaSpiritHealerQueryOpcode(Player* player, ObjectGuid const& guid)
{
WorldPacket data(SMSG_AREA_SPIRIT_HEALER_TIME, 12);
uint32 time = LastResurrectTimer; // resurrect every 30 seconds
Milliseconds remaining = _scheduler.GetNextGroupOccurrence(BATTLEFIELD_TIMER_GROUP_RESURRECT);
uint32 time = static_cast<uint32>(std::clamp(remaining,
Milliseconds::zero(), Milliseconds(RESURRECTION_INTERVAL)).count());
data << guid << time;
ASSERT(player);
+10 -4
View File
@@ -22,6 +22,7 @@
#include "GameObject.h"
#include "ObjectAccessor.h"
#include "SharedDefines.h"
#include "TaskScheduler.h"
#include "ZoneScript.h"
enum BattlefieldTypes
@@ -55,6 +56,13 @@ enum BattlefieldSounds
constexpr auto BATTLEFIELD_OBJECTIVE_UPDATE_INTERVAL = 1000;
enum BattlefieldTimerGroups
{
BATTLEFIELD_TIMER_GROUP_RESURRECT = 1,
BATTLEFIELD_TIMER_GROUP_WAR = 2,
BATTLEFIELD_TIMER_GROUP_SAVE = 3,
};
const uint32 BattlefieldFactions[PVP_TEAMS_COUNT] =
{
1732, // Alliance
@@ -401,18 +409,16 @@ protected:
uint32 NoWarBattleTime; // Time between two battles
uint32 RestartAfterCrash; // Delay to restart Wintergrasp if the server crashed during a running battle
uint32 TimeForAcceptInvite;
uint32 KickDontAcceptTimer;
WorldLocation KickPosition; // Position where players are teleported if they switch to afk during the battle or if they don't accept invitation
uint32 KickAfkPlayersTimer; // Timer for check Afk in war
// Graveyard variables
GraveyardVect GraveyardList; // Vector which contains the different GY of the battle
uint32 LastResurrectTimer; // Timer for resurrect player every 30 sec
uint32 StartGroupingTimer; // Timer for invite players in area 15 minutes before start battle
bool StartGrouping; // bool for knowing if all players in area have been invited
TaskScheduler _scheduler;
GuidUnorderedSet Groups[PVP_TEAMS_COUNT]; // Contains different raid groups
std::vector<uint64> Data64;
@@ -77,8 +77,6 @@ bool BattlefieldWG::SetupBattlefield()
Data32.resize(BATTLEFIELD_WG_DATA_MAX);
SaveTimer = 60000;
// Init Graveyards
SetGraveyardNumber(BATTLEFIELD_WG_GRAVEYARD_MAX);
@@ -189,37 +187,31 @@ bool BattlefieldWG::SetupBattlefield()
}
UpdateCounterVehicle(true);
// Schedule always-running periodic timers
_scheduler.Schedule(Milliseconds(RESURRECTION_INTERVAL),
BATTLEFIELD_TIMER_GROUP_RESURRECT, [this](TaskContext context)
{
for (BfGraveyard* gy : GraveyardList)
if (gy)
gy->Resurrect();
context.Repeat();
});
_scheduler.Schedule(60s, BATTLEFIELD_TIMER_GROUP_SAVE, [this](TaskContext context)
{
sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_ACTIVE, Active);
sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_DEFENDER, DefenderTeam);
sWorldState->setWorldState(ClockWorldState[0], Timer);
context.Repeat();
});
return true;
}
bool BattlefieldWG::Update(uint32 diff)
{
bool result = Battlefield::Update(diff);
if (SaveTimer <= diff)
{
sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_ACTIVE, Active);
sWorldState->setWorldState(WORLD_STATE_BATTLEFIELD_WG_DEFENDER, DefenderTeam);
sWorldState->setWorldState(ClockWorldState[0], Timer);
SaveTimer = 60 * IN_MILLISECONDS;
}
else
SaveTimer -= diff;
// Update Tenacity
if (IsWarTime())
{
if (TenacityUpdateTimer <= diff)
{
TenacityUpdateTimer = 10000;
if (!UpdateTenacityList.empty())
UpdateTenacity();
UpdateTenacityList.clear();
}
else
TenacityUpdateTimer -= diff;
}
return result;
return Battlefield::Update(diff);
}
void BattlefieldWG::OnBattleStart()
@@ -293,7 +285,14 @@ void BattlefieldWG::OnBattleStart()
// Xinef: reset tenacity counter
TenacityStack = 0;
TenacityUpdateTimer = 20000;
_scheduler.Schedule(20s, BATTLEFIELD_TIMER_GROUP_WAR, [this](TaskContext context)
{
if (!UpdateTenacityList.empty())
UpdateTenacity();
UpdateTenacityList.clear();
context.Repeat(10s);
});
if (sWorld->getBoolConfig(CONFIG_BATTLEGROUND_QUEUE_ANNOUNCER_ENABLE))
ChatHandler(nullptr).SendWorldText(BATTLEFIELD_WG_WORLD_START_MESSAGE);
@@ -449,8 +449,6 @@ protected:
GuidUnorderedSet UpdateTenacityList;
int32 TenacityStack;
uint32 TenacityUpdateTimer;
uint32 SaveTimer;
ObjectGuid TitansRelic;
};
@@ -132,6 +132,8 @@ bool ArenaTeam::AddMember(ObjectGuid playerGuid)
if (sWorld->getIntConfig(CONFIG_ARENA_START_PERSONAL_RATING) > 0)
personalRating = sWorld->getIntConfig(CONFIG_ARENA_START_PERSONAL_RATING);
else if (sArenaSeasonMgr->GetCurrentSeason() < 6)
personalRating = 1500;
else if (GetRating() >= 1000)
personalRating = 1000;
+3
View File
@@ -427,6 +427,9 @@ bool CombatManager::UpdateOwnerCombatState() const
_owner->AtExitCombat();
if (!_owner->IsCreature())
_owner->AtDisengage();
if (Player* player = _owner->ToPlayer())
sScriptMgr->OnPlayerLeaveCombat(player);
}
if (Unit* master = _owner->GetCharmerOrOwner())
+5
View File
@@ -540,6 +540,11 @@ void ThreatManager::TauntUpdate()
// taunt aura update also re-evaluates all suppressed states (retail behavior)
EvaluateSuppressed(true);
// immediately reselect victim so taunt takes effect without waiting
// for the next THREAT_UPDATE_INTERVAL (1 s) timer tick
UpdateVictim();
_updateTimer = THREAT_UPDATE_INTERVAL;
}
void ThreatManager::SetTauntStateForTesting(
+2
View File
@@ -182,6 +182,8 @@ public:
// Test-only: directly set taunt state on a threat ref
// Uses uint32 to avoid incomplete-type issue with ThreatReference
void SetTauntStateForTesting(Unit* target, uint32 state);
// Immediately reselect victim (bypasses 1-second Update timer)
void UpdateVictimForTesting() { UpdateVictim(); }
///== REDIRECT SYSTEM ==
void RegisterRedirectThreat(uint32 spellId, ObjectGuid const& victim, uint32 pct);
+13 -2
View File
@@ -17,7 +17,6 @@
#include "DisableMgr.h"
#include "GameEventMgr.h"
#include "MMapFactory.h"
#include "ObjectMgr.h"
#include "OutdoorPvP.h"
#include "Player.h"
@@ -448,5 +447,17 @@ bool DisableMgr::IsPathfindingEnabled(Map const* map)
if (!map)
return false;
return !MMAP::MMapFactory::forbiddenMaps[map->GetId()] && (sWorld->getBoolConfig(CONFIG_ENABLE_MMAPS) ? true : map->IsBattlegroundOrArena());
// Still not an ideal solution but removes the extra awful forbiddenMaps hack
// int32 f[] = {616 /*EoE*/, 649 /*ToC25*/, 650 /*ToC5*/, -1};
switch (map->GetId())
{
case 616: // EoE
case 649: // ToC25
case 650: // ToC5
return false;
default:
break;
}
return (sWorld->getBoolConfig(CONFIG_ENABLE_MMAPS) ? true : map->IsBattlegroundOrArena());
}
+73 -28
View File
@@ -388,6 +388,33 @@ void Creature::SearchFormation()
}
}
bool Creature::IsFormationLeader() const
{
if (!m_formation)
return false;
return m_formation->GetLeader() == this;
}
void Creature::SignalFormationMovement()
{
if (!m_formation)
return;
if (!m_formation->GetLeader() || m_formation->GetLeader() != this)
return;
m_formation->LeaderStartedMoving();
}
bool Creature::IsFormationLeaderMoveAllowed() const
{
if (!m_formation)
return true;
return m_formation->CanLeaderStartMoving();
}
void Creature::RemoveCorpse(bool setSpawnTime, bool skipVisibility)
{
if (getDeathState() != DeathState::Corpse)
@@ -643,7 +670,7 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele
SetControlled(true, UNIT_STATE_ROOT);
UpdateMovementFlags();
LoadSpellTemplateImmunity();
LoadTemplateImmunities(cInfo->CreatureImmunitiesId);
if (updateAI)
{
@@ -2132,39 +2159,55 @@ void Creature::InitializeReactState()
SetReactState(REACT_AGGRESSIVE);
}
bool Creature::HasMechanicTemplateImmunity(uint32 mask) const
bool Creature::HasMechanicTemplateImmunity(uint64 mask) const
{
return !GetOwnerGUID().IsPlayer() && (GetCreatureTemplate()->MechanicImmuneMask & mask);
if (GetOwnerGUID().IsPlayer())
return false;
if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(_creatureImmunitiesId))
return (immunities->Mechanic.to_ullong() & mask) != 0;
// no custom immunity entry => no mechanic immunity
return false;
}
void Creature::LoadSpellTemplateImmunity()
void Creature::LoadTemplateImmunities(int32 creatureImmunitiesId)
{
// uint32 max used for "spell id", the immunity system will not perform SpellInfo checks against invalid spells
// used so we know which immunities were loaded from template
static uint32 const placeholderSpellId = std::numeric_limits<uint32>::max();
static uint32 constexpr placeholderSpellId = std::numeric_limits<uint32>::max();
// unapply template immunities (in case we're updating entry)
for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i)
auto applyCreatureImmunities = [this](CreatureImmunities const* immunities, bool apply)
{
ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, i, false);
}
if (!immunities)
return;
for (std::size_t i = 0; i < immunities->School.size(); ++i)
if (immunities->School[i])
ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, apply);
for (std::size_t i = 0; i < immunities->DispelType.size(); ++i)
if (immunities->DispelType[i])
ApplySpellImmune(placeholderSpellId, IMMUNITY_DISPEL, i, apply);
for (std::size_t i = 0; i < immunities->Mechanic.size(); ++i)
if (immunities->Mechanic[i])
ApplySpellImmune(placeholderSpellId, IMMUNITY_MECHANIC, i, apply);
for (SpellEffects effect : immunities->Effect)
ApplySpellImmune(placeholderSpellId, IMMUNITY_EFFECT, effect, apply);
for (AuraType aura : immunities->Aura)
ApplySpellImmune(placeholderSpellId, IMMUNITY_STATE, aura, apply);
};
// don't inherit immunities for hunter pets
if (GetOwnerGUID().IsPlayer() && IsHunterPet())
{
return;
}
// unapply old template if any
if (CreatureImmunities const* oldImmunities = sSpellMgr->GetCreatureImmunities(_creatureImmunitiesId))
applyCreatureImmunities(oldImmunities, false);
if (uint8 mask = GetCreatureTemplate()->SpellSchoolImmuneMask)
// apply requested immunities
if (CreatureImmunities const* newImmunities = sSpellMgr->GetCreatureImmunities(creatureImmunitiesId))
{
for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i)
{
if (mask & (1 << i))
{
ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, true);
}
}
_creatureImmunitiesId = creatureImmunitiesId;
applyCreatureImmunities(newImmunities, true);
}
else
_creatureImmunitiesId = 0;
}
bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
@@ -2179,11 +2222,13 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
// Xinef: this should exclude self casts...
// Spells that don't have effectMechanics.
if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Mechanic - 1)))
if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Mechanic))
return true;
// This check must be done instead of 'if (GetCreatureTemplate()->MechanicImmuneMask & (1 << (spellInfo->Mechanic - 1)))' for not break
// the check of mechanic immunity on DB (tested) because GetCreatureTemplate()->MechanicImmuneMask and m_spellImmune[IMMUNITY_MECHANIC] don't have same data.
// The above helper uses the creature_immunities table rather than a
// simple mask on creature_template. We can't rely on the old mask field
// (which has been removed) because it no longer exists and did not always
// match the runtime immunity set stored in m_spellImmune.
bool immunedToAllEffects = true;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (spellInfo->Effects[i].IsEffect() && !IsImmunedToSpellEffect(spellInfo, i))
@@ -2197,16 +2242,16 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
return Unit::IsImmunedToSpell(spellInfo, spell);
}
bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const
bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const
{
// Xinef: this should exclude self casts...
if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Effects[index].Mechanic - 1)))
if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Effects[index].Mechanic))
return true;
if (GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL && spellInfo->Effects[index].Effect == SPELL_EFFECT_HEAL)
return true;
return Unit::IsImmunedToSpellEffect(spellInfo, index);
return Unit::IsImmunedToSpellEffect(spellInfo, index, caster);
}
SpellInfo const* Creature::reachWithSpellAttack(Unit* victim)
+14 -3
View File
@@ -105,12 +105,11 @@ public:
bool isCanInteractWithBattleMaster(Player* player, bool msg) const;
bool CanResetTalents(Player* player) const;
bool CanCreatureAttack(Unit const* victim, bool skipDistCheck = false) const;
void LoadSpellTemplateImmunity();
bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr) override;
[[nodiscard]] bool HasMechanicTemplateImmunity(uint32 mask) const;
[[nodiscard]] bool HasMechanicTemplateImmunity(uint64 mask) const;
// redefine Unit::IsImmunedToSpell
bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override;
bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const override;
// redefine Unit::IsImmunedToSpellEffect
[[nodiscard]] bool isElite() const
{
@@ -186,6 +185,8 @@ public:
void UpdateAttackPowerAndDamage(bool ranged = false) override;
void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) override;
void LoadTemplateImmunities(int32 creatureImmunitiesId);
void LoadSparringPct();
[[nodiscard]] float GetSparringPct() const { return _sparringPct; }
@@ -357,6 +358,14 @@ public:
[[nodiscard]] uint32 GetCurrentWaypointID() const { return m_waypointID; }
void UpdateWaypointID(uint32 wpID) { m_waypointID = wpID; }
// nodeId, pathId
std::pair<uint32, uint32> GetCurrentWaypointInfo() const { return _currentWaypointNodeInfo; }
void UpdateCurrentWaypointInfo(uint32 nodeId, uint32 pathId) { _currentWaypointNodeInfo = { nodeId, pathId }; }
bool IsFormationLeader() const;
void SignalFormationMovement();
bool IsFormationLeaderMoveAllowed() const;
void SearchFormation();
[[nodiscard]] CreatureGroup const* GetFormation() const { return m_formation; }
[[nodiscard]] CreatureGroup* GetFormation() { return m_formation; }
@@ -486,6 +495,7 @@ protected:
SpellSchoolMask m_meleeDamageSchoolMask;
uint32 m_originalEntry;
int32 _creatureImmunitiesId;
uint32 _gossipMenuId;
bool m_moveInLineOfSightDisabled;
@@ -517,6 +527,7 @@ private:
// WaypointMovementGenerator variable
uint32 m_waypointID;
uint32 m_path_id;
std::pair<uint32, uint32> _currentWaypointNodeInfo{0, 0};
// Formation variable
CreatureGroup* m_formation;
@@ -22,6 +22,7 @@
#include "DatabaseEnv.h"
#include "ItemTemplate.h"
#include "LootMgr.h"
#include "SpawnData.h"
#include "Unit.h"
#include <list>
@@ -202,7 +203,6 @@ struct CreatureTemplate
float speed_swim;
float speed_flight;
float detection_range; // Detection Range for Line of Sight aggro
float scale;
uint32 rank;
uint32 dmgschool;
float DamageModifier;
@@ -237,8 +237,7 @@ struct CreatureTemplate
bool RacialLeader;
uint32 movementId;
bool RegenHealth;
uint32 MechanicImmuneMask;
uint8 SpellSchoolImmuneMask;
int32 CreatureImmunitiesId;
uint32 flags_extra;
uint32 ScriptID;
WorldPacket queryData; // pussywizard
@@ -367,33 +366,23 @@ typedef std::unordered_map<uint8, EquipmentInfo> EquipmentInfoContainerInternal;
typedef std::unordered_map<uint32, EquipmentInfoContainerInternal> EquipmentInfoContainer;
// from `creature` table
struct CreatureData
struct CreatureData : public SpawnData
{
CreatureData() = default;
ObjectGuid::LowType spawnId{ 0 }; // mod_playerbots
CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) {}
uint32 id1{0}; // entry in creature_template
uint32 id2{0}; // entry in creature_template
uint32 id3{0}; // entry in creature_template
uint16 mapid{0};
uint32 phaseMask{0};
uint32 displayid{0};
int8 equipmentId{0};
float posX{0.0f};
float posY{0.0f};
float posZ{0.0f};
float orientation{0.0f};
uint32 spawntimesecs{0};
float wander_distance{0.0f};
uint32 currentwaypoint{0};
uint32 curhealth{0};
uint32 curmana{0};
uint8 movementType{0};
uint8 spawnMask{0};
uint32 npcflag{0};
uint32 unit_flags{0}; // enum UnitFlags mask values
uint32 dynamicflags{0};
uint32 ScriptId;
bool dbData{true};
};
struct CreatureModelInfo
@@ -130,7 +130,7 @@ namespace Trainer
return nullptr;
}
bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell)
bool Trainer::CanTeachSpell(Player const* player, Spell const* trainerSpell) const
{
SpellState state = GetSpellState(player, trainerSpell);
if (state != SpellState::Available)
+1 -1
View File
@@ -70,7 +70,7 @@ namespace Trainer
[[nodiscard]] Spell const* GetSpell(uint32 spellId) const;
[[nodiscard]] std::vector<Spell> const& GetSpells() const { return _spells; }
void SendSpells(Creature* npc, Player* player, LocaleConstant locale) const;
bool CanTeachSpell(Player const* player, Spell const* trainerSpell);
bool CanTeachSpell(Player const* player, Spell const* trainerSpell) const;
void TeachSpell(Creature* npc, Player* player, uint32 spellId);
[[nodiscard]] Type GetTrainerType() const { return _type; }
@@ -1703,7 +1703,7 @@ void GameObject::Use(Unit* user)
Player* player = user->ToPlayer();
if (info->camera.cinematicId)
player->SendCinematicStart(info->camera.cinematicId);
player->GetCinematicMgr().StartCinematic(info->camera.cinematicId);
if (info->camera.eventID)
{
@@ -2142,7 +2142,32 @@ void GameObject::SendCustomAnim(uint32 anim)
SendMessageToSet(&data, true);
}
bool GameObject::IsInRange(float x, float y, float z, float radius) const
bool GameObject::IsInRange2d(float x, float y, float radius) const
{
GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId);
if (!info)
return IsWithinDist2d(x, y, radius);
float sinA = std::sin(GetOrientation());
float cosA = cos(GetOrientation());
float dx = x - GetPositionX();
float dy = y - GetPositionY();
float dist = std::sqrt(dx * dx + dy * dy);
//! Check if the distance between the 2 objects is 0, can happen if both objects are on the same position.
//! The code below this check wont crash if dist is 0 because 0/0 in float operations is valid, and returns infinite
if (G3D::fuzzyEq(dist, 0.0f))
return true;
float scale = GetObjectScale();
float sinB = dx / dist;
float cosB = dy / dist;
dx = dist * (cosA * cosB + sinA * sinB);
dy = dist * (cosA * sinB - sinA * cosB);
return dx < (info->maxX * scale) + radius && dx >(info->minX * scale) - radius
&& dy < (info->maxY * scale) + radius && dy >(info->minY * scale) - radius;
}
bool GameObject::IsInRange3d(float x, float y, float z, float radius) const
{
GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId);
if (!info)
@@ -2217,6 +2242,11 @@ void GameObject::UpdatePackedRotation()
m_packedRotation = z | (y << 21) | (x << 42);
}
bool GameObject::IsWithinSightRange(Position const& pos, float dist) const
{
return IsInRange2d(pos.GetPositionX(), pos.GetPositionY(), dist);
}
void GameObject::SetWorldRotation(G3D::Quat const& rot)
{
G3D::Quat rotation = rot;
@@ -284,7 +284,8 @@ public:
void CastSpell(Unit* target, uint32 spell);
void SendCustomAnim(uint32 anim);
[[nodiscard]] bool IsInRange(float x, float y, float z, float radius) const;
bool IsInRange2d(float x, float y, float radius) const;
bool IsInRange3d(float x, float y, float z, float radius) const;
void ModifyHealth(int32 change, Unit* attackerOrHealer = nullptr, uint32 spellId = 0);
void SetDestructibleBuildingModifyState(bool allow) { m_allowModifyDestructibleBuilding = allow; }
@@ -418,8 +419,11 @@ private:
{
//! Following check does check 3d distance
dist2compare += obj->GetObjectSize();
return IsInRange(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare);
return IsInRange3d(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare);
}
bool IsWithinSightRange(Position const& pos, float dist) const override;
GameObjectAI* m_AI;
bool m_saveStateOnDb = false;
@@ -19,6 +19,7 @@
#define GAMEOBJECTDATA_H
#include "SharedDefines.h"
#include "SpawnData.h"
#include <array>
#include <vector>
#include <string>
@@ -710,24 +711,15 @@ enum GOState
};
// from `gameobject`
struct GameObjectData
struct GameObjectData : public SpawnData
{
explicit GameObjectData() = default;
uint32 id{ 0 }; // entry in gamobject_template
uint16 mapid{ 0 };
uint32 phaseMask{ 0 };
float posX{ 0.0f };
float posY{ 0.0f };
float posZ{ 0.0f };
float orientation{ 0.0f };
GameObjectData() : SpawnData(SPAWN_TYPE_GAMEOBJECT) {}
uint32 id{0}; // entry in gameobject_template
G3D::Quat rotation;
int32 spawntimesecs{ 0 };
uint32 ScriptId;
uint32 animprogress{ 0 };
GOState go_state{ GO_STATE_ACTIVE };
uint8 spawnMask{ 0 };
uint8 artKit{ 0 };
bool dbData{ true };
int32 spawntimesecs{0};
uint32 animprogress{0};
GOState go_state{GO_STATE_ACTIVE};
uint8 artKit{0};
};
#endif // GameObjectData_h__
+11 -6
View File
@@ -1362,6 +1362,12 @@ bool WorldObject::IsWithinDist2d(const Position* pos, float dist) const
return IsInDist2d(pos, dist + GetObjectSize());
}
// Visibility always uses 2d checks, factors in self-object size already. Gameobjects will override this for custom calc
bool WorldObject::IsWithinSightRange(Position const& pos, float dist) const
{
return IsInDist2d(&pos, dist + GetObjectSize());
}
// use only if you will sure about placing both object at same map
bool WorldObject::IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D, bool incOwnRadius, bool incTargetRadius) const
{
@@ -1674,7 +1680,7 @@ float WorldObject::GetGridActivationRange() const
{
if (ToPlayer())
{
if (ToPlayer()->GetCinematicMgr()->IsOnCinematic())
if (ToPlayer()->GetCinematicMgr().IsOnCinematic())
{
return DEFAULT_VISIBILITY_INSTANCE;
}
@@ -1725,7 +1731,7 @@ float WorldObject::GetSightRange(WorldObject const* target) const
return VISIBILITY_DIST_WINTERGRASP;
else if (target->IsVisibilityOverridden())
return target->GetVisibilityOverrideDistance();
else if (ToPlayer()->GetCinematicMgr()->IsOnCinematic())
else if (ToPlayer()->GetCinematicMgr().IsOnCinematic())
return DEFAULT_VISIBILITY_INSTANCE;
else
return GetMap()->GetVisibilityRange();
@@ -1794,10 +1800,10 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo
if (distanceCheck)
{
bool corpseCheck = false;
WorldObject const* viewpoint = this;
Position const* sightPosition = this;
if (Player const* thisPlayer = ToPlayer())
{
viewpoint = thisPlayer->GetSeer();
sightPosition = &thisPlayer->GetSightPosition();
if (Creature const* creature = obj->ToCreature())
{
@@ -1843,8 +1849,7 @@ bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, boo
return false;
}
// Xinef: check reversely obj vs viewpoint, object could be a gameObject which overrides _IsWithinDist function to include gameobject size
if (!corpseCheck && !viewpoint->IsWithinDist(obj, GetSightRange(obj), false))
if (!corpseCheck && !obj->IsWithinSightRange(*sightPosition, GetSightRange(obj)))
return false;
}
+1
View File
@@ -543,6 +543,7 @@ public:
bool IsWithinDist3d(const Position* pos, float dist) const;
[[nodiscard]] bool IsWithinDist2d(float x, float y, float dist) const;
bool IsWithinDist2d(const Position* pos, float dist) const;
virtual bool IsWithinSightRange(Position const& pos, float dist) const;
// use only if you will sure about placing both object at same map
bool IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D = true, bool incOwnRadius = true, bool incTargetRadius = true) const;
bool IsWithinDistInMap(WorldObject const* obj, float dist2compare, bool is3D = true, bool incOwnRadius = true, bool incTargetRadius = true) const;
+1 -1
View File
@@ -491,7 +491,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c
}
// must be after SetMinion (owner guid check)
//LoadTemplateImmunities();
LoadTemplateImmunities(0);
//LoadMechanicTemplateImmunity();
m_loading = false;
});
@@ -17,92 +17,84 @@
#include "CinematicMgr.h"
#include "M2Stores.h"
#include "MotionMaster.h"
#include "Player.h"
CinematicMgr::CinematicMgr(Player* playerref)
CinematicMgr::CinematicMgr(Player& player) : _player(player)
{
player = playerref;
m_cinematicDiff = 0;
m_lastCinematicCheck = 0;
m_activeCinematicCameraId = 0;
m_cinematicLength = 0;
m_cinematicCamera = nullptr;
m_remoteSightPosition = Position(0.0f, 0.0f, 0.0f);
m_CinematicObject = nullptr;
_cinematicDiff = 0;
_activeCinematicCameraId = 0;
_cinematicCamera = nullptr;
_remoteSightPosition = Position(0.0f, 0.0f, 0.0f);
_cinematicUpdateTimer.SetInterval(CINEMATIC_UPDATEDIFF);
}
CinematicMgr::~CinematicMgr()
void CinematicMgr::StartCinematic(uint32 const cinematicSequenceId)
{
if (m_cinematicCamera && m_activeCinematicCameraId)
EndCinematic();
_player.SendCinematicStart(cinematicSequenceId);
if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(cinematicSequenceId))
SetActiveCinematicCamera(sequence->cinematicCamera);
}
void CinematicMgr::BeginCinematic()
void CinematicMgr::StartCinematicCamera()
{
// Sanity check for active camera set
if (m_activeCinematicCameraId == 0)
if (_activeCinematicCameraId == 0)
return;
if (std::vector<FlyByCamera> const* flyByCameras = GetFlyByCameras(m_activeCinematicCameraId))
if (std::vector<FlyByCamera> const* flyByCameras = GetFlyByCameras(_activeCinematicCameraId))
{
// Initialize diff, and set camera
m_cinematicDiff = 0;
m_cinematicCamera = flyByCameras;
_cinematicDiff = 0;
_cinematicUpdateTimer.Reset();
_cinematicCamera = flyByCameras;
auto camitr = m_cinematicCamera->begin();
if (camitr != m_cinematicCamera->end())
auto camitr = _cinematicCamera->begin();
if (camitr != _cinematicCamera->end())
{
Position const& pos = camitr->locations;
if (!pos.IsPositionValid())
return;
player->GetMap()->LoadGrid(pos.GetPositionX(), pos.GetPositionY());
m_CinematicObject = player->SummonCreature(VISUAL_WAYPOINT, pos.m_positionX, pos.m_positionY, pos.m_positionZ, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 120000);
if (m_CinematicObject)
{
m_CinematicObject->setActive(true);
player->SetViewpoint(m_CinematicObject, true);
}
// Get cinematic length
m_cinematicLength = flyByCameras->back().timeStamp;
_player.GetMap()->LoadGridsInRange(pos, MAX_VISIBILITY_DISTANCE);
_remoteSightPosition.Relocate(pos);
_player.UpdateVisibilityForPlayer();
}
}
}
void CinematicMgr::EndCinematic()
{
if (m_activeCinematicCameraId == 0)
if (_activeCinematicCameraId == 0)
return;
m_cinematicDiff = 0;
m_cinematicCamera = nullptr;
m_activeCinematicCameraId = 0;
if (m_CinematicObject)
{
if (WorldObject* vpObject = player->GetViewpoint())
if (vpObject == m_CinematicObject)
player->SetViewpoint(m_CinematicObject, false);
m_CinematicObject->AddObjectToRemoveList();
}
_cinematicDiff = 0;
_cinematicUpdateTimer.Reset();
_cinematicCamera = nullptr;
_activeCinematicCameraId = 0;
_player.UpdateVisibilityForPlayer();
}
void CinematicMgr::UpdateCinematicLocation(uint32 /*diff*/)
void CinematicMgr::UpdateCinematic(uint32 const diff)
{
if (m_activeCinematicCameraId == 0 || !m_cinematicCamera || m_cinematicCamera->size() == 0)
if (_activeCinematicCameraId == 0 || !_cinematicCamera || _cinematicCamera->empty())
return;
_cinematicDiff += diff;
_cinematicUpdateTimer.Update(diff);
if (!_cinematicUpdateTimer.Passed())
return;
_cinematicUpdateTimer.Reset();
Position lastPosition;
uint32 lastTimestamp = 0;
Position nextPosition;
uint32 nextTimestamp = 0;
// Obtain direction of travel
for (FlyByCamera cam : *m_cinematicCamera)
for (FlyByCamera const& cam : *_cinematicCamera)
{
if (cam.timeStamp > m_cinematicDiff)
if (cam.timeStamp > _cinematicDiff)
{
nextPosition.Relocate(cam.locations);
nextTimestamp = cam.timeStamp;
@@ -113,26 +105,25 @@ void CinematicMgr::UpdateCinematicLocation(uint32 /*diff*/)
}
float angle = lastPosition.GetAbsoluteAngle(&nextPosition);
angle -= lastPosition.GetOrientation();
if (angle < 0)
angle += 2 * float(M_PI);
angle = Position::NormalizeOrientation(angle);
// Look for position around 2 second ahead of us.
int32 workDiff = m_cinematicDiff;
int32 workDiff = _cinematicDiff;
// Modify result based on camera direction (Humans for example, have the camera point behind)
workDiff += static_cast<int32>(float(CINEMATIC_LOOKAHEAD) * cos(angle));
// Get an iterator to the last entry in the cameras, to make sure we don't go beyond the end
auto endItr = m_cinematicCamera->rbegin();
if (endItr != m_cinematicCamera->rend() && workDiff > static_cast<int32>(endItr->timeStamp))
auto endItr = _cinematicCamera->rbegin();
if (endItr != _cinematicCamera->rend() && workDiff > static_cast<int32>(endItr->timeStamp))
workDiff = endItr->timeStamp;
// Never try to go back in time before the start of cinematic!
if (workDiff < 0)
workDiff = m_cinematicDiff;
workDiff = _cinematicDiff;
// Obtain the previous and next waypoint based on timestamp
for (FlyByCamera cam : *m_cinematicCamera)
for (FlyByCamera const& cam : *_cinematicCamera)
{
if (static_cast<int32>(cam.timeStamp) >= workDiff)
{
@@ -157,12 +148,15 @@ void CinematicMgr::UpdateCinematicLocation(uint32 /*diff*/)
Position interPosition(lastPosition.m_positionX + (xDiff * (float(interDiff) / float(timeDiff))), lastPosition.m_positionY +
(yDiff * (float(interDiff) / float(timeDiff))), lastPosition.m_positionZ + (zDiff * (float(interDiff) / float(timeDiff))));
// Advance (at speed) to this position. The remote sight object is used
// to send update information to player in cinematic
if (m_CinematicObject && interPosition.IsPositionValid())
m_CinematicObject->MonsterMoveWithSpeed(interPosition.m_positionX, interPosition.m_positionY, interPosition.m_positionZ, 500.0f);
// Advance _remoteSightPosition to new position
if (interPosition.IsPositionValid())
{
_player.GetMap()->LoadGridsInRange(interPosition, MAX_VISIBILITY_DISTANCE);
_remoteSightPosition.Relocate(interPosition);
_player.UpdateVisibilityForPlayer();
}
// If we never received an end packet 10 seconds after the final timestamp then force an end
if (m_cinematicDiff > m_cinematicLength + 10 * IN_MILLISECONDS)
if (_cinematicDiff > _cinematicCamera->back().timeStamp + 10 * IN_MILLISECONDS)
EndCinematic();
}
+16 -20
View File
@@ -20,7 +20,7 @@
#include "Define.h"
#include "Position.h"
#include "TemporarySummon.h"
#include "Timer.h"
#include <vector>
constexpr auto CINEMATIC_UPDATEDIFF = 500;
@@ -31,31 +31,27 @@ struct FlyByCamera;
class AC_GAME_API CinematicMgr
{
friend class Player;
public:
explicit CinematicMgr(Player* playerref);
~CinematicMgr();
explicit CinematicMgr(Player& player);
~CinematicMgr() = default;
// Cinematic camera data and remote sight functions
uint32 GetActiveCinematicCamera() const { return m_activeCinematicCameraId; }
void SetActiveCinematicCamera(uint32 cinematicCameraId = 0) { m_activeCinematicCameraId = cinematicCameraId; }
bool IsOnCinematic() const { return (m_cinematicCamera != nullptr); }
void BeginCinematic();
void StartCinematic(uint32 const cinematicSequenceId);
uint32 GetActiveCinematicCamera() const { return _activeCinematicCameraId; }
void SetActiveCinematicCamera(uint32 cinematicCameraId = 0) { _activeCinematicCameraId = cinematicCameraId; }
bool IsOnCinematic() const { return (_cinematicCamera != nullptr); }
void StartCinematicCamera();
void EndCinematic();
void UpdateCinematicLocation(uint32 diff);
void UpdateCinematic(uint32 const diff);
Position const& GetRemoteSightPosition() const { return _remoteSightPosition; }
private:
// Remote location information
Player* player;
protected:
uint32 m_cinematicDiff;
uint32 m_lastCinematicCheck;
uint32 m_activeCinematicCameraId;
uint32 m_cinematicLength;
std::vector<FlyByCamera> const* m_cinematicCamera;
Position m_remoteSightPosition;
TempSummon* m_CinematicObject;
Player& _player;
uint32 _cinematicDiff;
uint32 _activeCinematicCameraId;
std::vector<FlyByCamera> const* _cinematicCamera;
Position _remoteSightPosition;
IntervalTimer _cinematicUpdateTimer;
};
#endif
+53 -16
View File
@@ -154,7 +154,7 @@ static uint32 copseReclaimDelay[MAX_DEATH_COUNT] = { 30, 60, 120 };
#ifdef _MSC_VER
#pragma warning(disable:4355)
#endif
Player::Player(WorldSession* session): Unit(), m_mover(this)
Player::Player(WorldSession* session): Unit(), m_mover(this), _cinematicMgr(*this)
{
#ifdef _MSC_VER
#pragma warning(default:4355)
@@ -395,8 +395,6 @@ Player::Player(WorldSession* session): Unit(), m_mover(this)
m_creationTime = 0s;
_cinematicMgr = new CinematicMgr(this);
m_achievementMgr = new AchievementMgr(this);
m_reputationMgr = new ReputationMgr(this);
@@ -462,7 +460,6 @@ Player::~Player()
delete m_runes;
delete m_achievementMgr;
delete m_reputationMgr;
delete _cinematicMgr;
sWorldSessionMgr->DecreasePlayerCount();
@@ -772,7 +769,9 @@ bool Player::IsImmuneToEnvironmentalDamage()
uint32 Player::EnvironmentalDamage(EnviromentalDamage type, uint32 damage)
{
if (IsImmuneToEnvironmentalDamage())
// DAMAGE_FALL_TO_VOID bypasses all immunities (e.g. Divine Shield) to prevent
// players from being stuck infinitely falling below the map
if (type != DAMAGE_FALL_TO_VOID && IsImmuneToEnvironmentalDamage())
return 0;
// Absorb, resist some environmental damage type
@@ -5697,10 +5696,6 @@ void Player::SendCinematicStart(uint32 CinematicSequenceId) const
WorldPacket data(SMSG_TRIGGER_CINEMATIC, 4);
data << uint32(CinematicSequenceId);
SendDirectMessage(&data);
if (CinematicSequencesEntry const* sequence = sCinematicSequencesStore.LookupEntry(CinematicSequenceId))
{
_cinematicMgr->SetActiveCinematicCamera(sequence->cinematicCamera);
}
}
void Player::SendMovieStart(uint32 MovieId)
@@ -10457,7 +10452,7 @@ void Player::SendTaxiNodeStatusMultiple()
if (!creature->HasNpcFlag(UNIT_NPC_FLAG_FLIGHTMASTER))
return;
uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetMapId(), GetTeamId());
uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(*creature, GetTeamId(true));
if (!nearestNode)
return;
@@ -12515,21 +12510,52 @@ bool Player::HasItemFitToSpellRequirements(SpellInfo const* spellInfo, Item cons
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
// Keep active non-passive auras (e.g. Bladestorm) when disarmed
if (!spellInfo->IsPassive())
{
bool hasWeaponInSlot = false;
for (uint8 i = EQUIPMENT_SLOT_MAINHAND; i < EQUIPMENT_SLOT_TABARD; ++i)
if (Item* item = GetItemByPos(INVENTORY_SLOT_BAG_0, i))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
{
hasWeaponInSlot = true;
break;
}
if (!hasWeaponInSlot)
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (spellInfo->Effects[i].IsAura())
return true;
}
break;
}
case ITEM_CLASS_ARMOR:
{
// most used check: shield only
if (spellInfo->EquippedItemSubClassMask & (1 << ITEM_SUBCLASS_ARMOR_SHIELD))
{
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
// Keep active non-passive auras (e.g. Shield Wall) when disarmed
Item* offhand = GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND);
if (!spellInfo->IsPassive() && offhand && offhand != ignoreItem)
{
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (spellInfo->Effects[i].IsAura())
return true;
}
}
// tabard not have dependent spells
for (uint8 i = EQUIPMENT_SLOT_START; i < EQUIPMENT_SLOT_MAINHAND; ++i)
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, i))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
// shields can be equipped to offhand slot
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_OFFHAND))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
return true;
// ranged slot can have some armor subclasses
if (Item* item = GetUseableItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED))
if (item != ignoreItem && item->IsFitToSpellRequirements(spellInfo))
@@ -13210,6 +13236,14 @@ WorldObject* Player::GetViewpoint() const
return nullptr;
}
Position const& Player::GetSightPosition() const
{
if (_cinematicMgr.IsOnCinematic())
return _cinematicMgr.GetRemoteSightPosition();
return *m_seer;
}
bool Player::CanUseBattlegroundObject(GameObject* gameobject) const
{
// It is possible to call this method will a nullptr pointer, only skipping faction check.
@@ -16279,7 +16313,10 @@ bool Player::IsWorldObjectOutOfSightRange(WorldObject const* target) const
}
// Check if out of range
return !m_seer->IsWithinDist(target, GetSightRange(target), false);
if (!target->IsWithinSightRange(GetSightPosition(), GetSightRange(target)))
return true;
return false;
}
std::string Player::GetPlayerName()
+4 -2
View File
@@ -1394,7 +1394,8 @@ public:
[[nodiscard]] TradeData* GetTradeData() const { return m_trade; }
void TradeCancel(bool sendback, TradeStatus status = TRADE_STATUS_TRADE_CANCELED);
CinematicMgr* GetCinematicMgr() const { return _cinematicMgr; }
CinematicMgr& GetCinematicMgr() { return _cinematicMgr; }
CinematicMgr const& GetCinematicMgr() const { return _cinematicMgr; }
void UpdateEnchantTime(uint32 time);
void UpdateSoulboundTradeItems();
@@ -2388,6 +2389,7 @@ public:
WorldObject* GetSeer() const { return m_seer; }
void SetViewpoint(WorldObject* target, bool apply);
[[nodiscard]] WorldObject* GetViewpoint() const;
Position const& GetSightPosition() const;
void StopCastingCharm(Aura* except = nullptr);
void StopCastingBindSight(Aura* except = nullptr);
@@ -2984,7 +2986,7 @@ private:
Item* _StoreItem(uint16 pos, Item* pItem, uint32 count, bool clone, bool update);
Item* _LoadItem(CharacterDatabaseTransaction trans, uint32 zoneId, uint32 timeDiff, Field* fields);
CinematicMgr* _cinematicMgr;
CinematicMgr _cinematicMgr;
typedef GuidSet RefundableItemsSet;
RefundableItemsSet m_refundableItems;
@@ -68,14 +68,8 @@ void Player::Update(uint32 p_time)
m_nextMailDelivereTime = time_t(0);
}
// Update cinematic location, if 500ms have passed and we're doing a
// cinematic now.
_cinematicMgr->m_cinematicDiff += p_time;
if (_cinematicMgr->m_cinematicCamera && _cinematicMgr->m_activeCinematicCameraId && GetMSTimeDiffToNow(_cinematicMgr->m_lastCinematicCheck) > CINEMATIC_UPDATEDIFF)
{
_cinematicMgr->m_lastCinematicCheck = getMSTime();
_cinematicMgr->UpdateCinematicLocation(p_time);
}
// Update cinematic camera (if needed)
_cinematicMgr.UpdateCinematic(p_time);
// used to implement delayed far teleports
SetMustDelayTeleport(true);
@@ -1609,8 +1603,8 @@ void Player::UpdateVisibilityForPlayer(bool mapChange)
m_seer = this;
Acore::VisibleNotifier notifier(*this, mapChange);
Cell::VisitObjects(m_seer, notifier, GetSightRange());
Cell::VisitFarVisibleObjects(m_seer, notifier, VISIBILITY_DISTANCE_GIGANTIC);
Cell::VisitObjects(GetSightPosition().GetPositionX(), GetSightPosition().GetPositionY(), GetMap(), notifier, GetSightRange());
Cell::VisitFarVisibleObjects(GetSightPosition().GetPositionX(), GetSightPosition().GetPositionY(), GetMap(), notifier, VISIBILITY_DISTANCE_GIGANTIC);
notifier.SendToSelf();
if (mapChange)
+2 -2
View File
@@ -179,7 +179,7 @@ void Totem::UnSummon(Milliseconds msTime)
AddObjectToRemoveList();
}
bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const
bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const
{
// xinef: immune to all positive spells, except of stoneclaw totem absorb, sentry totem bind sight and intervene
// totems positive spells have unit_caster target
@@ -209,5 +209,5 @@ bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) con
break;
}
return Creature::IsImmunedToSpellEffect(spellInfo, index);
return Creature::IsImmunedToSpellEffect(spellInfo, index, caster);
}
+1 -1
View File
@@ -70,7 +70,7 @@ public:
void UpdateAttackPowerAndDamage(bool /*ranged*/) override {}
void UpdateDamagePhysical(WeaponAttackType /*attType*/) override {}
bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override;
bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const override;
protected:
TotemType m_type;
+279 -235
View File
@@ -72,6 +72,7 @@
#include "Vehicle.h"
#include "World.h"
#include "WorldPacket.h"
#include <algorithm>
#include <cmath>
float baseMoveSpeed[MAX_MOVE_TYPE] =
@@ -125,6 +126,28 @@ DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex)
m_damageType(DIRECT_DAMAGE), m_attackType(dmgInfo.attackType), m_absorb(dmgInfo.damages[damageIndex].absorb), m_resist(dmgInfo.damages[damageIndex].resist), m_block(dmgInfo.blocked_amount),
m_cleanDamage(dmgInfo.cleanDamage), m_hitMask(0)
{
switch (dmgInfo.TargetState)
{
case VICTIMSTATE_IS_IMMUNE:
m_hitMask |= PROC_HIT_IMMUNE;
break;
case VICTIMSTATE_BLOCKS:
m_hitMask |= PROC_HIT_FULL_BLOCK;
break;
}
if (dmgInfo.HitInfo & (HITINFO_PARTIAL_ABSORB | HITINFO_FULL_ABSORB))
m_hitMask |= PROC_HIT_ABSORB;
if (dmgInfo.HitInfo & HITINFO_FULL_RESIST)
m_hitMask |= PROC_HIT_FULL_RESIST;
if (m_block)
m_hitMask |= PROC_HIT_BLOCK;
bool const damageNullified = (dmgInfo.HitInfo & (HITINFO_FULL_ABSORB | HITINFO_FULL_RESIST)) != 0
|| (m_hitMask & (PROC_HIT_IMMUNE | PROC_HIT_FULL_BLOCK)) != 0;
switch (dmgInfo.hitOutCome)
{
case MELEE_HIT_MISS:
@@ -140,25 +163,19 @@ DamageInfo::DamageInfo(CalcDamageInfo const& dmgInfo, uint8 damageIndex)
m_hitMask |= PROC_HIT_EVADE;
break;
case MELEE_HIT_BLOCK:
m_hitMask |= PROC_HIT_BLOCK;
[[fallthrough]];
case MELEE_HIT_CRUSHING:
case MELEE_HIT_GLANCING:
case MELEE_HIT_NORMAL:
m_hitMask |= PROC_HIT_NORMAL;
if (!damageNullified)
m_hitMask |= PROC_HIT_NORMAL;
break;
case MELEE_HIT_CRIT:
m_hitMask |= PROC_HIT_CRITICAL;
if (!damageNullified)
m_hitMask |= PROC_HIT_CRITICAL;
break;
default:
break;
}
if (dmgInfo.damages[damageIndex].absorb)
m_hitMask |= PROC_HIT_ABSORB;
if (dmgInfo.blocked_amount)
m_hitMask |= (dmgInfo.damages[damageIndex].damage == dmgInfo.blocked_amount) ? PROC_HIT_FULL_BLOCK : PROC_HIT_BLOCK;
}
DamageInfo::DamageInfo(SpellNonMeleeDamage const& spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, uint32 hitMask)
@@ -372,9 +389,6 @@ Unit::Unit() : WorldObject(),
m_transform = 0;
m_canModifyStats = false;
for (uint8 i = 0; i < MAX_SPELL_IMMUNITY; ++i)
m_spellImmune[i].clear();
for (uint8 i = 0; i < UNIT_MOD_END; ++i)
{
m_auraFlatModifiersGroup[i][BASE_VALUE] = 0.0f;
@@ -891,6 +905,48 @@ bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint
return false;
}
bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster /*= nullptr*/)
{
if (!spellInfo)
return false;
bool immuneToAllEffects = true;
bool hasCheckedEffect = false;
for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i)
{
if (!(effectMask & (1u << i)))
continue;
if (!spellInfo->Effects[i].IsEffect())
continue;
hasCheckedEffect = true;
if (IsImmunedToSpellEffect(spellInfo, i, caster))
{
if (spellInfo->HasAttribute(SPELL_ATTR4_NO_PARTIAL_IMMUNITY))
return true;
continue;
}
immuneToAllEffects = false;
break;
}
if (hasCheckedEffect && immuneToAllEffects)
return true;
if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
if (IsImmunedToSchool(spellInfo))
return true;
}
return false;
}
bool Unit::HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura) const
{
AuraEffectList const& auras = GetAuraEffectsByType(type);
@@ -2075,7 +2131,7 @@ void Unit::DealDamageShieldDamage(Unit* victim)
}
// ...or immuned
if (IsImmunedToDamageOrSchool(i_spellProto))
if (IsImmunedToDamage(victim, i_spellProto))
{
victim->SendSpellDamageImmune(this, i_spellProto->Id);
continue;
@@ -3203,7 +3259,7 @@ void Unit::SendMeleeAttackStop(Unit* victim)
bool Unit::isSpellBlocked(Unit* victim, SpellInfo const* spellProto, WeaponAttackType attackType)
{
// These spells can't be blocked
if (spellProto && spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE))
if (spellProto && (spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE) || spellProto->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)))
return false;
if (victim->HasIgnoreHitDirectionAura() || victim->HasInArc(M_PI, this))
@@ -3571,6 +3627,9 @@ SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo
// Resist
SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool CanReflect)
{
if (spell->HasAttribute(SPELL_ATTR3_ALWAYS_HIT))
return SPELL_MISS_NONE;
// Check for immune
if (victim->IsImmunedToSpell(spell))
return SPELL_MISS_IMMUNE;
@@ -3581,11 +3640,6 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool Ca
&& (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell
return SPELL_MISS_NONE;
// Check for immune
// xinef: check for school immunity only
if (victim->IsImmunedToSchool(spell))
return SPELL_MISS_IMMUNE;
if (this == victim)
return SPELL_MISS_NONE;
@@ -3650,13 +3704,17 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, Spell const* spell, bool CanRef
return SPELL_MISS_NONE;
}
// Check for immune
// xinef: check for school immunity only
if (victim->IsImmunedToSchool(spell))
// Check for immune to spell effects (includes school, mechanics, state, dispel immunities)
if (victim->IsImmunedToSpell(spellInfo, MAX_EFFECT_MASK, this))
{
return SPELL_MISS_IMMUNE;
}
// Damage immunity is only checked if the spell has damage effects, this immunity must not prevent aura apply
// returns SPELL_MISS_IMMUNE in that case, for other spells, the SMSG_SPELL_GO must show hit
if (spellInfo->HasOnlyDamageEffects() && victim->IsImmunedToDamage(this, spellInfo))
return SPELL_MISS_IMMUNE;
if (this == victim)
{
return SPELL_MISS_NONE;
@@ -5447,7 +5505,7 @@ void Unit::RemoveMovementImpairingAuras(bool withRoot)
}
}
void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode, uint32 except)
void Unit::RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemode, uint32 except)
{
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
@@ -5466,7 +5524,7 @@ void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemo
void Unit::RemoveAurasByShapeShift()
{
uint32 mechanic_mask = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT);
uint64 mechanic_mask = (UI64LIT(1) << MECHANIC_SNARE) | (UI64LIT(1) << MECHANIC_ROOT);
for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();)
{
Aura const* aura = iter->second->GetBase();
@@ -6015,17 +6073,17 @@ bool Unit::HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid)
return false;
}
bool Unit::HasAuraWithMechanic(uint32 mechanicMask) const
bool Unit::HasAuraWithMechanic(uint64 mechanicMask) const
{
for (AuraApplicationMap::const_iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end(); ++iter)
{
SpellInfo const* spellInfo = iter->second->GetBase()->GetSpellInfo();
if (spellInfo->Mechanic && (mechanicMask & (1 << spellInfo->Mechanic)))
if (spellInfo->Mechanic && (mechanicMask & (UI64LIT(1) << spellInfo->Mechanic)))
return true;
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
if (iter->second->HasEffect(i) && spellInfo->Effects[i].Effect && spellInfo->Effects[i].Mechanic)
if (mechanicMask & (1 << spellInfo->Effects[i].Mechanic))
if (mechanicMask & (UI64LIT(1) << spellInfo->Effects[i].Mechanic))
return true;
}
@@ -7339,19 +7397,22 @@ bool Unit::Attack(Unit* victim, bool meleeAttack)
// set position before any AI calls/assistance
//if (IsCreature())
// ToCreature()->SetCombatStartPosition(GetPositionX(), GetPositionY(), GetPositionZ());
if (creature && !IsControlledByPlayer())
if (creature)
{
EngageWithTarget(victim);
creature->SendAIReaction(AI_REACTION_HOSTILE);
if (!IsControlledByPlayer())
{
creature->SendAIReaction(AI_REACTION_HOSTILE);
/// @todo: Implement aggro range, detection range and assistance range templates
if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE)))
creature->CallAssistance();
/// @todo: Implement aggro range, detection range and assistance range templates
if (!(creature->HasFlagsExtra(CREATURE_FLAG_EXTRA_DONT_CALL_ASSISTANCE)))
creature->CallAssistance();
creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD));
creature->SetAssistanceTimer(sWorld->getIntConfig(CONFIG_CREATURE_FAMILY_ASSISTANCE_PERIOD));
SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
SetUInt32Value(UNIT_NPC_EMOTESTATE, EMOTE_ONESHOT_NONE);
}
}
// delay offhand weapon attack by 50% of the base attack time
@@ -8836,7 +8897,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui
});
}
if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask())
if (uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask())
{
int32 modifierMax = 0;
int32 modifierMin = 0;
@@ -8851,7 +8912,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui
if (!caster || caster->GetGUID() != (*i)->GetCasterGUID())
continue;
if (mechanicMask & uint32(1 << (*i)->GetMiscValue()))
if (mechanicMask & uint64(UI64LIT(1) << (*i)->GetMiscValue()))
{
if ((*i)->GetAmount() > 0)
{
@@ -9742,107 +9803,90 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask)
return AdvertisedBenefit;
}
bool Unit::IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const
uint32 Unit::GetDamageImmunityMask() const
{
if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE)
{
return false;
}
// If m_immuneToDamage type contain magic, IMMUNE damage.
SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE];
for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr)
if ((itr->type & meleeSchoolMask) == meleeSchoolMask)
return true;
return false;
uint32 mask = 0;
SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE];
for (auto const& [immunitySchoolMask, _] : damageList)
mask |= immunitySchoolMask;
return mask;
}
bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const
bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const
{
if (!spellInfo)
{
return false;
}
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura())
{
return false;
}
if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
return false;
}
uint32 schoolMask = spellInfo->GetSchoolMask();
if (schoolMask == SPELL_SCHOOL_MASK_NONE)
{
return false;
}
// If m_immuneToDamage type contain magic, IMMUNE damage.
SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE];
for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr)
if ((itr->type & schoolMask) == schoolMask)
// If m_immuneToSchool type contains all requested schools, IMMUNE damage.
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto const& itr : schoolList)
if (IsImmuneMaskFully(SpellSchoolMask(itr.first), schoolMask))
return true;
return false;
// If m_immuneToDamage type contains all requested schools, IMMUNE damage.
SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE];
return std::ranges::any_of(damageList, [schoolMask](auto const& immune)
{
return Unit::IsImmuneMaskFully(SpellSchoolMask(immune.first), schoolMask);
});
}
bool Unit::IsImmunedToDamage(Spell const* spell) const
bool Unit::IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const
{
SpellInfo const* spellInfo = spell->GetSpellInfo();
if (!spellInfo)
{
return false;
}
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura())
{
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
return false;
}
if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
return false;
}
uint32 schoolMask = spell->GetSpellSchoolMask();
SpellSchoolMask schoolMask = SpellSchoolMask(spellInfo->GetSchoolMask());
if (schoolMask == SPELL_SCHOOL_MASK_NONE)
{
return false;
}
// If m_immuneToDamage type contain magic, IMMUNE damage.
SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE];
for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr)
auto hasImmunity = [&](SpellImmuneContainer const& container)
{
if ((itr->type & schoolMask) == schoolMask)
uint32 schoolImmunityMask = 0;
for (auto const& [immunitySchoolMask, immunityAuraId] : container)
{
return true;
SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId);
if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this))
continue;
if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo))
continue;
schoolImmunityMask |= immunitySchoolMask;
}
}
return false;
}
bool Unit::IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const
{
if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE)
{
return false;
}
// We need to be immune to all types
return (schoolImmunityMask & schoolMask) == schoolMask;
};
// If m_immuneToSchool type contain this school type, IMMUNE damage.
SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr)
if ((itr->type & meleeSchoolMask) == meleeSchoolMask)
return true;
if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL]))
return true;
// If m_immuneToDamage type contain magic, IMMUNE damage.
if (hasImmunity(m_spellImmune[IMMUNITY_DAMAGE]))
return true;
return false;
}
bool Unit::IsImmunedToSchool(SpellSchoolMask schoolMask) const
{
if (schoolMask == SPELL_SCHOOL_MASK_NONE)
return false;
// Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered by at least one immunity
// (e.g., Anti-Magic Shell provides school immunity to all magic schools)
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
return std::ranges::any_of(schoolList, [schoolMask](auto const& immune)
{
return (immune.first & schoolMask) == schoolMask;
});
}
bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const
{
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura())
@@ -9856,10 +9900,10 @@ bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const
if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471)
{
// If m_immuneToSchool type contain this school type, IMMUNE damage.
SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr)
if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId)))
// Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered and spell can't pierce
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second)))
return true;
}
@@ -9882,11 +9926,11 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const
if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471)
{
// If m_immuneToSchool type contain this school type, IMMUNE damage.
SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr)
// Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered and spell can't pierce
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
{
if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId)))
if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second)))
{
return true;
}
@@ -9896,19 +9940,49 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const
return false;
}
bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const
bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask schoolMask) const
{
if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE)
if (schoolMask == SPELL_SCHOOL_MASK_NONE)
{
return false;
}
return IsImmunedToDamage(meleeSchoolMask) || IsImmunedToSchool(meleeSchoolMask);
return IsImmunedToDamage(schoolMask) || IsImmunedToSchool(schoolMask);
}
bool Unit::IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const
bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spellInfo) const
{
return IsImmunedToDamage(spellInfo) || IsImmunedToSchool(spellInfo);
if (!spellInfo)
return false;
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
return false;
SpellSchoolMask schoolMask = SpellSchoolMask(spellInfo->GetSchoolMask());
if (schoolMask == SPELL_SCHOOL_MASK_NONE)
return false;
auto hasImmunity = [&](SpellImmuneContainer const& container)
{
uint32 schoolImmunityMask = 0;
for (auto const& [immunitySchoolMask, immunityAuraId] : container)
{
SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId);
if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this))
continue;
schoolImmunityMask |= immunitySchoolMask;
}
// We need to be immune to all types
return (schoolImmunityMask & schoolMask) == schoolMask;
};
// If m_immuneToSchool type contain this school type, IMMUNE periodic tick.
if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL]))
return true;
return false;
}
bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
@@ -9917,39 +9991,35 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
return false;
// Single spell immunity.
SpellImmuneList const& idList = m_spellImmune[IMMUNITY_ID];
for (SpellImmuneList::const_iterator itr = idList.begin(); itr != idList.end(); ++itr)
if (itr->type == spellInfo->Id)
return true;
SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID];
if (idList.count(spellInfo->Id) > 0)
return true;
// xinef: my special immunity, if spellid is not on this list it means npc is immune
SpellImmuneList const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID];
SpellImmuneContainer const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID];
if (!allowIdList.empty())
{
for (SpellImmuneList::const_iterator itr = allowIdList.begin(); itr != allowIdList.end(); ++itr)
if (itr->type == spellInfo->Id)
return false;
return true;
if (allowIdList.count(spellInfo->Id) == 0)
return true;
return false;
}
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura())
return false;
if (spellInfo->Dispel)
if (uint32 dispel = spellInfo->Dispel)
{
SpellImmuneList const& dispelList = m_spellImmune[IMMUNITY_DISPEL];
for (SpellImmuneList::const_iterator itr = dispelList.begin(); itr != dispelList.end(); ++itr)
if (itr->type == spellInfo->Dispel)
return true;
SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL];
if (dispelList.count(dispel) > 0)
return true;
}
// Spells that don't have effectMechanics.
if (spellInfo->Mechanic)
if (uint32 mechanic = spellInfo->Mechanic)
{
SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr)
if (itr->type == spellInfo->Mechanic)
return true;
SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
if (mechanicList.count(mechanic) > 0)
return true;
}
bool immuneToAllEffects = true;
@@ -9974,7 +10044,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects.
return true;
if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471)
if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask();
if (spell)
@@ -9984,16 +10054,24 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
if (spellSchoolMask != SPELL_SCHOOL_MASK_NONE)
{
SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr)
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr)
{
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->spellId);
if (((itr->type & spellSchoolMask) == spellSchoolMask)
&& (!immuneSpellInfo || immuneSpellInfo->IsPositive()) && !spellInfo->IsPositive()
&& !spellInfo->CanPierceImmuneAura(immuneSpellInfo))
SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second);
if (!(itr->first & spellSchoolMask))
continue;
if (immuneSpellInfo && !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS))
{
return true;
Unit const* spellCaster = spell ? spell->GetCaster() : nullptr;
if (spellCaster && spellCaster->IsFriendlyTo(this))
continue;
}
if (spellInfo->CanPierceImmuneAura(immuneSpellInfo))
continue;
return true;
}
}
}
@@ -10001,7 +10079,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
return false;
}
bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const
bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const
{
if (!spellInfo || !spellInfo->Effects[index].IsEffect())
return false;
@@ -10013,12 +10091,16 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) cons
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura())
return false;
// Special-case handling for Vezax's Aura of Despair.
static constexpr uint32 SPELL_AURA_OF_DESPAIR_1 = 62692;
static constexpr uint32 SPELL_AURA_OF_DESPAIR_2 = 64848;
//If m_immuneToEffect type contain this effect type, IMMUNE effect.
uint32 effect = spellInfo->Effects[index].Effect;
SpellImmuneList const& effectList = m_spellImmune[IMMUNITY_EFFECT];
for (SpellImmuneList::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr)
SpellImmuneContainer const& effectList = m_spellImmune[IMMUNITY_EFFECT];
for (SpellImmuneContainer::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr)
{
if (itr->type == effect && (itr->spellId != 62692 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo))))
if (itr->first == effect && (itr->second != SPELL_AURA_OF_DESPAIR_1 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo))))
{
return true;
}
@@ -10026,41 +10108,32 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) cons
if (uint32 mechanic = spellInfo->Effects[index].Mechanic)
{
SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr)
if (itr->type == mechanic)
return true;
auto const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC];
if (mechanicList.count(mechanic) > 0)
return true;
}
if (uint32 aura = spellInfo->Effects[index].ApplyAuraName)
if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT))
{
SpellImmuneList const& list = m_spellImmune[IMMUNITY_STATE];
for (SpellImmuneList::const_iterator itr = list.begin(); itr != list.end(); ++itr)
if (uint32 aura = spellInfo->Effects[index].ApplyAuraName)
{
if (itr->type == aura && (itr->spellId != 64848 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo))))
SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE];
for (SpellImmuneContainer::const_iterator itr = list.begin(); itr != list.end(); ++itr)
{
if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT))
{
if (itr->blockType == SPELL_BLOCK_TYPE_ALL || spellInfo->IsPositive()) // xinef: added for pet scaling
{
return true;
}
}
}
}
if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
// Check for immune to application of harmful magical effects
AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL);
for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter)
{
if (/*(spellInfo->Dispel == DISPEL_MAGIC || spellInfo->Dispel == DISPEL_CURSE || spellInfo->Dispel == DISPEL_DISEASE) &&*/ // Magic debuff, xinef: all kinds?
((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school
!spellInfo->IsPositiveEffect(index) && // Harmful
spellInfo->Effects[index].Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA) // Not Persistent area auras
{
if (itr->first == aura && (itr->second != SPELL_AURA_OF_DESPAIR_2 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo))))
return true;
}
if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{
// Check for immune to application of harmful magical effects
for (AuraEffect* immuneAuraApply : GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL))
{
if (!(immuneAuraApply->GetMiscValue() & spellInfo->GetSchoolMask())) // Check school
continue;
if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || (caster && !IsFriendlyTo(caster))) // Harmful
return true;
}
}
}
@@ -10289,17 +10362,17 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT
});
// Mod damage from spell mechanic
uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask();
uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask();
// Shred, Maul - "Effects which increase Bleed damage also increase Shred damage"
if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800)
mechanicMask |= (1 << MECHANIC_BLEED);
mechanicMask |= (UI64LIT(1) << MECHANIC_BLEED);
if (mechanicMask)
{
TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool
{
if (mechanicMask & uint32(1 << (aurEff->GetMiscValue())))
if (mechanicMask & uint64(UI64LIT(1) << (aurEff->GetMiscValue())))
return true;
return false;
});
@@ -10367,51 +10440,17 @@ private:
uint32 _type;
};
void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType)
void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType /*blockType*/)
{
if (apply)
{
// xinef: immunities with spellId 0 are intended to be applied only once (script purposes mosty)
if (spellId == 0 && std::find_if(m_spellImmune[op].begin(), m_spellImmune[op].end(), spellIdImmunityPredicate(type)) != m_spellImmune[op].end())
return;
SpellImmune immune;
immune.spellId = spellId;
immune.type = type;
immune.blockType = blockType;
m_spellImmune[op].push_back(std::move(immune));
}
m_spellImmune[op].emplace(type, spellId);
else
{
for (SpellImmuneList::iterator itr = m_spellImmune[op].begin(); itr != m_spellImmune[op].end(); ++itr)
auto bounds = m_spellImmune[op].equal_range(type);
for (auto itr = bounds.first; itr != bounds.second;)
{
if (itr->spellId == spellId && itr->type == type)
{
m_spellImmune[op].erase(itr);
break;
}
}
}
}
void Unit::ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply)
{
ApplySpellImmune(spellProto->Id, IMMUNITY_DISPEL, type, apply);
if (apply && spellProto->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
{
// Create dispel mask by dispel type
uint32 dispelMask = SpellInfo::GetDispelMask(type);
// Dispel all existing auras vs current dispel type
AuraApplicationMap& auras = GetAppliedAuras();
for (AuraApplicationMap::iterator itr = auras.begin(); itr != auras.end();)
{
SpellInfo const* spell = itr->second->GetBase()->GetSpellInfo();
if (spell->GetDispelMask() & dispelMask)
{
// Dispel aura
RemoveAura(itr);
}
if (itr->second == spellId)
itr = m_spellImmune[op].erase(itr);
else
++itr;
}
@@ -11177,8 +11216,9 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced)
{
if (Creature* creature = ToCreature())
{
uint32 immuneMask = creature->GetCreatureTemplate()->MechanicImmuneMask;
if (immuneMask & (1 << (MECHANIC_SNARE - 1)) || immuneMask & (1 << (MECHANIC_DAZE - 1)))
// use creature helper which now consults creature_immunities table
if (creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE) ||
creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_DAZE))
break;
}
@@ -11199,7 +11239,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced)
if (creature
&& !IsPet()
&& !(IsControlledByPlayer() && IsVehicle())
&& !(creature->HasMechanicTemplateImmunity(MECHANIC_SNARE))
&& !(creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE))
&& !(creature->IsDungeonBoss()))
{
// 1.6% for each % under 30.
@@ -11395,6 +11435,8 @@ void Unit::AtTargetAttacked(Unit* target, bool canInitialAggro)
target->EngageWithTarget(this);
if (Unit* targetOwner = target->GetCharmerOrOwner())
targetOwner->EngageWithTarget(this);
if (Unit* myOwner = GetCharmerOrOwner())
target->EngageWithTarget(myOwner);
// Patch 3.0.8: All player spells which cause a creature to become aggressive
// to you will now also immediately cause the creature to be tapped.
@@ -11422,12 +11464,8 @@ Unit* Creature::SelectVictim()
Unit* target = nullptr;
// Taunt handling is now done inside ThreatManager via TauntState
if (CanHaveThreatList())
{
if (!m_threatManager.IsThreatListEmpty())
target = m_threatManager.GetCurrentVictim();
}
target = m_threatManager.GetCurrentVictim();
else if (!HasReactState(REACT_PASSIVE))
{
// we have player pet probably
@@ -15087,7 +15125,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target)
{
if (!(effMask & (1 << i)))
continue;
if (target->IsImmunedToSpellEffect(spellInfo, i))
if (target->IsImmunedToSpellEffect(spellInfo, i, this))
effMask &= ~(1 << i);
}
@@ -15739,6 +15777,12 @@ void Unit::_ExitVehicle(Position const* exitPosition)
pos.RelocateOffset({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO });
else if (seatAddon->ExitParameter == VehicleExitParameters::VehicleExitParamDest)
pos.Relocate({ seatAddon->ExitParameterX, seatAddon->ExitParameterY, seatAddon->ExitParameterZ, seatAddon->ExitParameterO });
bool isInLoS = GetMap()->isInLineOfSight(GetPositionX(), GetPositionY(), GetPositionZ(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), GetPhaseMask(), LINEOFSIGHT_ALL_CHECKS, VMAP::ModelIgnoreFlags::Nothing);
float floorZ = GetMapHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
if (!isInLoS || pos.GetPositionZ() < floorZ)
pos = vehicleBase->GetPosition();
}
}
@@ -16344,8 +16388,8 @@ void Unit::ExecuteDelayedUnitRelocationEvent()
GetMap()->LoadGridsInRange(*player, MAX_VISIBILITY_DISTANCE);
Acore::PlayerRelocationNotifier notifier(*player);
Cell::VisitObjects(viewPoint, notifier, player->GetSightRange());
Cell::VisitFarVisibleObjects(viewPoint, notifier, VISIBILITY_DISTANCE_GIGANTIC);
Cell::VisitObjects(player->GetSightPosition().GetPositionX(), player->GetSightPosition().GetPositionY(), player->GetMap(), notifier, player->GetSightRange());
Cell::VisitFarVisibleObjects(player->GetSightPosition().GetPositionX(), player->GetSightPosition().GetPositionY(), player->GetMap(), notifier, VISIBILITY_DISTANCE_GIGANTIC);
notifier.SendToSelf();
this->AddToNotify(NOTIFY_AI_RELOCATION);
+17 -11
View File
@@ -1408,7 +1408,7 @@ public:
void RemoveAurasWithInterruptFlags(uint32 flag, uint32 except = 0, bool isAutoshot = false);
void RemoveAurasWithAttribute(uint32 flags);
void RemoveAurasWithFamily(SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, ObjectGuid casterGUID);
void RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode = AURA_REMOVE_BY_DEFAULT, uint32 except = 0);
void RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemode = AURA_REMOVE_BY_DEFAULT, uint32 except = 0);
void RemoveMovementImpairingAuras(bool withRoot);
void RemoveAurasByShapeShift();
@@ -1500,7 +1500,7 @@ public:
bool HasNegativeAuraWithInterruptFlag(uint32 flag, ObjectGuid guid = ObjectGuid::Empty);
[[nodiscard]] bool HasVisibleAuraType(AuraType auraType) const;
bool HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid = ObjectGuid::Empty);
[[nodiscard]] bool HasAuraWithMechanic(uint32 mechanicMask) const;
[[nodiscard]] bool HasAuraWithMechanic(uint64 mechanicMask) const;
[[nodiscard]] bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const;
@@ -1636,17 +1636,22 @@ public:
// Spells immunities
void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL);
void ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply);
virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr);
[[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const;
[[nodiscard]] bool IsImmunedToDamage(SpellInfo const* spellInfo) const;
[[nodiscard]] bool IsImmunedToDamage(Spell const* spell) const;
[[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const;
bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = nullptr);
[[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask schoolMask) const;
[[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const;
[[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const;
static bool IsImmuneMaskFully(SpellSchoolMask immuneMask, SpellSchoolMask schoolMask) { return (immuneMask & schoolMask) == schoolMask; }
[[nodiscard]] uint32 GetSchoolImmunityMask() const;
[[nodiscard]] uint32 GetDamageImmunityMask() const;
[[nodiscard]] bool IsImmunedToSchool(SpellInfo const* spellInfo) const;
[[nodiscard]] bool IsImmunedToSchool(Spell const* spell) const;
[[nodiscard]] bool IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const;
bool IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const;
virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const;
[[nodiscard]] bool IsImmunedToDamageOrSchool(SpellSchoolMask schoolMask) const;
[[nodiscard]] bool IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spellInfo) const;
virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const;
// Critic chances
bool isBlockCritical();
@@ -2082,7 +2087,8 @@ public:
float m_modAttackSpeedPct[3];
SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY];
typedef std::unordered_multimap<uint32 /*type*/, uint32 /*spellId*/> SpellImmuneContainer;
SpellImmuneContainer m_spellImmune[MAX_SPELL_IMMUNITY];
uint32 m_lastSanctuaryTime;
// pet auras
+101 -58
View File
@@ -310,6 +310,9 @@ ObjectMgr::ObjectMgr():
{
_playerClassInfo[i] = nullptr;
}
// Initialize default spawn group
_spawnGroupDataStore[0] = {0, "Default Group", 0, SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM)};
}
ObjectMgr::~ObjectMgr()
@@ -522,18 +525,18 @@ void ObjectMgr::LoadCreatureTemplates()
// 0 1 2 3 4 5 6 7 8
QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, "
// 9 10 11 12 13 14 15 16 17 18 19 20 21 22
"gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, "
// 23 24 25 26 27 28 29 30 31 32
// 9 10 11 12 13 14 15 16 17 18 19 20 21
"gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, "
// 22 23 24 25 26 27 28 29 30 31
"DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, "
// 33 34 35 36 37
// 32 33 34 35 36
"type, type_flags, lootid, pickpocketloot, skinloot, "
// 38 39 40 41 42 43 44 45 46 47
// 37 38 39 40 41 42 43 44 45 46
"PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, "
// 48 49 50 51 52 53 54 55 56 57 58 59
"ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, "
// 60 61 62
"spell_school_immune_mask, flags_extra, ScriptName "
// 47 48 49 50 51 52 53 54 55 56 57
"ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, "
// 58 59 60
"CreatureImmunitiesId, flags_extra, ScriptName "
"FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;");
if (!result)
@@ -619,24 +622,23 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
creatureTemplate.speed_swim = fields[17].Get<float>();
creatureTemplate.speed_flight = fields[18].Get<float>();
creatureTemplate.detection_range = fields[19].Get<float>();
creatureTemplate.scale = fields[20].Get<float>();
creatureTemplate.rank = uint32(fields[21].Get<uint8>());
creatureTemplate.dmgschool = uint32(fields[22].Get<int8>());
creatureTemplate.DamageModifier = fields[23].Get<float>();
creatureTemplate.BaseAttackTime = fields[24].Get<uint32>();
creatureTemplate.RangeAttackTime = fields[25].Get<uint32>();
creatureTemplate.BaseVariance = fields[26].Get<float>();
creatureTemplate.RangeVariance = fields[27].Get<float>();
creatureTemplate.unit_class = uint32(fields[28].Get<uint8>());
creatureTemplate.unit_flags = fields[29].Get<uint32>();
creatureTemplate.unit_flags2 = fields[30].Get<uint32>();
creatureTemplate.dynamicflags = fields[31].Get<uint32>();
creatureTemplate.family = uint32(fields[32].Get<uint8>());
creatureTemplate.type = uint32(fields[33].Get<uint8>());
creatureTemplate.type_flags = fields[34].Get<uint32>();
creatureTemplate.lootid = fields[35].Get<uint32>();
creatureTemplate.pickpocketLootId = fields[36].Get<uint32>();
creatureTemplate.SkinLootId = fields[37].Get<uint32>();
creatureTemplate.rank = uint32(fields[20].Get<uint8>());
creatureTemplate.dmgschool = uint32(fields[21].Get<int8>());
creatureTemplate.DamageModifier = fields[22].Get<float>();
creatureTemplate.BaseAttackTime = fields[23].Get<uint32>();
creatureTemplate.RangeAttackTime = fields[24].Get<uint32>();
creatureTemplate.BaseVariance = fields[25].Get<float>();
creatureTemplate.RangeVariance = fields[26].Get<float>();
creatureTemplate.unit_class = uint32(fields[27].Get<uint8>());
creatureTemplate.unit_flags = fields[28].Get<uint32>();
creatureTemplate.unit_flags2 = fields[29].Get<uint32>();
creatureTemplate.dynamicflags = fields[30].Get<uint32>();
creatureTemplate.family = uint32(fields[31].Get<uint8>());
creatureTemplate.type = uint32(fields[32].Get<uint8>());
creatureTemplate.type_flags = fields[33].Get<uint32>();
creatureTemplate.lootid = fields[34].Get<uint32>();
creatureTemplate.pickpocketLootId = fields[35].Get<uint32>();
creatureTemplate.SkinLootId = fields[36].Get<uint32>();
for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i)
{
@@ -648,49 +650,56 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook)
creatureTemplate.spells[i] = 0;
}
creatureTemplate.PetSpellDataId = fields[38].Get<uint32>();
creatureTemplate.VehicleId = fields[39].Get<uint32>();
creatureTemplate.mingold = fields[40].Get<uint32>();
creatureTemplate.maxgold = fields[41].Get<uint32>();
creatureTemplate.AIName = fields[42].Get<std::string>();
creatureTemplate.MovementType = uint32(fields[43].Get<uint8>());
if (!fields[44].IsNull())
creatureTemplate.PetSpellDataId = fields[37].Get<uint32>();
creatureTemplate.VehicleId = fields[38].Get<uint32>();
creatureTemplate.mingold = fields[39].Get<uint32>();
creatureTemplate.maxgold = fields[40].Get<uint32>();
creatureTemplate.AIName = fields[41].Get<std::string>();
creatureTemplate.MovementType = uint32(fields[42].Get<uint8>());
if (!fields[43].IsNull())
{
creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[44].Get<uint8>());
creatureTemplate.Movement.Ground = static_cast<CreatureGroundMovementType>(fields[43].Get<uint8>());
}
creatureTemplate.Movement.Swim = fields[45].Get<bool>();
if (!fields[46].IsNull())
creatureTemplate.Movement.Swim = fields[44].Get<bool>();
if (!fields[45].IsNull())
{
creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[46].Get<uint8>());
creatureTemplate.Movement.Flight = static_cast<CreatureFlightMovementType>(fields[45].Get<uint8>());
}
creatureTemplate.Movement.Rooted = fields[47].Get<bool>();
creatureTemplate.Movement.Rooted = fields[46].Get<bool>();
if (!fields[47].IsNull())
{
creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[47].Get<uint8>());
}
if (!fields[48].IsNull())
{
creatureTemplate.Movement.Chase = static_cast<CreatureChaseMovementType>(fields[48].Get<uint8>());
creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[48].Get<uint8>());
}
if (!fields[49].IsNull())
{
creatureTemplate.Movement.Random = static_cast<CreatureRandomMovementType>(fields[49].Get<uint8>());
}
if (!fields[50].IsNull())
{
creatureTemplate.Movement.InteractionPauseTimer = fields[50].Get<uint32>();
creatureTemplate.Movement.InteractionPauseTimer = fields[49].Get<uint32>();
}
creatureTemplate.HoverHeight = fields[51].Get<float>();
creatureTemplate.ModHealth = fields[52].Get<float>();
creatureTemplate.ModMana = fields[53].Get<float>();
creatureTemplate.ModArmor = fields[54].Get<float>();
creatureTemplate.ModExperience = fields[55].Get<float>();
creatureTemplate.RacialLeader = fields[56].Get<bool>();
creatureTemplate.movementId = fields[57].Get<uint32>();
creatureTemplate.RegenHealth = fields[58].Get<bool>();
creatureTemplate.MechanicImmuneMask = fields[59].Get<uint32>();
creatureTemplate.SpellSchoolImmuneMask = fields[60].Get<uint8>();
creatureTemplate.flags_extra = fields[61].Get<uint32>();
creatureTemplate.ScriptID = GetScriptId(fields[62].Get<std::string>());
creatureTemplate.HoverHeight = fields[50].Get<float>();
creatureTemplate.ModHealth = fields[51].Get<float>();
creatureTemplate.ModMana = fields[52].Get<float>();
creatureTemplate.ModArmor = fields[53].Get<float>();
creatureTemplate.ModExperience = fields[54].Get<float>();
creatureTemplate.RacialLeader = fields[55].Get<bool>();
creatureTemplate.movementId = fields[56].Get<uint32>();
creatureTemplate.RegenHealth = fields[57].Get<bool>();
creatureTemplate.CreatureImmunitiesId = fields[58].Get<int32>();
creatureTemplate.flags_extra = fields[59].Get<uint32>();
creatureTemplate.ScriptID = GetScriptId(fields[60].Get<std::string>());
// Warn about deprecated immunity flags that should be moved to `creature_immunities` table
if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_NO_TAUNT)
LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit NO_TAUNT (0x100) set. This will be migrated to the `creature_immunities` table in a future update.", entry);
if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_AVOID_AOE)
LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit AVOID_AOE (0x400000) set. This will be migrated to the `creature_immunities` table in a future update.", entry);
if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK)
LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit IMMUNITY_KNOCKBACK (0x40000000) set. This will be migrated to the `creature_immunities` table in a future update.", entry);
// useful if the creature template load is being triggered from outside this class
if (triggerHook)
@@ -2394,6 +2403,7 @@ void ObjectMgr::LoadCreatures()
data.unit_flags = fields[21].Get<uint32>();
data.dynamicflags = fields[22].Get<uint32>();
data.ScriptId = GetScriptId(fields[23].Get<std::string>());
data.spawnGroupId = 0;
if (!data.ScriptId)
data.ScriptId = cInfo->ScriptID;
@@ -2572,6 +2582,7 @@ CreatureData const* ObjectMgr::LoadCreatureDataFromDB(ObjectGuid::LowType spawnI
creatureData.unit_flags = fields[19].Get<uint32>();
creatureData.dynamicflags = fields[20].Get<uint32>();
creatureData.ScriptId = GetScriptId(fields[21].Get<std::string>());
creatureData.spawnGroupId = 0;
if (!creatureData.ScriptId)
creatureData.ScriptId = cInfo->ScriptID;
@@ -2754,6 +2765,7 @@ ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, float x, fl
data.phaseMask = PHASEMASK_NORMAL;
data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0;
data.dbData = false;
data.spawnGroupId = 0;
AddGameobjectToGrid(spawnId, &data);
@@ -2909,6 +2921,7 @@ void ObjectMgr::LoadGameobjects()
data.rotation.w = fields[10].Get<float>();
data.spawntimesecs = fields[11].Get<int32>();
data.ScriptId = GetScriptId(fields[18].Get<std::string>());
data.spawnGroupId = 0;
if (!data.ScriptId)
data.ScriptId = gInfo->ScriptId;
@@ -7160,6 +7173,11 @@ void ObjectMgr::LoadAreaTriggerScripts()
LOG_INFO("server.loading", " ");
}
uint32 ObjectMgr::GetNearestTaxiNode(WorldLocation const& loc, uint32 teamId)
{
return GetNearestTaxiNode(loc.GetPositionX(), loc.GetPositionY(), loc.GetPositionZ(), loc.GetMapId(), teamId);
}
uint32 ObjectMgr::GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 teamId)
{
bool found = false;
@@ -8672,6 +8690,19 @@ void ObjectMgr::DeleteGOData(ObjectGuid::LowType guid)
_gameObjectDataStore.erase(guid);
}
SpawnData const* ObjectMgr::GetSpawnData(SpawnObjectType type, ObjectGuid::LowType spawnId) const
{
switch (type)
{
case SPAWN_TYPE_CREATURE:
return GetCreatureData(spawnId);
case SPAWN_TYPE_GAMEOBJECT:
return GetGameObjectData(spawnId);
default:
return nullptr;
}
}
void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, std::string const& table, bool starter, bool go)
{
uint32 oldMSTime = getMSTime();
@@ -9719,6 +9750,7 @@ void ObjectMgr::LoadTrainers()
// For reload case
_trainers.clear();
_classTrainers.clear();
std::unordered_map<int32, std::vector<Trainer::Spell>> spellsByTrainer;
if (QueryResult trainerSpellsResult = WorldDatabase.Query("SELECT TrainerId, SpellId, MoneyCost, ReqSkillLine, ReqSkillRank, ReqAbility1, ReqAbility2, ReqAbility3, ReqLevel FROM trainer_spell"))
@@ -9795,7 +9827,18 @@ void ObjectMgr::LoadTrainers()
spellsByTrainer.erase(spellsItr);
}
_trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells)));
auto [it, isNew] = _trainers.emplace(std::piecewise_construct, std::forward_as_tuple(trainerId), std::forward_as_tuple(trainerId, trainerType, requirement, std::move(greeting), std::move(spells)));
ASSERT(isNew);
if (trainerType == Trainer::Type::Class)
{
if (!requirement || requirement >= MAX_CLASSES)
LOG_ERROR("sql.sql", "Table `trainer` has invalid class requirement for trainer {}, ignoring");
else
{
uint8 classId = static_cast<uint8>(requirement);
_classTrainers[classId].push_back(&it->second);
}
}
} while (trainersResult->NextRow());
}
+13
View File
@@ -506,6 +506,7 @@ struct AcoreString
typedef std::map<ObjectGuid, ObjectGuid> LinkedRespawnContainer;
typedef std::unordered_map<ObjectGuid::LowType, CreatureData> CreatureDataContainer;
typedef std::unordered_map<ObjectGuid::LowType, GameObjectData> GameObjectDataContainer;
typedef std::unordered_map<uint32, SpawnGroupTemplateData> SpawnGroupDataContainer;
typedef std::map<TempSummonGroupKey, std::vector<TempSummonData> > TempSummonDataContainer;
typedef std::map<TempSummonGroupKey, std::vector<GameObjectSummonData> > GameObjectSummonDataContainer;
typedef std::unordered_map<uint32, CreatureLocale> CreatureLocaleContainer;
@@ -811,6 +812,7 @@ public:
void GetPlayerLevelInfo(uint32 race, uint32 class_, uint8 level, PlayerLevelInfo* info) const;
uint32 GetNearestTaxiNode(float x, float y, float z, uint32 mapid, uint32 teamId);
uint32 GetNearestTaxiNode(WorldLocation const& loc, uint32 teamId);
void GetTaxiPath(uint32 source, uint32 destination, uint32& path, uint32& cost);
uint32 GetTaxiMountDisplayId(uint32 id, TeamId teamId, bool allowed_alt_team = false);
@@ -1269,6 +1271,14 @@ public:
if (itr == _gameObjectDataStore.end()) return nullptr;
return &itr->second;
}
[[nodiscard]] SpawnData const* GetSpawnData(SpawnObjectType type, ObjectGuid::LowType spawnId) const;
[[nodiscard]] SpawnGroupTemplateData const* GetSpawnGroupData(uint32 groupId) const
{
auto itr = _spawnGroupDataStore.find(groupId);
return itr != _spawnGroupDataStore.end() ? &itr->second : nullptr;
}
[[nodiscard]] CreatureLocale const* GetCreatureLocale(uint32 entry) const
{
CreatureLocaleContainer::const_iterator itr = _creatureLocaleStore.find(entry);
@@ -1419,6 +1429,7 @@ public:
bool DeleteGameTele(std::string_view name);
Trainer::Trainer* GetTrainer(uint32 creatureId);
std::vector<Trainer::Trainer const*> const& GetClassTrainers(uint8 classId) const { return _classTrainers.at(classId); }
[[nodiscard]] VendorItemData const* GetNpcVendorItemList(uint32 entry) const
{
@@ -1642,6 +1653,7 @@ private:
LinkedRespawnContainer _linkedRespawnStore;
CreatureLocaleContainer _creatureLocaleStore;
GameObjectDataContainer _gameObjectDataStore;
SpawnGroupDataContainer _spawnGroupDataStore;
GameObjectLocaleContainer _gameObjectLocaleStore;
GameObjectTemplateContainer _gameObjectTemplateStore;
GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore;
@@ -1667,6 +1679,7 @@ private:
CacheVendorItemContainer _cacheVendorItemStore;
std::unordered_map<uint32, Trainer::Trainer> _trainers;
std::unordered_map<uint8, std::vector<Trainer::Trainer const*>> _classTrainers;
std::unordered_map<uint32, uint32> _creatureDefaultTrainers;
std::set<uint32> _difficultyEntries[MAX_DIFFICULTY - 1]; // already loaded difficulty 1 value in creatures, used in CheckCreatureTemplate
+1
View File
@@ -107,6 +107,7 @@ struct Cell
template<class T> static void VisitObjects(float x, float y, Map* map, T& visitor, float radius);
template<class T> static void VisitFarVisibleObjects(WorldObject const* obj, T& visitor, float radius);
template<class T> static void VisitFarVisibleObjects(float x, float y, Map* map, T& visitor, float radius);
private:
template<class T, class CONTAINER> void VisitCircle(TypeContainerVisitor<T, CONTAINER>&, Map&, CellCoord const&, CellCoord const&) const;
+9 -7
View File
@@ -164,11 +164,7 @@ inline void Cell::VisitCircle(TypeContainerVisitor<T, CONTAINER>& visitor, Map&
template<class T>
inline void Cell::VisitObjects(WorldObject const* center_obj, T& visitor, float radius)
{
CellCoord p(Acore::ComputeCellCoord(center_obj->GetPositionX(), center_obj->GetPositionY()));
Cell cell(p);
TypeContainerVisitor<T, GridTypeMapContainer> gnotifier(visitor);
cell.Visit(p, gnotifier, *center_obj->GetMap(), *center_obj, radius);
VisitObjects(center_obj->GetPositionX(), center_obj->GetPositionY(), center_obj->GetMap(), visitor, radius);
}
template<class T>
@@ -184,11 +180,17 @@ inline void Cell::VisitObjects(float x, float y, Map* map, T& visitor, float rad
template<class T>
inline void Cell::VisitFarVisibleObjects(WorldObject const* center_obj, T& visitor, float radius)
{
CellCoord p(Acore::ComputeCellCoord(center_obj->GetPositionX(), center_obj->GetPositionY()));
VisitFarVisibleObjects(center_obj->GetPositionX(), center_obj->GetPositionY(), center_obj->GetMap(), visitor, radius);
}
template<class T>
inline void Cell::VisitFarVisibleObjects(float x, float y, Map* map, T& visitor, float radius)
{
CellCoord p(Acore::ComputeCellCoord(x, y));
Cell cell(p);
TypeContainerVisitor<T, FarVisibleGridContainer> gnotifier(visitor);
cell.Visit(p, gnotifier, *center_obj->GetMap(), *center_obj, radius);
cell.Visit(p, gnotifier, *map, x, y, radius);
}
#endif
+10 -21
View File
@@ -1,6 +1,6 @@
#include "DisableMgr.h"
#include "GridTerrainLoader.h"
#include "MMapFactory.h"
#include "IVMapMgr.h"
#include "Map.h"
#include "MMapMgr.h"
#include "ScriptMgr.h"
#include "VMapFactory.h"
@@ -9,6 +9,7 @@
void GridTerrainLoader::LoadTerrain()
{
LoadMap();
if (_map->GetInstanceId() == 0)
{
LoadVMap();
@@ -18,13 +19,10 @@ void GridTerrainLoader::LoadTerrain()
void GridTerrainLoader::LoadMap()
{
// Instances will point to the parent maps terrain data
// Instances will point to the parent maps terrain data, no need to load anything.
if (_map->GetInstanceId() != 0)
{
// load grid map for base map
Map* parentMap = const_cast<Map*>(_map->GetParent());
// GetGridTerrainData will create the parent map grid
_grid.SetTerrainData(parentMap->GetGridTerrainDataSharedPtr(GridCoord(_grid.GetX(), _grid.GetY())));
return;
}
@@ -51,7 +49,7 @@ void GridTerrainLoader::LoadMap()
void GridTerrainLoader::LoadVMap()
{
int vmapLoadResult = VMAP::VMapFactory::createOrGetVMapMgr()->loadMap((sWorld->GetDataPath() + "vmaps").c_str(), _map->GetId(), _grid.GetX(), _grid.GetY());
int const vmapLoadResult = _map->GetMapCollisionData().LoadVMapTile(_grid.GetX(), _grid.GetY());
switch (vmapLoadResult)
{
case VMAP::VMAP_LOAD_RESULT_OK:
@@ -66,15 +64,14 @@ void GridTerrainLoader::LoadVMap()
LOG_DEBUG("maps", "Ignored VMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})",
_map->GetMapName(), _map->GetId(), _grid.GetX(), _grid.GetY(), _grid.GetX(), _grid.GetY());
break;
default:
break;
}
}
void GridTerrainLoader::LoadMMap()
{
if (!DisableMgr::IsPathfindingEnabled(_map))
return;
int mmapLoadResult = MMAP::MMapFactory::createOrGetMMapMgr()->loadMap(_map->GetId(), _grid.GetX(), _grid.GetY());
int const mmapLoadResult = _map->GetMapCollisionData().LoadMMapTile(_grid.GetX(), _grid.GetY());
switch (mmapLoadResult)
{
case MMAP::MMAP_LOAD_RESULT_OK:
@@ -89,6 +86,8 @@ void GridTerrainLoader::LoadMMap()
LOG_DEBUG("maps", "Ignored MMAP name:{}, id:{}, x:{}, y:{} (vmap rep.: x:{}, y:{})",
_map->GetMapName(), _map->GetId(), _grid.GetX(), _grid.GetY(), _grid.GetX(), _grid.GetY());
break;
default:
break;
}
}
@@ -145,13 +144,3 @@ bool GridTerrainLoader::ExistVMap(uint32 mapid, int gx, int gy)
return true;
}
void GridTerrainUnloader::UnloadTerrain()
{
// Only parent maps manage terrain data
if (_map->GetInstanceId() != 0)
return;
VMAP::VMapFactory::createOrGetVMapMgr()->unloadMap(_map->GetId(), _grid.GetX(), _grid.GetY());
MMAP::MMapFactory::createOrGetMMapMgr()->unloadMap(_map->GetId(), _grid.GetX(), _grid.GetY());
}
+2 -13
View File
@@ -20,6 +20,8 @@
#include "GridDefines.h"
class Map;
class GridTerrainLoader
{
public:
@@ -40,17 +42,4 @@ private:
Map* _map;
};
class GridTerrainUnloader
{
public:
GridTerrainUnloader(MapGridType& grid, Map* map)
: _grid(grid), _map(map) { }
void UnloadTerrain();
private:
MapGridType& _grid;
Map* _map;
};
#endif
+11 -3
View File
@@ -8,9 +8,19 @@ void MapGridManager::CreateGrid(uint16 const x, uint16 const y)
if (IsGridCreated(x, y))
return;
// If we are an instance, ensure parent map has already created the grid before proceeding.
// Note: grid loading is locked and is safe across multiple map threads in the
// event of multiple child maps attempting to create the same parent map grid
if (_map->GetInstanceId() != 0)
{
Map* parentMap = const_cast<Map*>(_map->GetParent());
parentMap->EnsureGridCreated(GridCoord(x, y));
}
std::unique_ptr<MapGridType> grid = std::make_unique<MapGridType>(x, y);
grid->link(_map);
// Terrain is loading during create (should/can we move this to LoadGrid?)
GridTerrainLoader loader(*grid, _map);
loader.LoadTerrain();
@@ -19,6 +29,7 @@ void MapGridManager::CreateGrid(uint16 const x, uint16 const y)
++_createdGridsCount;
}
// Loads objects, terrain already loaded in CreateGrid
bool MapGridManager::LoadGrid(uint16 const x, uint16 const y)
{
MapGridType* grid = GetGrid(x, y);
@@ -55,9 +66,6 @@ void MapGridManager::UnloadGrid(uint16 const x, uint16 const y)
grid->VisitAllCells(visitor);
}
GridTerrainUnloader terrainUnloader(*grid, _map);
terrainUnloader.UnloadTerrain();
_mapGrid[x][y] = nullptr;
}
@@ -192,7 +192,7 @@ void MessageDistDeliverer::Visit(VisiblePlayersMap const& m)
for (auto const& kvPair : m)
{
Player const* target = kvPair.second;
if (i_distSq != 0.0f && target->m_seer->GetExactDist2dSq(i_source) > i_distSq)
if (i_distSq != 0.0f && target->GetSightPosition().GetExactDist2dSq(i_source) > i_distSq)
continue;
// @todo: Might not need this check anymore
@@ -1384,7 +1384,7 @@ namespace Acore
AnyPlayerExactPositionInGameObjectRangeCheck(GameObject const* go, float range) : _go(go), _range(range) {}
bool operator()(Player* u)
{
if (!_go->IsInRange(u->GetPositionX(), u->GetPositionY(), u->GetPositionZ(), _range))
if (!_go->IsInRange3d(u->GetPositionX(), u->GetPositionY(), u->GetPositionZ(), _range))
return false;
return true;
@@ -1604,7 +1604,7 @@ namespace Acore
bool operator() (GameObject* go)
{
if (!entry || (go->GetGOInfo() && go->GetGOInfo()->entry == entry))
return go->IsInRange(x, y, z, range);
return go->IsInRange3d(x, y, z, range);
else return false;
}
private:
@@ -868,9 +868,9 @@ void WorldSession::HandlePlayerLoginFromDB(LoginQueryHolder const& holder)
if (ChrClassesEntry const* cEntry = sChrClassesStore.LookupEntry(pCurrChar->getClass()))
{
if (cEntry->CinematicSequence)
pCurrChar->SendCinematicStart(cEntry->CinematicSequence);
pCurrChar->GetCinematicMgr().StartCinematic(cEntry->CinematicSequence);
else if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(pCurrChar->getRace()))
pCurrChar->SendCinematicStart(rEntry->CinematicSequence);
pCurrChar->GetCinematicMgr().StartCinematic(rEntry->CinematicSequence);
// send new char string if not empty
std::string_view newCharString = sWorld->getStringConfig(CONFIG_NEW_CHAR_STRING);
+5 -1
View File
@@ -874,7 +874,11 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid, uint32 vendorEntry)
// Stop the npc if moving
if (uint32 pause = vendor->GetMovementTemplate().GetInteractionPauseTimer())
vendor->PauseMovement(pause);
vendor->SetHomePosition(vendor->GetPosition());
// Update home position for patrolling NPCs only (prevents drift for stationary NPCs)
if (vendor->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE ||
vendor->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
vendor->SetHomePosition(vendor->GetPosition());
SetCurrentVendor(vendorEntry);
+5 -4
View File
@@ -732,7 +732,8 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recv_data)
if (player->isDebugAreaTriggers)
ChatHandler(this).PSendSysMessage(LANG_DEBUG_AREATRIGGER_REACHED, triggerId);
if (sScriptMgr->OnAreaTrigger(player, atEntry))
// Skip areatrigger scripts for GMs unless debug areatriggers is enabled
if ((!player->IsGameMaster() || player->isDebugAreaTriggers) && sScriptMgr->OnAreaTrigger(player, atEntry))
return;
if (player->IsAlive())
@@ -947,14 +948,14 @@ void WorldSession::HandleSetActionButtonOpcode(WorldPacket& recv_data)
void WorldSession::HandleCompleteCinematic(WorldPacket& /*recv_data*/)
{
// If player has sight bound to visual waypoint NPC we should remove it
GetPlayer()->GetCinematicMgr()->EndCinematic();
// End the current cinematic and restore the normal player view
GetPlayer()->GetCinematicMgr().EndCinematic();
}
void WorldSession::HandleNextCinematicCamera(WorldPacket& /*recv_data*/)
{
// Sent by client when cinematic actually begun. So we begin the server side process
GetPlayer()->GetCinematicMgr()->BeginCinematic();
GetPlayer()->GetCinematicMgr().StartCinematicCamera();
}
void WorldSession::HandleSetActionBarToggles(WorldPacket& recv_data)
+5 -1
View File
@@ -162,7 +162,11 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData)
// Stop the npc if moving
if (uint32 pause = unit->GetMovementTemplate().GetInteractionPauseTimer())
unit->PauseMovement(pause);
unit->SetHomePosition(unit->GetPosition());
// Update home position for patrolling NPCs only (prevents drift for stationary NPCs)
if (unit->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE ||
unit->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
unit->SetHomePosition(unit->GetPosition());
// If spiritguide, no need for gossip menu, just put player into resurrect queue
if (unit->IsSpiritGuide())
+5 -1
View File
@@ -96,7 +96,11 @@ void WorldSession::HandleQuestgiverHelloOpcode(WorldPacket& recvData)
// Stop the npc if moving
if (uint32 pause = creature->GetMovementTemplate().GetInteractionPauseTimer())
creature->PauseMovement(pause);
creature->SetHomePosition(creature->GetPosition());
// Update home position for patrolling NPCs only (prevents drift for stationary NPCs)
if (creature->GetDefaultMovementType() == WAYPOINT_MOTION_TYPE ||
creature->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE)
creature->SetHomePosition(creature->GetPosition());
if (sScriptMgr->OnGossipHello(_player, creature))
return;
+3 -3
View File
@@ -43,7 +43,7 @@ void WorldSession::SendTaxiStatus(ObjectGuid guid)
}
// find taxi node
uint32 nearest = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), player->GetTeamId());
uint32 nearest = sObjectMgr->GetNearestTaxiNode(*unit, player->GetTeamId(true));
if (!nearest)
{
return;
@@ -84,7 +84,7 @@ void WorldSession::HandleTaxiQueryAvailableNodes(WorldPacket& recvData)
void WorldSession::SendTaxiMenu(Creature* unit)
{
// find current node
uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), GetPlayer()->GetTeamId());
uint32 curloc = sObjectMgr->GetNearestTaxiNode(*unit, GetPlayer()->GetTeamId(true));
if (curloc == 0)
return;
@@ -127,7 +127,7 @@ void WorldSession::SendDoFlight(uint32 mountDisplayId, uint32 path, uint32 pathN
bool WorldSession::SendLearnNewTaxiNode(Creature* unit)
{
// find current node
uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), GetPlayer()->GetTeamId());
uint32 curloc = sObjectMgr->GetNearestTaxiNode(*unit, GetPlayer()->GetTeamId(true));
if (curloc == 0)
return true; // `true` send to avoid WorldSession::SendTaxiMenu call with one more curlock seartch with same false result.
@@ -134,6 +134,11 @@ void InstanceScript::HandleGameObject(ObjectGuid GUID, bool open, GameObject* go
}
}
void InstanceScript::HandleGameObject(uint32 type, bool open)
{
HandleGameObject(ObjectGuid::Empty, open, GetGameObject(type));
}
bool InstanceScript::IsEncounterInProgress() const
{
for (std::vector<BossInfo>::const_iterator itr = bosses.begin(); itr != bosses.end(); ++itr)
+13 -3
View File
@@ -193,11 +193,21 @@ public:
//Called when a player enters/leaves water bodies.
virtual void OnPlayerInWaterStateUpdate(Player* /*player*/, bool /*inWater*/) {}
//Handle open / close objects
//use HandleGameObject(ObjectGuid::Empty, boolen, GO); in OnObjectCreate in instance scripts
//use HandleGameObject(GUID, boolen, nullptr); in any other script
/**
* @brief Open or close a GameObject by GUID.
* @param guid The GUID of the GameObject. Pass ObjectGuid::Empty when providing the go pointer directly.
* @param open true to open (GO_STATE_ACTIVE), false to close (GO_STATE_READY).
* @param go Optional pointer to the GameObject. If nullptr, the object is looked up by GUID.
*/
void HandleGameObject(ObjectGuid guid, bool open, GameObject* go = nullptr);
/**
* @brief Open or close a GameObject registered via LoadObjectData.
* @param type The ObjectData type constant (e.g. GO_FRONT_DOOR) used in the gameObjectData array.
* @param open true to open (GO_STATE_ACTIVE), false to close (GO_STATE_READY).
*/
void HandleGameObject(uint32 type, bool open);
//change active state of doors or buttons
void DoUseDoorOrButton(ObjectGuid guid, uint32 withRestoreTime = 0, bool useAlternativeState = false);
+20 -68
View File
@@ -418,83 +418,34 @@ bool LootItem::AllowedForPlayer(Player const* player, ObjectGuid source) const
{
ItemTemplate const* pProto = sObjectMgr->GetItemTemplate(itemid);
if (!pProto)
{
return false;
}
if (sDisableMgr->IsDisabledFor(DISABLE_TYPE_LOOT, itemid, nullptr))
{
return false;
}
bool isMasterLooter = player->GetGroup() && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID();
bool itemVisibleForMasterLooter = !needs_quest && (!follow_loot_rules || !is_underthreshold);
// DB conditions check
if (!sConditionMgr->IsObjectMeetToConditions(const_cast<Player*>(player), conditions))
{
// Master Looter can see conditioned recipes
if (isMasterLooter && itemVisibleForMasterLooter)
{
if (pProto->HasFlag(ITEM_FLAG_HIDE_UNUSABLE_RECIPE) || (pProto->Class == ITEM_CLASS_RECIPE && pProto->Bonding == BIND_WHEN_PICKED_UP && pProto->Spells[1].SpellId != 0))
{
return true;
}
}
return false;
}
// not show loot for not own team
if (pProto->HasFlag2(ITEM_FLAG2_FACTION_HORDE) && player->GetTeamId(true) != TEAM_HORDE)
{
return false;
}
if (pProto->HasFlag2(ITEM_FLAG2_FACTION_ALLIANCE) && player->GetTeamId(true) != TEAM_ALLIANCE)
{
return false;
}
// Master looter can see all items even if the character can't loot them
if (isMasterLooter && itemVisibleForMasterLooter)
{
return true;
}
// Don't allow loot for players without profession or those who already know the recipe
// profession / recipe checks
if (pProto->HasFlag(ITEM_FLAG_HIDE_UNUSABLE_RECIPE) && (!player->HasSkill(pProto->RequiredSkill) || player->HasSpell(pProto->Spells[1].SpellId)))
{
return false;
}
// Don't allow to loot soulbound recipes that the player has already learned
if (pProto->Class == ITEM_CLASS_RECIPE && pProto->Bonding == BIND_WHEN_PICKED_UP && pProto->Spells[1].SpellId != 0 && player->HasSpell(pProto->Spells[1].SpellId))
{
return false;
}
// check quest requirements
if (!pProto->HasFlagCu(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS))
{
// Don't drop quest items if the player is missing the relevant quest
if (needs_quest && !player->HasQuestForItem(itemid))
return false;
// for items that start quests
if (pProto->StartQuest)
{
// Don't drop the item if the player has already finished the quest OR player already has the item in their inventory, and that item is unique OR the player has not finished a prerequisite quest
uint32 prevQuestId = sObjectMgr->GetQuestTemplate(pProto->StartQuest) ? sObjectMgr->GetQuestTemplate(pProto->StartQuest)->GetPrevQuestId() : 0;
if (player->GetQuestStatus(pProto->StartQuest) != QUEST_STATUS_NONE || (player->HasItemCount(itemid, pProto->MaxCount) && pProto->MaxCount) || (prevQuestId && !player->GetQuestRewardStatus(prevQuestId)))
return false;
}
}
if (needs_quest && !pProto->HasFlagCu(ITEM_FLAGS_CU_IGNORE_QUEST_STATUS) && !player->HasQuestForItem(itemid))
return false;
if (!sScriptMgr->OnAllowedForPlayerLootCheck(player, source))
{
return false;
}
return true;
}
@@ -691,7 +642,7 @@ QuestItemList* Loot::FillQuestLoot(Player* player)
QuestItemList* ql = new QuestItemList();
Player* lootOwner = (roundRobinPlayer) ? ObjectAccessor::FindPlayer(roundRobinPlayer) : player;
bool isMasterLooter = player->GetGroup() && player->GetGroup()->GetLootMethod() == MASTER_LOOT && player->GetGroup()->GetMasterLooterGuid() == player->GetGUID();
for (uint8 i = 0; i < quest_items.size(); ++i)
{
@@ -699,32 +650,33 @@ QuestItemList* Loot::FillQuestLoot(Player* player)
sScriptMgr->OnPlayerBeforeFillQuestLootItem(player, item);
// Quest item is not free for all and is already assigned to another player
// or player doesn't need it
if (item.is_blocked || !item.AllowedForPlayer(player, sourceWorldObjectGUID))
{
continue;
}
bool allowed = item.AllowedForPlayer(player, sourceWorldObjectGUID);
// Player is not the loot owner, and loot owner still needs this quest item
if (!item.freeforall &&
lootOwner && lootOwner != player &&
item.AllowedForPlayer(lootOwner, sourceWorldObjectGUID))
{
if (!allowed && !isMasterLooter)
continue;
}
ql->push_back(QuestItem(i));
++unlootedCount;
if (!item.freeforall)
// Only add "allowed looter" if you are actually allowed to loot.
if (allowed)
{
item.is_blocked = true;
item.AddAllowedLooter(player);
if (item.freeforall)
{
++unlootedCount;
}
else if (!item.is_counted)
{
++unlootedCount;
item.is_counted = true;
}
}
if (items.size() + ql->size() == MAX_NR_LOOT_ITEMS)
break;
}
if (ql->empty())
{
delete ql;
+20 -42
View File
@@ -32,7 +32,6 @@
#include "MapInstanced.h"
#include "Metric.h"
#include "MiscPackets.h"
#include "MMapFactory.h"
#include "Object.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
@@ -62,13 +61,12 @@ Map::~Map()
if (!m_scriptSchedule.empty())
sScriptMgr->DecreaseScheduledScriptCount(m_scriptSchedule.size());
MMAP::MMapFactory::createOrGetMMapMgr()->unloadMapInstance(GetId(), i_InstanceId);
}
Map::Map(uint32 id, uint32 InstanceId, uint8 SpawnMode, Map* _parent) :
_mapGridManager(this), i_mapEntry(sMapStore.LookupEntry(id)), i_spawnMode(SpawnMode), i_InstanceId(InstanceId),
m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), _instanceResetPeriod(0),
_mapGridManager(this), i_mapEntry(sMapStore.LookupEntry(id)), _mapCollisionData(*this, _parent),
i_spawnMode(SpawnMode), i_InstanceId(InstanceId), m_unloadTimer(0),
m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), _instanceResetPeriod(0),
_transportsUpdateIter(_transports.end()), i_scriptLock(false), _defaultLight(GetDefaultMapLight(id))
{
m_parentMap = (_parent ? _parent : this);
@@ -432,7 +430,7 @@ void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone)
void Map::Update(const uint32 t_diff, const uint32 s_diff, bool /*thread*/)
{
if (t_diff)
_dynamicTree.update(t_diff);
_mapCollisionData.GetDynamicTree().update(t_diff);
// Update world sessions and players
for (m_mapRefIter = m_mapRefMgr.begin(); m_mapRefIter != m_mapRefMgr.end(); ++m_mapRefIter)
@@ -1037,9 +1035,10 @@ void Map::UnloadAll()
for (GridRefMgr<MapGridType>::iterator i = GridRefMgr<MapGridType>::begin(); i != GridRefMgr<MapGridType>::end();)
{
MapGridType& grid(*i->GetSource());
MapGridType* grid = i->GetSource();
++i;
UnloadGrid(grid); // deletes the grid and removes it from the GridRefMgr
if (grid)
UnloadGrid(*grid);
}
// pussywizard: crashfix, some npc can be left on transport (not a default passenger)
@@ -1073,9 +1072,9 @@ void Map::UnloadAll()
std::shared_ptr<GridTerrainData> Map::GetGridTerrainDataSharedPtr(GridCoord const& gridCoord)
{
// ensure GridMap is created
EnsureGridCreated(gridCoord);
return _mapGridManager.GetGrid(gridCoord.x_coord, gridCoord.y_coord)->GetTerrainDataSharedPtr();
MapGridType* grid = _mapGridManager.GetGrid(gridCoord.x_coord, gridCoord.y_coord);
ASSERT(grid); // Grid should always exist during this call
return grid->GetTerrainDataSharedPtr();
}
GridTerrainData* Map::GetGridTerrainData(GridCoord const& gridCoord)
@@ -1152,10 +1151,7 @@ float Map::GetHeight(float x, float y, float z, bool checkVMap /*= true*/, float
float vmapHeight = VMAP_INVALID_HEIGHT_VALUE;
if (checkVMap)
{
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
vmapHeight = vmgr->getHeight(GetId(), x, y, z, maxSearchDist); // look from a bit higher pos to find the floor
}
vmapHeight = _mapCollisionData.GetStaticTree().getHeight(x, y, z, maxSearchDist); // look from a bit higher pos to find the floor
// mapHeight set for any above raw ground Z or <= INVALID_HEIGHT
// vmapheight set for any under Z value or <= INVALID_HEIGHT
@@ -1203,12 +1199,11 @@ static inline bool IsInWMOInterior(uint32 mogpFlags)
bool Map::GetAreaInfo(uint32 phaseMask, float x, float y, float z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
{
float check_z = z;
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
VMAP::AreaAndLiquidData vdata;
VMAP::AreaAndLiquidData ddata;
bool hasVmapAreaInfo = vmgr->GetAreaAndLiquidData(GetId(), x, y, z, {}, vdata) && vdata.areaInfo.has_value();
bool hasDynamicAreaInfo = _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseMask, {}, ddata) && ddata.areaInfo.has_value();
bool hasVmapAreaInfo = _mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, {}, vdata) && vdata.areaInfo.has_value();
bool hasDynamicAreaInfo = _mapCollisionData.GetDynamicTree().GetAreaAndLiquidData(x, y, z, phaseMask, {}, ddata) && ddata.areaInfo.has_value();
auto useVmap = [&] { check_z = vdata.floorZ; groupId = vdata.areaInfo->groupId; adtId = vdata.areaInfo->adtId; rootId = vdata.areaInfo->rootId; flags = vdata.areaInfo->mogpFlags; };
auto useDyn = [&] { check_z = ddata.floorZ; groupId = ddata.areaInfo->groupId; adtId = ddata.areaInfo->adtId; rootId = ddata.areaInfo->rootId; flags = ddata.areaInfo->mogpFlags; };
if (hasVmapAreaInfo)
@@ -1299,10 +1294,9 @@ LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z,
LiquidData liquidData;
liquidData.Status = LIQUID_MAP_NO_WATER;
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
VMAP::AreaAndLiquidData vmapData;
bool useGridLiquid = true;
if (vmgr->GetAreaAndLiquidData(GetId(), x, y, z, ReqLiquidType, vmapData) && vmapData.liquidInfo)
if (_mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, ReqLiquidType, vmapData) && vmapData.liquidInfo)
{
useGridLiquid = !vmapData.areaInfo || !IsInWMOInterior(vmapData.areaInfo->mogpFlags);
LOG_DEBUG("maps", "GetLiquidStatus(): vmap liquid level: {} ground: {} type: {}", vmapData.liquidInfo->level, vmapData.floorZ, vmapData.liquidInfo->type);
@@ -1382,11 +1376,10 @@ void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y
{
GridTerrainData* gmap = GetGridTerrainData(x, y);
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
VMAP::AreaAndLiquidData vmapData;
// VMAP::AreaAndLiquidData dynData;
VMAP::AreaAndLiquidData* wmoData = nullptr;
vmgr->GetAreaAndLiquidData(GetId(), x, y, z, reqLiquidType, vmapData);
_mapCollisionData.GetStaticTree().GetAreaAndLiquidData(x, y, z, reqLiquidType, vmapData);
// _dynamicTree.GetAreaAndLiquidData(x, y, z, phaseMask, reqLiquidType, dynData);
uint32 gridAreaId = 0;
@@ -1547,7 +1540,7 @@ bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, floa
}
}
if ((checks & LINEOFSIGHT_CHECK_VMAP) && !VMAP::VMapFactory::createOrGetVMapMgr()->isInLineOfSight(GetId(), x1, y1, z1, x2, y2, z2, ignoreFlags))
if ((checks & LINEOFSIGHT_CHECK_VMAP) && !_mapCollisionData.GetStaticTree().isInLineOfSight(x1, y1, z1, x2, y2, z2, ignoreFlags))
{
return false;
}
@@ -1560,7 +1553,7 @@ bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, floa
ignoreFlags = VMAP::ModelIgnoreFlags::M2;
}
if (!_dynamicTree.isInLineOfSight(x1, y1, z1, x2, y2, z2, phasemask, ignoreFlags))
if (!_mapCollisionData.GetDynamicTree().isInLineOfSight(x1, y1, z1, x2, y2, z2, phasemask, ignoreFlags))
{
return false;
}
@@ -1569,25 +1562,11 @@ bool Map::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, floa
return true;
}
bool Map::GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist)
{
G3D::Vector3 startPos(x1, y1, z1);
G3D::Vector3 dstPos(x2, y2, z2);
G3D::Vector3 resultPos;
bool result = _dynamicTree.GetObjectHitPos(phasemask, startPos, dstPos, resultPos, modifyDist);
rx = resultPos.x;
ry = resultPos.y;
rz = resultPos.z;
return result;
}
float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist /*= DEFAULT_HEIGHT_SEARCH*/) const
{
float h1, h2;
h1 = GetHeight(x, y, z, vmap, maxSearchDist);
h2 = _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask);
h2 = _mapCollisionData.GetDynamicTree().getHeight(x, y, z, maxSearchDist, phasemask);
return std::max<float>(h1, h2);
}
@@ -3056,8 +3035,7 @@ bool Map::CheckCollisionAndGetValidCoords(WorldObject const* source, float start
// Unit is not on the ground, check for potential collision via vmaps
if (notOnGround)
{
bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(source->GetMapId(),
startX, startY, startZ + halfHeight,
bool col = _mapCollisionData.GetStaticTree().GetObjectHitPos(startX, startY, startZ + halfHeight,
destX, destY, destZ + halfHeight,
destX, destY, destZ, -CONTACT_DISTANCE);
@@ -3071,7 +3049,7 @@ bool Map::CheckCollisionAndGetValidCoords(WorldObject const* source, float start
}
// check dynamic collision
bool col = source->GetMap()->GetObjectHitPos(source->GetPhaseMask(),
bool col = _mapCollisionData.GetDynamicTree().GetObjectHitPos(source->GetPhaseMask(),
startX, startY, startZ + halfHeight,
destX, destY, destZ + halfHeight,
destX, destY, destZ, -CONTACT_DISTANCE);
+11 -11
View File
@@ -27,6 +27,7 @@
#include "GameObjectModel.h"
#include "GridDefines.h"
#include "GridRefMgr.h"
#include "MapCollisionData.h"
#include "MapGridManager.h"
#include "MapRefMgr.h"
#include "ObjectDefines.h"
@@ -230,8 +231,6 @@ public:
[[nodiscard]] Map const* GetParent() const { return m_parentMap; }
// pussywizard: movemaps, mmaps
[[nodiscard]] std::shared_mutex& GetMMapLock() const { return *(const_cast<std::shared_mutex*>(&MMapLock)); }
// pussywizard:
std::unordered_set<Unit*> i_objectsForDelayedVisibility;
void HandleDelayedVisibility();
@@ -389,15 +388,14 @@ public:
bool CanReachPositionAndGetValidCoords(WorldObject const* source, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const;
bool CanReachPositionAndGetValidCoords(WorldObject const* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision = true, bool failOnSlopes = true) const;
bool CheckCollisionAndGetValidCoords(WorldObject const* source, float startX, float startY, float startZ, float &destX, float &destY, float &destZ, bool failOnCollision = true) const;
void Balance() { _dynamicTree.balance(); }
void RemoveGameObjectModel(const GameObjectModel& model) { _dynamicTree.remove(model); }
void InsertGameObjectModel(const GameObjectModel& model) { _dynamicTree.insert(model); }
[[nodiscard]] bool ContainsGameObjectModel(const GameObjectModel& model) const { return _dynamicTree.contains(model);}
[[nodiscard]] DynamicMapTree const& GetDynamicMapTree() const { return _dynamicTree; }
bool GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist);
void Balance() { _mapCollisionData.GetDynamicTree().balance(); }
void RemoveGameObjectModel(const GameObjectModel& model) { _mapCollisionData.GetDynamicTree().remove(model); }
void InsertGameObjectModel(const GameObjectModel& model) { _mapCollisionData.GetDynamicTree().insert(model); }
[[nodiscard]] bool ContainsGameObjectModel(const GameObjectModel& model) const { return _mapCollisionData.GetDynamicTree().contains(model);}
[[nodiscard]] DynamicMapTree const& GetDynamicMapTree() const { return _mapCollisionData.GetDynamicTree(); }
[[nodiscard]] float GetGameObjectFloor(uint32 phasemask, float x, float y, float z, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const
{
return _dynamicTree.getHeight(x, y, z, maxSearchDist, phasemask);
return _mapCollisionData.GetDynamicTree().getHeight(x, y, z, maxSearchDist, phasemask);
}
/*
RESPAWN TIMES
@@ -524,6 +522,9 @@ public:
return 0;
};
MapCollisionData& GetMapCollisionData() { return _mapCollisionData; }
MapCollisionData const& GetMapCollisionData() const { return _mapCollisionData; }
private:
template<class T> void InitializeObject(T* obj);
@@ -551,15 +552,14 @@ protected:
void AddToGrid(T* object, Cell const& cell);
std::mutex Lock;
std::shared_mutex MMapLock;
MapGridManager _mapGridManager;
MapEntry const* i_mapEntry;
MapCollisionData _mapCollisionData;
uint8 i_spawnMode;
uint32 i_InstanceId;
uint32 m_unloadTimer;
float m_VisibleDistance;
DynamicMapTree _dynamicTree;
time_t _instanceResetPeriod; // pussywizard
MapRefMgr m_mapRefMgr;
+183
View File
@@ -0,0 +1,183 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "DBCStores.h"
#include "DetourNavMeshQuery.h"
#include "DisableMgr.h"
#include "MapCollisionData.h"
#include "MapTree.h"
#include "ModelInstance.h"
#include "VMapFactory.h"
#include "VMapMgr2.h"
#include "World.h"
#include "WorldModel.h"
#include <G3D/Vector3.h>
MapCollisionData::MapCollisionData(Map const& map, Map const* parentMap) :
_map(map), _staticVMapData(map.GetId())
{
if (parentMap)
{
// If we have a parent map, point our static tree and mmap nav mesh to the parent maps
_staticVMapData._staticTree = parentMap->GetMapCollisionData().GetStaticTreeSharedPtr();
_mmapData._navMesh = parentMap->GetMapCollisionData().GetMMapNavMeshSharedPtr();
}
else
{
// If we are a base map create a new static tree and mmap nav mesh
std::string const mapFileName = VMAP::VMapMgr2::getMapFileName(map.GetId());
std::shared_ptr<VMAP::StaticMapTree> newTree = std::make_shared<VMAP::StaticMapTree>(map.GetId(), (sWorld->GetDataPath() + "vmaps"));
if (newTree->InitMap(mapFileName))
_staticVMapData._staticTree = newTree;
_mmapData._navMesh = MMAP::MMapMgr::LoadNavMesh(map.GetId());
}
}
int MapCollisionData::LoadVMapTile(uint32 tileX, uint32 tileY)
{
if (!VMAP::VMapFactory::createOrGetVMapMgr()->isMapLoadingEnabled() || !_staticVMapData._staticTree)
return VMAP::VMAP_LOAD_RESULT_IGNORED;
if (!_staticVMapData._staticTree->LoadMapTile(tileX, tileY))
return VMAP::VMAP_LOAD_RESULT_ERROR;
return VMAP::VMAP_LOAD_RESULT_OK;
}
int MapCollisionData::LoadMMapTile(uint32 tileX, uint32 tileY)
{
if (!DisableMgr::IsPathfindingEnabled(&_map) || !_mmapData._navMesh)
return MMAP::MMAP_LOAD_RESULT_IGNORED;
return MMAP::MMapMgr::LoadTile(_mmapData._navMesh.get(), _map.GetId(), tileX, tileY);
}
bool StaticVMapCollisionData::isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, VMAP::ModelIgnoreFlags ignoreFlags) const
{
#if defined(ENABLE_VMAP_CHECKS)
if (!sWorld->getBoolConfig(CONFIG_VMAP_ENABLE_LOS) || DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_LOS))
return true;
#endif
if (!_staticTree)
return true;
G3D::Vector3 const pos1 = VMAP::VMapMgr2::convertPositionToInternalRep(x1, y1, z1);
G3D::Vector3 const pos2 = VMAP::VMapMgr2::convertPositionToInternalRep(x2, y2, z2);
if (pos1 != pos2)
return _staticTree->isInLineOfSight(pos1, pos2, ignoreFlags);
return true;
}
bool StaticVMapCollisionData::GetObjectHitPos(float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const
{
#if defined(ENABLE_VMAP_CHECKS)
if (sWorld->getBoolConfig(CONFIG_VMAP_ENABLE_LOS) && !DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_LOS))
#endif
{
if (_staticTree)
{
G3D::Vector3 const pos1 = VMAP::VMapMgr2::convertPositionToInternalRep(x1, y1, z1);
G3D::Vector3 const pos2 = VMAP::VMapMgr2::convertPositionToInternalRep(x2, y2, z2);
G3D::Vector3 resultPos;
bool result = _staticTree->GetObjectHitPos(pos1, pos2, resultPos, modifyDist);
resultPos = VMAP::VMapMgr2::convertPositionToInternalRep(resultPos.x, resultPos.y, resultPos.z);
rx = resultPos.x;
ry = resultPos.y;
rz = resultPos.z;
return result;
}
}
rx = x2;
ry = y2;
rz = z2;
return false;
}
float StaticVMapCollisionData::getHeight(float x, float y, float z, float maxSearchDist) const
{
#if defined(ENABLE_VMAP_CHECKS)
if (sWorld->getBoolConfig(CONFIG_VMAP_ENABLE_HEIGHT) && !DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_HEIGHT))
#endif
{
if (_staticTree)
{
G3D::Vector3 const pos = VMAP::VMapMgr2::convertPositionToInternalRep(x, y, z);
float height = _staticTree->getHeight(pos, maxSearchDist);
if (height >= G3D::finf())
return VMAP_INVALID_HEIGHT_VALUE; // No height
return height;
}
}
return VMAP_INVALID_HEIGHT_VALUE;
}
bool StaticVMapCollisionData::GetAreaAndLiquidData(float x, float y, float z, Optional<uint8> reqLiquidType, VMAP::AreaAndLiquidData& data) const
{
if (_staticTree)
{
VMAP::LocationInfo info;
G3D::Vector3 const pos = VMAP::VMapMgr2::convertPositionToInternalRep(x, y, z);
if (_staticTree->GetLocationInfo(pos, info))
{
data.floorZ = info.ground_Z;
if (!DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_LIQUIDSTATUS))
{
uint32 liquidType = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc
float liquidLevel;
if (!reqLiquidType || (GetLiquidFlags(liquidType) & *reqLiquidType))
if (info.hitInstance->GetLiquidLevel(pos, info, liquidLevel))
data.liquidInfo.emplace(liquidType, liquidLevel);
}
if (!DisableMgr::IsVMAPDisabledFor(_mapId, VMAP::VMAP_DISABLE_AREAFLAG))
data.areaInfo.emplace(info.hitModel->GetWmoID(), info.hitInstance->adtId, info.rootId, info.hitModel->GetMogpFlags(), info.hitInstance->ID);
return true;
}
}
return false;
}
bool DynamicVMapCollisionData::GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const
{
G3D::Vector3 startPos(x1, y1, z1);
G3D::Vector3 dstPos(x2, y2, z2);
G3D::Vector3 resultPos;
bool result = DynamicMapTree::GetObjectHitPos(phasemask, startPos, dstPos, resultPos, modifyDist);
rx = resultPos.x;
ry = resultPos.y;
rz = resultPos.z;
return result;
}
dtNavMeshQuery const* MMapData::GetNavMeshQuery()
{
if (_navMesh && !_navMeshQuery)
_navMeshQuery = MMAP::MMapMgr::CreateNavMeshQuery(_navMesh.get());
return _navMeshQuery.get();
}
+100
View File
@@ -0,0 +1,100 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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 MAP_COLLISION_DATA_H
#define MAP_COLLISION_DATA_H
#include "DynamicTree.h"
#include "MapTree.h"
#include "MMapMgr.h"
#include "IVMapMgr.h"
class Map;
namespace VMAP
{
enum class ModelIgnoreFlags : uint32;
}
// Simple wrappers for vmap static & dynamic trees to enable clear separation of use for increased readability
class StaticVMapCollisionData
{
friend class MapCollisionData;
public:
StaticVMapCollisionData(uint32 mapId) : _mapId(mapId) {}
bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, VMAP::ModelIgnoreFlags ignoreFlags) const;
bool GetObjectHitPos(float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const;
float getHeight(float x, float y, float z, float maxSearchDist) const;
bool GetAreaAndLiquidData(float x, float y, float z, Optional<uint8> reqLiquidType, VMAP::AreaAndLiquidData& data) const;
protected:
// _staticTree is a shared_ptr as it will point to a parent maps static tree (if exists) to save on memory
std::shared_ptr<VMAP::StaticMapTree> _staticTree;
uint32 _mapId;
};
class DynamicVMapCollisionData : public DynamicMapTree
{
public:
bool GetObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float x2, float y2, float z2, float& rx, float& ry, float& rz, float modifyDist) const;
};
class MMapData
{
friend class MapCollisionData;
public:
dtNavMesh const* GetNavMesh() const { return _navMesh.get(); }
dtNavMeshQuery const* GetNavMeshQuery();
protected:
// _navMesh is a shared_ptr as it will point to a parent maps nav mesh (if exists) to save on memory
std::shared_ptr<dtNavMesh> _navMesh;
// navMeshQuery is not thread safe and needs its own instance per map
MMAP::ManagedNavMeshQuery _navMeshQuery;
};
// Map collision data holders (dynamic&static vmap, mmaps)
class MapCollisionData
{
public:
MapCollisionData(Map const& map, Map const* parentMap);
~MapCollisionData() = default;
int LoadVMapTile(uint32 tileX, uint32 tileY);
int LoadMMapTile(uint32 tileX, uint32 tileY);
DynamicVMapCollisionData& GetDynamicTree() { return _dynamicVMapData; }
DynamicVMapCollisionData const& GetDynamicTree() const { return _dynamicVMapData; }
StaticVMapCollisionData& GetStaticTree() { return _staticVMapData; }
StaticVMapCollisionData const& GetStaticTree() const { return _staticVMapData; }
MMapData& GetMMapData() { return _mmapData; }
MMapData const& GetMMapData() const { return _mmapData; }
std::shared_ptr<VMAP::StaticMapTree> const GetStaticTreeSharedPtr() const { return _staticVMapData._staticTree; }
std::shared_ptr<dtNavMesh> const GetMMapNavMeshSharedPtr() const { return _mmapData._navMesh; }
private:
Map const& _map;
DynamicVMapCollisionData _dynamicVMapData;
StaticVMapCollisionData _staticVMapData;
MMapData _mmapData;
};
#endif
+79
View File
@@ -0,0 +1,79 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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 AZEROTHCORE_SPAWNDATA_H
#define AZEROTHCORE_SPAWNDATA_H
#include "Define.h"
#include <string>
enum SpawnObjectType : uint8
{
SPAWN_TYPE_CREATURE = 0,
SPAWN_TYPE_GAMEOBJECT = 1,
SPAWN_TYPE_MAX
};
enum SpawnObjectTypeMask : uint8
{
SPAWN_TYPEMASK_CREATURE = (1 << SPAWN_TYPE_CREATURE),
SPAWN_TYPEMASK_GAMEOBJECT = (1 << SPAWN_TYPE_GAMEOBJECT),
SPAWN_TYPEMASK_ALL = SPAWN_TYPEMASK_CREATURE | SPAWN_TYPEMASK_GAMEOBJECT
};
enum SpawnGroupFlags : uint32
{
SPAWNGROUP_FLAG_NONE = 0x00,
SPAWNGROUP_FLAG_SYSTEM = 0x01,
SPAWNGROUP_FLAG_MANUAL_SPAWN = 0x04,
SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE = 0x08,
SPAWNGROUP_FLAG_ESCORTQUESTNPC = 0x10,
SPAWNGROUP_FLAG_ALL = SPAWNGROUP_FLAG_SYSTEM |
SPAWNGROUP_FLAG_MANUAL_SPAWN | SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE |
SPAWNGROUP_FLAG_ESCORTQUESTNPC
};
struct SpawnGroupTemplateData
{
uint32 groupId;
std::string name;
uint16 mapid;
SpawnGroupFlags flags;
};
struct SpawnData
{
SpawnObjectType const type;
uint16 mapid{0};
uint32 phaseMask{0};
float posX{0.0f};
float posY{0.0f};
float posZ{0.0f};
float orientation{0.0f};
uint8 spawnMask{0};
uint32 ScriptId{0};
bool dbData{true};
uint32 spawnGroupId{0};
protected:
SpawnData(SpawnObjectType t) : type(t) {}
};
#endif // AZEROTHCORE_SPAWNDATA_H
+57 -2
View File
@@ -815,6 +815,7 @@ enum AcoreStrings
LANG_NO_REASON = 35407,
LANG_UNAUTHORIZED = 35408,
LANG_PINFO_CHR_MAP_WITH_AREA = 35409,
LANG_PINFO_CHR_ONLINETIME = 35410,
LANG_CHARACTER_GENDER_MALE = 855,
LANG_CHARACTER_GENDER_FEMALE = 856,
@@ -1202,7 +1203,30 @@ enum AcoreStrings
LANG_BF_QUEUE_PLAYER_QUEUE = 5123,
LANG_BF_QUEUE_PLAYER_INVITED = 5124,
LANG_BF_QUEUE_PLAYER_WAR = 5125,
// Room for more strings 5126-9999
// Autobroadcast commands
LANG_AUTOBROADCAST_LIST_HEADER = 5126,
LANG_AUTOBROADCAST_LIST_ENTRY = 5127,
LANG_AUTOBROADCAST_LIST_EMPTY = 5128,
LANG_AUTOBROADCAST_ADD_SUCCESS = 5129,
LANG_AUTOBROADCAST_REMOVE_SUCCESS = 5130,
LANG_AUTOBROADCAST_NOT_FOUND = 5131,
LANG_AUTOBROADCAST_LOCALE_SUCCESS = 5132,
LANG_AUTOBROADCAST_LOCALE_ENTRY = 5133,
LANG_AUTOBROADCAST_INVALID_LOCALE = 5134,
// Mail commands
LANG_MAIL_LIST_HEADER = 5135,
LANG_MAIL_LIST_ENTRY = 5136,
LANG_MAIL_LIST_EMPTY = 5137,
LANG_MAIL_RETURN_SUCCESS = 5138,
LANG_MAIL_RETURN_NOT_FOUND = 5139,
LANG_MAIL_RETURN_NOT_NORMAL = 5140,
LANG_MAIL_RETURN_NO_SENDER = 5141,
LANG_MAIL_RETURN_ALREADY_RETURNED = 5142,
LANG_MAIL_RETURN_HOOK_BLOCKED = 5143,
// Room for more strings 5144-9999
// Level requirement notifications
LANG_SAY_REQ = 6604,
@@ -1390,6 +1414,37 @@ enum AcoreStrings
LANG_DEBUG_LFG_ON = 30096,
LANG_DEBUG_LFG_OFF = 30097,
LANG_DEBUG_LFG_CONF = 30098
LANG_DEBUG_LFG_CONF = 30098,
// debug loot command
LANG_DEBUG_LOOT_HEADER = 30099,
LANG_DEBUG_LOOT_ITEM = 30100,
LANG_DEBUG_LOOT_ITEM_QUEST = 30101,
LANG_DEBUG_LOOT_GOLD = 30102,
LANG_DEBUG_LOOT_EMPTY = 30103,
LANG_DEBUG_LOOT_INVALID_TYPE = 30104,
LANG_DEBUG_LOOT_NO_TEMPLATE = 30105,
LANG_DEBUG_LOOT_HEADER_MULTI = 30106,
LANG_DEBUG_LOOT_ITEM_MULTI = 30107,
LANG_DEBUG_LOOT_ITEM_QUEST_MULTI = 30108,
LANG_DEBUG_LOOT_GOLD_MULTI = 30109,
// debug factionchange command (30110-30125)
LANG_DEBUG_FACTIONCHANGE_HEADER = 30110,
LANG_DEBUG_FACTIONCHANGE_FLAG_FACTION = 30111,
LANG_DEBUG_FACTIONCHANGE_FLAG_RACE = 30112,
LANG_DEBUG_FACTIONCHANGE_FLAG_NONE = 30113,
LANG_DEBUG_FACTIONCHANGE_GUILD_FAIL = 30114,
LANG_DEBUG_FACTIONCHANGE_GUILD_OK = 30115,
LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_FAIL = 30116,
LANG_DEBUG_FACTIONCHANGE_ARENA_CAPTAIN_OK = 30117,
LANG_DEBUG_FACTIONCHANGE_MAIL_FAIL = 30118,
LANG_DEBUG_FACTIONCHANGE_MAIL_OK = 30119,
LANG_DEBUG_FACTIONCHANGE_AUCTION_FAIL = 30120,
LANG_DEBUG_FACTIONCHANGE_AUCTION_OK = 30121,
LANG_DEBUG_FACTIONCHANGE_GOLD_FAIL = 30122,
LANG_DEBUG_FACTIONCHANGE_GOLD_OK = 30123,
LANG_DEBUG_FACTIONCHANGE_GOLD_NOLIMIT = 30124,
LANG_DEBUG_FACTIONCHANGE_NA = 30125
};
#endif
+5 -5
View File
@@ -538,9 +538,9 @@ void MotionMaster::MovePath(uint32 path_id, ForcedMovement forcedMovement, PathS
}
Movement::PointsArray points;
for (auto& point : *path)
for (auto const& node : path->Nodes)
{
points.push_back(G3D::Vector3(point.second.x, point.second.y, point.second.z));
points.push_back(G3D::Vector3(node.X, node.Y, node.Z));
}
// pass the new PointsArray* to the appropriate MoveSplinePath function
@@ -879,10 +879,10 @@ void MotionMaster::MoveDistract(uint32 timer)
void MotionMaster::Mutate(MovementGenerator* m, MovementSlot slot)
{
bool const delayed = (_cleanFlag & MMCF_UPDATE);
while (MovementGenerator* curr = Impl[slot])
{
bool delayed = (_top == slot && (_cleanFlag & MMCF_UPDATE));
// clear slot AND decrease top immediately to avoid crashes when referencing null top in DirectDelete
Impl[slot] = nullptr;
while (!empty() && !top())
@@ -918,7 +918,7 @@ void MotionMaster::MoveWaypoint(uint32 path_id, bool repeatable, PathSource path
if (_owner->HasUnitFlag(UNIT_FLAG_DISABLE_MOVE))
return;
Mutate(new WaypointMovementGenerator<Creature>(path_id, pathSource, repeatable), MOTION_SLOT_IDLE);
Mutate(new WaypointMovementGenerator<Creature>(path_id, repeatable, pathSource), MOTION_SLOT_IDLE);
LOG_DEBUG("movement.motionmaster", "{} ({}) start moving over path(Id:{}, repeatable: {})",
_owner->IsPlayer() ? "Player" : "Creature", _owner->GetGUID().ToString(), path_id, repeatable ? "YES" : "NO");
@@ -20,7 +20,6 @@
#include "DetourCommon.h"
#include "Geometry.h"
#include "Log.h"
#include "MMapFactory.h"
#include "MMapMgr.h"
#include "Map.h"
#include "Metric.h"
@@ -34,12 +33,10 @@ PathGenerator::PathGenerator(WorldObject const* owner) :
{
memset(_pathPolyRefs, 0, sizeof(_pathPolyRefs));
uint32 mapId = _source->GetMapId();
//if (sDisableMgr->IsPathfindingEnabled(_sourceUnit->FindMap()))
{
MMAP::MMapMgr* mmap = MMAP::MMapFactory::createOrGetMMapMgr();
_navMesh = mmap->GetNavMesh(mapId);
_navMeshQuery = mmap->GetNavMeshQuery(mapId, _source->GetInstanceId());
_navMesh = _source->GetMap()->GetMapCollisionData().GetMMapData().GetNavMesh();
_navMeshQuery = _source->GetMap()->GetMapCollisionData().GetMMapData().GetNavMeshQuery();
}
CreateFilter();
@@ -259,12 +259,7 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
owner->SetInFront(target);
if (cOwner)
{
if (cOwner->IsWithinMeleeRange(target))
cOwner->SetCannotReachTarget();
else
cOwner->SetCannotReachTarget(target->GetGUID());
}
cOwner->SetCannotReachTarget();
MovementInform(owner);
}
@@ -27,45 +27,69 @@
#include "Player.h"
#include "Spell.h"
#include "Transport.h"
#include "World.h"
#include "SmartScriptMgr.h"
#include "World.h"
void WaypointMovementGenerator<Creature>::LoadPath(Creature* creature)
inline G3D::Vector3 PositionToVector3(Position const& p) { return { p.GetPositionX(), p.GetPositionY(), p.GetPositionZ() }; }
WaypointMovementGenerator<Creature>::WaypointMovementGenerator(uint32 pathId, bool repeating, PathSource pathSource) : PathMovementBase((WaypointPath const*)nullptr),
_lastSplineId(0), _pathId(pathId), _waypointDelay(0),
_waypointReached(true), _recalculateSpeed(false), _repeating(repeating), _loadedFromDB(true), _stalled(false), _hasBeenStalled(false), _done(false), _pathSource(pathSource),
_smoothSplineLaunched(false), _lastPassedSplineIdx(0)
{
switch (i_pathSource)
{
case PathSource::WAYPOINT_MGR:
{
if (!path_id)
path_id = creature->GetWaypointPath();
}
i_path = sWaypointMgr->GetPath(path_id);
break;
}
case PathSource::SMART_WAYPOINT_MGR:
WaypointMovementGenerator<Creature>::WaypointMovementGenerator(WaypointPath& path, bool repeating) : PathMovementBase((WaypointPath const*)nullptr),
_lastSplineId(0), _pathId(0), _waypointDelay(0),
_waypointReached(true), _recalculateSpeed(false), _repeating(repeating), _loadedFromDB(false), _stalled(false), _hasBeenStalled(false), _done(false), _pathSource(PathSource::WAYPOINT_MGR),
_smoothSplineLaunched(false), _lastPassedSplineIdx(0)
{
i_path = &path;
}
void WaypointMovementGenerator<Creature>::DoInitialize(Creature* creature)
{
_done = false;
if (_loadedFromDB)
{
if (!_pathId)
_pathId = creature->GetWaypointPath();
switch (_pathSource)
{
i_path = sSmartWaypointMgr->GetPath(path_id);
break;
default:
case PathSource::WAYPOINT_MGR:
i_path = sWaypointMgr->GetPath(_pathId);
break;
case PathSource::SMART_WAYPOINT_MGR:
i_path = sSmartWaypointMgr->GetPath(_pathId);
break;
}
}
if (!i_path)
{
// No movement found for entry
LOG_ERROR("sql.sql", "WaypointMovementGenerator::LoadPath: creature {} ({}) doesn't have waypoint path id: {}",
creature->GetName(), creature->GetGUID().ToString(), path_id);
LOG_ERROR("sql.sql", "WaypointMovementGenerator::DoInitialize: creature {} ({}) doesn't have waypoint path id: {}",
creature->GetName(), creature->GetGUID().ToString(), _pathId);
return;
}
i_currentNode = i_path->begin()->first;
// Determine our first waypoint from the creature's stored waypoint
if (CreatureData const* creatureData = creature->GetCreatureData())
{
if (i_path->Nodes.size() > creatureData->currentwaypoint)
{
creature->UpdateCurrentWaypointInfo(creatureData->currentwaypoint, i_path->Id);
i_currentNode = creatureData->currentwaypoint;
}
}
StartMoveNow(creature);
}
void WaypointMovementGenerator<Creature>::DoInitialize(Creature* creature)
{
LoadPath(creature);
creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
// Inform AI
if (CreatureAI* AI = creature->AI())
AI->WaypointPathStarted(i_path->Id);
}
void WaypointMovementGenerator<Creature>::DoFinalize(Creature* creature)
@@ -75,133 +99,219 @@ void WaypointMovementGenerator<Creature>::DoFinalize(Creature* creature)
void WaypointMovementGenerator<Creature>::DoReset(Creature* creature)
{
if (stalled)
// We did not reach our last waypoint before reset, treat this scenario as resuming movement.
if (!_done && !_waypointReached)
_hasBeenStalled = true;
else if (_done)
{
return;
}
creature->AddUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE);
StartMoveNow(creature);
}
void WaypointMovementGenerator<Creature>::OnArrived(Creature* creature)
{
if (!i_path || i_path->empty())
return;
if (m_isArrivalDone)
return;
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
m_isArrivalDone = true;
auto currentNodeItr = i_path->find(i_currentNode);
if (currentNodeItr->second.event_id && urand(0, 99) < currentNodeItr->second.event_chance)
{
LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.",
currentNodeItr->second.event_id, i_currentNode, creature->GetGUID().ToString());
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
creature->GetMap()->ScriptsStart(sWaypointScripts, currentNodeItr->second.event_id, creature, nullptr);
}
// Inform script
MovementInform(creature);
creature->UpdateWaypointID(i_currentNode);
if (currentNodeItr->second.delay)
{
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
Stop(currentNodeItr->second.delay);
// mimic IdleMovementGenerator
if (!creature->IsStopped())
creature->StopMoving();
}
}
bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
inline void UpdateHomePosition(Creature* creature, WaypointNode const& waypointNode)
{
if (!i_path || i_path->empty())
return false;
// Xinef: Dont allow dead creatures to move
if (!creature->IsAlive())
return false;
if (Stopped())
return true;
float x = waypointNode.X;
float y = waypointNode.Y;
float z = waypointNode.Z;
float o = creature->GetOrientation();
bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
if (m_isArrivalDone)
if (!transportPath)
creature->SetHomePosition(x, y, z, o);
else
{
if (Transport* trans = (creature->GetTransport() ? creature->GetTransport()->ToMotionTransport() : nullptr))
{
auto currentNodeItr = i_path->find(i_currentNode);
float x = currentNodeItr->second.x;
float y = currentNodeItr->second.y;
float z = currentNodeItr->second.z;
float o = creature->GetOrientation();
o -= trans->GetOrientation();
creature->SetTransportHomePosition(x, y, z, o);
trans->CalculatePassengerPosition(x, y, z, &o);
creature->SetHomePosition(x, y, z, o);
}
}
}
if (!transportPath)
creature->SetHomePosition(x, y, z, o);
else
void WaypointMovementGenerator<Creature>::ProcessWaypointArrival(Creature* creature, WaypointNode const& waypoint)
{
if (_waypointReached)
return;
if (waypoint.Delay > 0)
{
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
_waypointDelay = waypoint.Delay;
}
bool pathEnded = (i_currentNode == i_path->Nodes.size() - 1) && !_repeating && !_done;
if (pathEnded)
_done = true;
UpdateHomePosition(creature, waypoint);
if (waypoint.EventId && urand(0, 99) < waypoint.EventChance)
{
LOG_DEBUG("maps.script", "Creature movement start script {} at point {} for {}.",
waypoint.EventId, i_currentNode, creature->GetGUID().ToString());
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.EventId, creature, nullptr);
}
// scripts can invalidate current path, store what we need
uint32 const waypointId = waypoint.Id;
uint32 const pathId = i_path->Id;
creature->UpdateWaypointID(waypointId);
creature->UpdateCurrentWaypointInfo(waypointId, pathId);
// Inform AI
if (CreatureAI* AI = creature->AI())
{
AI->MovementInform(WAYPOINT_MOTION_TYPE, waypointId);
AI->WaypointReached(waypointId, pathId);
}
if (Unit* owner = creature->GetCharmerOrOwner())
{
if (UnitAI* AI = owner->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypointId);
}
else
{
if (TempSummon* tempSummon = creature->ToTempSummon())
if (Unit* owner2 = tempSummon->GetSummonerUnit())
if (UnitAI* AI = owner2->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, waypointId);
}
// Path end notifications fire after WaypointReached so that m_path_id
// is still valid when SmartAI checks it for SMART_EVENT_WAYPOINT_REACHED.
if (pathEnded)
{
creature->UpdateCurrentWaypointInfo(0, 0);
if (CreatureAI* AI = creature->AI())
AI->PathEndReached(pathId);
// Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI
if (CreatureAI* AI = creature->AI())
AI->WaypointPathEnded(waypointId, pathId);
}
// All hooks called and infos updated. Time to increment the waypoint node id
if (i_path && !i_path->Nodes.empty()) // ensure that the path has not been changed in one of the hooks.
i_currentNode = (i_currentNode + 1) % i_path->Nodes.size();
_waypointReached = true;
}
void WaypointMovementGenerator<Creature>::StartMove(Creature* creature, bool relaunch /*= false*/)
{
// Formation checks. Do not launch a new spline when one of our formation members is currently in combat.
if (!relaunch)
{
if (!IsAllowedToMove(creature) || (creature->IsFormationLeader() && !creature->IsFormationLeaderMoveAllowed()))
{
_waypointDelay = 1000;
return;
}
}
// Dont allow dead creatures to move
if (!creature->IsAlive())
return;
// Step two: node selection is done, build spline data
creature->AddUnitState(UNIT_STATE_ROAMING_MOVE);
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
bool const useTransportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
Movement::MoveSplineInit init(creature);
//! If the creature is on transport, we assume waypoints set in DB are already transport offsets
if (useTransportPath)
init.DisableTransportPathTransformations();
if (waypoint.SmoothTransition && i_path->Nodes.size() > 2)
{
// Build a catmullrom spline segment, stopping at delay waypoints
init.Path().push_back(PositionToVector3(creature->GetPosition()));
bool hasDelayInSegment = false;
uint32 segmentNodes = 0;
for (uint32 i = 0; i < i_path->Nodes.size(); ++i)
{
uint32 idx = (i_currentNode + i) % i_path->Nodes.size();
WaypointNode const& node = i_path->Nodes.at(idx);
init.Path().push_back(G3D::Vector3(node.X, node.Y, node.Z));
segmentNodes++;
// Stop the segment at a waypoint with a delay
if (node.Delay > 0)
{
if (Transport* trans = (creature->GetTransport() ? creature->GetTransport()->ToMotionTransport() : nullptr))
{
o -= trans->GetOrientation();
creature->SetTransportHomePosition(x, y, z, o);
trans->CalculatePassengerPosition(x, y, z, &o);
creature->SetHomePosition(x, y, z, o);
}
else
transportPath = false;
// else if (vehicle) - this should never happen, vehicle offsets are const
hasDelayInSegment = true;
break;
}
}
// Xinef: moved the upper IF here
uint32 lastPoint = i_path->rbegin()->first;
if ((i_currentNode == lastPoint) && !repeating) // If that's our last waypoint
// If no delays found and repeating, add wrap-around points for seamless loop
if (!hasDelayInSegment && _repeating)
{
creature->AI()->PathEndReached(path_id);
creature->GetMotionMaster()->Initialize();
return false;
for (uint32 i = 0; i < std::min<uint32>(3, i_path->Nodes.size()); ++i)
{
uint32 idx = (i_currentNode + i) % i_path->Nodes.size();
WaypointNode const& node = i_path->Nodes.at(idx);
init.Path().push_back(G3D::Vector3(node.X, node.Y, node.Z));
}
}
++i_currentNode;
if (lastPoint < i_currentNode)
i_currentNode = i_path->begin()->first;
// Need at least 3 waypoints for a meaningful catmullrom spline
if (segmentNodes >= 3)
{
init.SetFirstPointId(i_currentNode);
init.SetSmooth();
_smoothSplineLaunched = true;
_lastPassedSplineIdx = i_currentNode;
}
else
{
// Too few points for catmullrom, fall back to linear point-to-point
init.Path().clear();
init.MoveTo(G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
}
}
// xinef: do not initialize motion if we got stunned in movementinform
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
else if (!waypoint.SplinePoints.empty())
{
return true;
// We have spline points in waypoint_data_addon table
int32 splineIndex = 0;
auto itr = waypoint.SplinePoints.begin();
if (splineIndex)
std::advance(itr, splineIndex);
init.Path().reserve(waypoint.SplinePoints.size() - splineIndex);
std::copy(itr, waypoint.SplinePoints.end(), std::back_inserter(init.Path()));
// Add starting vertex and destination
init.Path().insert(init.Path().begin(), PositionToVector3(creature->GetPosition()));
init.Path().insert(init.Path().end(), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
}
auto currentNodeItr = i_path->find(i_currentNode);
WaypointData const& node = currentNodeItr->second;
m_isArrivalDone = false;
creature->AddUnitState(UNIT_STATE_ROAMING_MOVE);
Movement::Location formationDest(node.x, node.y, node.z, 0.0f);
Movement::MoveSplineInit init(creature);
//! If creature is on transport, we assume waypoints set in DB are already transport offsets
if (transportPath)
else
{
init.DisableTransportPathTransformations();
if (TransportBase* trans = creature->GetDirectTransport())
trans->CalculatePassengerPosition(formationDest.x, formationDest.y, formationDest.z, &formationDest.orientation);
// Smooth transition for short paths (<=2 nodes): use previous spline endpoint as start
if (waypoint.SmoothTransition && !creature->movespline->Finalized() && _lastSplineId == creature->movespline->GetId())
{
init.MoveTo(creature->movespline->FinalDestination(), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
if (!init.Path().empty())
init.Path().insert(init.Path().begin(), PositionToVector3(creature->GetPosition()));
}
else
init.MoveTo(PositionToVector3(creature->GetPosition()), G3D::Vector3(waypoint.X, waypoint.Y, waypoint.Z));
}
float z = node.z;
creature->UpdateAllowedPositionZ(node.x, node.y, z);
//! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call
//! but formationDest contains global coordinates
init.MoveTo(node.x, node.y, z, true, true);
if (waypoint.Orientation.has_value() && waypoint.Delay > 0)
init.SetFacing(*waypoint.Orientation);
if (node.orientation.has_value() && node.delay > 0)
init.SetFacing(*node.orientation);
switch (node.move_type)
switch (waypoint.MoveType)
{
case WAYPOINT_MOVE_TYPE_LAND:
init.SetAnimation(AnimTier::Ground);
@@ -219,89 +329,214 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature)
break;
}
if (creature->CanFly())
init.SetFly();
if (waypoint.Velocity > 0.f)
init.SetVelocity(waypoint.Velocity);
init.Launch();
//Call for creature group update
if (creature->GetFormation() && creature->GetFormation()->GetLeader() == creature && creature->GetFormation()->CanLeaderStartMoving())
creature->GetFormation()->LeaderStartedMoving();
if (!creature->movespline->Finalized())
_lastSplineId = creature->movespline->GetId();
return true;
// Inform formation
creature->SignalFormationMovement();
// Inform AI
if (!relaunch)
if (CreatureAI* AI = creature->AI())
AI->WaypointStarted(waypoint.Id, i_path->Id);
_waypointReached = false;
_recalculateSpeed = false;
_hasBeenStalled = false;
}
bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 diff)
{
// Waypoint movement can be switched on/off
// This is quite handy for escort quests and other stuff
if (stalled)
{
Stop(1000);
if (!creature || !creature->IsAlive())
return true;
}
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
if (_done || !i_path || i_path->Nodes.empty())
return true;
// Stop movement if paused, rooted, or casting
if (!IsAllowedToMove(creature) && !creature->movespline->Finalized())
{
creature->StopMoving();
Stop(1000);
_lastSplineId = 0;
_smoothSplineLaunched = false;
}
// Set home position to current position.
if (!creature->movespline->Finalized())
{
bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID();
if (!transportPath)
creature->SetHomePosition(creature->GetPosition());
}
// Smooth spline: track waypoint passages without rebuilding
if (_smoothSplineLaunched && creature->movespline->GetId() == _lastSplineId)
{
int32 currentIdx = creature->movespline->currentPathIdx();
// Process passed waypoints
while (_lastPassedSplineIdx < currentIdx)
{
_lastPassedSplineIdx++;
WaypointNode const& passedWp = i_path->Nodes.at(i_currentNode);
UpdateHomePosition(creature, passedWp);
// Save data before AI callbacks — they can invalidate the reference
uint32 const wpId = passedWp.Id;
uint32 const wpPathId = i_path->Id;
uint32 const wpDelay = passedWp.Delay;
std::optional<float> const wpOrientation = passedWp.Orientation;
creature->UpdateWaypointID(wpId);
creature->UpdateCurrentWaypointInfo(wpId, wpPathId);
if (passedWp.EventId && urand(0, 99) < passedWp.EventChance)
{
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
creature->GetMap()->ScriptsStart(sWaypointScripts, passedWp.EventId, creature, nullptr);
}
if (CreatureAI* AI = creature->AI())
{
AI->MovementInform(WAYPOINT_MOTION_TYPE, wpId);
AI->WaypointReached(wpId, wpPathId);
}
// Advance node
if (i_path && !i_path->Nodes.empty())
i_currentNode = (i_currentNode + 1) % i_path->Nodes.size();
// If this waypoint has a delay, stop the spline and pause
if (wpDelay > 0)
{
creature->StopMoving();
creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE);
_waypointDelay = wpDelay;
_waypointReached = true;
_smoothSplineLaunched = false;
if (wpOrientation.has_value())
creature->SetFacingTo(*wpOrientation);
return true;
}
}
if (creature->movespline->Finalized())
{
if (!_repeating)
{
// Path ended
uint32 const endWpId = i_path->Nodes.at(i_currentNode).Id;
uint32 const endPathId = i_path->Id;
_done = true;
_smoothSplineLaunched = false;
creature->UpdateCurrentWaypointInfo(0, 0);
if (CreatureAI* AI = creature->AI())
AI->PathEndReached(endPathId);
// Re-fetch AI — PathEndReached may have despawned the creature or swapped its AI
if (CreatureAI* AI = creature->AI())
AI->WaypointPathEnded(endWpId, endPathId);
}
else
{
// Repeating: rebuild spline
_smoothSplineLaunched = false;
StartMove(creature);
}
}
return true;
}
// prevent a crash at empty waypoint path.
if (!i_path || i_path->empty())
return false;
// Non-smooth: per-waypoint logic
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
UpdateWaypointState(creature, waypoint);
// Xinef: Dont allow dead creatures to move
if (!creature->IsAlive())
return false;
// Process movement preventing timers
if (_waypointDelay > 0)
{
_waypointDelay -= diff;
if (_waypointDelay > 0)
return true;
}
if (Stopped())
if (_pauseTime.has_value())
{
if (CanMove(diff))
return StartMove(creature);
}
else
{
if (creature->movespline->Finalized())
{
OnArrived(creature);
return StartMove(creature);
}
*_pauseTime -= diff;
if (*_pauseTime > 0)
return true;
else
_pauseTime.reset();
}
// Timers are ready, let's try to move
if (IsAllowedToMove(creature) && (_waypointReached || _recalculateSpeed || _hasBeenStalled))
StartMove(creature, _recalculateSpeed || _hasBeenStalled);
return true;
}
void WaypointMovementGenerator<Creature>::MovementInform(Creature* creature)
void WaypointMovementGenerator<Creature>::Pause(uint32 timer /*= 0*/)
{
if (creature->AI())
creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode);
if (Unit* owner = creature->GetCharmerOrOwner())
{
if (UnitAI* AI = owner->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
}
else
{
if (TempSummon* tempSummon = creature->ToTempSummon())
if (Unit* owner = tempSummon->GetSummonerUnit())
if (UnitAI* AI = owner->GetAI())
AI->SummonMovementInform(creature, WAYPOINT_MOTION_TYPE, i_currentNode);
}
_stalled = timer ? false : true;
_hasBeenStalled = !_waypointReached;
_pauseTime = timer;
}
void WaypointMovementGenerator<Creature>::Pause(uint32 timer)
void WaypointMovementGenerator<Creature>::Resume(uint32 overrideTimer /*= 0*/)
{
if (timer)
i_nextMoveTime.Reset(timer);
else
{
// No timer? Will be paused forever until ::Resume is called
stalled = true;
i_nextMoveTime.Reset(1);
}
_hasBeenStalled = !_waypointReached;
_stalled = false;
if (overrideTimer)
_pauseTime = overrideTimer;
}
void WaypointMovementGenerator<Creature>::Resume(uint32 /*overrideTimer/*/)
bool WaypointMovementGenerator<Creature>::GetResetPosition(float& x, float& y, float& z)
{
stalled = false;
// prevent a crash at empty waypoint path.
if (!i_path || i_path->Nodes.empty())
return false;
ASSERT(i_currentNode < i_path->Nodes.size(), "WaypointMovementGenerator::GetResetPos: tried to reference a node id ({}) which is not included in path ({})", i_currentNode, i_path->Id);
WaypointNode const& waypoint = i_path->Nodes.at(i_currentNode);
x = waypoint.X;
y = waypoint.Y;
z = waypoint.Z;
return true;
}
bool WaypointMovementGenerator<Creature>::IsAllowedToMove(Creature* creature) const
{
if (_stalled || _done)
return false;
if (_pauseTime.has_value())
return false;
if (creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting())
return false;
return true;
}
void WaypointMovementGenerator<Creature>::UpdateWaypointState(Creature* creature, WaypointNode const& waypointNode)
{
if (creature->movespline->GetId() != _lastSplineId)
return;
if (creature->movespline->Finalized())
ProcessWaypointArrival(creature, waypointNode);
}
//----------------------------------------------------//
@@ -54,49 +54,43 @@ class WaypointMovementGenerator<Creature> : public MovementGeneratorMedium< Crea
public PathMovementBase<Creature, WaypointPath const*>
{
public:
WaypointMovementGenerator(uint32 _path_id = 0, PathSource pathSource = PathSource::WAYPOINT_MGR, bool _repeating = true, bool _stalled = false)
: PathMovementBase((WaypointPath const*)nullptr), i_nextMoveTime(0), m_isArrivalDone(false), path_id(_path_id), repeating(_repeating), stalled(_stalled), i_pathSource(pathSource) {}
explicit WaypointMovementGenerator(uint32 pathId = 0, bool repeating = true, PathSource pathSource = PathSource::WAYPOINT_MGR);
explicit WaypointMovementGenerator(WaypointPath& path, bool repeating = true);
~WaypointMovementGenerator() { i_path = nullptr; }
void DoInitialize(Creature*);
void DoFinalize(Creature*);
void DoReset(Creature*);
bool DoUpdate(Creature*, uint32 diff);
void Pause(uint32 timer = 0);
void Resume(uint32 overrideTimer/* = 0*/);
void MovementInform(Creature*);
void unitSpeedChanged() override { _recalculateSpeed = true; }
void Pause(uint32 timer = 0) override;
void Resume(uint32 overrideTimer = 0) override;
bool GetResetPosition(float& x, float& y, float& z) override;
MovementGeneratorType GetMovementGeneratorType() { return WAYPOINT_MOTION_TYPE; }
// now path movement implmementation
void LoadPath(Creature*);
MovementGeneratorType GetMovementGeneratorType() override { return WAYPOINT_MOTION_TYPE; }
private:
void Stop(int32 time) { i_nextMoveTime.Reset(time);}
void ProcessWaypointArrival(Creature*, WaypointNode const&);
void StartMove(Creature*, bool relaunch = false);
bool IsAllowedToMove(Creature*) const;
void UpdateWaypointState(Creature*, WaypointNode const&);
bool Stopped() { return !i_nextMoveTime.Passed();}
uint32 _lastSplineId;
uint32 _pathId;
int32 _waypointDelay;
std::optional<int32> _pauseTime;
bool _waypointReached;
bool CanMove(int32 diff)
{
i_nextMoveTime.Update(diff);
return i_nextMoveTime.Passed();
}
void OnArrived(Creature*);
bool StartMove(Creature*);
void StartMoveNow(Creature* creature)
{
i_nextMoveTime.Reset(0);
StartMove(creature);
}
TimeTrackerSmall i_nextMoveTime;
bool m_isArrivalDone;
uint32 path_id;
bool repeating;
bool stalled;
PathSource i_pathSource;
bool _recalculateSpeed;
bool _repeating;
bool _loadedFromDB;
bool _stalled;
bool _hasBeenStalled;
bool _done;
PathSource _pathSource;
bool _smoothSplineLaunched;
int32 _lastPassedSplineIdx;
};
/** FlightPathMovementGenerator generates movement of the player for the paths
@@ -122,6 +122,7 @@ namespace Movement
[[nodiscard]] Vector3 FinalDestination() const { return Initialized() ? spline.getPoint(spline.last()) : Vector3(); }
[[nodiscard]] Vector3 CurrentDestination() const { return Initialized() ? spline.getPoint(point_Idx + 1) : Vector3(); }
[[nodiscard]] int32 currentPathIdx() const;
[[nodiscard]] int32 MaxPathIdx() const { return spline.last() - 1; }
[[nodiscard]] bool HasAnimation() const { return splineflags.animation; }
[[nodiscard]] uint8 GetAnimationType() const { return splineflags.animId; }
@@ -199,6 +199,25 @@ namespace Movement
args.flags.EnableFacingAngle();
}
void MoveSplineInit::MoveTo(Vector3 const& start, Vector3 const& dest, bool generatePath, bool forceDestination)
{
if (generatePath)
{
PathGenerator path(unit);
bool result = path.CalculatePath(start.x, start.y, start.z, dest.x, dest.y, dest.z, forceDestination);
if (result && !(path.GetPathType() & PATHFIND_NOPATH))
{
MovebyPath(path.GetPath());
return;
}
}
args.path_Idx_offset = 0;
args.path.resize(2);
TransportPathTransform transform(unit, args.TransformForTransport);
args.path[1] = transform(dest);
}
void MoveSplineInit::MoveTo(const Vector3& dest, bool generatePath, bool forceDestination)
{
if (generatePath)
@@ -99,6 +99,7 @@ namespace Movement
/* Initializes simple A to B motion, A is current unit's position, B is destination
*/
void MoveTo(Vector3 const& start, Vector3 const& destination, bool generatePath = true, bool forceDestination = false);
void MoveTo(const Vector3& destination, bool generatePath = false, bool forceDestination = false);
void MoveTo(float x, float y, float z, bool generatePath = false, bool forceDestination = false);
@@ -0,0 +1,72 @@
/*
* This file is part of the AzerothCore Project. See AUTHORS file for Copyright information
*
* 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 ACORE_WAYPOINTDEFINES_H
#define ACORE_WAYPOINTDEFINES_H
#include "Define.h"
#include "G3D/Vector3.h"
#include <optional>
#include <vector>
enum WaypointMoveType
{
WAYPOINT_MOVE_TYPE_WALK,
WAYPOINT_MOVE_TYPE_RUN,
WAYPOINT_MOVE_TYPE_LAND,
WAYPOINT_MOVE_TYPE_TAKEOFF,
WAYPOINT_MOVE_TYPE_MAX
};
struct WaypointNode
{
WaypointNode() : Id(0), X(0.f), Y(0.f), Z(0.f), Velocity(0.f), Delay(0), EventId(0), MoveType(WAYPOINT_MOVE_TYPE_RUN), EventChance(0), SmoothTransition(false) { }
WaypointNode(uint32 id, float x, float y, float z, std::optional<float> orientation = { }, float velocity = 0.f, uint32 delay = 0, bool smoothTransition = false) :
Id(id), X(x), Y(y), Z(z), Orientation(orientation), Velocity(velocity), Delay(delay), SmoothTransition(smoothTransition)
{
EventId = 0;
MoveType = WAYPOINT_MOVE_TYPE_WALK;
EventChance = 100;
}
uint32 Id;
float X, Y, Z;
std::optional<float> Orientation;
float Velocity;
uint32 Delay;
uint32 EventId;
uint32 MoveType;
uint8 EventChance;
bool SmoothTransition;
std::vector<G3D::Vector3> SplinePoints;
};
struct WaypointPath
{
WaypointPath() : Id(0) { }
WaypointPath(uint32 _id, std::vector<WaypointNode>&& _nodes)
{
Id = _id;
Nodes = _nodes;
}
std::vector<WaypointNode> Nodes;
uint32 Id;
};
#endif
@@ -22,20 +22,6 @@
#include "QueryResult.h"
#include "Timer.h"
WaypointMgr::WaypointMgr()
{
}
WaypointMgr::~WaypointMgr()
{
for (WaypointPathContainer::iterator itr = _waypointStore.begin(); itr != _waypointStore.end(); ++itr)
{
itr->second.clear();
}
_waypointStore.clear();
}
WaypointMgr* WaypointMgr::instance()
{
static WaypointMgr instance;
@@ -46,8 +32,8 @@ void WaypointMgr::Load()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5 6 7 8 9
QueryResult result = WorldDatabase.Query("SELECT id, point, position_x, position_y, position_z, orientation, move_type, delay, action, action_chance FROM waypoint_data ORDER BY id, point");
// 0 1 2 3 4 5 6 7 8 9 10 11
QueryResult result = WorldDatabase.Query("SELECT id, point, position_x, position_y, position_z, orientation, velocity, delay, smoothTransition, move_type, action, action_chance FROM waypoint_data ORDER BY id, point");
if (!result)
{
@@ -61,66 +47,113 @@ void WaypointMgr::Load()
do
{
Field* fields = result->Fetch();
WaypointData data;
uint32 pathId = fields[0].Get<uint32>();
WaypointPath& path = _waypointStore[pathId];
float x = fields[2].Get<float>();
float y = fields[3].Get<float>();
float z = fields[4].Get<float>();
std::optional<float > o;
std::optional<float> o;
if (!fields[5].IsNull())
o = fields[5].Get<float>();
float velocity = fields[6].Get<float>();
Acore::NormalizeMapCoord(x);
Acore::NormalizeMapCoord(y);
data.id = fields[1].Get<uint32>();
data.x = x;
data.y = y;
data.z = z;
data.orientation = o;
data.move_type = fields[6].Get<uint32>();
WaypointNode waypoint;
waypoint.Id = fields[1].Get<uint32>();
waypoint.X = x;
waypoint.Y = y;
waypoint.Z = z;
if (o.has_value())
waypoint.Orientation = o;
waypoint.Velocity = velocity;
waypoint.Delay = fields[7].Get<uint32>();
waypoint.SmoothTransition = fields[8].Get<bool>();
waypoint.MoveType = fields[9].Get<uint32>();
if (data.move_type >= WAYPOINT_MOVE_TYPE_MAX)
if (waypoint.MoveType >= WAYPOINT_MOVE_TYPE_MAX)
{
//LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", wp->id);
LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", waypoint.Id);
continue;
}
data.delay = fields[7].Get<uint32>();
data.event_id = fields[8].Get<uint32>();
data.event_chance = fields[9].Get<int16>();
waypoint.EventId = fields[10].Get<uint32>();
waypoint.EventChance = fields[11].Get<int16>();
path.emplace(data.id, data);
WaypointPath& path = _waypointStore[pathId];
path.Id = pathId;
path.Nodes.push_back(std::move(waypoint));
++count;
} while (result->NextRow());
for (auto itr = _waypointStore.begin(); itr != _waypointStore.end(); )
{
uint32 first = itr->second.begin()->first;
uint32 last = itr->second.rbegin()->first;
if (last - first + 1 != itr->second.size())
{
LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has non-contiguous pointids, skipping", itr->first);
itr = _waypointStore.erase(itr);
}
else
++itr;
}
LOG_INFO("server.loading", ">> Loaded {} waypoints in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void WaypointMgr::LoadWaypointAddons()
{
uint32 oldMSTime = getMSTime();
// 0 1 2 3 4 5
QueryResult result = WorldDatabase.Query("SELECT PathID, PointID, SplinePointIndex, PositionX, PositionY, PositionZ FROM waypoint_data_addon ORDER BY PathID, PointID, SplinePointIndex");
if (!result)
{
LOG_INFO("server.loading", ">> Loaded 0 waypoint addon data. DB table `waypoint_data_addon` is empty!");
LOG_INFO("server.loading", " ");
return;
}
uint32 count = 0;
do
{
Field* fields = result->Fetch();
uint32 pathId = fields[0].Get<uint32>();
auto it = _waypointStore.find(pathId);
if (it == _waypointStore.end())
{
LOG_ERROR("sql.sql", "Tried to load waypoint_data_addon data for PathID {} but there is no such path in waypoint_data. Ignoring.", pathId);
continue;
}
WaypointPath& path = it->second;
uint32 pointId = fields[1].Get<uint32>();
auto itr = std::find_if(path.Nodes.begin(), path.Nodes.end(), [pointId](WaypointNode const& node)
{
return node.Id == pointId;
});
if (itr == path.Nodes.end())
{
LOG_ERROR("sql.sql", "Tried to load waypoint_data_addon data for PointID {} of PathID {} but there is no such point in waypoint_data. Ignoring.", pointId, pathId);
continue;
}
float x = fields[3].Get<float>();
float y = fields[4].Get<float>();
float z = fields[5].Get<float>();
Acore::NormalizeMapCoord(x);
Acore::NormalizeMapCoord(y);
itr->SplinePoints.push_back(G3D::Vector3(x, y, z));
++count;
} while (result->NextRow());
LOG_INFO("server.loading", ">> Loaded {} waypoint addon data in {} ms", count, GetMSTimeDiffToNow(oldMSTime));
LOG_INFO("server.loading", " ");
}
void WaypointMgr::ReloadPath(uint32 id)
{
WaypointPathContainer::iterator itr = _waypointStore.find(id);
auto itr = _waypointStore.find(id);
if (itr != _waypointStore.end())
{
_waypointStore.erase(itr);
}
WorldDatabasePreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID);
@@ -131,13 +164,10 @@ void WaypointMgr::ReloadPath(uint32 id)
if (!result)
return;
WaypointPath& path = _waypointStore[id];
std::vector<WaypointNode> values;
do
{
Field* fields = result->Fetch();
WaypointData data;
float x = fields[1].Get<float>();
float y = fields[2].Get<float>();
float z = fields[3].Get<float>();
@@ -145,26 +175,34 @@ void WaypointMgr::ReloadPath(uint32 id)
if (!fields[4].IsNull())
o = fields[4].Get<float>();
float velocity = fields[5].Get<float>();
Acore::NormalizeMapCoord(x);
Acore::NormalizeMapCoord(y);
data.id = fields[0].Get<uint32>();
data.x = x;
data.y = y;
data.z = z;
data.orientation = o;
data.move_type = fields[5].Get<uint32>();
WaypointNode waypoint;
waypoint.Id = fields[0].Get<uint32>();
waypoint.X = x;
waypoint.Y = y;
waypoint.Z = z;
if (o.has_value())
waypoint.Orientation = o;
waypoint.Velocity = velocity;
waypoint.Delay = fields[6].Get<uint32>();
waypoint.SmoothTransition = fields[7].Get<bool>();
waypoint.MoveType = fields[8].Get<uint32>();
if (data.move_type >= WAYPOINT_MOVE_TYPE_MAX)
if (waypoint.MoveType >= WAYPOINT_MOVE_TYPE_MAX)
{
//LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", wp->id);
LOG_ERROR("sql.sql", "Waypoint {} in waypoint_data has invalid move_type, ignoring", waypoint.Id);
continue;
}
data.delay = fields[6].Get<uint32>();
data.event_id = fields[7].Get<uint32>();
data.event_chance = fields[8].Get<uint8>();
waypoint.EventId = fields[9].Get<uint32>();
waypoint.EventChance = fields[10].Get<uint8>();
path.emplace(data.id, data);
values.push_back(std::move(waypoint));
} while (result->NextRow());
_waypointStore[id] = WaypointPath(id, std::move(values));
}
@@ -18,35 +18,8 @@
#ifndef ACORE_WAYPOINTMANAGER_H
#define ACORE_WAYPOINTMANAGER_H
#include "Define.h"
#include <optional>
#include "WaypointDefines.h"
#include <unordered_map>
#include <vector>
#include <map>
enum WaypointMoveType
{
WAYPOINT_MOVE_TYPE_WALK,
WAYPOINT_MOVE_TYPE_RUN,
WAYPOINT_MOVE_TYPE_LAND,
WAYPOINT_MOVE_TYPE_TAKEOFF,
WAYPOINT_MOVE_TYPE_MAX
};
struct WaypointData
{
uint32 id;
float x, y, z;
std::optional<float> orientation;
uint32 delay;
uint32 event_id = 0;
uint32 move_type = 0;
uint8 event_chance = 0;
};
typedef std::map<uint32, WaypointData> WaypointPath;
typedef std::unordered_map<uint32, WaypointPath> WaypointPathContainer;
class WaypointMgr
{
@@ -59,10 +32,13 @@ public:
// Loads all paths from database, should only run on startup
void Load();
// Loads additional path data for waypoints from database. Should only be called on startup.
void LoadWaypointAddons();
// Returns the path from a given id
WaypointPath const* GetPath(uint32 id) const
{
WaypointPathContainer::const_iterator itr = _waypointStore.find(id);
auto itr = _waypointStore.find(id);
if (itr != _waypointStore.end())
return &itr->second;
@@ -70,10 +46,9 @@ public:
}
private:
WaypointMgr();
~WaypointMgr();
WaypointMgr() { }
WaypointPathContainer _waypointStore;
std::unordered_map<uint32, WaypointPath> _waypointStore;
};
#define sWaypointMgr WaypointMgr::instance()
+2
View File
@@ -137,6 +137,8 @@ public:
{
return _value;
}
[[nodiscard]] float GetMaxValue() const { return _maxValue; }
[[nodiscard]] float GetMinValue() const { return _minValue; }
protected:
bool AddObject(uint32 type, uint32 entry, uint32 map, float x, float y, float z, float o,
+2 -2
View File
@@ -379,8 +379,8 @@ void OpcodeTable::Initialize()
/*0x0F8*/ DEFINE_HANDLER(CMSG_TRIGGER_CINEMATIC_CHEAT, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL );
/*0x0F9*/ DEFINE_HANDLER(CMSG_OPENING_CINEMATIC, STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL );
/*0x0FA*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_TRIGGER_CINEMATIC, STATUS_NEVER);
/*0x0FB*/ DEFINE_HANDLER(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleNextCinematicCamera );
/*0x0FC*/ DEFINE_HANDLER(CMSG_COMPLETE_CINEMATIC, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleCompleteCinematic );
/*0x0FB*/ DEFINE_HANDLER(CMSG_NEXT_CINEMATIC_CAMERA, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleNextCinematicCamera );
/*0x0FC*/ DEFINE_HANDLER(CMSG_COMPLETE_CINEMATIC, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleCompleteCinematic );
/*0x0FD*/ DEFINE_SERVER_OPCODE_HANDLER(SMSG_TUTORIAL_FLAGS, STATUS_NEVER);
/*0x0FE*/ DEFINE_HANDLER(CMSG_TUTORIAL_FLAG, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialFlag );
/*0x0FF*/ DEFINE_HANDLER(CMSG_TUTORIAL_CLEAR, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTutorialClear );
+15 -380
View File
@@ -1223,7 +1223,7 @@ bool AuraEffect::CheckEffectProc(AuraApplication* aurApp, ProcEventInfo& eventIn
case SPELL_AURA_MECHANIC_IMMUNITY:
case SPELL_AURA_MOD_MECHANIC_RESISTANCE:
// compare mechanic
if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (1 << GetMiscValue())))
if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (UI64LIT(1) << GetMiscValue())))
return false;
break;
case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK:
@@ -3933,312 +3933,7 @@ void AuraEffect::HandleModStateImmunityMask(AuraApplication const* aurApp, uint8
return;
Unit* target = aurApp->GetTarget();
std::list <AuraType> aura_immunity_list;
uint32 mechanic_immunity_list = 0;
int32 miscVal = GetMiscValue();
switch (miscVal)
{
case 96:
case 1615:
{
if (!GetAmount())
{
mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT)
| (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN)
| (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM)
| (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR)
| (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED)
| (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_CHARM);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT);
aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE);
aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR);
}
break;
}
case 679:
{
if (GetId() == 57742)
{
mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT)
| (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN)
| (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM)
| (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR)
| (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED)
| (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT);
aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE);
aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR);
}
break;
}
case 1557:
{
if (GetId() == 64187)
{
mechanic_immunity_list = (1 << MECHANIC_STUN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
}
else
{
mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT)
| (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN)
| (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM)
| (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR)
| (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED)
| (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT);
aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE);
aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR);
}
break;
}
case 1614:
case 1694:
{
target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT);
break;
}
case 1630:
{
if (!GetAmount())
{
target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT);
}
else
{
mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT)
| (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN)
| (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM)
| (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR)
| (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED)
| (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT);
aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE);
aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR);
}
break;
}
case 477:
case 1733:
case 1632:
{
if (!GetAmount())
{
mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT)
| (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN)
| (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM)
| (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR)
| (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED)
| (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN) | (1 << MECHANIC_BANISH);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_BANISH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT);
aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE);
aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR);
}
break;
}
case 878:
{
if (GetAmount() == 1)
{
mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_STUN)
| (1 << MECHANIC_DISORIENTED) | (1 << MECHANIC_FREEZE);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
}
break;
}
default:
break;
}
if (aura_immunity_list.empty())
{
// Roots, OK
if (GetMiscValue() & (1 << 0))
{
mechanic_immunity_list = (1 << MECHANIC_SNARE);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT);
}
// Taunt, OK
if (GetMiscValue() & (1 << 1))
{
aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT);
}
// Crowd-Control auras?
if (GetMiscValue() & (1 << 2))
{
mechanic_immunity_list = (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE);
}
// Interrupt, OK
if (GetMiscValue() & (1 << 3))
{
mechanic_immunity_list = (1 << MECHANIC_INTERRUPT);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_INTERRUPT, apply);
}
// Transform?
if (GetMiscValue() & (1 << 4))
{
aura_immunity_list.push_back(SPELL_AURA_TRANSFORM);
}
// Stun auras breakable by damage (Incapacitate effects), OK
if (GetMiscValue() & (1 << 5))
{
mechanic_immunity_list = (1 << MECHANIC_KNOCKOUT);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_KNOCKOUT, apply);
}
// // Slowing effects
if (GetMiscValue() & (1 << 6))
{
mechanic_immunity_list = (1 << MECHANIC_SNARE);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED);
}
// Charm auras?, 90%
if ((GetMiscValue() & (1 << 7)))
{
mechanic_immunity_list = (1 << MECHANIC_CHARM);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_CHARM);
aura_immunity_list.push_back(SPELL_AURA_MOD_POSSESS);
}
// UNK
// if ((GetMiscValue() & (1 << 8)))
// {
// }
// Fear, OK
if (GetMiscValue() & (1 << 9))
{
mechanic_immunity_list = (1 << MECHANIC_FEAR);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR);
}
// Stuns, OK
if (GetMiscValue() & (1 << 10))
{
mechanic_immunity_list = (1 << MECHANIC_STUN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
aura_immunity_list.push_back(SPELL_AURA_MOD_STUN);
}
}
// apply immunities
for (std::list <AuraType>::iterator iter = aura_immunity_list.begin(); iter != aura_immunity_list.end(); ++iter)
target->ApplySpellImmune(GetId(), IMMUNITY_STATE, *iter, apply);
// Patch 3.0.3 Bladestorm now breaks all snares and roots on the warrior when activated.
if (GetId() == 46924)
{
// Knockback and hex
target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, apply);
}
if (apply && GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
{
target->RemoveAurasWithMechanic(mechanic_immunity_list, AURA_REMOVE_BY_DEFAULT, GetId());
for (std::list <AuraType>::iterator iter = aura_immunity_list.begin(); iter != aura_immunity_list.end(); ++iter)
target->RemoveAurasByType(*iter);
}
m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply);
}
void AuraEffect::HandleModMechanicImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4247,67 +3942,7 @@ void AuraEffect::HandleModMechanicImmunity(AuraApplication const* aurApp, uint8
return;
Unit* target = aurApp->GetTarget();
uint32 mechanic = 0;
switch (GetId())
{
case 46924: // BladeStorm
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
break;
case 34471: // The Beast Within
case 19574: // Bestial Wrath
case 38484: // Bestial Wrath
case 40081: // Free friend (Black Temple)
mechanic = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK;
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_KNOCKOUT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_BANISH, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SHACKLE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DAZE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply);
break;
case 42292: // PvP trinket
case 59752: // Every Man for Himself
case 65547: // PvP trinket for Faction Champions (ToC 25)
case 53490: // Bullheaded
case 46227: // Medalion of Immunity
mechanic = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK;
target->RemoveAurasByType(SPELL_AURA_PREVENTS_FLEEING); // xinef: Patch 2.3.0 PvP Trinkets: Insignia of the Alliance, Insignia of the Horde, Medallion of the Alliance, and Medallion of the Horde now clear the debuff from Judgement of Justice.
// Actually we should apply immunities here, too, but the aura has only 100 ms duration, so there is practically no point
break;
case 54508: // Demonic Empowerment
mechanic = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT);
target->RemoveAurasByType(SPELL_AURA_MOD_STUN);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply);
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply);
break;
default:
if (GetMiscValue() < 1)
return;
mechanic = 1 << GetMiscValue();
target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, GetMiscValue(), apply);
break;
}
if (apply && GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
{
// Xinef: exception for purely snare mechanic (eg. hands of freedom)!
if (mechanic == (1 << MECHANIC_SNARE))
target->RemoveMovementImpairingAuras(false);
else
target->RemoveAurasWithMechanic(mechanic, AURA_REMOVE_BY_DEFAULT, GetId());
}
m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply);
}
void AuraEffect::HandleAuraModEffectImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const
@@ -4415,7 +4050,7 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m
Unit* target = aurApp->GetTarget();
target->ApplySpellImmune(GetId(), IMMUNITY_DAMAGE, GetMiscValue(), apply);
m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply);
if (apply)
target->GetThreatMgr().EvaluateSuppressed();
@@ -4427,8 +4062,7 @@ void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint
return;
Unit* target = aurApp->GetTarget();
target->ApplySpellDispelImmunity(m_spellInfo, DispelType(GetMiscValue()), (apply));
m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply);
}
/*********************************************************/
@@ -6643,7 +6277,7 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const
if (!target->IsAlive())
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo()) || target->IsTotem())
if (target->IsImmunedToDamage(caster, GetSpellInfo()) || target->IsTotem())
{
SendTickImmune(target, caster);
return;
@@ -6795,7 +6429,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c
if (!target->IsAlive())
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo()))
if (target->IsImmunedToDamage(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -6905,7 +6539,7 @@ void AuraEffect::HandlePeriodicHealthFunnelAuraTick(Unit* target, Unit* caster)
if (!caster || !caster->IsAlive() || !target->IsAlive())
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED))
if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -6934,7 +6568,7 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const
if (!target->IsAlive())
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED))
if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -7073,7 +6707,7 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con
if (!caster || !caster->IsAlive() || !target->IsAlive() || !target->HasActivePowerType(PowerType))
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo()))
if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -7148,7 +6782,7 @@ void AuraEffect::HandleObsModPowerAuraTick(Unit* target, Unit* caster) const
if (!target->IsAlive() || !target->GetMaxPower(PowerType))
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED))
if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -7181,7 +6815,7 @@ void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) cons
if (!target->IsAlive() || !target->GetMaxPower(PowerType))
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED))
if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -7212,7 +6846,7 @@ void AuraEffect::HandlePeriodicPowerBurnAuraTick(Unit* target, Unit* caster) con
if (!caster || !target->IsAlive() || !target->HasActivePowerType(PowerType))
return;
if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo()))
if (target->IsImmunedToDamage(caster, GetSpellInfo()))
{
SendTickImmune(target, caster);
return;
@@ -7306,7 +6940,8 @@ void AuraEffect::HandleProcTriggerDamageAuraProc(AuraApplication* aurApp, ProcEv
Unit* triggerTarget = target == eventInfo.GetActor() ? eventInfo.GetActionTarget() : eventInfo.GetActor();
if (!triggerTarget)
return;
if (triggerTarget->HasUnitState(UNIT_STATE_ISOLATED) || triggerTarget->IsImmunedToDamageOrSchool(GetSpellInfo()))
if (triggerTarget->IsImmunedToDamage(target, GetSpellInfo()))
{
SendTickImmune(triggerTarget, target);
return;
+3 -3
View File
@@ -552,7 +552,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply)
if (IsArea())
for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
{
if ((existing->second & (1 << effIndex)) && existing->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex))
if ((existing->second & (1 << effIndex)) && existing->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster()))
existing->second &= ~(1 << effIndex);
}
@@ -594,7 +594,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply)
// check target immunities
for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
{
if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex))
if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster()))
itr->second &= ~(1 << effIndex);
}
if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo()) || !CanBeAppliedOn(itr->first))
@@ -2822,7 +2822,7 @@ void UnitAura::FillTargetMap(std::map<Unit*, uint8>& targets, Unit* caster)
{
float radius = GetSpellInfo()->Effects[effIndex].CalcRadius(caster);
if (!GetUnitOwner()->HasUnitState(UNIT_STATE_ISOLATED))
if (!GetUnitOwner()->HasAuraState(AURA_STATE_BANISHED, GetSpellInfo(), caster))
{
switch (GetSpellInfo()->Effects[effIndex].Effect)
{
+167 -129
View File
@@ -1315,7 +1315,7 @@ void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTarge
// Xinef: the distance should be increased by caster size, it is neglected in latter calculations
std::list<WorldObject*> targets;
float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod;
SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions);
SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions, Acore::WorldObjectSpellAreaTargetSearchReason::Area);
CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType);
@@ -1414,7 +1414,6 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici
{
float distance = m_spellInfo->Effects[effIndex].CalcRadius(m_caster);
Map* map = m_caster->GetMap();
uint32 mapid = m_caster->GetMapId();
uint32 phasemask = m_caster->GetPhaseMask();
float collisionHeight = m_caster->GetCollisionHeight();
float destz = 0.0f, startx = 0.0f, starty = 0.0f, startz = 0.0f, starto = 0.0f;
@@ -1556,9 +1555,9 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici
//LOG_ERROR("spells", "total path > than distance in 3D , need to move back a bit for save distance, total path = {}, overdistance = {}", totalpath, overdistance);
}
bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(mapid, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f);
bool col = m_caster->GetMap()->GetMapCollisionData().GetStaticTree().GetObjectHitPos(prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f);
// check dynamic collision
bool dcol = m_caster->GetMap()->GetObjectHitPos(phasemask, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f);
bool dcol = m_caster->GetMap()->GetMapCollisionData().GetDynamicTree().GetObjectHitPos(phasemask, prevX, prevY, prevZ + 0.5f, tstX, tstY, tstZ + 0.5f, tstX, tstY, tstZ, -0.5f);
// collision occured
if (col || dcol || (overdistance > 0.0f && !map->IsInWater(phasemask, tstX, tstY, ground, collisionHeight)) || (fabs(prevZ - tstZ) > maxtravelDistZ && (tstZ > prevZ)))
@@ -1620,9 +1619,9 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici
else
{
float z = pos.GetPositionZ();
bool col = VMAP::VMapFactory::createOrGetVMapMgr()->GetObjectHitPos(mapid, pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f);
bool col = m_caster->GetMap()->GetMapCollisionData().GetStaticTree().GetObjectHitPos(pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f);
// check dynamic collision
bool dcol = m_caster->GetMap()->GetObjectHitPos(phasemask, pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f);
bool dcol = m_caster->GetMap()->GetMapCollisionData().GetDynamicTree().GetObjectHitPos(phasemask, pos.GetPositionX(), pos.GetPositionY(), z, destx, desty, z, destx, desty, z, -0.5f);
// collision occured
if (col || dcol)
@@ -2091,12 +2090,12 @@ WorldObject* Spell::SearchNearbyTarget(float range, SpellTargetObjectTypes objec
return target;
}
void Spell::SearchAreaTargets(std::list<WorldObject*>& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList)
void Spell::SearchAreaTargets(std::list<WorldObject*>& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason)
{
uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList);
if (!containerTypeMask)
return;
Acore::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList);
Acore::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList, searchReason);
Acore::WorldObjectListSearcher<Acore::WorldObjectSpellAreaTargetCheck> searcher(m_caster, targets, check, containerTypeMask);
SearchTargets<Acore::WorldObjectListSearcher<Acore::WorldObjectSpellAreaTargetCheck> > (searcher, containerTypeMask, m_caster, position, range);
}
@@ -2142,7 +2141,7 @@ void Spell::SearchChainTargets(std::list<WorldObject*>& targets, uint32 chainTar
WorldObject* chainSource = m_spellInfo->HasAttribute(SPELL_ATTR2_CHAIN_FROM_CASTER) ? m_caster : target;
std::list<WorldObject*> tempTargets;
SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList);
SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList, Acore::WorldObjectSpellAreaTargetSearchReason::Chain);
tempTargets.remove(target);
// remove targets which are always invalid for chain spells
@@ -2310,7 +2309,7 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*=
// Check for effect immune skip if immuned
for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex))
if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex, m_caster))
effectMask &= ~(1 << effIndex);
ObjectGuid targetGUID = target->GetGUID();
@@ -2593,6 +2592,9 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
m_spellAura = nullptr; // Set aura to null for every target-make sure that pointer is not used for unit without aura applied
if (m_originalCaster && missInfo != SPELL_MISS_EVADE && !m_originalCaster->IsFriendlyTo(effectUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || effectUnit->IsEngaged()))
effectUnit->SetInCombatWith(m_originalCaster);
PrepareScriptHitHandlers();
CallScriptBeforeHitHandlers(missInfo);
@@ -2774,83 +2776,93 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
// Fill base damage struct (unitTarget - is real spell target)
SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_spellSchoolMask);
// Add bonuses and fill damageInfo struct
// Dancing Rune Weapon...
if (m_caster->GetEntry() == 27893)
// Check damage immunity
if (unitTarget->IsImmunedToDamage(caster, m_spellInfo))
{
if (Unit* owner = m_caster->GetOwner())
owner->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit);
m_damage = 0;
// no packet found in sniffs
}
else
caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit);
// xinef: override miss info after absorb / block calculations
if (missInfo == SPELL_MISS_NONE && damageInfo.damage == 0)
{
//if (damageInfo.absorb > 0)
// missInfo = SPELL_MISS_ABSORB;
if (damageInfo.blocked)
missInfo = SPELL_MISS_BLOCK;
}
// Xinef: override with forced crit, only visual result
if (GetSpellValue()->ForcedCritResult)
{
damageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT;
}
Unit::DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb);
// xinef: health leech handling
if (m_spellInfo->HasEffect(SPELL_EFFECT_HEALTH_LEECH))
{
uint8 effIndex = EFFECT_0;
for (; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_HEALTH_LEECH)
break;
float healMultiplier = m_spellInfo->Effects[effIndex].CalcValueMultiplier(m_originalCaster, this);
// get max possible damage, don't count overkill for heal
uint32 healthGain = uint32(-unitTarget->GetHealthGain(-int32(damageInfo.damage)) * healMultiplier);
if (m_caster->IsAlive())
// Add bonuses and fill damageInfo struct
// Dancing Rune Weapon...
if (m_caster->GetEntry() == 27893)
{
healthGain = m_caster->SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, HEAL, effIndex);
healthGain = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, HEAL);
HealInfo healInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellInfo->GetSchoolMask());
m_caster->HealBySpell(healInfo);
if (Unit* owner = m_caster->GetOwner())
owner->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit);
}
else
caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit);
// xinef: override miss info after absorb / block calculations
if (missInfo == SPELL_MISS_NONE && damageInfo.damage == 0)
{
//if (damageInfo.absorb > 0)
// missInfo = SPELL_MISS_ABSORB;
if (damageInfo.blocked)
missInfo = SPELL_MISS_BLOCK;
}
// Xinef: override with forced crit, only visual result
if (GetSpellValue()->ForcedCritResult)
{
damageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT;
}
Unit::DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb);
// xinef: health leech handling
if (m_spellInfo->HasEffect(SPELL_EFFECT_HEALTH_LEECH))
{
uint8 effIndex = EFFECT_0;
for (; effIndex < MAX_SPELL_EFFECTS; ++effIndex)
if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_HEALTH_LEECH)
break;
float healMultiplier = m_spellInfo->Effects[effIndex].CalcValueMultiplier(m_originalCaster, this);
// get max possible damage, don't count overkill for heal
uint32 healthGain = uint32(-unitTarget->GetHealthGain(-int32(damageInfo.damage)) * healMultiplier);
if (m_caster->IsAlive())
{
healthGain = m_caster->SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, HEAL, effIndex);
healthGain = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, HEAL);
HealInfo healInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellInfo->GetSchoolMask());
m_caster->HealBySpell(healInfo);
}
}
// Send log damage message to client
caster->SendSpellNonMeleeDamageLog(&damageInfo);
// Xinef: send info to target about reflect
if (reflectedSpell)
effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit);
procVictim |= PROC_FLAG_TAKEN_DAMAGE;
caster->DealSpellDamage(&damageInfo, true, this);
// do procs after damage, eg healing effects
// no need to check if target is alive, done in procdamageandspell
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger)
{
DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo);
uint32 hitMask = m_procEx | dmgInfo.GetHitMask();
Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo,
m_triggeredByAuraSpell.effectIndex, this, &dmgInfo);
if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 &&
m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED))
caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask());
}
m_damage = damageInfo.damage;
}
// Send log damage message to client
caster->SendSpellNonMeleeDamageLog(&damageInfo);
// Xinef: send info to target about reflect
if (reflectedSpell)
effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit);
procVictim |= PROC_FLAG_TAKEN_DAMAGE;
caster->DealSpellDamage(&damageInfo, true, this);
// do procs after damage, eg healing effects
// no need to check if target is alive, done in procdamageandspell
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger)
{
DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo);
uint32 hitMask = m_procEx | dmgInfo.GetHitMask();
Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo,
m_triggeredByAuraSpell.effectIndex, this, &dmgInfo);
if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 &&
m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED))
caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask());
}
m_damage = damageInfo.damage;
}
// Passive spell hits/misses or active spells only misses (only triggers)
else
@@ -2887,16 +2899,9 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target)
if (!m_triggeredByAuraSpell.spellInfo || m_damage || (!(m_triggeredByAuraSpell.spellInfo->Effects[m_triggeredByAuraSpell.effectIndex].TriggerSpell == m_spellInfo->Id) && !(m_triggeredByAuraSpell.spellInfo->IsAuraEffectEqual(m_spellInfo))))
m_caster->AtTargetAttacked(effectUnit, !(m_spellInfo->HasAttribute(SPELL_ATTR1_NO_THREAT) || m_spellInfo->HasAttribute(SPELL_ATTR3_SUPPRESS_TARGET_PROCS)));
// Unsure if there are more spells that are not supposed to stop enemy from
// regenerating HP from food, so for now it stays as an ID.
const uint32 SPELL_PREMEDITATION = 14183;
if (m_spellInfo->Id != SPELL_PREMEDITATION)
{
if (!effectUnit->IsStandState())
{
effectUnit->SetStandState(UNIT_STAND_STATE_STAND);
}
}
// Spells that don't initiate combat shouldn't affect stand state (e.g. Premeditation, Storm Giant/Felreaver Passive)
if (m_spellInfo->HasInitialAggro() && !effectUnit->IsStandState())
effectUnit->SetStandState(UNIT_STAND_STATE_STAND);
}
}
@@ -2943,7 +2948,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
return SPELL_MISS_EVADE;
// For delayed spells immunity may be applied between missile launch and hit - check immunity for that case
if (m_spellInfo->Speed && ((m_damage > 0 && unit->IsImmunedToDamage(this)) || unit->IsImmunedToSchool(this) || unit->IsImmunedToSpell(m_spellInfo, this)))
if (m_spellInfo->Speed && ((m_damage > 0 && unit->IsImmunedToDamage(m_caster, m_spellInfo)) || unit->IsImmunedToSchool(this) || unit->IsImmunedToSpell(m_spellInfo, this)))
{
return SPELL_MISS_IMMUNE;
}
@@ -2954,7 +2959,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
{
if (effectMask & (1 << effectNumber))
{
if (unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber))
if (unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber, m_caster))
effectMask &= ~(1 << effectNumber);
// Xinef: Buggs out polymorph
// Xinef: And this is checked in MagicSpellHitResult, why we check resistance twice?
@@ -3100,14 +3105,6 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA
if (m_spellAura)
{
// Prevent aura application if target is banished and immuned
if (m_targets.GetUnitTarget() && m_targets.GetUnitTarget()->IsImmunedToDamageOrSchool(m_spellAura->GetSpellInfo())
&& m_targets.GetUnitTarget()->HasUnitState(UNIT_STATE_ISOLATED))
{
m_spellAura->Remove();
return SPELL_MISS_IMMUNE;
}
// Set aura stack amount to desired value
if (m_spellValue->AuraStackAmount > 1)
{
@@ -5610,7 +5607,7 @@ void Spell::HandleEffects(Unit* pUnitTarget, Item* pItemTarget, GameObject* pGOT
}
}
SpellCastResult Spell::CheckCast(bool strict)
SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*param2*/)
{
// check death state
if (!m_caster->IsAlive() && !m_spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !(m_spellInfo->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD) || (IsTriggered() && !m_triggeredByAuraSpell)))
@@ -6019,10 +6016,9 @@ SpellCastResult Spell::CheckCast(bool strict)
// xinef: Enraged Regeneration: While this is active, the warrior is blocked from using abilities that trigger being enraged (which would do nothing and waste the cooldowns).
if (m_spellInfo->Mechanic && m_spellInfo->IsSelfCast())
{
SpellImmuneList const& mechanicList = m_caster->m_spellImmune[IMMUNITY_MECHANIC];
for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr)
if (itr->type == m_spellInfo->Mechanic)
return SPELL_FAILED_DAMAGE_IMMUNE;
auto const& mechanicList = m_caster->m_spellImmune[IMMUNITY_MECHANIC];
if (mechanicList.count(m_spellInfo->Mechanic) > 0)
return SPELL_FAILED_DAMAGE_IMMUNE;
}
}
@@ -7083,18 +7079,18 @@ SpellCastResult Spell::CheckRange(bool strict)
else if (!m_caster->IsWithinCombatRange(target, max_range))
return SPELL_FAILED_OUT_OF_RANGE; //0x5A;
if (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED && range_type == SPELL_RANGE_RANGED)
{
if (m_caster->IsWithinMeleeRange(target))
return SPELL_FAILED_TOO_CLOSE;
}
if (m_caster->IsPlayer() && (m_spellInfo->FacingCasterFlags & SPELL_FACING_FLAG_INFRONT) && !m_caster->HasInArc(static_cast<float>(M_PI), target) && !m_caster->IsWithinBoundaryRadius(target))
return SPELL_FAILED_UNIT_NOT_INFRONT;
}
// Xinef: check min range for self casts
if (min_range && range_type != SPELL_RANGE_RANGED && m_caster->IsWithinCombatRange(target, min_range)) // skip this check if min_range = 0
// Check min range - for ranged spells, min range is the spell's min range + melee range (no leeway)
if (range_type == SPELL_RANGE_RANGED)
{
float minRangeCombined = min_range + m_caster->GetMeleeRange(target);
if (m_caster->IsWithinRange(target, minRangeCombined))
return SPELL_FAILED_TOO_CLOSE;
}
else if (min_range > 0 && m_caster->IsWithinCombatRange(target, min_range))
return SPELL_FAILED_TOO_CLOSE;
}
@@ -7162,7 +7158,7 @@ SpellCastResult Spell::CheckPower()
return SPELL_CAST_OK;
}
SpellCastResult Spell::CheckItems()
SpellCastResult Spell::CheckItems(uint32* param1, uint32* param2)
{
Player* player = m_caster->ToPlayer();
if (!player)
@@ -7570,21 +7566,29 @@ SpellCastResult Spell::CheckItems()
}
case SPELL_EFFECT_PROSPECTING:
{
if (!m_targets.GetItemTarget())
Item* item = m_targets.GetItemTarget();
if (!item)
return SPELL_FAILED_CANT_BE_PROSPECTED;
//ensure item is a prospectable ore
if (!(m_targets.GetItemTarget()->GetTemplate()->HasFlag(ITEM_FLAG_IS_PROSPECTABLE)))
if (!(item->GetTemplate()->HasFlag(ITEM_FLAG_IS_PROSPECTABLE)))
return SPELL_FAILED_CANT_BE_PROSPECTED;
//prevent prospecting in trade slot
if (m_targets.GetItemTarget()->GetOwnerGUID() != m_caster->GetGUID())
if (item->GetOwnerGUID() != m_caster->GetGUID())
return SPELL_FAILED_CANT_BE_PROSPECTED;
//Check for enough skill in jewelcrafting
uint32 item_prospectingskilllevel = m_targets.GetItemTarget()->GetTemplate()->RequiredSkillRank;
uint32 item_prospectingskilllevel = item->GetTemplate()->RequiredSkillRank;
if (item_prospectingskilllevel > player->GetSkillValue(SKILL_JEWELCRAFTING))
return SPELL_FAILED_LOW_CASTLEVEL;
//make sure the player has the required ores in inventory
if (m_targets.GetItemTarget()->GetCount() < 5)
if (item->GetCount() < 5)
{
if (param1 && param2)
{
*param1 = item->GetEntry();
*param2 = 5;
}
return SPELL_FAILED_NEED_MORE_ITEMS;
}
if (!LootTemplates_Prospecting.HaveLootFor(m_targets.GetItemTargetEntry()))
return SPELL_FAILED_CANT_BE_PROSPECTED;
@@ -7593,21 +7597,29 @@ SpellCastResult Spell::CheckItems()
}
case SPELL_EFFECT_MILLING:
{
if (!m_targets.GetItemTarget())
Item* item = m_targets.GetItemTarget();
if (!item)
return SPELL_FAILED_CANT_BE_MILLED;
//ensure item is a millable herb
if (!(m_targets.GetItemTarget()->GetTemplate()->HasFlag(ITEM_FLAG_IS_MILLABLE)))
if (!(item->GetTemplate()->HasFlag(ITEM_FLAG_IS_MILLABLE)))
return SPELL_FAILED_CANT_BE_MILLED;
//prevent milling in trade slot
if (m_targets.GetItemTarget()->GetOwnerGUID() != m_caster->GetGUID())
if (item->GetOwnerGUID() != m_caster->GetGUID())
return SPELL_FAILED_CANT_BE_MILLED;
//Check for enough skill in inscription
uint32 item_millingskilllevel = m_targets.GetItemTarget()->GetTemplate()->RequiredSkillRank;
uint32 item_millingskilllevel = item->GetTemplate()->RequiredSkillRank;
if (item_millingskilllevel > player->GetSkillValue(SKILL_INSCRIPTION))
return SPELL_FAILED_LOW_CASTLEVEL;
//make sure the player has the required herbs in inventory
if (m_targets.GetItemTarget()->GetCount() < 5)
if (item->GetCount() < 5)
{
if (param1 && param2)
{
*param1 = item->GetEntry();
*param2 = 5;
}
return SPELL_FAILED_NEED_MORE_ITEMS;
}
if (!LootTemplates_Milling.HaveLootFor(m_targets.GetItemTargetEntry()))
return SPELL_FAILED_CANT_BE_MILLED;
@@ -8296,6 +8308,13 @@ void Spell::HandleLaunchPhase()
}
}
if (m_originalCaster && target.missCondition != SPELL_MISS_EVADE)
{
Unit* targetUnit = m_caster->GetGUID() == target.targetGUID ? m_caster : ObjectAccessor::GetUnit(*m_caster, target.targetGUID);
if (targetUnit && !m_originalCaster->IsFriendlyTo(targetUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || targetUnit->IsEngaged()))
m_originalCaster->SetInCombatWith(targetUnit, true);
}
DoAllEffectOnLaunchTarget(target, multiplier);
}
@@ -9060,8 +9079,8 @@ namespace Acore
}
WorldObjectSpellAreaTargetCheck::WorldObjectSpellAreaTargetCheck(float range, Position const* position, Unit* caster,
Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList)
: WorldObjectSpellTargetCheck(caster, referer, spellInfo, selectionType, condList), _range(range), _position(position)
Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason)
: WorldObjectSpellTargetCheck(caster, referer, spellInfo, selectionType, condList), _range(range), _position(position), _searchReason(searchReason)
{
}
@@ -9069,13 +9088,32 @@ namespace Acore
{
if (target->IsGameObject())
{
if (!target->ToGameObject()->IsInRange(_position->GetPositionX(), _position->GetPositionY(), _position->GetPositionZ(), _range))
if (!target->ToGameObject()->IsInRange3d(_position->GetPositionX(), _position->GetPositionY(), _position->GetPositionZ(), _range))
return false;
}
else if (!target->IsWithinDist3d(_position, _range))
return false;
else if (target->IsCreature() && target->ToCreature()->IsAvoidingAOE()) // pussywizard
return false;
else if (Creature* c = target->ToCreature())
{
if (c->IsAvoidingAOE()) // pussywizard
return false;
if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(c->GetCreatureTemplate()->CreatureImmunitiesId))
{
switch (_searchReason)
{
case Acore::WorldObjectSpellAreaTargetSearchReason::Area:
if (immunities->ImmuneAoE)
return false;
break;
case Acore::WorldObjectSpellAreaTargetSearchReason::Chain:
if (immunities->ImmuneChain)
return false;
break;
default:
break;
}
}
}
return WorldObjectSpellTargetCheck::operator ()(target);
}
+14 -4
View File
@@ -38,6 +38,15 @@ class SpellEvent;
class ByteBuffer;
class BasicEvent;
namespace Acore
{
enum class WorldObjectSpellAreaTargetSearchReason
{
Area,
Chain
};
}
#define SPELL_CHANNEL_UPDATE_INTERVAL (1 * IN_MILLISECONDS)
#define TRAJECTORY_MISSILE_SIZE 3.0f
@@ -444,7 +453,7 @@ public:
template<class SEARCHER> void SearchTargets(SEARCHER& searcher, uint32 containerMask, Unit* referer, Position const* pos, float radius);
WorldObject* SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList = nullptr);
void SearchAreaTargets(std::list<WorldObject*>& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList);
void SearchAreaTargets(std::list<WorldObject*>& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason = Acore::WorldObjectSpellAreaTargetSearchReason::Area);
void SearchChainTargets(std::list<WorldObject*>& targets, uint32 chainTargets, WorldObject* target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, SpellTargetSelectionCategories selectCategory, ConditionList* condList, bool isChainHeal);
SpellCastResult prepare(SpellCastTargets const* targets, AuraEffect const* triggeredByAura = nullptr);
@@ -460,7 +469,7 @@ public:
void TakeReagents();
void TakeCastItem();
SpellCastResult CheckCast(bool strict);
SpellCastResult CheckCast(bool strict, uint32* param1 = nullptr, uint32* param2 = nullptr);
SpellCastResult CheckPetCast(Unit* target);
// handlers
@@ -472,7 +481,7 @@ public:
void OnSpellLaunch();
SpellCastResult CheckItems();
SpellCastResult CheckItems(uint32* param1 = nullptr, uint32* param2 = nullptr);
SpellCastResult CheckSpellFocus();
SpellCastResult CheckRange(bool strict);
SpellCastResult CheckPower();
@@ -827,8 +836,9 @@ namespace Acore
{
float _range;
Position const* _position;
Acore::WorldObjectSpellAreaTargetSearchReason _searchReason;
WorldObjectSpellAreaTargetCheck(float range, Position const* position, Unit* caster,
Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList);
Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason = Acore::WorldObjectSpellAreaTargetSearchReason::Area);
bool operator()(WorldObject* target);
};
+7 -4
View File
@@ -856,7 +856,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex)
SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo();
// Pounce Bleed shouldn't be removed by Cloak of Shadows.
if (spell->GetAllEffectsMechanicMask() & 1 << MECHANIC_BLEED)
if (spell->GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED))
return;
bool dmgClassNone = false;
@@ -1889,7 +1889,7 @@ void Spell::EffectEnergize(SpellEffIndex effIndex)
if (!unitTarget->IsAlive())
return;
if (unitTarget->HasUnitState(UNIT_STATE_ISOLATED))
if (unitTarget->IsImmunedToAuraPeriodicTick(m_caster, m_spellInfo))
{
m_caster->SendSpellDamageImmune(unitTarget, GetSpellInfo()->Id);
return;
@@ -3681,7 +3681,7 @@ void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/)
if (!unitTarget || !unitTarget->IsAlive())
return;
if (unitTarget->HasUnitState(UNIT_STATE_ISOLATED))
if (unitTarget->IsImmunedToAuraPeriodicTick(m_caster, m_spellInfo))
{
m_caster->SendSpellDamageImmune(unitTarget, GetSpellInfo()->Id);
return;
@@ -5152,7 +5152,7 @@ void Spell::EffectDispelMechanic(SpellEffIndex effIndex)
continue;
if (roll_chance_i(aura->CalcDispelChance(unitTarget, !unitTarget->IsFriendlyTo(m_caster))))
{
if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1 << mechanic)))
if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (UI64LIT(1) << mechanic)))
{
dispel_list.push(std::make_pair(aura->GetId(), aura->GetCasterGUID()));
@@ -5943,6 +5943,9 @@ void Spell::SummonGuardian(uint32 i, uint32 entry, SummonPropertiesEntry const*
if (caster->IsTotem())
caster = caster->ToTotem()->GetOwner();
else if (caster->IsPet())
if (Unit* owner = caster->GetOwner())
caster = owner;
// in another case summon new
uint8 summonLevel = caster->GetLevel();

Some files were not shown because too many files have changed in this diff Show More