Merge branch 'master' into Playerbot

This commit is contained in:
Yunfan Li
2024-12-13 18:51:37 +08:00
48 changed files with 1584 additions and 64 deletions
@@ -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())
+14 -3
View File
@@ -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)
+9 -2
View File
@@ -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
+24 -2
View File
@@ -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:
{
+27 -1
View File
@@ -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);
+1 -2
View File
@@ -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();
-3
View File
@@ -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;
+71
View File
@@ -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;
+1
View File
@@ -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; };
+2
View File
@@ -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
};
+4
View File
@@ -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);
}