mirror of
https://github.com/azerothcore/azerothcore-wotlk.git
synced 2025-11-10 12:24:22 +08:00
Compare commits
29 Commits
89017af6ca
...
61f27e65a7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61f27e65a7 | ||
|
|
0edf2ee98f | ||
|
|
5bef92d5ea | ||
|
|
723aae9039 | ||
|
|
283f03bdcd | ||
|
|
57daeed03a | ||
|
|
36d739ee42 | ||
|
|
e86727c5b9 | ||
|
|
5c9623d855 | ||
|
|
4bcbe2a45a | ||
|
|
d5e766a67c | ||
|
|
5e4c8001e1 | ||
|
|
a94e5bc69c | ||
|
|
b4dce52620 | ||
|
|
3b585b9add | ||
|
|
2e00e00f62 | ||
|
|
e3c7b0a8f9 | ||
|
|
b7c9922fac | ||
|
|
297d0cd787 | ||
|
|
56d4ea4685 | ||
|
|
13b73f2a92 | ||
|
|
c1ce87aee8 | ||
|
|
35bea2f1cb | ||
|
|
defb2ff5cb | ||
|
|
c2075322f8 | ||
|
|
300bd81aea | ||
|
|
772eaade7c | ||
|
|
30777af88a | ||
|
|
be57851191 |
6
data/sql/updates/db_world/2025_11_09_04.sql
Normal file
6
data/sql/updates/db_world/2025_11_09_04.sql
Normal file
@ -0,0 +1,6 @@
|
||||
-- DB update 2025_11_09_03 -> 2025_11_09_04
|
||||
--
|
||||
DELETE FROM `spell_custom_attr` WHERE `spell_id` IN (52671, 59834);
|
||||
INSERT INTO `spell_custom_attr` (`spell_id`, `attributes`) VALUES
|
||||
(52671, 0x00400000),
|
||||
(59834, 0x00400000);
|
||||
79
data/sql/updates/db_world/2025_11_09_05.sql
Normal file
79
data/sql/updates/db_world/2025_11_09_05.sql
Normal file
@ -0,0 +1,79 @@
|
||||
-- DB update 2025_11_09_04 -> 2025_11_09_05
|
||||
--
|
||||
-- v11_2_5_63906
|
||||
SET @VBUILD := 63906;
|
||||
|
||||
DELETE FROM `creature_template_addon` WHERE (`entry` = 28503);
|
||||
INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
|
||||
(28503, 0, 0, 0, 0, 0, 0, '58837');
|
||||
|
||||
DELETE FROM `creature_template_addon` WHERE (`entry` = 28998);
|
||||
INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
|
||||
(28998, 0, 0, 0, 0, 0, 0, '58837');
|
||||
|
||||
DELETE FROM `creature` WHERE (`id1` = 28998) AND (`guid` IN (1974609));
|
||||
INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `Comment`, `VerifiedBuild`) VALUES
|
||||
(1974609, 28998, 0, 0, 571, 0, 0, 1, 1, 0, 6175.2456, -2017.6545, 590.9613, 3.0019662, 300, 0, 0, 550001, 0, 0, 0, 0, 0, '', NULL, @VBUILD);
|
||||
|
||||
DELETE FROM `creature_template_addon` WHERE (`entry` = 28998);
|
||||
INSERT INTO `creature_template_addon` (`entry`, `path_id`, `mount`, `bytes1`, `bytes2`, `emote`, `visibilityDistanceType`, `auras`) VALUES
|
||||
(28998, 0, 0, 0, 1, 0, 0, '');
|
||||
|
||||
UPDATE `spell_target_position` SET `PositionX`=6161.15, `PositionY`=-2015.36, `PositionZ`=590.878, `Orientation`=6.283189773559570312, `VerifiedBuild`=@VBUILD WHERE `ID`=52863 AND `EffectIndex`=0;
|
||||
|
||||
UPDATE `creature_template_addon` SET `bytes2` = 1 WHERE (`entry` = 28717);
|
||||
|
||||
-- Update comments
|
||||
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 28498) AND (`source_type` = 0) AND (`id` IN (0, 1, 2, 3, 4));
|
||||
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
|
||||
(28498, 0, 0, 0, 54, 0, 100, 512, 0, 0, 0, 0, 0, 0, 53, 1, 28498, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'The Lich King - On Just Summoned - Start Waypoint Path 28498'),
|
||||
(28498, 0, 1, 2, 40, 0, 100, 512, 2, 0, 0, 0, 0, 0, 54, 83000, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'The Lich King - On Point 2 of Path Any Reached - Pause Waypoint'),
|
||||
(28498, 0, 2, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 80, 2849800, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'The Lich King - On Point 2 of Path Any Reached - Run Script'),
|
||||
(28498, 0, 3, 4, 40, 0, 100, 512, 3, 0, 0, 0, 0, 0, 45, 0, 2, 0, 0, 0, 0, 10, 127495, 0, 0, 0, 0, 0, 0, 0, 'The Lich King - On Point 3 of Path Any Reached - Set Data 0 2'),
|
||||
(28498, 0, 4, 0, 61, 0, 100, 512, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 'The Lich King - On Point 3 of Path Any Reached - Despawn Instant');
|
||||
|
||||
-- Disable gravity
|
||||
DELETE FROM `creature_template_movement` WHERE (`CreatureId` = 29100);
|
||||
INSERT INTO `creature_template_movement` (`CreatureId`, `Ground`, `Swim`, `Flight`, `Rooted`, `Chase`, `Random`, `InteractionPauseTimer`) VALUES
|
||||
(29100, 0, 0, 1, 0, 0, 0, 0);
|
||||
|
||||
-- Idle
|
||||
UPDATE `creature` SET `MovementType` = 0, `wander_distance` = 0 WHERE `id1` = 29100 AND `guid` IN (112307, 112308, 112309, 112310);
|
||||
|
||||
UPDATE `gameobject_template` SET `AIName` = 'SmartGameObjectAI' WHERE `entry` = 202357;
|
||||
DELETE FROM `smart_scripts` WHERE (`entryorguid` = 202357) AND (`source_type` = 1) AND (`id` IN (0));
|
||||
INSERT INTO `smart_scripts` (`entryorguid`, `source_type`, `id`, `link`, `event_type`, `event_phase_mask`, `event_chance`, `event_flags`, `event_param1`, `event_param2`, `event_param3`, `event_param4`, `event_param5`, `event_param6`, `action_type`, `action_param1`, `action_param2`, `action_param3`, `action_param4`, `action_param5`, `action_param6`, `target_type`, `target_param1`, `target_param2`, `target_param3`, `target_param4`, `target_x`, `target_y`, `target_z`, `target_o`, `comment`) VALUES
|
||||
(202357, 1, 0, 0, 62, 0, 100, 0, 11091, 0, 0, 0, 0, 0, 11, 57553, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 'Drakuru\'s Last Wish - On Gossip Option 0 Selected - Cast \'Escape Voltarus\'');
|
||||
|
||||
-- Drakuru's Last Wish
|
||||
UPDATE `gameobject_template_addon` SET `flags` = 32 WHERE (`entry` = 202357);
|
||||
|
||||
-- Skull and Portal spells target 'Totally Generic Bunny (JSB)'
|
||||
DELETE FROM `creature` WHERE (`id1` = 28960) and `guid` IN (98914, 98920);
|
||||
INSERT INTO `creature` (`guid`, `id1`, `id2`, `id3`, `map`, `zoneId`, `areaId`, `spawnMask`, `phaseMask`, `equipment_id`, `position_x`, `position_y`, `position_z`, `orientation`, `spawntimesecs`, `wander_distance`, `currentwaypoint`, `curhealth`, `curmana`, `MovementType`, `npcflag`, `unit_flags`, `dynamicflags`, `ScriptName`, `Comment`, `VerifiedBuild`) VALUES
|
||||
(98914, 28960, 0, 0, 571, 0, 0, 1, 1, 0, 6144.01, -2011.8, 590.963, 6.16101, 300, 0, 0, 4979, 0, 0, 0, 0, 0, '', '\'Throw Portal Crystal\' guid target', @VBUILD),
|
||||
(98920, 28960, 0, 0, 571, 0, 0, 1, 1, 0, 6181.5137, -2032.4258, 590.96124, 1.01229, 300, 0, 0, 4979, 0, 0, 0, 0, 0, '', '\'Drakuru\'s Skull Missile\' guid target', @VBUILD);
|
||||
|
||||
UPDATE `conditions` SET `ConditionValue3` = 98914, `Comment` = 'target Totally Generic Bunny (JSB)' WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceGroup` = 1) AND (`SourceEntry` = 54209) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 28960) AND (`ConditionValue3` = 0);
|
||||
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceGroup` = 1) AND (`SourceEntry` = 54250) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 31) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 3) AND (`ConditionValue2` = 28960);
|
||||
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
|
||||
(13, 1, 54250, 0, 0, 31, 0, 3, 28960, 98920, 0, 0, 0, '', 'target Totally Generic Bunny (JSB)');
|
||||
|
||||
DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 13) AND (`SourceGroup` = 1) AND (`SourceEntry` = 54089) AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 1) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 51966) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0);
|
||||
INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES
|
||||
(13, 1, 54089, 0, 0, 1, 0, 51966, 0, 0, 0, 0, 0, '', 'Has Aura \'Scourge Disguise\'');
|
||||
|
||||
-- 54104 Blight Fog
|
||||
UPDATE `creature_template_addon` SET `auras` = '54104' WHERE (`entry` = 28998);
|
||||
|
||||
DELETE FROM `creature_summon_groups` WHERE `summonerId` = 28998 and `summonerType` = 0 AND `groupId` = 1;
|
||||
INSERT INTO `creature_summon_groups` (`summonerId`, `summonerType`, `groupId`, `entry`, `position_x`, `position_y`, `position_z`, `orientation`, `summonType`, `summonTime`, `Comment`) VALUES
|
||||
(28998, 0, 1, 28931, 6184.1455, -1970.1699, 586.84186, 4.5902, 8, 0, 'Overlord Drakuru - Group 1 - Blightblood Troll'),
|
||||
(28998, 0, 1, 28931, 6222.855, -2026.6315, 586.84186, 3.00197, 8, 0, 'Overlord Drakuru - Group 1 - Blightblood Troll'),
|
||||
(28998, 0, 1, 28931, 6166.278, -2065.3123, 586.84186, 1.44862, 8, 0, 'Overlord Drakuru - Group 1 - Blightblood Troll'),
|
||||
(28998, 0, 1, 28931, 6127.5117, -2008.6506, 586.84186, 6.16101, 8, 0, 'Overlord Drakuru - Group 1 - Blightblood Troll');
|
||||
|
||||
-- 54105 Blight Fog
|
||||
DELETE FROM `spell_script_names` WHERE (`spell_id` = 54105);
|
||||
INSERT INTO `spell_script_names` (`spell_id`, `ScriptName`) VALUES
|
||||
(54105, 'spell_blight_fog');
|
||||
@ -70,6 +70,7 @@ private:
|
||||
{
|
||||
tree.clear();
|
||||
objects.clear();
|
||||
bounds = G3D::AABox::empty();
|
||||
// create space for the first node
|
||||
tree.push_back(3u << 30u); // dummy leaf
|
||||
tree.insert(tree.end(), 2, 0);
|
||||
@ -116,6 +117,7 @@ public:
|
||||
delete[] dat.indices;
|
||||
}
|
||||
[[nodiscard]] uint32 primCount() const { return objects.size(); }
|
||||
G3D::AABox const& bound() const { return bounds; }
|
||||
|
||||
template<typename RayCallback>
|
||||
void intersectRay(const G3D::Ray& r, RayCallback& intersectCallback, float& maxDist, bool stopAtFirstHit) const
|
||||
|
||||
@ -170,25 +170,6 @@ private:
|
||||
VMAP::ModelIgnoreFlags _ignoreFlags;
|
||||
};
|
||||
|
||||
struct DynamicTreeAreaInfoCallback
|
||||
{
|
||||
DynamicTreeAreaInfoCallback(uint32 phaseMask) : _phaseMask(phaseMask) { }
|
||||
|
||||
void operator()(G3D::Vector3 const& p, GameObjectModel const& obj)
|
||||
{
|
||||
obj.IntersectPoint(p, _areaInfo, _phaseMask);
|
||||
}
|
||||
|
||||
VMAP::AreaInfo const& GetAreaInfo() const
|
||||
{
|
||||
return _areaInfo;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32 _phaseMask;
|
||||
VMAP::AreaInfo _areaInfo;
|
||||
};
|
||||
|
||||
struct DynamicTreeLocationInfoCallback
|
||||
{
|
||||
DynamicTreeLocationInfoCallback(uint32 phaseMask)
|
||||
@ -308,24 +289,7 @@ float DynamicMapTree::getHeight(float x, float y, float z, float maxSearchDist,
|
||||
}
|
||||
}
|
||||
|
||||
bool DynamicMapTree::GetAreaInfo(float x, float y, float& z, uint32 phasemask, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
|
||||
{
|
||||
G3D::Vector3 v(x, y, z + 0.5f);
|
||||
DynamicTreeAreaInfoCallback intersectionCallBack(phasemask);
|
||||
impl->intersectPoint(v, intersectionCallBack);
|
||||
if (intersectionCallBack.GetAreaInfo().result)
|
||||
{
|
||||
flags = intersectionCallBack.GetAreaInfo().flags;
|
||||
adtId = intersectionCallBack.GetAreaInfo().adtId;
|
||||
rootId = intersectionCallBack.GetAreaInfo().rootId;
|
||||
groupId = intersectionCallBack.GetAreaInfo().groupId;
|
||||
z = intersectionCallBack.GetAreaInfo().ground_Z;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DynamicMapTree::GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, uint8 reqLiquidType, VMAP::AreaAndLiquidData& data) const
|
||||
bool DynamicMapTree::GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, Optional<uint8> reqLiquidType, VMAP::AreaAndLiquidData& data) const
|
||||
{
|
||||
G3D::Vector3 v(x, y, z + 0.5f);
|
||||
DynamicTreeLocationInfoCallback intersectionCallBack(phasemask);
|
||||
@ -335,13 +299,16 @@ void DynamicMapTree::GetAreaAndLiquidData(float x, float y, float z, uint32 phas
|
||||
data.floorZ = intersectionCallBack.GetLocationInfo().ground_Z;
|
||||
uint32 liquidType = intersectionCallBack.GetLocationInfo().hitModel->GetLiquidType();
|
||||
float liquidLevel;
|
||||
if (!reqLiquidType || (dynamic_cast<VMAP::VMapMgr2*>(VMAP::VMapFactory::createOrGetVMapMgr())->GetLiquidFlagsPtr(liquidType) & reqLiquidType))
|
||||
if (!reqLiquidType || (dynamic_cast<VMAP::VMapMgr2*>(VMAP::VMapFactory::createOrGetVMapMgr())->GetLiquidFlagsPtr(liquidType) & *reqLiquidType))
|
||||
if (intersectionCallBack.GetHitModel()->GetLiquidLevel(v, intersectionCallBack.GetLocationInfo(), liquidLevel))
|
||||
data.liquidInfo.emplace(liquidType, liquidLevel);
|
||||
|
||||
data.areaInfo.emplace(0,
|
||||
data.areaInfo.emplace(intersectionCallBack.GetLocationInfo().hitModel->GetWmoID(),
|
||||
0,
|
||||
intersectionCallBack.GetLocationInfo().rootId,
|
||||
intersectionCallBack.GetLocationInfo().hitModel->GetWmoID(),
|
||||
intersectionCallBack.GetLocationInfo().hitModel->GetMogpFlags());
|
||||
intersectionCallBack.GetLocationInfo().hitModel->GetMogpFlags(),
|
||||
0);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#define _DYNTREE_H
|
||||
|
||||
#include "Define.h"
|
||||
#include "Optional.h"
|
||||
|
||||
namespace G3D
|
||||
{
|
||||
@ -47,8 +48,7 @@ public:
|
||||
|
||||
bool GetIntersectionTime(uint32 phasemask, const G3D::Ray& ray, const G3D::Vector3& endPos, float& maxDist) const;
|
||||
|
||||
bool GetAreaInfo(float x, float y, float& z, uint32 phasemask, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
|
||||
void GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, uint8 reqLiquidType, VMAP::AreaAndLiquidData& data) const;
|
||||
bool GetAreaAndLiquidData(float x, float y, float z, uint32 phasemask, Optional<uint8> reqLiquidType, VMAP::AreaAndLiquidData& data) const;
|
||||
|
||||
bool GetObjectHitPos(uint32 phasemask, const G3D::Vector3& pPos1,
|
||||
const G3D::Vector3& pPos2, G3D::Vector3& pResultHitPos,
|
||||
|
||||
@ -52,20 +52,23 @@ namespace VMAP
|
||||
{
|
||||
struct AreaInfo
|
||||
{
|
||||
AreaInfo(int32 _adtId, int32 _rootId, int32 _groupId, uint32 _flags)
|
||||
: adtId(_adtId), rootId(_rootId), groupId(_groupId), mogpFlags(_flags) { }
|
||||
int32 const adtId;
|
||||
int32 const rootId;
|
||||
int32 const groupId;
|
||||
uint32 const mogpFlags;
|
||||
AreaInfo() = default;
|
||||
AreaInfo(int32 _groupId, int32 _adtId, int32 _rootId, uint32 _mogpFlags, uint32 _uniqueId)
|
||||
: groupId(_groupId), adtId(_adtId), rootId(_rootId), mogpFlags(_mogpFlags), uniqueId(_uniqueId) { }
|
||||
int32 groupId = 0;
|
||||
int32 adtId = 0;
|
||||
int32 rootId = 0;
|
||||
uint32 mogpFlags = 0;
|
||||
uint32 uniqueId = 0;
|
||||
};
|
||||
|
||||
struct LiquidInfo
|
||||
{
|
||||
LiquidInfo() = default;
|
||||
LiquidInfo(uint32 _type, float _level)
|
||||
: type(_type), level(_level) {}
|
||||
uint32 const type;
|
||||
float const level;
|
||||
uint32 type = 0;
|
||||
float level = 0.0f;
|
||||
};
|
||||
|
||||
float floorZ = VMAP_INVALID_HEIGHT;
|
||||
@ -120,14 +123,12 @@ 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 GetAreaInfo(uint32 pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const = 0;
|
||||
virtual bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 ReqLiquidType, float& level, float& floor, uint32& type, uint32& mogpFlags) const = 0;
|
||||
// get both area + liquid data in a single vmap lookup
|
||||
virtual void GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, AreaAndLiquidData& data) const = 0;
|
||||
virtual bool GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional<uint8> reqLiquidType, AreaAndLiquidData& data) const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -253,70 +253,8 @@ namespace VMAP
|
||||
return VMAP_INVALID_HEIGHT_VALUE;
|
||||
}
|
||||
|
||||
bool VMapMgr2::GetAreaInfo(uint32 mapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
|
||||
bool VMapMgr2::GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, Optional<uint8> reqLiquidType, AreaAndLiquidData& data) const
|
||||
{
|
||||
#if defined(ENABLE_VMAP_CHECKS)
|
||||
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_AREAFLAG))
|
||||
#endif
|
||||
{
|
||||
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
Vector3 pos = convertPositionToInternalRep(x, y, z);
|
||||
bool result = instanceTree->second->GetAreaInfo(pos, flags, adtId, rootId, groupId);
|
||||
// z is not touched by convertPositionToInternalRep(), so just copy
|
||||
z = pos.z;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VMapMgr2::GetLiquidLevel(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type, uint32& mogpFlags) const
|
||||
{
|
||||
#if defined(ENABLE_VMAP_CHECKS)
|
||||
if (!IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS))
|
||||
#endif
|
||||
{
|
||||
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
LocationInfo info;
|
||||
Vector3 pos = convertPositionToInternalRep(x, y, z);
|
||||
if (instanceTree->second->GetLocationInfo(pos, info))
|
||||
{
|
||||
floor = info.ground_Z;
|
||||
ASSERT(floor < std::numeric_limits<float>::max());
|
||||
type = info.hitModel->GetLiquidType(); // entry from LiquidType.dbc
|
||||
mogpFlags = info.hitModel->GetMogpFlags();
|
||||
if (reqLiquidType && !(GetLiquidFlagsPtr(type) & reqLiquidType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (info.hitInstance->GetLiquidLevel(pos, info, level))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void VMapMgr2::GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, AreaAndLiquidData& data) const
|
||||
{
|
||||
if (IsVMAPDisabledForPtr(mapId, VMAP_DISABLE_LIQUIDSTATUS))
|
||||
{
|
||||
data.floorZ = z;
|
||||
int32 adtId, rootId, groupId;
|
||||
uint32 flags;
|
||||
if (GetAreaInfo(mapId, x, y, data.floorZ, flags, adtId, rootId, groupId))
|
||||
data.areaInfo.emplace(adtId, rootId, groupId, flags);
|
||||
return;
|
||||
}
|
||||
|
||||
InstanceTreeMap::const_iterator instanceTree = GetMapTree(mapId);
|
||||
if (instanceTree != iInstanceMapTrees.end())
|
||||
{
|
||||
@ -325,16 +263,22 @@ namespace VMAP
|
||||
if (instanceTree->second->GetLocationInfo(pos, info))
|
||||
{
|
||||
data.floorZ = info.ground_Z;
|
||||
uint32 liquidType = info.hitModel->GetLiquidType();
|
||||
float liquidLevel;
|
||||
if (!reqLiquidType || (GetLiquidFlagsPtr(liquidType) & reqLiquidType))
|
||||
if (info.hitInstance->GetLiquidLevel(pos, info, liquidLevel))
|
||||
data.liquidInfo.emplace(liquidType, liquidLevel);
|
||||
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.hitInstance->adtId, info.rootId, info.hitModel->GetWmoID(), info.hitModel->GetMogpFlags());
|
||||
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 */)
|
||||
|
||||
@ -115,9 +115,7 @@ namespace VMAP
|
||||
|
||||
bool processCommand(char* /*command*/) override { return false; } // for debug and extensions
|
||||
|
||||
bool GetAreaInfo(uint32 pMapId, float x, float y, float& z, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const override;
|
||||
bool GetLiquidLevel(uint32 pMapId, float x, float y, float z, uint8 reqLiquidType, float& level, float& floor, uint32& type, uint32& mogpFlags) const override;
|
||||
void GetAreaAndLiquidData(uint32 mapId, float x, float y, float z, uint8 reqLiquidType, AreaAndLiquidData& data) const override;
|
||||
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);
|
||||
|
||||
@ -51,22 +51,6 @@ namespace VMAP
|
||||
bool hit;
|
||||
};
|
||||
|
||||
class AreaInfoCallback
|
||||
{
|
||||
public:
|
||||
AreaInfoCallback(ModelInstance* val): prims(val) {}
|
||||
void operator()(const Vector3& point, uint32 entry)
|
||||
{
|
||||
#if defined(VMAP_DEBUG)
|
||||
LOG_DEBUG("maps", "AreaInfoCallback: trying to intersect '{}'", prims[entry].name);
|
||||
#endif
|
||||
prims[entry].intersectPoint(point, aInfo);
|
||||
}
|
||||
|
||||
ModelInstance* prims;
|
||||
AreaInfo aInfo;
|
||||
};
|
||||
|
||||
class LocationInfoCallback
|
||||
{
|
||||
public:
|
||||
@ -99,22 +83,6 @@ namespace VMAP
|
||||
return tilefilename.str();
|
||||
}
|
||||
|
||||
bool StaticMapTree::GetAreaInfo(Vector3& pos, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const
|
||||
{
|
||||
AreaInfoCallback intersectionCallBack(iTreeValues);
|
||||
iTree.intersectPoint(pos, intersectionCallBack);
|
||||
if (intersectionCallBack.aInfo.result)
|
||||
{
|
||||
flags = intersectionCallBack.aInfo.flags;
|
||||
adtId = intersectionCallBack.aInfo.adtId;
|
||||
rootId = intersectionCallBack.aInfo.rootId;
|
||||
groupId = intersectionCallBack.aInfo.groupId;
|
||||
pos.z = intersectionCallBack.aInfo.ground_Z;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StaticMapTree::GetLocationInfo(const Vector3& pos, LocationInfo& info) const
|
||||
{
|
||||
LocationInfoCallback intersectionCallBack(iTreeValues, info);
|
||||
|
||||
@ -30,6 +30,12 @@ namespace VMAP
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
enum class LoadResult : uint8;
|
||||
|
||||
struct GroupLocationInfo
|
||||
{
|
||||
const GroupModel* hitModel = nullptr;
|
||||
int32 rootId = -1;
|
||||
};
|
||||
|
||||
struct LocationInfo
|
||||
{
|
||||
LocationInfo(): ground_Z(-G3D::inf()) { }
|
||||
@ -73,7 +79,6 @@ namespace VMAP
|
||||
[[nodiscard]] bool isInLineOfSight(const G3D::Vector3& pos1, const G3D::Vector3& pos2, ModelIgnoreFlags ignoreFlags) const;
|
||||
bool GetObjectHitPos(const G3D::Vector3& pos1, const G3D::Vector3& pos2, G3D::Vector3& pResultHitPos, float pModifyDist) const;
|
||||
[[nodiscard]] float getHeight(const G3D::Vector3& pPos, float maxSearchDist) const;
|
||||
bool GetAreaInfo(G3D::Vector3& pos, uint32& flags, int32& adtId, int32& rootId, int32& groupId) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& pos, LocationInfo& info) const;
|
||||
|
||||
bool InitMap(const std::string& fname, VMapMgr2* vm);
|
||||
|
||||
@ -203,27 +203,6 @@ bool GameObjectModel::intersectRay(const G3D::Ray& ray, float& MaxDist, bool Sto
|
||||
return hit;
|
||||
}
|
||||
|
||||
void GameObjectModel::IntersectPoint(G3D::Vector3 const& point, VMAP::AreaInfo& info, uint32 ph_mask) const
|
||||
{
|
||||
if (!(phasemask & ph_mask) || !owner->IsSpawned() || !IsMapObject())
|
||||
return;
|
||||
|
||||
if (!iBound.contains(point))
|
||||
return;
|
||||
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (point - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (iModel->IntersectPoint(pModel, zDirModel, zDist, info))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
if (info.ground_Z < world_Z)
|
||||
info.ground_Z = world_Z;
|
||||
}
|
||||
}
|
||||
|
||||
bool GameObjectModel::GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationInfo& info, uint32 ph_mask) const
|
||||
{
|
||||
if (!(phasemask & ph_mask) || !owner->IsSpawned() || !IsMapObject())
|
||||
@ -236,7 +215,9 @@ bool GameObjectModel::GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationI
|
||||
Vector3 pModel = iInvRot * (point - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info))
|
||||
|
||||
VMAP::GroupLocationInfo groupInfo;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, groupInfo))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
|
||||
@ -70,7 +70,6 @@ public:
|
||||
[[nodiscard]] bool IsMapObject() const { return isWmo; }
|
||||
|
||||
bool intersectRay(const G3D::Ray& Ray, float& MaxDist, bool StopAtFirstHit, uint32 ph_mask, VMAP::ModelIgnoreFlags ignoreFlags) const;
|
||||
void IntersectPoint(G3D::Vector3 const& point, VMAP::AreaInfo& info, uint32 ph_mask) const;
|
||||
bool GetLocationInfo(G3D::Vector3 const& point, VMAP::LocationInfo& info, uint32 ph_mask) const;
|
||||
bool GetLiquidLevel(G3D::Vector3 const& point, VMAP::LocationInfo& info, float& liqHeight) const;
|
||||
|
||||
|
||||
@ -63,44 +63,6 @@ namespace VMAP
|
||||
return hit;
|
||||
}
|
||||
|
||||
void ModelInstance::intersectPoint(const G3D::Vector3& p, AreaInfo& info) const
|
||||
{
|
||||
if (!iModel)
|
||||
{
|
||||
#ifdef VMAP_DEBUG
|
||||
std::cout << "<object not loaded>\n";
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
// M2 files don't contain area info, only WMO files
|
||||
if (flags & MOD_M2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!iBound.contains(p))
|
||||
{
|
||||
return;
|
||||
}
|
||||
// child bounds are defined in object space:
|
||||
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (iModel->IntersectPoint(pModel, zDirModel, zDist, info))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
// Transform back to world space. Note that:
|
||||
// Mat * vec == vec * Mat.transpose()
|
||||
// and for rotation matrices: Mat.inverse() == Mat.transpose()
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
if (info.ground_Z < world_Z)
|
||||
{
|
||||
info.ground_Z = world_Z;
|
||||
info.adtId = adtId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ModelInstance::GetLocationInfo(const G3D::Vector3& p, LocationInfo& info) const
|
||||
{
|
||||
if (!iModel)
|
||||
@ -124,7 +86,9 @@ namespace VMAP
|
||||
Vector3 pModel = iInvRot * (p - iPos) * iInvScale;
|
||||
Vector3 zDirModel = iInvRot * Vector3(0.f, 0.f, -1.f);
|
||||
float zDist;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, info))
|
||||
|
||||
GroupLocationInfo groupInfo;
|
||||
if (iModel->GetLocationInfo(pModel, zDirModel, zDist, groupInfo))
|
||||
{
|
||||
Vector3 modelGround = pModel + zDist * zDirModel;
|
||||
// Transform back to world space. Note that:
|
||||
@ -133,6 +97,8 @@ namespace VMAP
|
||||
float world_Z = ((modelGround * iInvRot) * iScale + iPos).z;
|
||||
if (info.ground_Z < world_Z) // hm...could it be handled automatically with zDist at intersection?
|
||||
{
|
||||
info.rootId = groupInfo.rootId;
|
||||
info.hitModel = groupInfo.hitModel;
|
||||
info.ground_Z = world_Z;
|
||||
info.hitInstance = this;
|
||||
return true;
|
||||
|
||||
@ -66,7 +66,6 @@ namespace VMAP
|
||||
ModelInstance(const ModelSpawn& spawn, WorldModel* model);
|
||||
void setUnloaded() { iModel = nullptr; }
|
||||
bool intersectRay(const G3D::Ray& pRay, float& pMaxDist, bool StopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
|
||||
void intersectPoint(const G3D::Vector3& p, AreaInfo& info) 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; }
|
||||
|
||||
@ -20,9 +20,9 @@
|
||||
#include "ModelIgnoreFlags.h"
|
||||
#include "ModelInstance.h"
|
||||
#include "VMapDefinitions.h"
|
||||
#include <array>
|
||||
|
||||
using G3D::Vector3;
|
||||
using G3D::Ray;
|
||||
|
||||
template<> struct BoundsTrait<VMAP::GroupModel>
|
||||
{
|
||||
@ -451,21 +451,46 @@ namespace VMAP
|
||||
return callback.hit;
|
||||
}
|
||||
|
||||
bool GroupModel::IsInsideObject(const Vector3& pos, const Vector3& down, float& z_dist) const
|
||||
inline bool IsInsideOrAboveBound(G3D::AABox const& bounds, const G3D::Point3& point)
|
||||
{
|
||||
if (triangles.empty() || !iBound.contains(pos))
|
||||
return point.x >= bounds.low().x
|
||||
&& point.y >= bounds.low().y
|
||||
&& point.z >= bounds.low().z
|
||||
&& point.x <= bounds.high().x
|
||||
&& point.y <= bounds.high().y;
|
||||
}
|
||||
|
||||
GroupModel::InsideResult GroupModel::IsInsideObject(G3D::Ray const& ray, float& z_dist) const
|
||||
{
|
||||
if (triangles.empty() || !IsInsideOrAboveBound(iBound, ray.origin()))
|
||||
return OUT_OF_BOUNDS;
|
||||
|
||||
if (meshTree.bound().high().z >= ray.origin().z)
|
||||
{
|
||||
return false;
|
||||
float dist = G3D::finf();
|
||||
if (IntersectRay(ray, dist, false))
|
||||
{
|
||||
z_dist = dist - 0.1f;
|
||||
return INSIDE;
|
||||
}
|
||||
if (meshTree.bound().contains(ray.origin()))
|
||||
return MAYBE_INSIDE;
|
||||
}
|
||||
Vector3 rPos = pos - 0.1f * down;
|
||||
float dist = G3D::inf();
|
||||
G3D::Ray ray(rPos, down);
|
||||
bool hit = IntersectRay(ray, dist, false);
|
||||
if (hit)
|
||||
else
|
||||
{
|
||||
z_dist = dist - 0.1f;
|
||||
// some group models don't have any floor to intersect with
|
||||
// so we should attempt to intersect with a model part below this group
|
||||
// then find back where we originated from (in WorldModel::GetLocationInfo)
|
||||
float dist = G3D::finf();
|
||||
float delta = ray.origin().z - meshTree.bound().high().z;
|
||||
if (IntersectRay(ray.bumpedRay(delta), dist, false))
|
||||
{
|
||||
z_dist = dist - 0.1f + delta;
|
||||
return ABOVE;
|
||||
}
|
||||
}
|
||||
return hit;
|
||||
|
||||
return OUT_OF_BOUNDS;
|
||||
}
|
||||
|
||||
bool GroupModel::GetLiquidLevel(const Vector3& pos, float& liqHeight) const
|
||||
@ -541,76 +566,58 @@ namespace VMAP
|
||||
class WModelAreaCallback
|
||||
{
|
||||
public:
|
||||
WModelAreaCallback(const std::vector<GroupModel>& vals, const Vector3& down):
|
||||
prims(vals.begin()), hit(vals.end()), minVol(G3D::inf()), zDist(G3D::inf()), zVec(down) { }
|
||||
std::vector<GroupModel>::const_iterator prims;
|
||||
std::vector<GroupModel>::const_iterator hit;
|
||||
float minVol;
|
||||
float zDist;
|
||||
Vector3 zVec;
|
||||
void operator()(const Vector3& point, uint32 entry)
|
||||
WModelAreaCallback(std::vector<GroupModel> const& vals) :
|
||||
prims(vals), hit() { }
|
||||
std::vector<GroupModel> const& prims;
|
||||
std::array<GroupModel const*, 3> hit;
|
||||
bool operator()(G3D::Ray const& ray, uint32 entry, float& distance, bool /*stopAtFirstHit*/)
|
||||
{
|
||||
float group_Z;
|
||||
//float pVol = prims[entry].GetBound().volume();
|
||||
//if (pVol < minVol)
|
||||
//{
|
||||
/* if (prims[entry].iBound.contains(point)) */
|
||||
if (prims[entry].IsInsideObject(point, zVec, group_Z))
|
||||
if (GroupModel::InsideResult result = prims[entry].IsInsideObject(ray, group_Z); result != GroupModel::OUT_OF_BOUNDS)
|
||||
{
|
||||
//minVol = pVol;
|
||||
//hit = prims + entry;
|
||||
if (group_Z < zDist)
|
||||
if (result != GroupModel::MAYBE_INSIDE)
|
||||
{
|
||||
zDist = group_Z;
|
||||
hit = prims + entry;
|
||||
if (group_Z < distance)
|
||||
{
|
||||
distance = group_Z;
|
||||
hit[result] = &prims[entry];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#ifdef VMAP_DEBUG
|
||||
const GroupModel& gm = prims[entry];
|
||||
printf("%10u %8X %7.3f, %7.3f, %7.3f | %7.3f, %7.3f, %7.3f | z=%f, p_z=%f\n", gm.GetWmoID(), gm.GetMogpFlags(),
|
||||
gm.GetBound().low().x, gm.GetBound().low().y, gm.GetBound().low().z,
|
||||
gm.GetBound().high().x, gm.GetBound().high().y, gm.GetBound().high().z, group_Z, point.z);
|
||||
#endif
|
||||
else
|
||||
hit[result] = &prims[entry];
|
||||
}
|
||||
//}
|
||||
//std::cout << "trying to intersect '" << prims[entry].name << "'\n";
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
bool WorldModel::IntersectPoint(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, AreaInfo& info) const
|
||||
bool WorldModel::GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, GroupLocationInfo& info) const
|
||||
{
|
||||
if (groupModels.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WModelAreaCallback callback(groupModels, down);
|
||||
groupTree.intersectPoint(p, callback);
|
||||
if (callback.hit != groupModels.end())
|
||||
WModelAreaCallback callback(groupModels);
|
||||
G3D::Ray r(p - down * 0.1f, down);
|
||||
float zDist = groupTree.bound().extent().length();
|
||||
groupTree.intersectRay(r, callback, zDist, false);
|
||||
if (callback.hit[GroupModel::INSIDE])
|
||||
{
|
||||
info.rootId = RootWMOID;
|
||||
info.groupId = callback.hit->GetWmoID();
|
||||
info.flags = callback.hit->GetMogpFlags();
|
||||
info.result = true;
|
||||
dist = callback.zDist;
|
||||
info.hitModel = callback.hit[GroupModel::INSIDE];
|
||||
dist = zDist;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WorldModel::GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, LocationInfo& info) const
|
||||
{
|
||||
if (groupModels.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
WModelAreaCallback callback(groupModels, down);
|
||||
groupTree.intersectPoint(p, callback);
|
||||
if (callback.hit != groupModels.end())
|
||||
// some group models don't have any floor to intersect with
|
||||
// so we should attempt to intersect with a model part below the group `p` is in (stored in GroupModel::ABOVE)
|
||||
// then find back where we originated from (GroupModel::MAYBE_INSIDE)
|
||||
if (callback.hit[GroupModel::MAYBE_INSIDE] && callback.hit[GroupModel::ABOVE])
|
||||
{
|
||||
info.rootId = RootWMOID;
|
||||
info.hitModel = &(*callback.hit);
|
||||
dist = callback.zDist;
|
||||
info.hitModel = callback.hit[GroupModel::MAYBE_INSIDE];
|
||||
dist = zDist;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -29,6 +29,7 @@ namespace VMAP
|
||||
class TreeNode;
|
||||
struct AreaInfo;
|
||||
struct LocationInfo;
|
||||
struct GroupLocationInfo;
|
||||
enum class ModelIgnoreFlags : uint32;
|
||||
|
||||
class MeshTriangle
|
||||
@ -81,12 +82,14 @@ namespace VMAP
|
||||
void setMeshData(std::vector<G3D::Vector3>& vert, std::vector<MeshTriangle>& tri);
|
||||
void setLiquidData(WmoLiquid*& liquid) { iLiquid = liquid; liquid = nullptr; }
|
||||
bool IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit) const;
|
||||
bool IsInsideObject(const G3D::Vector3& pos, const G3D::Vector3& down, float& z_dist) const;
|
||||
enum InsideResult { INSIDE = 0, MAYBE_INSIDE = 1, ABOVE = 2, OUT_OF_BOUNDS = -1 };
|
||||
InsideResult IsInsideObject(G3D::Ray const& ray, float& z_dist) const;
|
||||
bool GetLiquidLevel(const G3D::Vector3& pos, float& liqHeight) const;
|
||||
[[nodiscard]] uint32 GetLiquidType() const;
|
||||
bool writeToFile(FILE* wf);
|
||||
bool readFromFile(FILE* rf);
|
||||
[[nodiscard]] const G3D::AABox& GetBound() const { return iBound; }
|
||||
[[nodiscard]] G3D::AABox const& GetBound() const { return iBound; }
|
||||
[[nodiscard]] G3D::AABox const& GetMeshTreeBound() const { return meshTree.bound(); }
|
||||
[[nodiscard]] uint32 GetMogpFlags() const { return iMogpFlags; }
|
||||
[[nodiscard]] uint32 GetWmoID() const { return iGroupWMOID; }
|
||||
void GetMeshData(std::vector<G3D::Vector3>& outVertices, std::vector<MeshTriangle>& outTriangles, WmoLiquid*& liquid);
|
||||
@ -109,8 +112,7 @@ namespace VMAP
|
||||
void setGroupModels(std::vector<GroupModel>& models);
|
||||
void setRootWmoID(uint32 id) { RootWMOID = id; }
|
||||
bool IntersectRay(const G3D::Ray& ray, float& distance, bool stopAtFirstHit, ModelIgnoreFlags ignoreFlags) const;
|
||||
bool IntersectPoint(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, AreaInfo& info) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, LocationInfo& info) const;
|
||||
bool GetLocationInfo(const G3D::Vector3& p, const G3D::Vector3& down, float& dist, GroupLocationInfo& info) const;
|
||||
bool writeFile(const std::string& filename);
|
||||
bool readFile(const std::string& filename);
|
||||
void GetGroupModels(std::vector<GroupModel>& outGroupModels);
|
||||
|
||||
@ -527,9 +527,11 @@ float GridTerrainData::getLiquidLevel(float x, float y) const
|
||||
}
|
||||
|
||||
// Get water state on map
|
||||
LiquidData const GridTerrainData::GetLiquidData(float x, float y, float z, float collisionHeight, uint8 ReqLiquidType) const
|
||||
LiquidData const GridTerrainData::GetLiquidData(float x, float y, float z, float collisionHeight, Optional<uint8> ReqLiquidType) const
|
||||
{
|
||||
LiquidData liquidData;
|
||||
liquidData.Status = LIQUID_MAP_NO_WATER;
|
||||
|
||||
if (!_loadedLiquidData)
|
||||
return liquidData;
|
||||
|
||||
@ -575,7 +577,7 @@ LiquidData const GridTerrainData::GetLiquidData(float x, float y, float z, float
|
||||
}
|
||||
|
||||
// Check req liquid type mask
|
||||
if (type != 0 && (!ReqLiquidType || (ReqLiquidType & type) != 0))
|
||||
if (type != 0 && (!ReqLiquidType || (*ReqLiquidType & type) != 0))
|
||||
{
|
||||
// Check water level:
|
||||
// Check water height map
|
||||
|
||||
@ -186,7 +186,7 @@ struct LoadedHoleData
|
||||
HolesType holes;
|
||||
};
|
||||
|
||||
enum LiquidStatus
|
||||
enum LiquidStatus : uint32
|
||||
{
|
||||
LIQUID_MAP_NO_WATER = 0x00000000,
|
||||
LIQUID_MAP_ABOVE_WATER = 0x00000001,
|
||||
@ -249,7 +249,7 @@ public:
|
||||
inline float getHeight(float x, float y) const { return (this->*_gridGetHeight)(x, y); }
|
||||
float getMinHeight(float x, float y) const;
|
||||
float getLiquidLevel(float x, float y) const;
|
||||
LiquidData const GetLiquidData(float x, float y, float z, float collisionHeight, uint8 ReqLiquidType) const;
|
||||
LiquidData const GetLiquidData(float x, float y, float z, float collisionHeight, Optional<uint8> ReqLiquidType) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -1097,7 +1097,7 @@ float Map::GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, fl
|
||||
if (ground)
|
||||
*ground = ground_z;
|
||||
|
||||
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phasemask, x, y, ground_z, collisionHeight, MAP_ALL_LIQUIDS);
|
||||
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phasemask, x, y, ground_z, collisionHeight, {});
|
||||
switch (liquidData.Status)
|
||||
{
|
||||
case LIQUID_MAP_ABOVE_WATER:
|
||||
@ -1198,27 +1198,18 @@ 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 vmap_z = z;
|
||||
float dynamic_z = z;
|
||||
float check_z = z;
|
||||
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
|
||||
uint32 vflags;
|
||||
int32 vadtId;
|
||||
int32 vrootId;
|
||||
int32 vgroupId;
|
||||
uint32 dflags;
|
||||
int32 dadtId;
|
||||
int32 drootId;
|
||||
int32 dgroupId;
|
||||
|
||||
bool hasVmapAreaInfo = vmgr->GetAreaInfo(GetId(), x, y, vmap_z, vflags, vadtId, vrootId, vgroupId);
|
||||
bool hasDynamicAreaInfo = _dynamicTree.GetAreaInfo(x, y, dynamic_z, phaseMask, dflags, dadtId, drootId, dgroupId);
|
||||
auto useVmap = [&]() { check_z = vmap_z; flags = vflags; adtId = vadtId; rootId = vrootId; groupId = vgroupId; };
|
||||
auto useDyn = [&]() { check_z = dynamic_z; flags = dflags; adtId = dadtId; rootId = drootId; groupId = dgroupId; };
|
||||
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();
|
||||
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)
|
||||
{
|
||||
if (hasDynamicAreaInfo && dynamic_z > vmap_z)
|
||||
if (hasDynamicAreaInfo && ddata.floorZ > vdata.floorZ)
|
||||
useDyn();
|
||||
else
|
||||
useVmap();
|
||||
@ -1299,32 +1290,30 @@ void Map::GetZoneAndAreaId(uint32 phaseMask, uint32& zoneid, uint32& areaid, flo
|
||||
zoneid = area->zone;
|
||||
}
|
||||
|
||||
LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z, float collisionHeight, uint8 ReqLiquidType)
|
||||
LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z, float collisionHeight, Optional<uint8> ReqLiquidType)
|
||||
{
|
||||
LiquidData liquidData;
|
||||
liquidData.Status = LIQUID_MAP_NO_WATER;
|
||||
|
||||
VMAP::IVMapMgr* vmgr = VMAP::VMapFactory::createOrGetVMapMgr();
|
||||
float liquid_level = INVALID_HEIGHT;
|
||||
float ground_level = INVALID_HEIGHT;
|
||||
uint32 liquid_type = 0;
|
||||
uint32 mogpFlags = 0;
|
||||
VMAP::AreaAndLiquidData vmapData;
|
||||
bool useGridLiquid = true;
|
||||
if (vmgr->GetLiquidLevel(GetId(), x, y, z, ReqLiquidType, liquid_level, ground_level, liquid_type, mogpFlags))
|
||||
if (vmgr->GetAreaAndLiquidData(GetId(), x, y, z, ReqLiquidType, vmapData) && vmapData.liquidInfo)
|
||||
{
|
||||
useGridLiquid = !IsInWMOInterior(mogpFlags);
|
||||
LOG_DEBUG("maps", "GetLiquidStatus(): vmap liquid level: {} ground: {} type: {}", liquid_level, ground_level, liquid_type);
|
||||
useGridLiquid = !vmapData.areaInfo || !IsInWMOInterior(vmapData.areaInfo->mogpFlags);
|
||||
LOG_DEBUG("maps", "GetLiquidStatus(): vmap liquid level: {} ground: {} type: {}", vmapData.liquidInfo->level, vmapData.floorZ, vmapData.liquidInfo->type);
|
||||
// Check water level and ground level
|
||||
if (liquid_level > ground_level && G3D::fuzzyGe(z, ground_level - GROUND_HEIGHT_TOLERANCE))
|
||||
if (vmapData.liquidInfo->level > vmapData.floorZ && G3D::fuzzyGe(z, vmapData.floorZ - GROUND_HEIGHT_TOLERANCE))
|
||||
{
|
||||
// hardcoded in client like this
|
||||
if (GetId() == MAP_OUTLAND && liquid_type == 2)
|
||||
liquid_type = 15;
|
||||
if (GetId() == MAP_OUTLAND && vmapData.liquidInfo->type == 2)
|
||||
vmapData.liquidInfo->type = 15;
|
||||
|
||||
uint32 liquidFlagType = 0;
|
||||
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(liquid_type))
|
||||
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(vmapData.liquidInfo->type))
|
||||
liquidFlagType = liq->Type;
|
||||
|
||||
if (liquid_type && liquid_type < 21)
|
||||
if (vmapData.liquidInfo->type && vmapData.liquidInfo->type < 21)
|
||||
{
|
||||
if (AreaTableEntry const* area = sAreaTableStore.LookupEntry(GetAreaId(phaseMask, x, y, z)))
|
||||
{
|
||||
@ -1338,19 +1327,19 @@ LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z,
|
||||
|
||||
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(overrideLiquid))
|
||||
{
|
||||
liquid_type = overrideLiquid;
|
||||
vmapData.liquidInfo->type = overrideLiquid;
|
||||
liquidFlagType = liq->Type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
liquidData.Level = liquid_level;
|
||||
liquidData.DepthLevel = ground_level;
|
||||
liquidData.Entry = liquid_type;
|
||||
liquidData.Level = vmapData.liquidInfo->level;
|
||||
liquidData.DepthLevel = vmapData.floorZ;
|
||||
liquidData.Entry = vmapData.liquidInfo->type;
|
||||
liquidData.Flags = 1 << liquidFlagType;
|
||||
}
|
||||
|
||||
float delta = liquid_level - z;
|
||||
float delta = vmapData.liquidInfo->level - z;
|
||||
|
||||
// Get position delta
|
||||
if (delta > collisionHeight)
|
||||
@ -1369,7 +1358,7 @@ LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z,
|
||||
{
|
||||
LiquidData const& map_data = gmap->GetLiquidData(x, y, z, collisionHeight, ReqLiquidType);
|
||||
// Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
|
||||
if (map_data.Status != LIQUID_MAP_NO_WATER && (map_data.Level > ground_level))
|
||||
if (map_data.Status != LIQUID_MAP_NO_WATER && (map_data.Level > vmapData.floorZ))
|
||||
{
|
||||
// hardcoded in client like this
|
||||
uint32 liquidEntry = map_data.Entry;
|
||||
@ -1385,7 +1374,7 @@ LiquidData const Map::GetLiquidData(uint32 phaseMask, float x, float y, float z,
|
||||
return liquidData;
|
||||
}
|
||||
|
||||
void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, uint8 reqLiquidType)
|
||||
void Map::GetFullTerrainStatusForPosition(uint32 /*phaseMask*/, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, Optional<uint8> reqLiquidType)
|
||||
{
|
||||
GridTerrainData* gmap = GetGridTerrainData(x, y);
|
||||
|
||||
@ -1600,7 +1589,7 @@ float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=tr
|
||||
|
||||
bool Map::IsInWater(uint32 phaseMask, float x, float y, float pZ, float collisionHeight) const
|
||||
{
|
||||
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phaseMask, x, y, pZ, collisionHeight, MAP_ALL_LIQUIDS);
|
||||
LiquidData const& liquidData = const_cast<Map*>(this)->GetLiquidData(phaseMask, x, y, pZ, collisionHeight, {});
|
||||
return (liquidData.Status & MAP_LIQUID_STATUS_SWIMMING) != 0;
|
||||
}
|
||||
|
||||
|
||||
@ -243,8 +243,8 @@ public:
|
||||
[[nodiscard]] float GetMinHeight(float x, float y) const;
|
||||
Transport* GetTransportForPos(uint32 phase, float x, float y, float z, WorldObject* worldobject = nullptr);
|
||||
|
||||
void GetFullTerrainStatusForPosition(uint32 phaseMask, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, uint8 reqLiquidType = MAP_ALL_LIQUIDS);
|
||||
LiquidData const GetLiquidData(uint32 phaseMask, float x, float y, float z, float collisionHeight, uint8 ReqLiquidType);
|
||||
void GetFullTerrainStatusForPosition(uint32 phaseMask, float x, float y, float z, float collisionHeight, PositionFullTerrainStatus& data, Optional<uint8> reqLiquidType = {});
|
||||
LiquidData const GetLiquidData(uint32 phaseMask, float x, float y, float z, float collisionHeight, Optional<uint8> ReqLiquidType);
|
||||
|
||||
[[nodiscard]] bool GetAreaInfo(uint32 phaseMask, float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId) const;
|
||||
[[nodiscard]] uint32 GetAreaId(uint32 phaseMask, float x, float y, float z) const;
|
||||
|
||||
@ -211,8 +211,8 @@ void PathGenerator::BuildPolyPath(G3D::Vector3 const& startPos, G3D::Vector3 con
|
||||
{
|
||||
bool buildShortcut = false;
|
||||
|
||||
auto liquidDataStart = _source->GetMap()->GetLiquidData(_source->GetPhaseMask(), startPos.x, startPos.y, startPos.z, _source->GetCollisionHeight(), MAP_ALL_LIQUIDS);
|
||||
auto liquidDataEnd = _source->GetMap()->GetLiquidData(_source->GetPhaseMask(), endPos.x, endPos.y, endPos.z, _source->GetCollisionHeight(), MAP_ALL_LIQUIDS);
|
||||
auto liquidDataStart = _source->GetMap()->GetLiquidData(_source->GetPhaseMask(), startPos.x, startPos.y, startPos.z, _source->GetCollisionHeight(), {});
|
||||
auto liquidDataEnd = _source->GetMap()->GetLiquidData(_source->GetPhaseMask(), endPos.x, endPos.y, endPos.z, _source->GetCollisionHeight(), {});
|
||||
|
||||
bool startUnderWaterEndInWater = liquidDataStart.Status == LIQUID_MAP_UNDER_WATER &&
|
||||
(liquidDataEnd.Status & MAP_LIQUID_STATUS_IN_CONTACT) != 0;
|
||||
@ -698,7 +698,7 @@ void PathGenerator::UpdateFilter()
|
||||
|
||||
NavTerrain PathGenerator::GetNavTerrain(float x, float y, float z) const
|
||||
{
|
||||
LiquidData const& liquidData = _source->GetMap()->GetLiquidData(_source->GetPhaseMask(), x, y, z, _source->GetCollisionHeight(), MAP_ALL_LIQUIDS);
|
||||
LiquidData const& liquidData = _source->GetMap()->GetLiquidData(_source->GetPhaseMask(), x, y, z, _source->GetCollisionHeight(), {});
|
||||
if (liquidData.Status == LIQUID_MAP_NO_WATER)
|
||||
return NAV_GROUND;
|
||||
|
||||
|
||||
@ -1384,7 +1384,7 @@ void Spell::SelectImplicitCasterDestTargets(SpellEffIndex effIndex, SpellImplici
|
||||
|
||||
float ground = m_caster->GetMapHeight(x, y, z, true);
|
||||
float liquidLevel = VMAP_INVALID_HEIGHT_VALUE;
|
||||
LiquidData const& liquidData = m_caster->GetMap()->GetLiquidData(m_caster->GetPhaseMask(), x, y, z, m_caster->GetCollisionHeight(), MAP_ALL_LIQUIDS);
|
||||
LiquidData const& liquidData = m_caster->GetMap()->GetLiquidData(m_caster->GetPhaseMask(), x, y, z, m_caster->GetCollisionHeight(), {});
|
||||
if (liquidData.Status)
|
||||
liquidLevel = liquidData.Level;
|
||||
|
||||
|
||||
@ -2187,12 +2187,6 @@ void SpellMgr::LoadSpellInfoCorrections()
|
||||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(1);
|
||||
});
|
||||
|
||||
// Halls of Lightning, Arcing Burn
|
||||
ApplySpellFix({ 52671, 59834 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
spellInfo->AttributesEx3 |= SPELL_ATTR3_DOT_STACKING_RULE;
|
||||
});
|
||||
|
||||
// Trial of the Champion, Death's Respite
|
||||
ApplySpellFix({ 68306 }, [](SpellInfo* spellInfo)
|
||||
{
|
||||
|
||||
@ -41,6 +41,7 @@ enum VolkhanOther
|
||||
NPC_VOLKHAN_ANVIL = 28823,
|
||||
NPC_MOLTEN_GOLEM = 28695,
|
||||
NPC_BRITTLE_GOLEM = 28681,
|
||||
NPC_SLAG = 28585,
|
||||
|
||||
// Misc
|
||||
ACTION_SHATTER = 1,
|
||||
@ -77,7 +78,7 @@ enum Yells
|
||||
|
||||
struct boss_volkhan : public BossAI
|
||||
{
|
||||
boss_volkhan(Creature* creature) : BossAI(creature, DATA_VOLKHAN), summons(creature) { }
|
||||
boss_volkhan(Creature* creature) : BossAI(creature, DATA_VOLKHAN) { }
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
@ -104,6 +105,18 @@ struct boss_volkhan : public BossAI
|
||||
{
|
||||
_JustDied();
|
||||
Talk(SAY_DEATH);
|
||||
|
||||
std::list<Creature*> slags;
|
||||
GetCreatureListWithEntryInGrid(slags, me, NPC_SLAG, 100.0f);
|
||||
|
||||
if (!slags.empty())
|
||||
{
|
||||
for (Creature* slag : slags)
|
||||
{
|
||||
if (slag)
|
||||
slag->DespawnOrUnsummon();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GetNextPos()
|
||||
@ -286,8 +299,6 @@ struct boss_volkhan : public BossAI
|
||||
}
|
||||
|
||||
private:
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
float x, y, z;
|
||||
uint8 PointID;
|
||||
uint8 ShatteredCount;
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
#include "CreatureScript.h"
|
||||
#include "GameObjectScript.h"
|
||||
#include "GridNotifiers.h"
|
||||
#include "PassiveAI.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
@ -26,6 +27,7 @@
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
#include "Vehicle.h"
|
||||
#include <algorithm>
|
||||
|
||||
enum AlchemistItemRequirements
|
||||
{
|
||||
@ -234,297 +236,376 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
enum overlordDrakuru
|
||||
enum OverlordDrakuru
|
||||
{
|
||||
SPELL_SHADOW_BOLT = 54113,
|
||||
SPELL_SCOURGE_DISGUISE_EXPIRING = 52010,
|
||||
SPELL_THROW_BRIGHT_CRYSTAL = 54087,
|
||||
SPELL_TELEPORT_EFFECT = 52096,
|
||||
SPELL_SCOURGE_DISGUISE = 51966,
|
||||
SPELL_SCOURGE_DISGUISE_INSTANT_CAST = 52192,
|
||||
SPELL_BLIGHT_FOG = 54104,
|
||||
SPELL_THROW_PORTAL_CRYSTAL = 54209,
|
||||
SPELL_ARTHAS_PORTAL = 51807,
|
||||
SPELL_TOUCH_OF_DEATH = 54236,
|
||||
SPELL_DRAKURU_DEATH = 54248,
|
||||
SPELL_SUMMON_SKULL = 54253,
|
||||
SPELL_SHADOW_BOLT = 54113,
|
||||
SPELL_SCOURGE_DISGUISE_EXPIRING = 52010,
|
||||
SPELL_DROP_DISGUISE = 54089,
|
||||
SPELL_THROW_BRIGHT_CRYSTAL = 54087,
|
||||
SPELL_TELEPORT_EFFECT = 52096,
|
||||
SPELL_SCOURGE_SPOTLIGHT = 53104,
|
||||
SPELL_SCOURGE_DISGUISE = 51966,
|
||||
SPELL_SCOURGE_DISGUISE_INSTANT_CAST = 52192,
|
||||
SPELL_BLIGHT_FOG = 54104,
|
||||
SPELL_THROW_PORTAL_CRYSTAL = 54209,
|
||||
SPELL_ARTHAS_PORTAL = 51807,
|
||||
SPELL_TOUCH_OF_DEATH = 54236,
|
||||
SPELL_DRAKURU_DEATH = 54248,
|
||||
SPELL_SUMMON_SKULL = 54253,
|
||||
SPELL_BLOATED_ABOMINATION_FEIGN_DEATH = 52593,
|
||||
SPELL_EXPLODE_ABOMINATION_BLOODY_MEAT = 52523,
|
||||
SPELL_EXPLODE_ABOMINATION_MEAT = 52520,
|
||||
SPELL_DRAKURUS_SKULL_MISSILE = 54250,
|
||||
SPELL_BURST_AT_THE_SEAMS_BONE = 52516,
|
||||
|
||||
QUEST_BETRAYAL = 12713,
|
||||
QUEST_BETRAYAL = 12713,
|
||||
|
||||
NPC_BLIGHTBLOOD_TROLL = 28931,
|
||||
NPC_LICH_KING = 28498,
|
||||
NPC_BLIGHTBLOOD_TROLL = 28931,
|
||||
NPC_LICH_KING = 28498,
|
||||
NPC_TOTALLY_GENERIC_BUNNY = 29100,
|
||||
NPC_TOTALLY_GENERIC_BUNNY_JSB = 28960,
|
||||
GO_DRAKURUS_LAST_WISH = 202357,
|
||||
|
||||
EVENT_BETRAYAL_1 = 1,
|
||||
EVENT_BETRAYAL_2 = 2,
|
||||
EVENT_BETRAYAL_3 = 3,
|
||||
EVENT_BETRAYAL_4 = 4,
|
||||
EVENT_BETRAYAL_5 = 5,
|
||||
EVENT_BETRAYAL_6 = 6,
|
||||
EVENT_BETRAYAL_7 = 7,
|
||||
EVENT_BETRAYAL_8 = 8,
|
||||
EVENT_BETRAYAL_9 = 9,
|
||||
EVENT_BETRAYAL_10 = 10,
|
||||
EVENT_BETRAYAL_11 = 11,
|
||||
EVENT_BETRAYAL_12 = 12,
|
||||
EVENT_BETRAYAL_13 = 13,
|
||||
EVENT_BETRAYAL_14 = 14,
|
||||
EVENT_BETRAYAL_SHADOW_BOLT = 20,
|
||||
EVENT_BETRAYAL_CRYSTAL = 21,
|
||||
EVENT_BETRAYAL_COMBAT_TALK = 22,
|
||||
ACTION_SUMMON_DRAKURU_LAST_WISH = 1,
|
||||
ACTION_DESTROY_DRAKURU_LAST_WISH = 2,
|
||||
ACTION_REMOVE_SPOTLIGHTS = 3,
|
||||
|
||||
SAY_DRAKURU_0 = 0,
|
||||
SAY_DRAKURU_1 = 1,
|
||||
SAY_DRAKURU_2 = 2,
|
||||
SAY_DRAKURU_3 = 3,
|
||||
SAY_DRAKURU_4 = 4,
|
||||
SAY_DRAKURU_5 = 5,
|
||||
SAY_DRAKURU_6 = 6,
|
||||
SAY_DRAKURU_7 = 7,
|
||||
SAY_LICH_7 = 7,
|
||||
SAY_LICH_8 = 8,
|
||||
SAY_LICH_9 = 9,
|
||||
SAY_LICH_10 = 10,
|
||||
SAY_LICH_11 = 11,
|
||||
SAY_LICH_12 = 12,
|
||||
SUMMON_GROUP_BLIGHTBLOOD_TROLL = 1,
|
||||
|
||||
EVENT_BETRAYAL_INTRO_1 = 1,
|
||||
EVENT_BETRAYAL_INTRO_2 = 2,
|
||||
EVENT_BETRAYAL_INTRO_3 = 3,
|
||||
EVENT_BETRAYAL_INTRO_4 = 4,
|
||||
EVENT_BETRAYAL_EVADE_CHECK = 5,
|
||||
EVENT_BETRAYAL_EPILOGUE_1 = 6,
|
||||
EVENT_BETRAYAL_EPILOGUE_2 = 7,
|
||||
EVENT_BETRAYAL_EPILOGUE_3 = 8,
|
||||
EVENT_BETRAYAL_EPILOGUE_4 = 9,
|
||||
EVENT_BETRAYAL_EPILOGUE_5 = 10,
|
||||
EVENT_BETRAYAL_EPILOGUE_6 = 11,
|
||||
EVENT_BETRAYAL_EPILOGUE_7 = 12,
|
||||
EVENT_BETRAYAL_EPILOGUE_8 = 13,
|
||||
EVENT_BETRAYAL_EPILOGUE_9 = 14,
|
||||
EVENT_BETRAYAL_EPILOGUE_10 = 15,
|
||||
|
||||
SAY_DRAKURU_0 = 0,
|
||||
SAY_DRAKURU_1 = 1,
|
||||
SAY_DRAKURU_2 = 2,
|
||||
SAY_DRAKURU_3 = 3,
|
||||
SAY_DRAKURU_4 = 4,
|
||||
SAY_DRAKURU_5 = 5,
|
||||
SAY_DRAKURU_6 = 6,
|
||||
SAY_DRAKURU_7 = 7,
|
||||
SAY_LICH_7 = 7,
|
||||
SAY_LICH_8 = 8,
|
||||
SAY_LICH_9 = 9,
|
||||
SAY_LICH_10 = 10,
|
||||
SAY_LICH_11 = 11,
|
||||
SAY_LICH_12 = 12,
|
||||
};
|
||||
|
||||
class npc_overlord_drakuru_betrayal : public CreatureScript
|
||||
enum BetrayalState
|
||||
{
|
||||
public:
|
||||
npc_overlord_drakuru_betrayal() : CreatureScript("npc_overlord_drakuru_betrayal") { }
|
||||
BETRAYAL_NOT_STARTED,
|
||||
BETRAYAL_IN_PROGRESS,
|
||||
BETRAYAL_EPILOGUE,
|
||||
BETRAYAL_EVADE,
|
||||
};
|
||||
|
||||
CreatureAI* GetAI(Creature* creature) const override
|
||||
struct npc_overlord_drakuru_betrayal : public ScriptedAI
|
||||
{
|
||||
npc_overlord_drakuru_betrayal(Creature* creature) : ScriptedAI(creature), _summons(me), _state(BETRAYAL_NOT_STARTED)
|
||||
{
|
||||
return new npc_overlord_drakuru_betrayalAI(creature);
|
||||
me->SetCombatMovement(false);
|
||||
}
|
||||
|
||||
struct npc_overlord_drakuru_betrayalAI : public ScriptedAI
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
{
|
||||
npc_overlord_drakuru_betrayalAI(Creature* creature) : ScriptedAI(creature), summons(me)
|
||||
{
|
||||
}
|
||||
if (_state != BETRAYAL_EVADE)
|
||||
return;
|
||||
me->SetFaction(FACTION_UNDEAD_SCOURGE);
|
||||
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
ScriptedAI::EnterEvadeMode(why);
|
||||
}
|
||||
|
||||
EventMap events;
|
||||
SummonList summons;
|
||||
ObjectGuid playerGUID;
|
||||
ObjectGuid lichGUID;
|
||||
void Reset() override
|
||||
{
|
||||
events.Reset();
|
||||
scheduler.CancelAll();
|
||||
_summons.DespawnAll();
|
||||
_playerGUID.Clear();
|
||||
_lichGUID.Clear();
|
||||
me->SetFaction(FACTION_UNDEAD_SCOURGE);
|
||||
me->SetVisible(false);
|
||||
DoAction(ACTION_SUMMON_DRAKURU_LAST_WISH);
|
||||
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
me->SetImmuneToPC(true);
|
||||
_state = BETRAYAL_NOT_STARTED;
|
||||
DoAction(ACTION_REMOVE_SPOTLIGHTS);
|
||||
}
|
||||
|
||||
void EnterEvadeMode(EvadeReason why) override
|
||||
void DoAction(int32 action) override
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
if (playerGUID)
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
|
||||
if (player->IsWithinDistInMap(me, 80))
|
||||
return;
|
||||
me->SetFaction(FACTION_UNDEAD_SCOURGE);
|
||||
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
ScriptedAI::EnterEvadeMode(why);
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
events.Reset();
|
||||
summons.DespawnAll();
|
||||
playerGUID.Clear();
|
||||
lichGUID.Clear();
|
||||
me->SetFaction(FACTION_UNDEAD_SCOURGE);
|
||||
me->SetVisible(false);
|
||||
me->RemoveUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* who) override
|
||||
{
|
||||
if (who->IsPlayer())
|
||||
case ACTION_SUMMON_DRAKURU_LAST_WISH:
|
||||
if (!me->FindNearestGameObject(GO_DRAKURUS_LAST_WISH, 80.0f))
|
||||
me->SummonGameObject(GO_DRAKURUS_LAST_WISH, 6185.989, -2029.6979, 590.87787, 0, 0, 0, 0, 0, 0, true, GO_SUMMON_TIMED_DESPAWN);
|
||||
break;
|
||||
case ACTION_DESTROY_DRAKURU_LAST_WISH:
|
||||
if (GameObject* go = me->FindNearestGameObject(GO_DRAKURUS_LAST_WISH, 80.0f))
|
||||
go->Delete();
|
||||
break;
|
||||
case ACTION_REMOVE_SPOTLIGHTS:
|
||||
{
|
||||
if (playerGUID)
|
||||
std::list<Creature*> creatures;
|
||||
me->GetCreatureListWithEntryInGrid(creatures, NPC_TOTALLY_GENERIC_BUNNY, 55.0f);
|
||||
for (Creature* creature : creatures)
|
||||
creature->RemoveAurasDueToSpell(SPELL_SCOURGE_SPOTLIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPlayerOnQuest(Player* player)
|
||||
{
|
||||
return player->GetQuestStatus(QUEST_BETRAYAL) == QUEST_STATUS_INCOMPLETE;
|
||||
}
|
||||
|
||||
void MoveInLineOfSight(Unit* who) override
|
||||
{
|
||||
if (Player* player = who->ToPlayer())
|
||||
{
|
||||
bool shouldStartEvent = (_state == BETRAYAL_NOT_STARTED) && IsPlayerOnQuest(player) && player->HasAura(SPELL_SCOURGE_DISGUISE) && player->IsWithinDistInMap(me, 80.0f);
|
||||
if (shouldStartEvent)
|
||||
{
|
||||
me->SetVisible(true);
|
||||
_state = BETRAYAL_IN_PROGRESS;
|
||||
DoAction(ACTION_DESTROY_DRAKURU_LAST_WISH);
|
||||
_playerGUID = who->GetGUID();
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_INTRO_1, 6s);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EVADE_CHECK, 10s);
|
||||
}
|
||||
}
|
||||
else
|
||||
ScriptedAI::MoveInLineOfSight(who);
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* summon) override
|
||||
{
|
||||
_summons.Summon(summon);
|
||||
switch (summon->GetEntry())
|
||||
{
|
||||
case NPC_BLIGHTBLOOD_TROLL:
|
||||
if (Creature* target = summon->FindNearestCreature(NPC_TOTALLY_GENERIC_BUNNY, 10.0f, true))
|
||||
target->CastSpell(target, SPELL_TELEPORT_EFFECT, true);
|
||||
break;
|
||||
case NPC_LICH_KING:
|
||||
me->SetFacingToObject(summon);
|
||||
_lichGUID = summon->GetGUID();
|
||||
summon->GetMotionMaster()->MovePoint(0, 6164.2695, -2016.8978, 590.8636);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
{
|
||||
scheduler.Schedule(0s, [this](TaskContext context)
|
||||
{
|
||||
if (!me->IsWithinMeleeRange(me->GetVictim()))
|
||||
DoCastVictim(SPELL_SHADOW_BOLT);
|
||||
context.Repeat(2s);
|
||||
}).Schedule(5s, [this](TaskContext context)
|
||||
{
|
||||
if (Unit* target = SelectTarget(SelectTargetMethod::Random, 0, 0.0f, true))
|
||||
DoCast(target, SPELL_THROW_BRIGHT_CRYSTAL);
|
||||
context.Repeat(6s, 15s);
|
||||
}).Schedule(20s, [this](TaskContext context)
|
||||
{
|
||||
Talk(SAY_DRAKURU_4);
|
||||
context.Repeat(10s, 20s);
|
||||
});
|
||||
}
|
||||
|
||||
void DamageTaken(Unit* /*attacker*/, uint32& damage, DamageEffectType /*dmgType*/, SpellSchoolMask /*school*/) override
|
||||
{
|
||||
if (damage >= me->GetHealth() && !me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE))
|
||||
{
|
||||
damage = 0;
|
||||
me->RemoveAllAuras();
|
||||
me->CombatStop();
|
||||
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
me->SetFaction(2082);
|
||||
me->SetImmuneToPC(true);
|
||||
events.Reset();
|
||||
scheduler.CancelAll();
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_1, 4200ms);
|
||||
_state = BETRAYAL_EPILOGUE;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override
|
||||
{
|
||||
switch (spellInfo->Id)
|
||||
{
|
||||
case SPELL_THROW_PORTAL_CRYSTAL:
|
||||
if (Aura* aura = target->AddAura(SPELL_ARTHAS_PORTAL, target))
|
||||
aura->SetDuration(77'000);
|
||||
break;
|
||||
case SPELL_DRAKURUS_SKULL_MISSILE:
|
||||
target->CastSpell(target, SPELL_SUMMON_SKULL, true);
|
||||
break;
|
||||
case SPELL_DROP_DISGUISE:
|
||||
target->CastSpell(target, SPELL_SCOURGE_DISGUISE_EXPIRING, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override
|
||||
{
|
||||
if (spellInfo->Id == SPELL_TOUCH_OF_DEATH)
|
||||
{
|
||||
DoCastAOE(SPELL_DRAKURUS_SKULL_MISSILE, true);
|
||||
DoCastSelf(SPELL_BLOATED_ABOMINATION_FEIGN_DEATH, true);
|
||||
DoCastSelf(SPELL_BURST_AT_THE_SEAMS_BONE, true);
|
||||
DoCastSelf(SPELL_BURST_AT_THE_SEAMS_BONE, true);
|
||||
DoCastSelf(SPELL_BURST_AT_THE_SEAMS_BONE, true);
|
||||
DoCastSelf(SPELL_EXPLODE_ABOMINATION_MEAT, true);
|
||||
DoCastSelf(SPELL_EXPLODE_ABOMINATION_BLOODY_MEAT, true);
|
||||
DoCastSelf(SPELL_EXPLODE_ABOMINATION_BLOODY_MEAT, true);
|
||||
DoCastSelf(SPELL_EXPLODE_ABOMINATION_BLOODY_MEAT, true);
|
||||
DoCastSelf(SPELL_DRAKURU_DEATH, true);
|
||||
DoAction(ACTION_SUMMON_DRAKURU_LAST_WISH);
|
||||
me->SetImmuneToPC(true);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
events.Update(diff);
|
||||
switch (events.ExecuteEvent())
|
||||
{
|
||||
case EVENT_BETRAYAL_EVADE_CHECK:
|
||||
{
|
||||
if (_state == BETRAYAL_IN_PROGRESS)
|
||||
{
|
||||
if (who->GetGUID() != playerGUID)
|
||||
float radius = 80.0f;
|
||||
std::list<Player*> players;
|
||||
Acore::AnyPlayerInObjectRangeCheck checker(me, radius, true, true);
|
||||
Acore::PlayerListSearcher<Acore::AnyPlayerInObjectRangeCheck> searcher(me, players, checker);
|
||||
Cell::VisitObjects(me, searcher, radius);
|
||||
if (std::ranges::any_of(players, [this](Player* player)
|
||||
{
|
||||
Player* player = ObjectAccessor::GetPlayer(*me, playerGUID);
|
||||
if (player && player->IsWithinDistInMap(me, 80))
|
||||
who->ToPlayer()->NearTeleportTo(6143.76f, -1969.7f, 417.57f, 2.08f);
|
||||
else
|
||||
{
|
||||
EnterEvadeMode(EVADE_REASON_OTHER);
|
||||
return;
|
||||
}
|
||||
return IsPlayerOnQuest(player);
|
||||
}))
|
||||
{
|
||||
events.Repeat(10s);
|
||||
}
|
||||
else
|
||||
ScriptedAI::MoveInLineOfSight(who);
|
||||
{
|
||||
_state = BETRAYAL_EVADE;
|
||||
EnterEvadeMode(EVADE_REASON_OTHER);
|
||||
}
|
||||
}
|
||||
else if (who->ToPlayer()->GetQuestStatus(QUEST_BETRAYAL) == QUEST_STATUS_INCOMPLETE && who->HasAura(SPELL_SCOURGE_DISGUISE))
|
||||
break;
|
||||
}
|
||||
case EVENT_BETRAYAL_INTRO_1:
|
||||
Talk(SAY_DRAKURU_0);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_INTRO_2, 4s);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_INTRO_3, 6600ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_INTRO_2:
|
||||
me->SummonCreatureGroup(SUMMON_GROUP_BLIGHTBLOOD_TROLL);
|
||||
break;
|
||||
case EVENT_BETRAYAL_INTRO_3:
|
||||
Talk(SAY_DRAKURU_1);
|
||||
DoCastAOE(SPELL_DROP_DISGUISE);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_INTRO_4, 9600ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_INTRO_4:
|
||||
{
|
||||
Talk(SAY_DRAKURU_2);
|
||||
Talk(SAY_DRAKURU_3);
|
||||
me->SetImmuneToPC(false);
|
||||
std::list<Creature*> creatures;
|
||||
me->GetCreatureListWithEntryInGrid(creatures, NPC_TOTALLY_GENERIC_BUNNY, 55.0f);
|
||||
for (Creature* creature : creatures)
|
||||
creature->CastSpell(creature, SPELL_SCOURGE_SPOTLIGHT, true);
|
||||
break;
|
||||
}
|
||||
case EVENT_BETRAYAL_EPILOGUE_1:
|
||||
{
|
||||
Talk(SAY_DRAKURU_5);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_2, 4800ms);
|
||||
DoAction(ACTION_REMOVE_SPOTLIGHTS);
|
||||
break;
|
||||
}
|
||||
case EVENT_BETRAYAL_EPILOGUE_2:
|
||||
Talk(SAY_DRAKURU_6);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_3, 1800ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_3:
|
||||
DoCastSelf(SPELL_THROW_PORTAL_CRYSTAL, true);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_4, 3600ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_4:
|
||||
me->SummonCreature(NPC_LICH_KING, 6140.4233, -2010.9938, 589.1911, 6.126106, TEMPSUMMON_TIMED_DESPAWN, 77'000);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_5, 8400ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_5:
|
||||
Talk(SAY_DRAKURU_7);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_6, 9600ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_6:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, _lichGUID))
|
||||
{
|
||||
me->SetVisible(true);
|
||||
playerGUID = who->GetGUID();
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_1, 5s);
|
||||
lich->AI()->Talk(SAY_LICH_7);
|
||||
lich->AI()->Talk(SAY_LICH_8, 5400ms);
|
||||
}
|
||||
}
|
||||
else
|
||||
ScriptedAI::MoveInLineOfSight(who);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_7, 7800ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_7:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, _lichGUID))
|
||||
lich->CastSpell(me, SPELL_TOUCH_OF_DEATH, false);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_8, 4200ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_8:
|
||||
me->SetVisible(false);
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, _lichGUID))
|
||||
{
|
||||
lich->AI()->Talk(SAY_LICH_9, 3600ms);
|
||||
lich->AI()->Talk(SAY_LICH_10, 8400ms);
|
||||
lich->AI()->Talk(SAY_LICH_11, 22800ms);
|
||||
lich->AI()->Talk(SAY_LICH_12, 27600ms);
|
||||
}
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_9, 32600ms);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_EPILOGUE_10, 37200ms);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_9:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, _lichGUID))
|
||||
lich->GetMotionMaster()->MovePoint(0, 6141.2393, -2011.2728, 589.8653);
|
||||
break;
|
||||
case EVENT_BETRAYAL_EPILOGUE_10:
|
||||
EnterEvadeMode(EVADE_REASON_OTHER);
|
||||
break;
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* cr) override
|
||||
{
|
||||
summons.Summon(cr);
|
||||
if (cr->GetEntry() == NPC_BLIGHTBLOOD_TROLL)
|
||||
cr->CastSpell(cr, SPELL_TELEPORT_EFFECT, true);
|
||||
else
|
||||
{
|
||||
me->SetFacingToObject(cr);
|
||||
lichGUID = cr->GetGUID();
|
||||
float o = me->GetAngle(cr);
|
||||
cr->GetMotionMaster()->MovePoint(0, me->GetPositionX() + cos(o) * 6.0f, me->GetPositionY() + std::sin(o) * 6.0f, me->GetPositionZ());
|
||||
}
|
||||
}
|
||||
if (me->GetFaction() == 2082 || me->HasUnitState(UNIT_STATE_CASTING | UNIT_STATE_STUNNED))
|
||||
return;
|
||||
|
||||
void JustEngagedWith(Unit*) override
|
||||
{
|
||||
Talk(SAY_DRAKURU_3);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_SHADOW_BOLT, 2s);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_CRYSTAL, 5s);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_COMBAT_TALK, 20s);
|
||||
}
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
if (damage >= me->GetHealth() && !me->HasUnitFlag(UNIT_FLAG_NON_ATTACKABLE))
|
||||
{
|
||||
damage = 0;
|
||||
me->RemoveAllAuras();
|
||||
me->CombatStop();
|
||||
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
me->SetFaction(FACTION_FRIENDLY);
|
||||
events.Reset();
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_4, 1s);
|
||||
}
|
||||
}
|
||||
scheduler.Update(diff);
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
|
||||
void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override
|
||||
{
|
||||
if (spellInfo->Id == SPELL_THROW_PORTAL_CRYSTAL)
|
||||
if (Aura* aura = target->AddAura(SPELL_ARTHAS_PORTAL, target))
|
||||
aura->SetDuration(48000);
|
||||
}
|
||||
|
||||
void SpellHit(Unit* /*caster*/, SpellInfo const* spellInfo) override
|
||||
{
|
||||
if (spellInfo->Id == SPELL_TOUCH_OF_DEATH)
|
||||
{
|
||||
me->CastSpell(me, SPELL_DRAKURU_DEATH, true);
|
||||
me->CastSpell(me, SPELL_SUMMON_SKULL, true);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAI(uint32 diff) override
|
||||
{
|
||||
events.Update(diff);
|
||||
switch (events.ExecuteEvent())
|
||||
{
|
||||
case EVENT_BETRAYAL_1:
|
||||
Talk(SAY_DRAKURU_0);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_2, 5s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_2:
|
||||
me->SummonCreature(NPC_BLIGHTBLOOD_TROLL, 6184.1f, -1969.9f, 586.76f, 4.5f);
|
||||
me->SummonCreature(NPC_BLIGHTBLOOD_TROLL, 6222.9f, -2026.5f, 586.76f, 2.9f);
|
||||
me->SummonCreature(NPC_BLIGHTBLOOD_TROLL, 6166.2f, -2065.4f, 586.76f, 1.4f);
|
||||
me->SummonCreature(NPC_BLIGHTBLOOD_TROLL, 6127.5f, -2008.7f, 586.76f, 0.0f);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_3, 5s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_3:
|
||||
Talk(SAY_DRAKURU_1);
|
||||
Talk(SAY_DRAKURU_2);
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
|
||||
player->CastSpell(player, SPELL_SCOURGE_DISGUISE_EXPIRING, true);
|
||||
if (Aura* aur = me->AddAura(SPELL_BLIGHT_FOG, me))
|
||||
aur->SetDuration(22000);
|
||||
break;
|
||||
case EVENT_BETRAYAL_4:
|
||||
Talk(SAY_DRAKURU_5);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_5, 6s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_5:
|
||||
Talk(SAY_DRAKURU_6);
|
||||
me->CastSpell(me, SPELL_THROW_PORTAL_CRYSTAL, true);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_6, 8s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_6:
|
||||
me->SummonCreature(NPC_LICH_KING, 6142.9f, -2011.6f, 590.86f, 0.0f, TEMPSUMMON_TIMED_DESPAWN, 41000);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_7, 8s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_7:
|
||||
Talk(SAY_DRAKURU_7);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_8, 5s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_8:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, lichGUID))
|
||||
lich->AI()->Talk(SAY_LICH_7);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_9, 6s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_9:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, lichGUID))
|
||||
{
|
||||
lich->AI()->Talk(SAY_LICH_8);
|
||||
lich->CastSpell(me, SPELL_TOUCH_OF_DEATH, false);
|
||||
}
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_10, 4s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_10:
|
||||
me->SetVisible(false);
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, lichGUID))
|
||||
lich->AI()->Talk(SAY_LICH_9);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_11, 4s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_11:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, lichGUID))
|
||||
lich->AI()->Talk(SAY_LICH_10);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_12, 6s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_12:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, lichGUID))
|
||||
lich->AI()->Talk(SAY_LICH_11);
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_13, 3s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_13:
|
||||
if (Creature* lich = ObjectAccessor::GetCreature(*me, lichGUID))
|
||||
{
|
||||
lich->AI()->Talk(SAY_LICH_12);
|
||||
lich->GetMotionMaster()->MovePoint(0, 6143.8f, -2011.5f, 590.9f);
|
||||
}
|
||||
events.ScheduleEvent(EVENT_BETRAYAL_14, 7s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_14:
|
||||
playerGUID.Clear();
|
||||
EnterEvadeMode(EVADE_REASON_OTHER);
|
||||
break;
|
||||
}
|
||||
|
||||
if (me->GetFaction() == FACTION_FRIENDLY || me->HasUnitState(UNIT_STATE_CASTING | UNIT_STATE_STUNNED))
|
||||
return;
|
||||
|
||||
if (!UpdateVictim())
|
||||
return;
|
||||
|
||||
switch (events.ExecuteEvent())
|
||||
{
|
||||
case EVENT_BETRAYAL_SHADOW_BOLT:
|
||||
if (!me->IsWithinMeleeRange(me->GetVictim()))
|
||||
me->CastSpell(me->GetVictim(), SPELL_SHADOW_BOLT, false);
|
||||
events.Repeat(2s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_CRYSTAL:
|
||||
if (Player* player = ObjectAccessor::GetPlayer(*me, playerGUID))
|
||||
me->CastSpell(player, SPELL_THROW_BRIGHT_CRYSTAL, true);
|
||||
events.Repeat(6s, 15s);
|
||||
break;
|
||||
case EVENT_BETRAYAL_COMBAT_TALK:
|
||||
Talk(SAY_DRAKURU_4);
|
||||
events.Repeat(20s);
|
||||
break;
|
||||
}
|
||||
|
||||
DoMeleeAttackIfReady();
|
||||
}
|
||||
};
|
||||
private:
|
||||
SummonList _summons;
|
||||
ObjectGuid _playerGUID;
|
||||
ObjectGuid _lichGUID;
|
||||
BetrayalState _state;
|
||||
};
|
||||
|
||||
/*####
|
||||
@ -864,11 +945,32 @@ class spell_scourge_disguise_instability : public AuraScript
|
||||
}
|
||||
};
|
||||
|
||||
// 54105 - Blight Fog
|
||||
class spell_blight_fog : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_blight_fog);
|
||||
|
||||
void FilterTargets(std::list<WorldObject*>& targets)
|
||||
{
|
||||
targets.remove_if([](WorldObject* target) -> bool
|
||||
{
|
||||
float z = target->GetPositionZ();
|
||||
bool isInBlightFog = (582.0f <= z && z <= 583.0f) || (586.0f <= z && z <= 587.0f);
|
||||
return !isInBlightFog;
|
||||
});
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnObjectAreaTargetSelect += SpellObjectAreaTargetSelectFn(spell_blight_fog::FilterTargets, EFFECT_ALL, TARGET_UNIT_SRC_AREA_ENEMY);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_zuldrak()
|
||||
{
|
||||
new npc_finklestein();
|
||||
new go_finklestein_cauldron();
|
||||
new npc_overlord_drakuru_betrayal();
|
||||
RegisterCreatureAI(npc_overlord_drakuru_betrayal);
|
||||
new npc_drakuru_shackles();
|
||||
new npc_captured_rageclaw();
|
||||
new npc_released_offspring_harkoa();
|
||||
@ -876,4 +978,5 @@ void AddSC_zuldrak()
|
||||
new go_scourge_enclosure();
|
||||
|
||||
RegisterSpellScript(spell_scourge_disguise_instability);
|
||||
RegisterSpellScript(spell_blight_fog);
|
||||
}
|
||||
|
||||
@ -2045,7 +2045,7 @@ class spell_spawn_blood_pool : public SpellScript
|
||||
void SetDest(SpellDestination &dest)
|
||||
{
|
||||
Unit* caster = GetCaster();
|
||||
LiquidData liquidStatus = caster->GetMap()->GetLiquidData(caster->GetPhaseMask(), caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ(), caster->GetCollisionHeight(), MAP_ALL_LIQUIDS);
|
||||
LiquidData liquidStatus = caster->GetMap()->GetLiquidData(caster->GetPhaseMask(), caster->GetPositionX(), caster->GetPositionY(), caster->GetPositionZ(), caster->GetCollisionHeight(), {});
|
||||
|
||||
float level = liquidStatus.Level > INVALID_HEIGHT ? liquidStatus.Level : caster->GetPositionZ();
|
||||
Position pos = Position(caster->GetPositionX(), caster->GetPositionY(), level, caster->GetOrientation());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user