Merge branch 'master' into Playerbot
This commit is contained in:
@@ -2165,6 +2165,21 @@ NoResetTalentsCost = 0
|
||||
|
||||
ToggleXP.Cost = 100000
|
||||
|
||||
#
|
||||
# SpellQueue.Enabled
|
||||
# Description: Enable SpellQueue.
|
||||
# Default: 0 - (Disabled)
|
||||
# 1 - (Enabled)
|
||||
|
||||
SpellQueue.Enabled = 1
|
||||
|
||||
#
|
||||
# SpellQueue.Window
|
||||
# Description: Time (in milliseconds) for spells to be queued.
|
||||
# Default: 400 - (400ms)
|
||||
|
||||
SpellQueue.Window = 400
|
||||
|
||||
#
|
||||
###################################################################################################
|
||||
|
||||
|
||||
@@ -2605,6 +2605,13 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
|
||||
ProcessEventsFor((SMART_EVENT)SMART_EVENT_TIMED_EVENT_TRIGGERED, nullptr, eventId);
|
||||
break;
|
||||
}
|
||||
case SMART_ACTION_DISMOUNT:
|
||||
{
|
||||
for (WorldObject* const target : targets)
|
||||
if (IsUnit(target))
|
||||
target->ToUnit()->Dismount();
|
||||
break;
|
||||
}
|
||||
case SMART_ACTION_SET_HOVER:
|
||||
{
|
||||
for (WorldObject* target : targets)
|
||||
|
||||
@@ -2015,6 +2015,7 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e)
|
||||
case SMART_ACTION_FLEE:
|
||||
case SMART_ACTION_ADD_THREAT:
|
||||
case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT:
|
||||
case SMART_ACTION_DISMOUNT:
|
||||
case SMART_ACTION_SET_HOVER:
|
||||
case SMART_ACTION_ADD_IMMUNITY:
|
||||
case SMART_ACTION_REMOVE_IMMUNITY:
|
||||
|
||||
@@ -705,7 +705,7 @@ enum SMART_ACTION
|
||||
SMART_ACTION_EXIT_VEHICLE = 203, // none
|
||||
SMART_ACTION_SET_UNIT_MOVEMENT_FLAGS = 204, // flags
|
||||
SMART_ACTION_SET_COMBAT_DISTANCE = 205, // combatDistance
|
||||
// UNUSED = 206,
|
||||
SMART_ACTION_DISMOUNT = 206,
|
||||
SMART_ACTION_SET_HOVER = 207, // 0/1
|
||||
SMART_ACTION_ADD_IMMUNITY = 208, // type, id, value
|
||||
SMART_ACTION_REMOVE_IMMUNITY = 209, // type, id, value
|
||||
|
||||
@@ -446,6 +446,8 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u
|
||||
|
||||
void GameObject::Update(uint32 diff)
|
||||
{
|
||||
WorldObject::Update(diff);
|
||||
|
||||
if (AI())
|
||||
AI()->UpdateAI(diff);
|
||||
else if (!AIM_Initialize())
|
||||
|
||||
@@ -1050,7 +1050,7 @@ void MovementInfo::OutDebug()
|
||||
WorldObject::WorldObject(bool isWorldObject) : WorldLocation(),
|
||||
LastUsedScriptID(0), m_name(""), m_isActive(false), m_visibilityDistanceOverride(), m_isWorldObject(isWorldObject), m_zoneScript(nullptr),
|
||||
_zoneId(0), _areaId(0), _floorZ(INVALID_HEIGHT), _outdoors(false), _liquidData(), _updatePositionData(false), m_transport(nullptr),
|
||||
m_currMap(nullptr), m_InstanceId(0), m_phaseMask(PHASEMASK_NORMAL), m_useCombinedPhases(true), m_notifyflags(0), m_executed_notifies(0)
|
||||
m_currMap(nullptr), _heartbeatTimer(HEARTBEAT_INTERVAL), m_InstanceId(0), m_phaseMask(PHASEMASK_NORMAL), m_useCombinedPhases(true), m_notifyflags(0), m_executed_notifies(0)
|
||||
{
|
||||
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST);
|
||||
m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE);
|
||||
@@ -1058,9 +1058,18 @@ WorldObject::WorldObject(bool isWorldObject) : WorldLocation(),
|
||||
sScriptMgr->OnWorldObjectCreate(this);
|
||||
}
|
||||
|
||||
void WorldObject::Update(uint32 time_diff)
|
||||
void WorldObject::Update(uint32 diff)
|
||||
{
|
||||
sScriptMgr->OnWorldObjectUpdate(this, time_diff);
|
||||
m_Events.Update(diff);
|
||||
|
||||
_heartbeatTimer -= Milliseconds(diff);
|
||||
while (_heartbeatTimer <= 0ms)
|
||||
{
|
||||
_heartbeatTimer += HEARTBEAT_INTERVAL;
|
||||
Heartbeat();
|
||||
}
|
||||
|
||||
sScriptMgr->OnWorldObjectUpdate(this, diff);
|
||||
}
|
||||
|
||||
void WorldObject::SetWorldObject(bool on)
|
||||
@@ -1134,6 +1143,8 @@ void WorldObject::CleanupsBeforeDelete(bool /*finalCleanup*/)
|
||||
{
|
||||
if (IsInWorld())
|
||||
RemoveFromWorld();
|
||||
|
||||
m_Events.KillAllEvents(false); // non-delatable (currently cast spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
|
||||
}
|
||||
|
||||
void WorldObject::_Create(ObjectGuid::LowType guidlow, HighGuid guidhigh, uint32 phaseMask)
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include "Common.h"
|
||||
#include "DataMap.h"
|
||||
#include "EventProcessor.h"
|
||||
#include "G3D/Vector3.h"
|
||||
#include "GridDefines.h"
|
||||
#include "GridReference.h"
|
||||
@@ -96,6 +97,8 @@ struct PositionFullTerrainStatus;
|
||||
typedef std::unordered_map<Player*, UpdateData> UpdateDataMapType;
|
||||
typedef GuidUnorderedSet UpdatePlayerSet;
|
||||
|
||||
static constexpr Milliseconds HEARTBEAT_INTERVAL = 5s + 200ms;
|
||||
|
||||
class Object
|
||||
{
|
||||
public:
|
||||
@@ -220,6 +223,8 @@ public:
|
||||
|
||||
[[nodiscard]] inline bool IsItem() const { return GetTypeId() == TYPEID_ITEM; }
|
||||
|
||||
virtual void Heartbeat() {}
|
||||
|
||||
virtual std::string GetDebugInfo() const;
|
||||
|
||||
DataMap CustomData;
|
||||
@@ -408,7 +413,7 @@ protected:
|
||||
public:
|
||||
~WorldObject() override;
|
||||
|
||||
virtual void Update(uint32 /*time_diff*/);
|
||||
virtual void Update(uint32 diff);
|
||||
|
||||
void _Create(ObjectGuid::LowType guidlow, HighGuid guidhigh, uint32 phaseMask);
|
||||
|
||||
@@ -632,7 +637,9 @@ public:
|
||||
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
// Event handler
|
||||
ElunaEventProcessor* elunaEvents;
|
||||
EventProcessor m_Events;
|
||||
|
||||
protected:
|
||||
std::string m_name;
|
||||
@@ -666,7 +673,7 @@ protected:
|
||||
virtual bool IsAlwaysDetectableFor(WorldObject const* /*seer*/) const { return false; }
|
||||
private:
|
||||
Map* m_currMap; //current object's Map location
|
||||
|
||||
Milliseconds _heartbeatTimer;
|
||||
//uint32 m_mapId; // object at map with map_id
|
||||
uint32 m_InstanceId; // in map copy with instance id
|
||||
uint32 m_phaseMask; // in area phase state
|
||||
|
||||
@@ -1026,6 +1026,9 @@ void Player::setDeathState(DeathState s, bool /*despawn = false*/)
|
||||
return;
|
||||
}
|
||||
|
||||
// clear all pending spell cast requests when dying
|
||||
SpellQueue.clear();
|
||||
|
||||
// drunken state is cleared on death
|
||||
SetDrunkValue(0);
|
||||
// lost combo points at any target (targeted combo points clear in Unit::setDeathState)
|
||||
@@ -1889,8 +1892,27 @@ void Player::Regenerate(Powers power)
|
||||
}
|
||||
}
|
||||
break;
|
||||
case POWER_ENERGY: // Regenerate energy (rogue)
|
||||
addvalue += 0.01f * m_regenTimer * sWorld->getRate(RATE_POWER_ENERGY);
|
||||
case POWER_ENERGY:
|
||||
{
|
||||
float baseRegenRate = 10.0f * sWorld->getRate(RATE_POWER_ENERGY);
|
||||
float hasteModifier = 1.0f;
|
||||
|
||||
// Apply Vitality
|
||||
if (HasAura(61329))
|
||||
hasteModifier += 0.25f;
|
||||
|
||||
// Apply Overkill
|
||||
if (HasAura(58426))
|
||||
hasteModifier += 0.30f;
|
||||
|
||||
// Apply Adrenaline Rush
|
||||
if (HasAura(13750))
|
||||
hasteModifier += 1.0f;
|
||||
|
||||
float adjustedRegenRate = baseRegenRate * hasteModifier;
|
||||
|
||||
addvalue += adjustedRegenRate * 0.001f * m_regenTimer;
|
||||
}
|
||||
break;
|
||||
case POWER_RUNIC_POWER:
|
||||
{
|
||||
|
||||
@@ -1050,6 +1050,18 @@ struct EntryPointData
|
||||
[[nodiscard]] bool HasTaxiPath() const { return taxiPath[0] && taxiPath[1]; }
|
||||
};
|
||||
|
||||
struct PendingSpellCastRequest
|
||||
{
|
||||
uint32 spellId;
|
||||
uint32 category;
|
||||
WorldPacket requestPacket;
|
||||
bool isItem = false;
|
||||
bool cancelInProgress = false;
|
||||
|
||||
PendingSpellCastRequest(uint32 spellId, uint32 category, WorldPacket&& packet, bool item = false, bool cancel = false)
|
||||
: spellId(spellId), category(category), requestPacket(std::move(packet)), isItem(item) , cancelInProgress(cancel) {}
|
||||
};
|
||||
|
||||
class Player : public Unit, public GridObject<Player>
|
||||
{
|
||||
friend class WorldSession;
|
||||
@@ -2608,7 +2620,21 @@ public:
|
||||
|
||||
std::string GetDebugInfo() const override;
|
||||
|
||||
protected:
|
||||
/*********************************************************/
|
||||
/*** SPELL QUEUE SYSTEM ***/
|
||||
/*********************************************************/
|
||||
protected:
|
||||
uint32 GetSpellQueueWindow() const;
|
||||
void ProcessSpellQueue();
|
||||
|
||||
public:
|
||||
std::deque<PendingSpellCastRequest> SpellQueue;
|
||||
const PendingSpellCastRequest* GetCastRequest(uint32 category) const;
|
||||
bool CanExecutePendingSpellCastRequest(SpellInfo const* spellInfo);
|
||||
void ExecuteOrCancelSpellCastRequest(PendingSpellCastRequest* castRequest, bool isCancel = false);
|
||||
bool CanRequestSpellCast(SpellInfo const* spellInfo);
|
||||
|
||||
protected:
|
||||
// Gamemaster whisper whitelist
|
||||
WhisperListContainer WhisperList;
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ void Player::Update(uint32 p_time)
|
||||
|
||||
// used to implement delayed far teleports
|
||||
SetMustDelayTeleport(true);
|
||||
ProcessSpellQueue();
|
||||
Unit::Update(p_time);
|
||||
SetMustDelayTeleport(false);
|
||||
|
||||
@@ -2256,3 +2257,89 @@ void Player::ProcessTerrainStatusUpdate()
|
||||
else
|
||||
m_MirrorTimerFlags &= ~(UNDERWATER_INWATER | UNDERWATER_INLAVA | UNDERWATER_INSLIME | UNDERWATER_INDARKWATER);
|
||||
}
|
||||
|
||||
uint32 Player::GetSpellQueueWindow() const
|
||||
{
|
||||
return sWorld->getIntConfig(CONFIG_SPELL_QUEUE_WINDOW);
|
||||
}
|
||||
|
||||
bool Player::CanExecutePendingSpellCastRequest(SpellInfo const* spellInfo)
|
||||
{
|
||||
if (GetGlobalCooldownMgr().HasGlobalCooldown(spellInfo))
|
||||
return false;
|
||||
|
||||
if (GetSpellCooldownDelay(spellInfo->Id) > GetSpellQueueWindow())
|
||||
return false;
|
||||
|
||||
for (CurrentSpellTypes spellSlot : {CURRENT_MELEE_SPELL, CURRENT_GENERIC_SPELL})
|
||||
if (Spell* spell = GetCurrentSpell(spellSlot))
|
||||
{
|
||||
bool autoshot = spell->m_spellInfo->IsAutoRepeatRangedSpell();
|
||||
if (IsNonMeleeSpellCast(false, true, true, autoshot))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const PendingSpellCastRequest* Player::GetCastRequest(uint32 category) const
|
||||
{
|
||||
for (const PendingSpellCastRequest& request : SpellQueue)
|
||||
if (request.category == category)
|
||||
return &request;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Player::CanRequestSpellCast(SpellInfo const* spellInfo)
|
||||
{
|
||||
if (!sWorld->getBoolConfig(CONFIG_SPELL_QUEUE_ENABLED))
|
||||
return false;
|
||||
|
||||
// Check for existing cast request with the same category
|
||||
if (GetCastRequest(spellInfo->StartRecoveryCategory))
|
||||
return false;
|
||||
|
||||
if (GetGlobalCooldownMgr().GetGlobalCooldown(spellInfo) > GetSpellQueueWindow())
|
||||
return false;
|
||||
|
||||
if (GetSpellCooldownDelay(spellInfo->Id) > GetSpellQueueWindow())
|
||||
return false;
|
||||
|
||||
// If there is an existing cast that will last longer than the allowable
|
||||
// spell queue window, then we can't request a new spell cast
|
||||
for (CurrentSpellTypes spellSlot : { CURRENT_MELEE_SPELL, CURRENT_GENERIC_SPELL })
|
||||
if (Spell* spell = GetCurrentSpell(spellSlot))
|
||||
if (spell->GetCastTimeRemaining() > static_cast<int32>(GetSpellQueueWindow()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::ExecuteOrCancelSpellCastRequest(PendingSpellCastRequest* request, bool isCancel /* = false*/)
|
||||
{
|
||||
if (isCancel)
|
||||
request->cancelInProgress = true;
|
||||
|
||||
if (WorldSession* session = GetSession())
|
||||
{
|
||||
if (request->isItem)
|
||||
session->HandleUseItemOpcode(request->requestPacket);
|
||||
else
|
||||
session->HandleCastSpellOpcode(request->requestPacket);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::ProcessSpellQueue()
|
||||
{
|
||||
while (!SpellQueue.empty())
|
||||
{
|
||||
PendingSpellCastRequest& request = SpellQueue.front(); // Peek at the first spell
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(request.spellId);
|
||||
if (CanExecutePendingSpellCastRequest(spellInfo))
|
||||
{
|
||||
ExecuteOrCancelSpellCastRequest(&request);
|
||||
SpellQueue.pop_front(); // Remove from the queue
|
||||
}
|
||||
else // If the first spell can't execute, stop processing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,6 +400,22 @@ bool GlobalCooldownMgr::HasGlobalCooldown(SpellInfo const* spellInfo) const
|
||||
return itr != m_GlobalCooldowns.end() && itr->second.duration && getMSTimeDiff(itr->second.cast_time, GameTime::GetGameTimeMS().count()) < itr->second.duration;
|
||||
}
|
||||
|
||||
uint32 GlobalCooldownMgr::GetGlobalCooldown(SpellInfo const* spellInfo) const
|
||||
{
|
||||
if (!spellInfo)
|
||||
return 0;
|
||||
|
||||
auto itr = m_GlobalCooldowns.find(spellInfo->StartRecoveryCategory);
|
||||
if (itr == m_GlobalCooldowns.end() || itr->second.duration == 0)
|
||||
return 0;
|
||||
|
||||
uint32 start = itr->second.cast_time;
|
||||
uint32 delay = itr->second.duration;
|
||||
uint32 now = getMSTime();
|
||||
|
||||
return (start + delay > now) ? (start + delay) - now : 0;
|
||||
}
|
||||
|
||||
void GlobalCooldownMgr::AddGlobalCooldown(SpellInfo const* spellInfo, uint32 gcd)
|
||||
{
|
||||
m_GlobalCooldowns[spellInfo->StartRecoveryCategory] = GlobalCooldown(gcd, GameTime::GetGameTimeMS().count());
|
||||
|
||||
@@ -83,6 +83,7 @@ public:
|
||||
|
||||
public:
|
||||
bool HasGlobalCooldown(SpellInfo const* spellInfo) const;
|
||||
uint32 GetGlobalCooldown(SpellInfo const* spellInfo) const;
|
||||
void AddGlobalCooldown(SpellInfo const* spellInfo, uint32 gcd);
|
||||
void CancelGlobalCooldown(SpellInfo const* spellInfo);
|
||||
|
||||
|
||||
@@ -386,7 +386,7 @@ void Unit::Update(uint32 p_time)
|
||||
// WARNING! Order of execution here is important, do not change.
|
||||
// Spells must be processed with event system BEFORE they go to _UpdateSpells.
|
||||
// Or else we may have some SPELL_STATE_FINISHED spells stalled in pointers, that is bad.
|
||||
m_Events.Update(p_time);
|
||||
WorldObject::Update(p_time);
|
||||
|
||||
if (!IsInWorld())
|
||||
return;
|
||||
@@ -15777,7 +15777,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
|
||||
if (finalCleanup)
|
||||
m_cleanupDone = true;
|
||||
|
||||
m_Events.KillAllEvents(false); // non-delatable (currently casted spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
|
||||
CombatStop();
|
||||
ClearComboPoints();
|
||||
ClearComboPointHolders();
|
||||
|
||||
@@ -1958,9 +1958,6 @@ public:
|
||||
float m_threatModifier[MAX_SPELL_SCHOOL];
|
||||
float m_modAttackSpeedPct[3];
|
||||
|
||||
// Event handler
|
||||
EventProcessor m_Events;
|
||||
|
||||
SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY];
|
||||
uint32 m_lastSanctuaryTime;
|
||||
|
||||
|
||||
@@ -85,6 +85,41 @@ void WorldSession::HandleUseItemOpcode(WorldPacket& recvPacket)
|
||||
return;
|
||||
}
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
if (!spellInfo)
|
||||
{
|
||||
LOG_ERROR("network.opcode", "WORLD: unknown spell id {}", spellId);
|
||||
recvPacket.rfinish(); // prevent spam at ignore packet
|
||||
return;
|
||||
}
|
||||
|
||||
// fail if we are cancelling pending request
|
||||
if (!_player->SpellQueue.empty())
|
||||
{
|
||||
PendingSpellCastRequest& request = _player->SpellQueue.front(); // Peek at the first spell
|
||||
if (request.cancelInProgress)
|
||||
{
|
||||
pUser->SendEquipError(EQUIP_ERR_NONE, pItem, nullptr);
|
||||
recvPacket.rfinish(); // prevent spam at ignore packet
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// try queue spell if it can't be executed right now
|
||||
if (!_player->CanExecutePendingSpellCastRequest(spellInfo))
|
||||
if (_player->CanRequestSpellCast(spellInfo))
|
||||
{
|
||||
recvPacket.rpos(0); // Reset read position to the start of the buffer.
|
||||
_player->SpellQueue.emplace_back(
|
||||
spellId,
|
||||
spellInfo->GetCategory(),
|
||||
std::move(recvPacket), // Move ownership of recvPacket
|
||||
true // itemCast
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pItem->GetGUID() != itemGUID)
|
||||
{
|
||||
pUser->SendEquipError(EQUIP_ERR_ITEM_NOT_FOUND, nullptr, nullptr);
|
||||
@@ -341,6 +376,10 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
uint32 spellId;
|
||||
uint8 castCount, castFlags;
|
||||
|
||||
if (recvPacket.empty())
|
||||
return;
|
||||
|
||||
recvPacket >> castCount >> spellId >> castFlags;
|
||||
TriggerCastFlags triggerFlag = TRIGGERED_NONE;
|
||||
|
||||
@@ -365,6 +404,36 @@ void WorldSession::HandleCastSpellOpcode(WorldPacket& recvPacket)
|
||||
return;
|
||||
}
|
||||
|
||||
// fail if we are cancelling pending request
|
||||
if (!_player->SpellQueue.empty())
|
||||
{
|
||||
PendingSpellCastRequest& request = _player->SpellQueue.front(); // Peek at the first spell
|
||||
if (request.cancelInProgress)
|
||||
{
|
||||
Spell* spell = new Spell(_player, spellInfo, TRIGGERED_NONE);
|
||||
spell->m_cast_count = castCount;
|
||||
spell->SendCastResult(SPELL_FAILED_DONT_REPORT);
|
||||
spell->finish(false);
|
||||
recvPacket.rfinish(); // prevent spam at ignore packet
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// try queue spell if it can't be executed right now
|
||||
if (!_player->CanExecutePendingSpellCastRequest(spellInfo))
|
||||
{
|
||||
if (_player->CanRequestSpellCast(spellInfo))
|
||||
{
|
||||
recvPacket.rpos(0); // Reset read position to the start of the buffer.
|
||||
_player->SpellQueue.emplace_back(
|
||||
spellId,
|
||||
spellInfo->GetCategory(),
|
||||
std::move(recvPacket) // Move ownership of recvPacket
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// client provided targets
|
||||
SpellCastTargets targets;
|
||||
targets.Read(recvPacket, mover);
|
||||
@@ -483,6 +552,8 @@ void WorldSession::HandleCancelCastOpcode(WorldPacket& recvPacket)
|
||||
recvPacket.read_skip<uint8>(); // counter, increments with every CANCEL packet, don't use for now
|
||||
recvPacket >> spellId;
|
||||
|
||||
_player->SpellQueue.clear();
|
||||
|
||||
_player->InterruptSpell(CURRENT_MELEE_SPELL);
|
||||
if (_player->IsNonMeleeSpellCast(false))
|
||||
_player->InterruptNonMeleeSpells(false, spellId, false, true);
|
||||
|
||||
@@ -158,13 +158,13 @@ bool ChaseMovementGenerator<T>::DoUpdate(T* owner, uint32 time_diff)
|
||||
i_leashExtensionTimer.Update(time_diff);
|
||||
if (i_leashExtensionTimer.Passed())
|
||||
{
|
||||
i_leashExtensionTimer.Reset(1500);
|
||||
i_leashExtensionTimer.Reset(5000);
|
||||
if (cOwner)
|
||||
cOwner->UpdateLeashExtensionTime();
|
||||
}
|
||||
}
|
||||
else if (i_recalculateTravel)
|
||||
i_leashExtensionTimer.Reset(1500);
|
||||
i_leashExtensionTimer.Reset(5000);
|
||||
|
||||
// if the target moved, we have to consider whether to adjust
|
||||
if (!_lastTargetPosition || target->GetPosition() != _lastTargetPosition.value() || mutualChase != _mutualChase || !owner->IsWithinLOSInMap(target))
|
||||
|
||||
@@ -39,7 +39,7 @@ class ChaseMovementGenerator : public MovementGeneratorMedium<T, ChaseMovementGe
|
||||
{
|
||||
public:
|
||||
ChaseMovementGenerator(Unit* target, Optional<ChaseRange> range = {}, Optional<ChaseAngle> angle = {})
|
||||
: TargetedMovementGeneratorBase(target), i_leashExtensionTimer(1500), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {}
|
||||
: TargetedMovementGeneratorBase(target), i_leashExtensionTimer(5000), i_path(nullptr), i_recheckDistance(0), i_recalculateTravel(true), _range(range), _angle(angle) {}
|
||||
~ChaseMovementGenerator() { }
|
||||
|
||||
MovementGeneratorType GetMovementGeneratorType() { return CHASE_MOTION_TYPE; }
|
||||
|
||||
@@ -494,28 +494,33 @@ int32 AuraEffect::CalculateAmount(Unit* caster)
|
||||
case SPELL_AURA_MOD_STUN:
|
||||
case SPELL_AURA_MOD_ROOT:
|
||||
case SPELL_AURA_TRANSFORM:
|
||||
{
|
||||
m_canBeRecalculated = false;
|
||||
if (!m_spellInfo->ProcFlags || m_spellInfo->HasAura(SPELL_AURA_PROC_TRIGGER_SPELL)) // xinef: skip auras with proctriggerspell, they must have procflags...
|
||||
break;
|
||||
amount = int32(GetBase()->GetUnitOwner()->CountPctFromMaxHealth(10));
|
||||
if (caster)
|
||||
if (!caster)
|
||||
break;
|
||||
// not impacted by gear
|
||||
// not impacted by target level
|
||||
// not impacted by rank
|
||||
// asumption - depends on caster level
|
||||
amount = sObjectMgr->GetCreatureBaseStats(caster->GetLevel(), Classes::CLASS_WARRIOR)->BaseHealth[EXPANSION_WRATH_OF_THE_LICH_KING] / 4.75f;
|
||||
// Glyphs increasing damage cap
|
||||
Unit::AuraEffectList const& overrideClassScripts = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
|
||||
for (Unit::AuraEffectList::const_iterator itr = overrideClassScripts.begin(); itr != overrideClassScripts.end(); ++itr)
|
||||
{
|
||||
// Glyphs increasing damage cap
|
||||
Unit::AuraEffectList const& overrideClassScripts = caster->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_CLASS_SCRIPTS);
|
||||
for (Unit::AuraEffectList::const_iterator itr = overrideClassScripts.begin(); itr != overrideClassScripts.end(); ++itr)
|
||||
if ((*itr)->IsAffectedOnSpell(m_spellInfo))
|
||||
{
|
||||
if ((*itr)->IsAffectedOnSpell(m_spellInfo))
|
||||
// Glyph of Fear, Glyph of Frost nova and similar auras
|
||||
if ((*itr)->GetMiscValue() == 7801)
|
||||
{
|
||||
// Glyph of Fear, Glyph of Frost nova and similar auras
|
||||
if ((*itr)->GetMiscValue() == 7801)
|
||||
{
|
||||
AddPct(amount, (*itr)->GetAmount());
|
||||
break;
|
||||
}
|
||||
AddPct(amount, (*itr)->GetAmount());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SPELL_AURA_SCHOOL_ABSORB:
|
||||
case SPELL_AURA_MANA_SHIELD:
|
||||
m_canBeRecalculated = false;
|
||||
|
||||
@@ -551,6 +551,7 @@ public:
|
||||
bool IsAutoRepeat() const { return m_autoRepeat; }
|
||||
void SetAutoRepeat(bool rep) { m_autoRepeat = rep; }
|
||||
void ReSetTimer() { m_timer = m_casttime > 0 ? m_casttime : 0; }
|
||||
int32 GetCastTimeRemaining() { return m_timer;}
|
||||
bool IsNextMeleeSwingSpell() const;
|
||||
bool IsTriggered() const { return HasTriggeredCastFlag(TRIGGERED_FULL_MASK); };
|
||||
bool HasTriggeredCastFlag(TriggerCastFlags flag) const { return _triggeredCastFlags & flag; };
|
||||
|
||||
@@ -184,6 +184,7 @@ enum WorldBoolConfigs
|
||||
CONFIG_ALLOWS_RANK_MOD_FOR_PET_HEALTH,
|
||||
CONFIG_MUNCHING_BLIZZLIKE,
|
||||
CONFIG_ENABLE_DAZE,
|
||||
CONFIG_SPELL_QUEUE_ENABLED,
|
||||
BOOL_CONFIG_VALUE_COUNT
|
||||
};
|
||||
|
||||
@@ -420,6 +421,7 @@ enum WorldIntConfigs
|
||||
CONFIG_WATER_BREATH_TIMER,
|
||||
CONFIG_AUCTION_HOUSE_SEARCH_TIMEOUT,
|
||||
CONFIG_DAILY_RBG_MIN_LEVEL_AP_REWARD,
|
||||
CONFIG_SPELL_QUEUE_WINDOW,
|
||||
INT_CONFIG_VALUE_COUNT
|
||||
};
|
||||
|
||||
|
||||
@@ -1488,6 +1488,10 @@ void World::LoadConfigSettings(bool reload)
|
||||
// Realm Availability
|
||||
_bool_configs[CONFIG_REALM_LOGIN_ENABLED] = sConfigMgr->GetOption<bool>("World.RealmAvailability", true);
|
||||
|
||||
// SpellQueue
|
||||
_bool_configs[CONFIG_SPELL_QUEUE_ENABLED] = sConfigMgr->GetOption<bool>("SpellQueue.Enabled", true);
|
||||
_int_configs[CONFIG_SPELL_QUEUE_WINDOW] = sConfigMgr->GetOption<uint32>("SpellQueue.Window", 400);
|
||||
|
||||
// call ScriptMgr if we're reloading the configuration
|
||||
sScriptMgr->OnAfterConfigLoad(reload);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ struct boss_nightbane : public BossAI
|
||||
_triggerCountTakeOffWhileFlying = 0;
|
||||
_airPhasesCompleted = 0;
|
||||
|
||||
me->SetSpeed(MOVE_RUN, me->GetCreatureTemplate()->speed_run);
|
||||
me->SetSpeed(MOVE_RUN, 2.0f);
|
||||
me->SetCanFly(true);
|
||||
me->SetUnitFlag(UNIT_FLAG_NON_ATTACKABLE);
|
||||
me->SetReactState(REACT_PASSIVE);
|
||||
@@ -300,7 +300,6 @@ struct boss_nightbane : public BossAI
|
||||
{
|
||||
me->ClearUnitState(UNIT_STATE_IGNORE_PATHFINDING);
|
||||
me->GetMotionMaster()->MovePoint(POINT_INTRO_LAND, introLandPos);
|
||||
me->SetSpeed(MOVE_RUN, 2.0f);
|
||||
}).Schedule(3s, [this](TaskContext /*context*/)
|
||||
{
|
||||
me->SetDisableGravity(false);
|
||||
|
||||
@@ -83,6 +83,14 @@ struct boss_muru : public BossAI
|
||||
}, 10min);
|
||||
}
|
||||
|
||||
void JustSummoned(Creature* creature) override
|
||||
{
|
||||
if (creature->GetEntry() == NPC_ENTROPIUS)
|
||||
creature->SetInCombatWithZone();
|
||||
else
|
||||
BossAI::JustSummoned(creature);
|
||||
}
|
||||
|
||||
void DamageTaken(Unit*, uint32& damage, DamageEffectType, SpellSchoolMask) override
|
||||
{
|
||||
if (damage >= me->GetHealth())
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include "GridNotifiers.h"
|
||||
#include "GridNotifiersImpl.h"
|
||||
#include "ScriptedCreature.h"
|
||||
#include "SpellAuras.h"
|
||||
#include "SpellAuraEffects.h"
|
||||
#include "SpellScript.h"
|
||||
#include "SpellScriptLoader.h"
|
||||
#include "Weather.h"
|
||||
@@ -321,9 +323,30 @@ class spell_electrial_storm : public AuraScript
|
||||
}
|
||||
};
|
||||
|
||||
// 43657 - Electrical Storm
|
||||
class spell_electrical_storm_proc : public SpellScript
|
||||
{
|
||||
PrepareSpellScript(spell_electrical_storm_proc);
|
||||
|
||||
void HandleDamageCalc(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (Aura* aura = GetCaster()->GetAura(SPELL_ELECTRICAL_STORM))
|
||||
{
|
||||
uint8 multiplier = aura->GetEffect(EFFECT_1)->GetTickNumber();
|
||||
SetHitDamage(GetHitDamage() * multiplier);
|
||||
}
|
||||
}
|
||||
|
||||
void Register() override
|
||||
{
|
||||
OnEffectHitTarget += SpellEffectFn(spell_electrical_storm_proc::HandleDamageCalc, EFFECT_0, SPELL_EFFECT_SCHOOL_DAMAGE);
|
||||
}
|
||||
};
|
||||
|
||||
void AddSC_boss_akilzon()
|
||||
{
|
||||
RegisterZulAmanCreatureAI(boss_akilzon);
|
||||
RegisterZulAmanCreatureAI(npc_akilzon_eagle);
|
||||
RegisterSpellScript(spell_electrial_storm);
|
||||
RegisterSpellScript(spell_electrical_storm_proc);
|
||||
}
|
||||
|
||||
@@ -229,7 +229,13 @@ static PlayerAbilityStruct PlayerAbility[13][3] =
|
||||
|
||||
struct boss_hexlord_malacrass : public BossAI
|
||||
{
|
||||
boss_hexlord_malacrass(Creature* creature) : BossAI(creature, DATA_HEXLORD) { }
|
||||
boss_hexlord_malacrass(Creature* creature) : BossAI(creature, DATA_HEXLORD)
|
||||
{
|
||||
scheduler.SetValidator([this]
|
||||
{
|
||||
return !me->HasUnitState(UNIT_STATE_CASTING);
|
||||
});
|
||||
}
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
@@ -238,19 +244,28 @@ struct boss_hexlord_malacrass : public BossAI
|
||||
_classAbilityTimer = 10000ms;
|
||||
SpawnAdds();
|
||||
ScheduleHealthCheckEvent(80, [&] {
|
||||
ScheduleTimedEvent(0s, [&] {
|
||||
ScheduleTimedEvent(1s, [&] {
|
||||
DoCastSelf(SPELL_DRAIN_POWER, true);
|
||||
Talk(SAY_DRAIN_POWER);
|
||||
}, 30s, 30s);
|
||||
}, 30s);
|
||||
});
|
||||
}
|
||||
|
||||
void SpawnAdds()
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_ADD_COUNT; ++i)
|
||||
if (_creatureIndex.empty())
|
||||
{
|
||||
uint8 flip = urand(0, 1);
|
||||
me->SummonCreature(AddEntrySets[i][flip], AddPosition[i], TEMPSUMMON_DEAD_DESPAWN, 0);
|
||||
for (uint8 i = 0; i < MAX_ADD_COUNT; ++i)
|
||||
{
|
||||
uint8 flip = urand(0, 1);
|
||||
me->SummonCreature(AddEntrySets[i][flip], AddPosition[i], TEMPSUMMON_DEAD_DESPAWN, 0);
|
||||
_creatureIndex.push_back(flip);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_ADD_COUNT; ++i)
|
||||
me->SummonCreature(AddEntrySets[i][_creatureIndex[i]], AddPosition[i], TEMPSUMMON_DEAD_DESPAWN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +354,7 @@ struct boss_hexlord_malacrass : public BossAI
|
||||
private:
|
||||
uint8 _currentClass;
|
||||
std::chrono::milliseconds _classAbilityTimer;
|
||||
std::vector<uint8> _creatureIndex;
|
||||
};
|
||||
|
||||
struct boss_alyson_antille : public ScriptedAI
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CreatureGroups.h"
|
||||
#include "CreatureScript.h"
|
||||
#include "Player.h"
|
||||
#include "ScriptedCreature.h"
|
||||
@@ -144,6 +145,9 @@ struct boss_zuljin : public BossAI
|
||||
me->SetCombatMovement(true);
|
||||
me->m_Events.KillAllEvents(false);
|
||||
_nextPhase = 0;
|
||||
|
||||
if (CreatureGroup* formation = me->GetFormation())
|
||||
formation->RespawnFormation(true);
|
||||
}
|
||||
|
||||
void JustEngagedWith(Unit* /*who*/) override
|
||||
@@ -198,10 +202,16 @@ struct boss_zuljin : public BossAI
|
||||
me->ResumeChasingVictim();
|
||||
|
||||
ScheduleTimedEvent(5s, [&] {
|
||||
DoCastRandomTarget(SPELL_CLAW_RAGE_CHARGE);
|
||||
if (me->HasAura(SPELL_LYNX_RUSH_HASTE))
|
||||
return;
|
||||
|
||||
DoCastRandomTarget(SPELL_CLAW_RAGE_CHARGE, 1);
|
||||
}, 15s, 20s);
|
||||
|
||||
ScheduleTimedEvent(14s, [&] {
|
||||
if (me->HasAura(SPELL_CLAW_RAGE_AURA))
|
||||
return;
|
||||
|
||||
DoCastSelf(SPELL_LYNX_RUSH_HASTE);
|
||||
|
||||
for (int8 count = 0; count <= 8; ++count)
|
||||
@@ -233,6 +243,21 @@ struct boss_zuljin : public BossAI
|
||||
});
|
||||
}
|
||||
|
||||
void EnterEvadeMode(EvadeReason /*why*/) override
|
||||
{
|
||||
if (CreatureGroup* formation = me->GetFormation())
|
||||
{
|
||||
for (auto const& itr : formation->GetMembers())
|
||||
{
|
||||
if (itr.first && itr.first != me)
|
||||
itr.first->DespawnOnEvade(2min);
|
||||
}
|
||||
}
|
||||
|
||||
me->DespawnOnEvade(2min);
|
||||
_EnterEvadeMode();
|
||||
}
|
||||
|
||||
void SpellHitTarget(Unit* target, SpellInfo const* spellInfo) override
|
||||
{
|
||||
if (spellInfo->Id == SPELL_CLAW_RAGE_CHARGE && target != me)
|
||||
|
||||
@@ -53,7 +53,6 @@ DoorData const doorData[] =
|
||||
{ GO_DOOR_HALAZZI, DATA_HALAZZI, DOOR_TYPE_PASSAGE },
|
||||
{ GO_LYNX_TEMPLE_ENTRANCE, DATA_HALAZZI, DOOR_TYPE_ROOM },
|
||||
{ GO_DOOR_AKILZON, DATA_AKILZON, DOOR_TYPE_ROOM },
|
||||
{ GO_GATE_ZULJIN, DATA_HEXLORD, DOOR_TYPE_PASSAGE },
|
||||
{ 0, 0, DOOR_TYPE_ROOM } // END
|
||||
};
|
||||
|
||||
@@ -71,6 +70,7 @@ ObjectData const gameObjectData[] =
|
||||
{ GO_STRANGE_GONG, DATA_STRANGE_GONG },
|
||||
{ GO_MASSIVE_GATE, DATA_MASSIVE_GATE },
|
||||
{ GO_GATE_HEXLORD, DATA_HEXLORD_GATE },
|
||||
{ GO_GATE_ZULJIN, DATA_ZULJIN_GATE },
|
||||
{ 0, 0 }
|
||||
};
|
||||
|
||||
@@ -290,6 +290,11 @@ public:
|
||||
HandleGameObject(ObjectGuid::Empty, false, GetGameObject(DATA_HEXLORD_GATE));
|
||||
else if (state == NOT_STARTED)
|
||||
CheckInstanceStatus();
|
||||
else if (state == DONE)
|
||||
{
|
||||
if (GameObject* zuljinGate = GetGameObject(DATA_ZULJIN_GATE))
|
||||
zuljinGate->RemoveGameObjectFlag(GO_FLAG_LOCKED);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ enum DataTypes
|
||||
DATA_HARRISON_JONES = 13,
|
||||
TYPE_AKILZON_GAUNTLET = 14,
|
||||
DATA_LOOKOUT = 15,
|
||||
DATA_ZULJIN_GATE = 16
|
||||
};
|
||||
|
||||
enum CreatureIds
|
||||
|
||||
+20
-24
@@ -28,6 +28,7 @@
|
||||
|
||||
enum MountSpells
|
||||
{
|
||||
SPELL_LANCE_EQUIPPED = 62853,
|
||||
SPELL_PLAYER_VEHICLE_DEFEND = 66482,
|
||||
SPELL_MINIONS_DEFEND = 64100,
|
||||
SPELL_BOSS_DEFEND = 62719,
|
||||
@@ -152,16 +153,9 @@ public:
|
||||
return GetTrialOfTheChampionAI<npc_toc5_player_vehicleAI>(pCreature);
|
||||
}
|
||||
|
||||
struct npc_toc5_player_vehicleAI : public NullCreatureAI
|
||||
struct npc_toc5_player_vehicleAI : public VehicleAI
|
||||
{
|
||||
npc_toc5_player_vehicleAI(Creature* pCreature) : NullCreatureAI(pCreature)
|
||||
{
|
||||
conditions = sConditionMgr->GetConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, me->GetEntry());
|
||||
m_ConditionsTimer = 1000;
|
||||
}
|
||||
|
||||
ConditionList conditions;
|
||||
uint16 m_ConditionsTimer;
|
||||
npc_toc5_player_vehicleAI(Creature* creature) : VehicleAI(creature) { }
|
||||
|
||||
void Reset() override
|
||||
{
|
||||
@@ -203,23 +197,25 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
//void EnterEvadeMode() { CreatureAI::EnterEvadeMode(); }
|
||||
void MoveInLineOfSight(Unit* /*who*/) override {}
|
||||
void UpdateAI(uint32 diff) override
|
||||
bool BeforeSpellClick(Unit* clicker) override
|
||||
{
|
||||
if (m_ConditionsTimer <= diff)
|
||||
{
|
||||
if (!conditions.empty())
|
||||
if (Unit* passenger = me->GetVehicleKit()->GetPassenger(0))
|
||||
if (!sConditionMgr->IsObjectMeetToConditions(passenger, me, conditions))
|
||||
passenger->ExitVehicle();
|
||||
m_ConditionsTimer = VEHICLE_CONDITION_CHECK_TIME;
|
||||
}
|
||||
else
|
||||
m_ConditionsTimer -= diff;
|
||||
if (!clicker->IsPlayer())
|
||||
return true;
|
||||
|
||||
if (clicker->IsInDisallowedMountForm())
|
||||
clicker->RemoveAurasByType(SPELL_AURA_MOD_SHAPESHIFT);
|
||||
|
||||
if (clicker->HasAura(SPELL_LANCE_EQUIPPED))
|
||||
return true;
|
||||
|
||||
WorldPacket data(SMSG_CAST_FAILED, 1 + 4 + 1);
|
||||
data << uint8(0); // single cast or multi 2.3 (0/1)
|
||||
data << uint32(VEHICLE_SPELL_RIDE_HARDCODED);
|
||||
data << uint8(SPELL_FAILED_CUSTOM_ERROR);
|
||||
data << uint32(SPELL_CUSTOM_ERROR_MUST_HAVE_LANCE_EQUIPPED);
|
||||
clicker->ToPlayer()->GetSession()->SendPacket(&data);
|
||||
return false;
|
||||
}
|
||||
void AttackStart(Unit* /*who*/) override {}
|
||||
void JustEngagedWith(Unit* /*who*/) override {}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -912,7 +912,7 @@ public:
|
||||
npc_vengeful_shadeAI(Creature* creature) : ScriptedAI(creature)
|
||||
{
|
||||
me->SetControlled(true, UNIT_STATE_ROOT);
|
||||
unroot_timer = 500;
|
||||
unroot_timer = 2000;
|
||||
targetGUID.Clear();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user