Core/Objects: ported Heartbeat implementation fron master branch

This commit is contained in:
Ovahlord committed 2025-03-09 02:40:05 +01:00
1 parent 352ce7e1b3
commit e8588fd5ba
30 files changed
+257 -184

No files matched your search

@@ -467,7 +467,6 @@ void BossAI::_Reset()
if (!me->IsAlive())
return;
me->SetCombatPulseDelay(0);
me->ResetLootMode();
events.Reset();
summons.DespawnAll();
@@ -503,7 +502,6 @@ void BossAI::_JustEngagedWith(Unit* who)
instance->SetBossState(_bossId, IN_PROGRESS);
}
me->SetCombatPulseDelay(5);
me->setActive(true);
DoZoneInCombat();
ScheduleTasks();
+47 -29
View File
@@ -279,7 +279,7 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
Creature::Creature(bool isWorldObject): Unit(isWorldObject), MapObject(),
m_groupLootTimer(0), lootingGroupLowGUID(0), m_PlayerDamageReq(0),
m_lootRecipient(), m_lootRecipientGroup(0), _pickpocketLootRestore(0), m_corpseRemoveTime(0), m_respawnTime(0),
m_respawnDelay(300), m_corpseDelay(60), m_wanderDistance(0.0f), m_boundaryCheckTime(2500), m_combatPulseTime(0), m_combatPulseDelay(0), m_reactState(REACT_AGGRESSIVE),
m_respawnDelay(300), m_corpseDelay(60), m_wanderDistance(0.0f), m_boundaryCheckTime(2500), m_reactState(REACT_AGGRESSIVE),
m_defaultMovementType(IDLE_MOTION_TYPE), m_spawnId(0), m_equipmentId(0), m_originalEquipmentId(0), m_AlreadyCallAssistance(false),
m_AlreadySearchedAssistance(false), m_regenHealth(true), m_cannotReachTarget(false), m_cannotReachTimer(0), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL),
m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), _waypointPathId(0), _currentWaypointNodeInfo(0, 0), _cyclicSplinePathId(0),
@@ -809,34 +809,6 @@ void Creature::Update(uint32 diff)
m_boundaryCheckTime -= diff;
}
// if periodic combat pulse is enabled and we are both in combat and in a dungeon, do this now
if (m_combatPulseDelay > 0 && IsEngaged() && GetMap()->IsDungeon())
{
if (diff > m_combatPulseTime)
m_combatPulseTime = 0;
else
m_combatPulseTime -= diff;
if (m_combatPulseTime == 0)
{
Map::PlayerList const& players = GetMap()->GetPlayers();
if (!players.isEmpty())
for (Map::PlayerList::const_iterator it = players.begin(); it != players.end(); ++it)
{
if (Player* player = it->GetSource())
{
if (player->IsGameMaster())
continue;
if (player->IsAlive() && IsHostileTo(player))
EngageWithTarget(player);
}
}
m_combatPulseTime = m_combatPulseDelay * IN_MILLISECONDS;
}
}
// do not allow the AI to be changed during update
Unit::AIUpdateTick(diff);
@@ -866,6 +838,14 @@ void Creature::Update(uint32 diff)
}
}
void Creature::Heartbeat()
{
Unit::Heartbeat();
// Creatures with CREATURE_STATIC_FLAG_2_FORCE_PARTY_MEMBERS_INTO_COMBAT periodically force party members into combat
ForcePartyMembersIntoCombat();
}
void Creature::RegenerateHealth()
{
if (!isRegeneratingHealth())
@@ -3316,6 +3296,9 @@ void Creature::AtEngage(Unit* target)
if (GetAI() && !IsControlledByPlayer())
SendAIReaction(AI_REACTION_HOSTILE);
// Creatures with CREATURE_STATIC_FLAG_2_FORCE_PARTY_MEMBERS_INTO_COMBAT periodically force party members into combat
ForcePartyMembersIntoCombat();
}
void Creature::AtDisengage()
@@ -3327,6 +3310,41 @@ void Creature::AtDisengage()
SetUInt32Value(UNIT_DYNAMIC_FLAGS, GetCreatureTemplate()->dynamicflags);
}
void Creature::ForcePartyMembersIntoCombat()
{
if (!_staticFlags.HasFlag(CREATURE_STATIC_FLAG_2_FORCE_PARTY_MEMBERS_INTO_COMBAT) || !IsEngaged())
return;
Trinity::Containers::FlatSet<Group const*> partiesToForceIntoCombat;
for (auto const& [_, combatReference] : GetCombatManager().GetPvECombatRefs())
{
//if (combatReference->IsSuppressedFor(this))
// continue;
Unit* other = combatReference->GetOther(this);
Player* player = other ? other->ToPlayer() : nullptr;
if (!player || player->IsGameMaster())
continue;
if (Group const* group = player->GetGroup())
partiesToForceIntoCombat.insert(group);
}
for (Group const* partyToForceIntoCombat : partiesToForceIntoCombat)
{
for (GroupReference const* ref = partyToForceIntoCombat->GetFirstMember(); ref != nullptr; ref = ref->next())
{
Player* player = ref->GetSource();
if (!player || !player->IsInWorld() || player->GetMap() != GetMap() || player->IsGameMaster())
continue;
EngageWithTarget(player);
}
}
}
bool Creature::IsEscorted() const
{
if (CreatureAI* ai = AI())
+5 -10
View File
@@ -86,7 +86,9 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
ObjectGuid::LowType GetSpawnId() const { return m_spawnId; }
void Update(uint32 time) override; // overwrited Unit::Update
void Update(uint32 time) override; // overwritten Unit::Update
void Heartbeat() override;
void GetRespawnPosition(float &x, float &y, float &z, float* ori = nullptr, float* dist = nullptr) const;
bool IsSpawnedOnTransport() const { return m_creatureData && m_creatureData->mapId != GetMapId(); }
@@ -273,13 +275,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
void SetWanderDistance(float dist) { m_wanderDistance = dist; }
void DoImmediateBoundaryCheck() { m_boundaryCheckTime = 0; }
uint32 GetCombatPulseDelay() const { return m_combatPulseDelay; }
void SetCombatPulseDelay(uint32 delay) // (secs) interval at which the creature pulses the entire zone into combat (only works in dungeons)
{
m_combatPulseDelay = delay;
if (m_combatPulseTime == 0 || m_combatPulseTime > delay)
m_combatPulseTime = delay;
}
uint32 m_groupLootTimer; // (msecs)timer used for group loot
ObjectGuid::LowType lootingGroupLowGUID; // used to find group which is looting corpse
@@ -377,6 +372,8 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
void AtEngage(Unit* target) override;
void AtDisengage() override;
void ForcePartyMembersIntoCombat();
bool HasStaticFlag(CreatureStaticFlags flag) const { return _staticFlags.HasFlag(flag); }
bool HasStaticFlag(CreatureStaticFlags2 flag) const { return _staticFlags.HasFlag(flag); }
bool HasStaticFlag(CreatureStaticFlags3 flag) const { return _staticFlags.HasFlag(flag); }
@@ -416,8 +413,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma
uint32 m_corpseDelay; // (secs) delay between death and corpse disappearance
float m_wanderDistance;
uint32 m_boundaryCheckTime; // (msecs) remaining time for next evade boundary check
uint32 m_combatPulseTime; // (msecs) remaining time for next zone-in-combat pulse
uint32 m_combatPulseDelay; // (secs) how often the creature puts the entire zone in combat (only works in dungeons)
ReactStates m_reactState; // for AI, not charmInfo
void RegenerateHealth() override;
@@ -827,7 +827,7 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, P
void GameObject::Update(uint32 diff)
{
m_Events.Update(diff);
WorldObject::Update(diff);
if (AI())
AI()->UpdateAI(diff);
+15 -1
View File
@@ -101,6 +101,18 @@ WorldObject::~WorldObject()
}
}
void WorldObject::Update(uint32 diff)
{
m_Events.Update(diff);
_heartbeatTimer -= Milliseconds(diff);
while (_heartbeatTimer <= 0ms)
{
_heartbeatTimer += HEARTBEAT_INTERVAL;
Heartbeat();
}
}
Object::~Object()
{
if (IsInWorld())
@@ -1117,7 +1129,7 @@ bool Object::PrintIndexError(uint32 index, bool set) const
WorldObject::WorldObject(bool isWorldObject) : Object(), WorldLocation(), LastUsedScriptID(0),
m_movementInfo(), m_name(""), m_isActive(false), m_isFarVisible(false), m_isWorldObject(isWorldObject), m_zoneScript(nullptr),
m_transport(nullptr), m_zoneId(0), m_areaId(0), m_staticFloorZ(VMAP_INVALID_HEIGHT), m_outdoors(true), m_liquidStatus(LIQUID_MAP_NO_WATER),
m_wmoGroupID(0), m_currMap(nullptr), m_InstanceId(0), _dbPhase(0), m_notifyflags(0),
m_wmoGroupID(0), m_currMap(nullptr), m_InstanceId(0), _dbPhase(0), m_notifyflags(0), _heartbeatTimer(HEARTBEAT_INTERVAL),
m_aiAnimKitId(0), m_movementAnimKitId(0), m_meleeAnimKitId(0)
{
m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST);
@@ -1190,6 +1202,8 @@ void WorldObject::CleanupsBeforeDelete(bool /*finalCleanup*/)
if (TransportBase* transport = GetTransport())
transport->RemovePassenger(this);
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)
+6 -1
View File
@@ -88,6 +88,7 @@ struct CreateObjectBits
};
float const DEFAULT_COLLISION_HEIGHT = 2.03128f; // Most common value in dbc
static constexpr Milliseconds const HEARTBEAT_INTERVAL = 5s + 200ms;
class TC_GAME_API Object
{
@@ -314,7 +315,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
public:
virtual ~WorldObject();
virtual void Update (uint32 /*time_diff*/) { }
virtual void Update(uint32 diff);
void _Create(ObjectGuid::LowType guidlow, HighGuid guidhigh);
void AddToWorld() override;
@@ -608,6 +609,8 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
virtual bool IsInvisibleDueToDespawn() const { return false; }
//difference from IsAlwaysVisibleFor: 1. after distance check; 2. use owner or charmer as seer
virtual bool IsAlwaysDetectableFor(WorldObject const* /*seer*/) const { return false; }
virtual void Heartbeat() { }
private:
Map* m_currMap; // current object's Map location
@@ -620,6 +623,8 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
ObjectGuid _privateObjectOwner;
Milliseconds _heartbeatTimer;
virtual bool _IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D, bool incOwnRadius = true, bool incTargetRadius = true) const;
bool CanNeverSee(WorldObject const* obj) const;
+55 -88
View File
@@ -118,8 +118,6 @@
#include "WorldStatePackets.h"
#include <G3D/g3dmath.h>
#define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS)
// corpse reclaim times
#define DEATH_EXPIRE_STEP (5*MINUTE)
#define MAX_DEATH_COUNT 3
@@ -171,11 +169,9 @@ Player::Player(WorldSession* session): Unit(true)
m_comboPoints = 0;
m_foodEmoteTimerCount = 0;
m_weaponChangeTimer = 0;
m_zoneUpdateId = uint32(-1);
m_zoneUpdateTimer = 0;
m_areaUpdateId = 0;
m_team = 0;
@@ -355,7 +351,6 @@ Player::Player(WorldSession* session): Unit(true)
_hasValidLFGLeavePoint = false;
_archaeology = std::make_unique<Archaeology>(this);
m_petScalingSynchTimer.Reset(1000);
m_groupUpdateTimer.Reset(5000);
}
Player::~Player()
@@ -1133,37 +1128,6 @@ void Player::Update(uint32 p_time)
m_weaponChangeTimer -= p_time;
}
if (m_zoneUpdateTimer > 0)
{
if (p_time >= m_zoneUpdateTimer)
{
// On zone update tick check if we are still in an inn if we are supposed to be in one
if (HasRestFlag(REST_FLAG_IN_TAVERN))
{
AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(GetInnTriggerId());
if (!atEntry || !IsInAreaTriggerRadius(atEntry))
RemoveRestFlag(REST_FLAG_IN_TAVERN);
}
uint32 newzone, newarea;
GetZoneAndAreaId(newzone, newarea);
if (m_zoneUpdateId != newzone)
UpdateZone(newzone, newarea); // also update area
else
{
// use area updates as well
// needed for free far all arenas for example
if (m_areaUpdateId != newarea)
UpdateArea(newarea);
m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
}
}
else
m_zoneUpdateTimer -= p_time;
}
// Power regeneration update
_powerUpdateTimer -= p_time;
if (IsAlive())
@@ -1267,14 +1231,6 @@ void Player::Update(uint32 p_time)
}
}
// group update
m_groupUpdateTimer.Update(p_time);
if (m_groupUpdateTimer.Passed())
{
SendUpdateToOutOfRangeGroupMembers();
m_groupUpdateTimer.Reset(5000);
}
Pet* pet = GetPet();
if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityRange()) && !pet->isPossessed())
//if (pet && !pet->IsWithinDistInMap(this, GetMap()->GetVisibilityDistance()) && (GetCharmGUID() && (pet->GetGUID() != GetCharmGUID())))
@@ -1311,6 +1267,24 @@ void Player::Update(uint32 p_time)
}
void Player::Heartbeat()
{
Unit::Heartbeat();
// Group update
SendUpdateToOutOfRangeGroupMembers();
// Updating Zone and AreaId. This will also trigger spell_area and phasing related updates
UpdateZoneAndAreaId();
// Updating auras which can only be used inside or outside (such as Mounts)
UpdateIndoorsOutdoorsAuras();
// Updating the resting state when entering resting places
UpdateTavernRestingState();
}
void Player::setDeathState(DeathState s)
{
uint32 ressSpellId = 0;
@@ -1808,8 +1782,6 @@ bool Player::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Wo
void Player::RegenerateAll(uint32 diff)
{
m_foodEmoteTimerCount += diff;
Regenerate(POWER_ENERGY, diff);
Regenerate(POWER_MANA, diff);
Regenerate(POWER_FOCUS, diff);
@@ -1838,37 +1810,6 @@ void Player::RegenerateAll(uint32 diff)
SetRuneCooldown(runeToRegen, (cd > (diff * cdmod)) ? cd - (diff * cdmod) : 0);
}
}
// Handles the emotes for drinking and eating.
// According to sniffs there is a background timer going on that repeats independed from the time window where the aura applies.
// That's why we dont need to reset the timer on apply. In sniffs I have seen that the first call for the spell visual is totally random, then after
// 5 seconds over and over again which confirms my theory that we have a independed timer.
if (m_foodEmoteTimerCount >= 5000)
{
std::vector<AuraEffect*> auraList;
AuraEffectList const& ModRegenAuras = GetAuraEffectsByType(SPELL_AURA_MOD_REGEN);
AuraEffectList const& ModPowerRegenAuras = GetAuraEffectsByType(SPELL_AURA_MOD_POWER_REGEN);
auraList.reserve(ModRegenAuras.size() + ModPowerRegenAuras.size());
auraList.insert(auraList.end(), ModRegenAuras.begin(), ModRegenAuras.end());
auraList.insert(auraList.end(), ModPowerRegenAuras.begin(), ModPowerRegenAuras.end());
for (auto itr = auraList.begin(); itr != auraList.end(); ++itr)
{
// Food emote comes above drinking emote if we have to decide (mage regen food for example)
if ((*itr)->GetBase()->HasEffectType(SPELL_AURA_MOD_REGEN) && (*itr)->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing))
{
SendPlaySpellVisualKit(SPELL_VISUAL_KIT_FOOD, 0, 0);
break;
}
else if ((*itr)->GetBase()->HasEffectType(SPELL_AURA_MOD_POWER_REGEN) && (*itr)->GetSpellInfo()->HasAuraInterruptFlag(SpellAuraInterruptFlags::Standing))
{
SendPlaySpellVisualKit(SPELL_VISUAL_KIT_DRINK, 0, 0);
break;
}
}
m_foodEmoteTimerCount -= 5000;
}
}
void Player::RegenerateHealth()
@@ -5913,7 +5854,7 @@ bool Player::UpdatePosition(float x, float y, float z, float orientation, bool t
SetGroupUpdateFlag(GROUP_UPDATE_FLAG_WMO_GROUP_ID);
}
CheckAreaExploreAndOutdoor();
CheckAreaExplore();
return true;
}
@@ -5969,7 +5910,7 @@ void Player::SendMovieStart(uint32 movieId)
SendDirectMessage(packet.Write());
}
void Player::CheckAreaExploreAndOutdoor()
void Player::CheckAreaExplore()
{
if (!IsAlive())
return;
@@ -5977,9 +5918,6 @@ void Player::CheckAreaExploreAndOutdoor()
if (IsInFlight())
return;
if (sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK) && !IsOutdoors())
RemoveAurasWithAttribute(SPELL_ATTR0_ONLY_OUTDOORS);
uint32 const areaId = GetAreaId();
SetAreaExplored(areaId);
}
@@ -6052,6 +5990,39 @@ void Player::SetAreaExplored(uint32 areaId)
}
}
void Player::UpdateZoneAndAreaId()
{
uint32 newzone = 0, newarea = 0;
GetZoneAndAreaId(newzone, newarea);
if (m_zoneUpdateId != newzone)
UpdateZone(newzone, newarea); // also update area
else
{
// use area updates as well
// needed for free far all arenas for example
if (m_areaUpdateId != newarea)
UpdateArea(newarea);
}
}
void Player::UpdateIndoorsOutdoorsAuras()
{
if (sWorld->getBoolConfig(CONFIG_VMAP_INDOOR_CHECK))
RemoveAurasWithAttribute(IsOutdoors() ? SPELL_ATTR0_ONLY_INDOORS : SPELL_ATTR0_ONLY_OUTDOORS);
}
void Player::UpdateTavernRestingState()
{
AreaTriggerEntry const* atEntry = sAreaTriggerStore.LookupEntry(GetInnTriggerId());
if (HasRestFlag(REST_FLAG_IN_TAVERN) && (!atEntry || !IsInAreaTriggerRadius(atEntry)))
RemoveRestFlag(REST_FLAG_IN_TAVERN);
else if (!HasRestFlag(REST_FLAG_IN_TAVERN) && IsInAreaTriggerRadius(atEntry))
SetRestFlag(REST_FLAG_IN_TAVERN);
}
uint32 Player::TeamForRace(uint8 race)
{
if (ChrRacesEntry const* rEntry = sChrRacesStore.LookupEntry(race))
@@ -7032,7 +7003,6 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea)
uint32 const oldZone = m_zoneUpdateId;
m_zoneUpdateId = newZone;
m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL;
GetMap()->UpdatePlayerZoneStats(oldZone, newZone);
@@ -27629,7 +27599,7 @@ void Player::SetActiveSpec(uint8 spec)
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeSpec);
}
void Player::SetRestFlag(RestFlag restFlag, uint32 triggerId /*= 0*/)
void Player::SetRestFlag(RestFlag restFlag)
{
uint32 oldRestMask = _restFlagMask;
_restFlagMask |= restFlag;
@@ -27639,10 +27609,7 @@ void Player::SetRestFlag(RestFlag restFlag, uint32 triggerId /*= 0*/)
_restTime = GameTime::GetGameTime();
SetFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING);
}
if (triggerId)
inn_triggerId = triggerId;
}
}
void Player::RemoveRestFlag(RestFlag restFlag)
{
+11 -6
View File
@@ -410,7 +410,7 @@ enum PlayerFlags
PLAYER_FLAGS_UNK21 = 0x00200000,
PLAYER_FLAGS_COMMENTATOR2 = 0x00400000,
PLAYER_FLAGS_DISABLE_CASTING_EXCEPT_ABILITIES = 0x00800000, // disables casting any ability except for the ones specified in the aura effect's SpellClassMask
PLAYER_FLAGS_DISABLE_ATTACKING_EXCEPT_ABILITIES = 0x01000000, // disables all attacks including ability casts except for the ones specified in the aura effect's SpellClassMask
PLAYER_FLAGS_DISABLE_ATTACKING_EXCEPT_ABILITIES = 0x01000000, // disables all attacks including ability casts except for the ones specified in the aura effect's SpellClassMask
PLAYER_FLAGS_NO_XP_GAIN = 0x02000000,
PLAYER_FLAGS_UNK26 = 0x04000000,
PLAYER_FLAGS_AUTO_DECLINE_GUILD = 0x08000000, // Automatically declines guild invites
@@ -1044,6 +1044,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void Update(uint32 time) override;
void Heartbeat() override;
bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute = false) const override;
bool IsInAreaTriggerRadius(AreaTriggerEntry const* trigger) const;
@@ -1118,11 +1120,12 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetRestBonus(float rest_bonus_new);
bool HasRestFlag(RestFlag restFlag) const { return (_restFlagMask & restFlag) != 0; }
void SetRestFlag(RestFlag restFlag, uint32 triggerId = 0);
void SetRestFlag(RestFlag restFlag);
void RemoveRestFlag(RestFlag restFlag);
uint32 GetXPRestBonus(uint32 xp);
uint32 GetInnTriggerId() const { return inn_triggerId; }
void SetInnTriggerID(uint32 id) { inn_triggerId = id; }
Pet* GetPet() const;
Pet* SummonPet(uint32 entry, float x, float y, float z, float ang, PetType petType, uint32 despwtime);
@@ -1924,9 +1927,14 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetSemaphoreTeleportFar(bool semphsetting) { mSemaphoreTeleport_Far = semphsetting; }
void ProcessDelayedOperations();
void CheckAreaExploreAndOutdoor(void);
void CheckAreaExplore();
void SetAreaExplored(uint32 areaId);
// These methods are used to periodically update certain area and aura based mechanics used in Heartbeat and Movement
void UpdateZoneAndAreaId();
void UpdateIndoorsOutdoorsAuras();
void UpdateTavernRestingState();
static uint32 TeamForRace(uint8 race);
uint32 GetTeam() const { return m_team; }
TeamId GetTeamId() const { return m_team == ALLIANCE ? TEAM_ALLIANCE : TEAM_HORDE; }
@@ -2390,7 +2398,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
protected:
// Gamemaster whisper whitelist
GuidList WhisperList;
uint32 m_foodEmoteTimerCount;
uint32 m_contestedPvPTimer;
/*********************************************************/
@@ -2615,7 +2622,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 m_weaponChangeTimer;
uint32 m_zoneUpdateId;
uint32 m_zoneUpdateTimer;
uint32 m_areaUpdateId;
uint32 m_deathTimer;
@@ -2758,7 +2764,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
std::vector<PlayerPetData*> PlayerPetDataStore;
TimeTrackerSmall m_petScalingSynchTimer;
TimeTrackerSmall m_groupUpdateTimer;
};
TC_GAME_API void AddItemsSetItem(Player* player, Item* item);
+31 -16
View File
@@ -413,7 +413,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);
CheckPendingMovementAcks();
@@ -503,6 +503,25 @@ void Unit::Update(uint32 p_time)
RefreshAI();
}
void Unit::Heartbeat()
{
WorldObject::Heartbeat();
// SMSG_FLIGHT_SPLINE_SYNC for cyclic splines
SendFlightSplineSyncUpdate();
// Trigger heartbeat procs and generic aura behavior such as food emotes and invoking aura script hooks
TriggerAuraHeartbeat();
}
void Unit::TriggerAuraHeartbeat()
{
for (auto const& [_, auraApplication] : m_appliedAuras)
auraApplication->GetBase()->Heartbeat();
Unit::ProcSkillsAndAuras(this, nullptr, PROC_FLAG_HEARTBEAT, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
bool Unit::haveOffhandWeapon() const
{
if (Player const* player = ToPlayer())
@@ -527,20 +546,6 @@ void Unit::UpdateSplineMovement(uint32 t_diff)
movespline->updateState(t_diff);
bool arrived = movespline->Finalized();
if (movespline->isCyclic())
{
m_splineSyncTimer.Update(t_diff);
if (m_splineSyncTimer.Passed())
{
m_splineSyncTimer.Reset(5000); // Retail value, do not change
WorldPackets::Movement::FlightSplineSync packet;
packet.Guid = GetGUID();
packet.SplineDist = Movement::PacketBuilder::GetFlightSplineSyncDist(*movespline);
SendMessageToSet(packet.Write(), IsPlayer());
}
}
if (arrived)
{
DisableSpline();
@@ -575,6 +580,17 @@ void Unit::UpdateSplinePosition()
UpdatePosition(loc.x, loc.y, loc.z, loc.orientation);
}
void Unit::SendFlightSplineSyncUpdate()
{
if (!movespline->isCyclic() || movespline->Finalized())
return;
WorldPackets::Movement::FlightSplineSync flightSplineSync;
flightSplineSync.Guid = GetGUID();
flightSplineSync.SplineDist = float(movespline->timePassed()) / movespline->Duration();
SendMessageToSet(flightSplineSync.Write(), true);
}
void Unit::InterruptMovementBasedAuras()
{
// TODO: Check if orientation transport offset changed instead of only global orientation
@@ -9625,7 +9641,6 @@ void Unit::CleanupBeforeRemoveFromMap(bool finalCleanup)
if (finalCleanup)
m_cleanupDone = true;
m_Events.KillAllEvents(false); // non-delatable (currently cast spells) will not deleted now but it will deleted at call in Map::RemoveAllObjectsInRemoveList
CombatStop();
ClearComboPointHolders();
}
+4 -1
View File
@@ -825,6 +825,9 @@ class TC_GAME_API Unit : public WorldObject
virtual void Update(uint32 time) override;
void Heartbeat() override;
void TriggerAuraHeartbeat();
void setAttackTimer(WeaponAttackType type, uint32 time) { m_attackTimer[type] = time; }
void resetAttackTimer(WeaponAttackType type = BASE_ATTACK);
uint32 getAttackTimer(WeaponAttackType type) const { return m_attackTimer[type]; }
@@ -1871,6 +1874,7 @@ class TC_GAME_API Unit : public WorldObject
void UpdateSplineMovement(uint32 t_diff);
void UpdateSplinePosition();
void SendFlightSplineSyncUpdate();
// player or player's pet
float GetCombatRatingReduction(CombatRating cr) const;
@@ -1886,7 +1890,6 @@ class TC_GAME_API Unit : public WorldObject
private:
uint32 m_state; // Even derived shouldn't modify
TimeTrackerSmall m_splineSyncTimer;
std::array<DiminishingReturn, DIMINISHING_MAX> m_Diminishing;
+1 -1
View File
@@ -681,7 +681,7 @@ void WorldSession::HandleAreaTriggerOpcode(WorldPacket& recvData)
if (sObjectMgr->IsTavernAreaTrigger(triggerId))
{
// set resting flag we are in the inn
player->SetRestFlag(REST_FLAG_IN_TAVERN, atEntry->ID);
player->SetInnTriggerID(atEntry->ID);
if (sWorld->IsFFAPvPRealm())
player->RemoveByteFlag(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, UNIT_BYTE2_FLAG_FFA_PVP);
@@ -445,12 +445,33 @@ void WorldSession::HandleMovementOpcode(uint16 opcode, MovementInfo& movementInf
}
}
}
else
plrMover->RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_IS_OUT_OF_BOUNDS);
if (opcode == MSG_MOVE_JUMP)
{
plrMover->RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::Jump);
Unit::ProcSkillsAndAuras(plrMover, nullptr, PROC_FLAG_JUMP, PROC_FLAG_NONE, PROC_SPELL_TYPE_MASK_ALL, PROC_SPELL_PHASE_NONE, PROC_HIT_NONE, nullptr, nullptr, nullptr);
}
// Whenever a player stops a movement action, several position based checks and updates are being performed
switch (opcode)
{
case CMSG_MOVE_SET_CAN_FLY:
case MSG_MOVE_FALL_LAND:
case MSG_MOVE_STOP:
case MSG_MOVE_STOP_STRAFE:
case MSG_MOVE_STOP_TURN:
case MSG_MOVE_STOP_SWIM:
case MSG_MOVE_STOP_PITCH:
case MSG_MOVE_STOP_ASCEND:
plrMover->UpdateZoneAndAreaId();
plrMover->UpdateIndoorsOutdoorsAuras();
plrMover->UpdateTavernRestingState();
break;
default:
break;
}
}
}
+5
View File
@@ -413,6 +413,11 @@ class CreatureGameObjectScriptRegistrySwapHooks
// Hook which is called before a gameobject is swapped
static void UnloadResetScript(GameObject* gameobject)
{
// Remove deletable events only,
// otherwise it causes crashes with non-deletable spell events.
gameobject->m_Events.KillAllEvents(false);
gameobject->AI()->Reset();
}
@@ -2056,6 +2056,19 @@ void Aura::CallScriptAfterDispel(DispelInfo* dispelInfo)
}
}
void Aura::CallScriptOnHeartbeat()
{
for (auto scritr = m_loadedScripts.begin(); scritr != m_loadedScripts.end(); ++scritr)
{
(*scritr)->_PrepareScriptCall(AURA_SCRIPT_HOOK_ON_HEARTBEAT);
auto hookItrEnd = (*scritr)->OnHeartbeat.end(), hookItr = (*scritr)->OnHeartbeat.begin();
for (; hookItr != hookItrEnd; ++hookItr)
hookItr->Call();
(*scritr)->_FinishScriptCall();
}
}
bool Aura::CallScriptEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode)
{
bool preventDefault = false;
@@ -2535,6 +2548,29 @@ void UnitAura::AddStaticApplication(Unit* target, uint8 effMask)
_staticApplications[target->GetGUID()] |= effMask;
}
void UnitAura::Heartbeat()
{
Aura::Heartbeat();
// Periodic food and drink emote animation
HandlePeriodicFoodSpellVisualKit();
}
void UnitAura::HandlePeriodicFoodSpellVisualKit()
{
SpellSpecificType specificType = GetSpellInfo()->GetSpellSpecific();
bool food = specificType == SPELL_SPECIFIC_FOOD || specificType == SPELL_SPECIFIC_FOOD_AND_DRINK;
bool drink = specificType == SPELL_SPECIFIC_DRINK || specificType == SPELL_SPECIFIC_FOOD_AND_DRINK;
if (food)
GetUnitOwner()->SendPlaySpellVisualKit(SPELL_VISUAL_KIT_FOOD, 0, 0);
if (drink)
GetUnitOwner()->SendPlaySpellVisualKit(SPELL_VISUAL_KIT_DRINK, 0, 0);
}
DynObjAura::DynObjAura(AuraCreateInfo const& createInfo)
: Aura(createInfo)
{
@@ -237,12 +237,14 @@ class TC_GAME_API Aura
uint8 GetProcEffectMask(AuraApplication* aurApp, ProcEventInfo& eventInfo, std::chrono::steady_clock::time_point now) const;
float CalcProcChance(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) const;
void TriggerProcOnEvent(uint8 procEffectMask, AuraApplication* aurApp, ProcEventInfo& eventInfo);
virtual void Heartbeat() { }
// AuraScript
void LoadScripts();
bool CallScriptCheckAreaTargetHandlers(Unit* target);
void CallScriptDispel(DispelInfo* dispelInfo);
void CallScriptAfterDispel(DispelInfo* dispelInfo);
void CallScriptOnHeartbeat();
bool CallScriptEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
bool CallScriptEffectRemoveHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
void CallScriptAfterEffectApplyHandlers(AuraEffect const* aurEff, AuraApplication const* aurApp, AuraEffectHandleModes mode);
@@ -337,6 +339,9 @@ class TC_GAME_API UnitAura : public Aura
void AddStaticApplication(Unit* target, uint8 effMask);
void Heartbeat() override;
void HandlePeriodicFoodSpellVisualKit();
private:
DiminishingGroup m_AuraDRGroup; // Diminishing
std::unordered_map<ObjectGuid, uint8> _staticApplications; // non-area auras
+1 -1
View File
@@ -112,7 +112,7 @@ enum ProcFlags
{
PROC_FLAG_NONE = 0x00000000,
PROC_FLAG_HEARTBEAT = 0x00000001, // 00 Killed by agressor - not sure about this flag
PROC_FLAG_HEARTBEAT = 0x00000001, // 00 Heartbeat
PROC_FLAG_KILL = 0x00000002, // 01 Kill target (in most cases need XP/Honor reward)
PROC_FLAG_DEAL_MELEE_SWING = 0x00000004, // 02 Done melee auto attack
+11 -4
View File
@@ -229,7 +229,7 @@ class TC_GAME_API _SpellScript
template <typename Derived = _SpellScript>
EffectNameHookHandler(Derived* owner, HookType<Derived> hookFunction, uint8 effIndex, uint16 effName)
: BaseHookHandler(owner, hookFunction, effIndex,
: BaseHookHandler(owner, hookFunction, effIndex,
[_effectName = effName](SpellEffectInfo const& effectInfo) {
return _effectName == SPELL_EFFECT_ANY || effectInfo.Effect == _effectName;
}), _effName(effName)
@@ -491,7 +491,7 @@ class TC_GAME_API SpellScript : public _SpellScript
// internal use classes & functions
// DO NOT OVERRIDE THESE IN SCRIPTS
public:
using CheckCastHook = HookHandler<SpellCastResult>;
using EffectNameHook = EffectNameHookHandler<void, SpellEffIndex>;
using HitHook = HookHandler<void>;
@@ -742,6 +742,7 @@ enum AuraScriptHookType
AURA_SCRIPT_HOOK_CHECK_AREA_TARGET,
AURA_SCRIPT_HOOK_DISPEL,
AURA_SCRIPT_HOOK_AFTER_DISPEL,
AURA_SCRIPT_HOOK_ON_HEARTBEAT,
// Spell Proc Hooks
AURA_SCRIPT_HOOK_CHECK_PROC,
AURA_SCRIPT_HOOK_CHECK_EFFECT_PROC,
@@ -767,6 +768,7 @@ class TC_GAME_API AuraScript : public _SpellScript
using AuraCheckAreaTargetHookHandler = HookHandler<bool, Unit*>;
using AuraDispelHookHandler = HookHandler<void, DispelInfo*>;
using AuraHeartbeatHookHandler = HookHandler<void>;
using AuraEffectPeriodicHookHandler = AuraNameHookHandler<void, AuraEffect const*>;
using AuraEffectUpdatePeriodicHookHandler = AuraNameHookHandler<void, AuraEffect*>;
using AuraEffectCalcAmountHookHandler = AuraNameHookHandler<void, AuraEffect const*, int32&, bool&>;
@@ -811,7 +813,7 @@ class TC_GAME_API AuraScript : public _SpellScript
};
using AuraEffectApplyHookHandler = AuraEffectHandleModeHookHandler<void, AuraEffect const*, AuraEffectHandleModes>;
struct TC_GAME_API AuraEffectAbsorbHookHandler : AuraNameHookHandler<void, AuraEffect*, DamageInfo&, uint32&>
{
//> Base type shorthand.
@@ -878,7 +880,7 @@ class TC_GAME_API AuraScript : public _SpellScript
AuraScript()
: _SpellScript(), m_aura(nullptr), m_auraApplication(nullptr), m_defaultActionPrevented(false),
DoCheckAreaTarget(this),
OnDispel(this), AfterDispel(this),
OnDispel(this), AfterDispel(this), OnHeartbeat(this),
OnEffectApply(this), AfterEffectApply(this),
OnEffectRemove(this), AfterEffectRemove(this),
OnEffectPeriodic(this), OnEffectUpdatePeriodic(this),
@@ -935,6 +937,11 @@ class TC_GAME_API AuraScript : public _SpellScript
// where function is: void function (DispelInfo* dispelInfo);
AuraDispelHookHandler::HookList AfterDispel;
// executed on every heartbeat of a unit
// example: OnHeartbeat.Register(this, class::function);
// where function is: void function();
AuraHeartbeatHookHandler::HookList OnHeartbeat;
// executed when aura effect is applied with specified mode to target
// should be used when when effect handler preventing/replacing is needed, do not use this hook for triggering spellcasts/removing auras etc - may be unsafe
// example: OnEffectApply.Register(this, &class::function, EffectIndexSpecifier, EffectAuraNameSpecifier);
@@ -38,7 +38,7 @@ namespace ThroneOfTheTides::Ozumat
enum Texts
{
// Neptulon
SAY_INTRO_1 = 0,
SAY_INTRO_1 = 0,
SAY_INTRO_2 = 1,
SAY_PURIFY_WATERS_1 = 2,
SAY_PURIFY_WATERS_2 = 3,
@@ -431,7 +431,7 @@ struct npc_ozumat_neptulon : public ScriptedAI
default:
break;
}
}
void SummonedCreatureDies(Creature* summon, Unit* /*killer*/) override
@@ -494,7 +494,6 @@ struct npc_ozumat_neptulon : public ScriptedAI
break;
case NPC_OZUMAT_COMBAT_TRIGGER:
summon->SetReactState(REACT_PASSIVE);
summon->SetCombatPulseDelay(1);
summon->AI()->DoZoneInCombat();
break;
default:
@@ -274,7 +274,6 @@ class boss_blood_council_controller : public CreatureScript
return;
}
me->SetCombatPulseDelay(5);
me->setActive(true);
DoZoneInCombat();
instance->SetBossState(DATA_BLOOD_PRINCE_COUNCIL, IN_PROGRESS);
@@ -452,7 +451,6 @@ struct BloodPrincesBossAI : public BossAI
{
events.Reset();
summons.DespawnAll();
me->SetCombatPulseDelay(0);
me->SetImmuneToPC(false);
_isEmpowered = false;
@@ -462,7 +460,6 @@ struct BloodPrincesBossAI : public BossAI
void JustEngagedWith(Unit* /*who*/) override
{
me->SetCombatPulseDelay(5);
me->setActive(true);
if (Creature* controller = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_BLOOD_PRINCES_CONTROL)))
DoZoneInCombat(controller);
@@ -251,7 +251,6 @@ class boss_lady_deathwhisper : public CreatureScript
}
_phase = PHASE_ONE;
me->SetCombatPulseDelay(5);
me->setActive(true);
DoZoneInCombat();
scheduler.CancelGroup(GROUP_INTRO);
@@ -281,7 +281,6 @@ class boss_professor_putricide : public CreatureScript
Talk(SAY_AGGRO);
DoCast(me, SPELL_OOZE_TANK_PROTECTION, true);
DoZoneInCombat(me);
me->SetCombatPulseDelay(5);
instance->SetBossState(DATA_PROFESSOR_PUTRICIDE, IN_PROGRESS);
}
@@ -284,7 +284,6 @@ class boss_sindragosa : public CreatureScript
DoCast(me, SPELL_PERMAEATING_CHILL);
Talk(SAY_AGGRO);
instance->SetBossState(DATA_SINDRAGOSA, IN_PROGRESS);
me->SetCombatPulseDelay(5);
me->setActive(true);
me->SetFarVisible(true);
DoZoneInCombat();
@@ -550,7 +550,6 @@ class boss_the_lich_king : public CreatureScript
}
me->setActive(true);
me->SetCombatPulseDelay(5);
DoZoneInCombat();
events.SetPhase(PHASE_ONE);
@@ -528,7 +528,6 @@ class npc_green_dragon_combat_trigger : public CreatureScript
EngagementStart(target);
me->setActive(true);
me->SetCombatPulseDelay(5);
DoZoneInCombat();
instance->SetBossState(DATA_VALITHRIA_DREAMWALKER, IN_PROGRESS);
if (Creature* valithria = ObjectAccessor::GetCreature(*me, instance->GetGuidData(DATA_VALITHRIA_DREAMWALKER)))
@@ -178,7 +178,6 @@ struct boss_four_horsemen_baseAI : public BossAI
case ACTION_BEGIN_FIGHTING:
if (_ourMovementFinished)
break;
me->SetCombatPulseDelay(5);
BeginFighting();
_ourMovementFinished = true;
break;
@@ -306,7 +305,6 @@ struct boss_four_horsemen_baseAI : public BossAI
_ourMovementFinished = false;
me->SetReactState(REACT_AGGRESSIVE);
SetCombatMovement(false);
me->SetCombatPulseDelay(0);
me->ResetLootMode();
events.Reset();
summons.DespawnAll();
@@ -346,7 +346,6 @@ class boss_gothik : public CreatureScript
if (me->IsInCombat())
{
summon->AI()->DoAction(_gateIsOpen ? ACTION_GATE_OPENED : ACTION_ACQUIRE_TARGET);
summon->SetCombatPulseDelay(5);
}
else
summon->DespawnOrUnsummon();
@@ -803,7 +803,6 @@ public:
me->SetVisible(false);
me->SetHomePosition(me->GetPosition());
DoZoneInCombat();
me->SetCombatPulseDelay(5);
_visibilityTimer = 2 * IN_MILLISECONDS;
_bloodTapTimer = 25 * IN_MILLISECONDS;
break;
@@ -836,7 +835,6 @@ public:
void Reset() override
{
me->SetCombatPulseDelay(0);
ScriptedAI::Reset();
}
@@ -330,14 +330,12 @@ struct PalehoofMinionsBossAI : public BossAI
void Reset() override
{
me->SetCombatPulseDelay(0);
events.Reset();
DoCastSelf(SPELL_FREEZE, true);
}
void JustEngagedWith(Unit* /*who*/) override
{
me->SetCombatPulseDelay(5);
me->setActive(true);
ScheduleTasks();
}
@@ -243,14 +243,12 @@ struct IllidariCouncilBossAI : public BossAI
void Reset() override
{
me->SetCombatPulseDelay(0);
events.Reset();
DoCastSelf(SPELL_BALANCE_OF_POWER, true);
}
void JustEngagedWith(Unit* /*who*/) override
{
me->SetCombatPulseDelay(5);
me->setActive(true);
if (Creature* illidari = instance->GetCreature(DATA_ILLIDARI_COUNCIL))
DoZoneInCombat(illidari);
@@ -334,7 +334,6 @@ struct boss_essence_of_suffering : public BossAI
void JustEngagedWith(Unit* /*who*/) override
{
me->SetCombatPulseDelay(5);
me->setActive(true);
events.ScheduleEvent(EVENT_SOUL_DRAIN, 20s);
@@ -405,7 +404,6 @@ struct boss_essence_of_desire : public BossAI
events.ScheduleEvent(EVENT_RUNE_SHIELD, 16s);
events.ScheduleEvent(EVENT_DEADEN, 31s);
me->SetCombatPulseDelay(5);
me->setActive(true);
Talk(DESI_SAY_FREED);
}
@@ -513,7 +511,6 @@ struct boss_essence_of_anger : public BossAI
events.ScheduleEvent(EVENT_SPITE, 20s);
events.ScheduleEvent(EVENT_FREED_2, Seconds(1), Minutes(3));
me->SetCombatPulseDelay(5);
me->setActive(true);
}