mirror of
https://github.com/mod-playerbots/azerothcore-wotlk.git
synced 2026-02-02 18:43:48 +00:00
Merge branch 'master' into Playerbot
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user