diff options
Diffstat (limited to 'src/server/game')
130 files changed, 4643 insertions, 2345 deletions
diff --git a/src/server/game/AI/CoreAI/PassiveAI.cpp b/src/server/game/AI/CoreAI/PassiveAI.cpp index 2eca6c64c9e..abbff7a870b 100644 --- a/src/server/game/AI/CoreAI/PassiveAI.cpp +++ b/src/server/game/AI/CoreAI/PassiveAI.cpp @@ -29,7 +29,7 @@ int32 NullCreatureAI::Permissible(Creature const* creature) return PERMIT_BASE_PROACTIVE + 50; if (creature->IsTrigger()) - return PERMIT_BASE_REACTIVE; + return PERMIT_BASE_PROACTIVE; return PERMIT_BASE_IDLE; } @@ -105,7 +105,7 @@ void TriggerAI::IsSummonedBy(Unit* summoner) int32 TriggerAI::Permissible(Creature const* creature) { if (creature->IsTrigger() && creature->m_spells[0]) - return PERMIT_BASE_PROACTIVE; + return PERMIT_BASE_SPECIAL; return PERMIT_BASE_NO; } diff --git a/src/server/game/AI/CoreAI/TotemAI.cpp b/src/server/game/AI/CoreAI/TotemAI.cpp index da484e20983..365f0ca5ce8 100644 --- a/src/server/game/AI/CoreAI/TotemAI.cpp +++ b/src/server/game/AI/CoreAI/TotemAI.cpp @@ -72,7 +72,7 @@ void TotemAI::UpdateAI(uint32 /*diff*/) me->IsFriendlyTo(victim) || !me->CanSeeOrDetect(victim)) { victim = nullptr; - Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, me, max_range); + Trinity::NearestAttackableUnitInObjectRangeCheck u_check(me, me->GetCharmerOrOwnerOrSelf(), max_range); Trinity::UnitLastSearcher<Trinity::NearestAttackableUnitInObjectRangeCheck> checker(me, victim, u_check); Cell::VisitAllObjects(me, checker, max_range); } diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index 92c9c8e3fb6..244b0b5e047 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -190,7 +190,7 @@ void UnitAI::DoCastAOE(uint32 spellId, bool triggered) if (!triggered && me->HasUnitState(UNIT_STATE_CASTING)) return; - me->CastSpell((Unit*)nullptr, spellId, triggered); + me->CastSpell(nullptr, spellId, triggered); } uint32 UnitAI::GetDialogStatus(Player* /*player*/) diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index 30a0575c3d2..1078c16f8de 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -330,6 +330,11 @@ class TC_GAME_API UnitAI // Called when the dialog status between a player and the creature is requested. virtual uint32 GetDialogStatus(Player* /*player*/); + virtual void WaypointPathStarted(uint32 /*nodeId*/, uint32 /*pathId*/) { } + virtual void WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/) { } + virtual void WaypointReached(uint32 /*nodeId*/, uint32 /*pathId*/) { } + virtual void WaypointPathEnded(uint32 /*nodeId*/, uint32 /*pathId*/) { } + private: UnitAI(UnitAI const& right) = delete; UnitAI& operator=(UnitAI const& right) = delete; diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index bd7a6efab22..015e46cccdd 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -134,8 +134,8 @@ class TC_GAME_API CreatureAI : public UnitAI virtual bool IsEscorted() const { return false; } - // Called when creature is spawned or respawned - virtual void JustRespawned() { } + // Called when creature appears in the world (spawn, respawn, grid load etc...) + virtual void JustAppeared() { } // Called at waypoint reached or point movement finished virtual void MovementInform(uint32 /*type*/, uint32 /*id*/) { } @@ -187,6 +187,9 @@ class TC_GAME_API CreatureAI : public UnitAI // If a PlayerAI* is returned, that AI is placed on the player instead of the default charm AI // Object destruction is handled by Unit::RemoveCharmedBy virtual PlayerAI* GetAIForCharmedPlayer(Player* /*who*/) { return nullptr; } + // Should return true if the NPC is target of an escort quest + // If onlyIfActive is set, should return true only if the escort quest is currently active + virtual bool IsEscortNPC(bool /*onlyIfActive*/) const { return false; } // intended for encounter design/debugging. do not use for other purposes. expensive. int32 VisualizeBoundary(uint32 duration, Unit* owner = nullptr, bool fill = false) const; diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp index 92f1c6f06dc..a103b34ff56 100644 --- a/src/server/game/AI/PlayerAI/PlayerAI.cpp +++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp @@ -769,18 +769,30 @@ Unit* PlayerAI::SelectAttackTarget() const return me->GetCharmer() ? me->GetCharmer()->GetVictim() : nullptr; } -struct UncontrolledTargetSelectPredicate +struct ValidTargetSelectPredicate { + ValidTargetSelectPredicate(UnitAI const* ai) : _ai(ai) { } + UnitAI const* const _ai; bool operator()(Unit const* target) const { - return !target->HasBreakableByDamageCrowdControlAura(); + return _ai->CanAIAttack(target); } }; +bool SimpleCharmedPlayerAI::CanAIAttack(Unit const* who) const +{ + if (!me->IsValidAttackTarget(who) || who->HasBreakableByDamageCrowdControlAura()) + return false; + if (Unit* charmer = me->GetCharmer()) + if (!charmer->IsValidAttackTarget(who)) + return false; + return UnitAI::CanAIAttack(who); +} + Unit* SimpleCharmedPlayerAI::SelectAttackTarget() const { if (Unit* charmer = me->GetCharmer()) - return charmer->IsAIEnabled ? charmer->GetAI()->SelectTarget(SELECT_TARGET_RANDOM, 0, UncontrolledTargetSelectPredicate()) : charmer->GetVictim(); + return charmer->IsAIEnabled ? charmer->GetAI()->SelectTarget(SELECT_TARGET_RANDOM, 0, ValidTargetSelectPredicate(this)) : charmer->GetVictim(); return nullptr; } @@ -1316,11 +1328,23 @@ void SimpleCharmedPlayerAI::UpdateAI(const uint32 diff) if (charmer->IsEngaged()) { Unit* target = me->GetVictim(); - if (!target || !charmer->IsValidAttackTarget(target) || target->HasBreakableByDamageCrowdControlAura()) + if (!target || !CanAIAttack(target)) { target = SelectAttackTarget(); - if (!target) + if (!target || !CanAIAttack(target)) + { + if (!_isFollowing) + { + _isFollowing = true; + me->AttackStop(); + me->CastStop(); + me->StopMoving(); + me->GetMotionMaster()->Clear(); + me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + } return; + } + _isFollowing = false; if (IsRangedAttacker()) { @@ -1373,8 +1397,9 @@ void SimpleCharmedPlayerAI::UpdateAI(const uint32 diff) DoAutoAttackIfReady(); } - else + else if (!_isFollowing) { + _isFollowing = true; me->AttackStop(); me->CastStop(); me->StopMoving(); diff --git a/src/server/game/AI/PlayerAI/PlayerAI.h b/src/server/game/AI/PlayerAI/PlayerAI.h index 8afc5ab3ce9..faff09b6b6e 100644 --- a/src/server/game/AI/PlayerAI/PlayerAI.h +++ b/src/server/game/AI/PlayerAI/PlayerAI.h @@ -97,11 +97,12 @@ class TC_GAME_API PlayerAI : public UnitAI class TC_GAME_API SimpleCharmedPlayerAI : public PlayerAI { public: - SimpleCharmedPlayerAI(Player* player) : PlayerAI(player), _castCheckTimer(500), _chaseCloser(false), _forceFacing(true) { } + SimpleCharmedPlayerAI(Player* player) : PlayerAI(player), _castCheckTimer(2500), _chaseCloser(false), _forceFacing(true), _isFollowing(false) { } void UpdateAI(uint32 diff) override; void OnCharmed(bool apply) override; protected: + bool CanAIAttack(Unit const* who) const override; Unit* SelectAttackTarget() const override; private: @@ -109,6 +110,7 @@ class TC_GAME_API SimpleCharmedPlayerAI : public PlayerAI uint32 _castCheckTimer; bool _chaseCloser; bool _forceFacing; + bool _isFollowing; }; #endif diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp index e9e252bcf35..afa7e789b7a 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.cpp @@ -478,7 +478,7 @@ void BossAI::_Reset() events.Reset(); summons.DespawnAll(); scheduler.CancelAll(); - if (instance) + if (instance && instance->GetBossState(_bossId) != DONE) instance->SetBossState(_bossId, NOT_STARTED); } @@ -564,12 +564,12 @@ bool BossAI::CanAIAttack(Unit const* target) const return CheckBoundary(target); } -void BossAI::_DespawnAtEvade(uint32 delayToRespawn /*= 30*/, Creature* who /*= nullptr*/) +void BossAI::_DespawnAtEvade(Seconds delayToRespawn, Creature* who) { - if (delayToRespawn < 2) + if (delayToRespawn < Seconds(2)) { - TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of %u seconds, defaulting to 2.", delayToRespawn); - delayToRespawn = 2; + TC_LOG_ERROR("scripts", "_DespawnAtEvade called with delay of %ld seconds, defaulting to 2.", delayToRespawn.count()); + delayToRespawn = Seconds(2); } if (!who) diff --git a/src/server/game/AI/ScriptedAI/ScriptedCreature.h b/src/server/game/AI/ScriptedAI/ScriptedCreature.h index 6fc85b6a81a..9f2e49c1ffb 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedCreature.h +++ b/src/server/game/AI/ScriptedAI/ScriptedCreature.h @@ -366,8 +366,8 @@ class TC_GAME_API BossAI : public ScriptedAI void _EnterCombat(); void _JustDied(); void _JustReachedHome(); - void _DespawnAtEvade(uint32 delayToRespawn = 30, Creature* who = nullptr); - void _DespawnAtEvade(Seconds const& time, Creature* who = nullptr) { _DespawnAtEvade(uint32(time.count()), who); } + void _DespawnAtEvade(Seconds delayToRespawn, Creature* who = nullptr); + void _DespawnAtEvade(uint32 delayToRespawn = 30, Creature* who = nullptr) { _DespawnAtEvade(Seconds(delayToRespawn), who); } void TeleportCheaters(); diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp index bcf060e2ad6..376aceba4ba 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.cpp @@ -16,21 +16,17 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -/* ScriptData -SDName: Npc_EscortAI -SD%Complete: 100 -SDComment: -SDCategory: Npc -EndScriptData */ - #include "ScriptedEscortAI.h" #include "Creature.h" #include "Group.h" #include "Log.h" #include "Map.h" #include "MotionMaster.h" +#include "MovementGenerator.h" #include "ObjectAccessor.h" #include "Player.h" +#include "ScriptSystem.h" +#include "World.h" enum Points { @@ -38,101 +34,75 @@ enum Points POINT_HOME = 0xFFFFFE }; -npc_escortAI::npc_escortAI(Creature* creature) : ScriptedAI(creature), - m_uiWPWaitTimer(2500), - m_uiPlayerCheckTimer(1000), - m_uiEscortState(STATE_ESCORT_NONE), - MaxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE), - m_pQuestForEscort(nullptr), - m_bIsActiveAttacker(true), - m_bIsRunning(false), - m_bCanInstantRespawn(false), - m_bCanReturnToStart(false), - DespawnAtEnd(true), - DespawnAtFar(true), - ScriptWP(false), - HasImmuneToNPCFlags(false) -{ } - -void npc_escortAI::AttackStart(Unit* who) +EscortAI::EscortAI(Creature* creature) : ScriptedAI(creature), _pauseTimer(2500), _playerCheckTimer(1000), _escortState(STATE_ESCORT_NONE), _maxPlayerDistance(DEFAULT_MAX_PLAYER_DISTANCE), + _escortQuest(nullptr), _activeAttacker(true), _running(false), _instantRespawn(false), _returnToStart(false), _despawnAtEnd(true), _despawnAtFar(true), _manualPath(false), + _hasImmuneToNPCFlags(false), _started(false), _ended(false), _resume(false) { - if (!who) - return; - - if (me->Attack(who, true)) - { - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == POINT_MOTION_TYPE) - me->GetMotionMaster()->MovementExpired(); - - if (IsCombatMovementAllowed()) - me->GetMotionMaster()->MoveChase(who); - } } -Player* npc_escortAI::GetPlayerForEscort() +Player* EscortAI::GetPlayerForEscort() { - return ObjectAccessor::GetPlayer(*me, m_uiPlayerGUID); + return ObjectAccessor::GetPlayer(*me, _playerGUID); } -//see followerAI -bool npc_escortAI::AssistPlayerInCombatAgainst(Unit* who) +// see followerAI +bool EscortAI::AssistPlayerInCombatAgainst(Unit* who) { if (!who || !who->GetVictim()) return false; - //experimental (unknown) flag not present + if (me->HasReactState(REACT_PASSIVE)) + return false; + + // experimental (unknown) flag not present if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST)) return false; - //not a player + // not a player if (!who->EnsureVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()) return false; - //never attack friendly - if (me->IsFriendlyTo(who)) + if (!who->isInAccessiblePlaceFor(me)) return false; - //too far away and no free sight? + if (!CanAIAttack(who)) + return false; + + // we cannot attack in evade mode + if (me->IsInEvadeMode()) + return false; + + // or if enemy is in evade mode + if (who->GetTypeId() == TYPEID_UNIT && who->ToCreature()->IsInEvadeMode()) + return false; + + if (!me->IsValidAssistTarget(who->GetVictim())) + return false; + + // too far away and no free sight if (me->IsWithinDistInMap(who, GetMaxPlayerDistance()) && me->IsWithinLOSInMap(who)) { - //already fighting someone? - if (!me->GetVictim()) - { - AttackStart(who); - return true; - } - else - { - me->EngageWithTarget(who); - return true; - } + me->EngageWithTarget(who); + return true; } return false; } -void npc_escortAI::MoveInLineOfSight(Unit* who) +void EscortAI::MoveInLineOfSight(Unit* who) { - if (me->HasReactState(REACT_AGGRESSIVE) && !me->HasUnitState(UNIT_STATE_STUNNED) && who->isTargetableForAttack() && who->isInAccessiblePlaceFor(me)) - { - if (HasEscortState(STATE_ESCORT_ESCORTING) && AssistPlayerInCombatAgainst(who)) - return; + if (!who) + return; - if (!me->CanFly() && me->GetDistanceZ(who) > CREATURE_Z_ATTACK_RANGE) - return; + if (HasEscortState(STATE_ESCORT_ESCORTING) && AssistPlayerInCombatAgainst(who)) + return; - if (me->IsHostileTo(who)) - { - float fAttackRadius = me->GetAttackDistance(who); - if (me->IsWithinDistInMap(who, fAttackRadius) && me->IsWithinLOSInMap(who)) - me->EngageWithTarget(who); - } - } + ScriptedAI::MoveInLineOfSight(who); } -void npc_escortAI::JustDied(Unit* /*killer*/) +void EscortAI::JustDied(Unit* /*killer*/) { - if (!HasEscortState(STATE_ESCORT_ESCORTING) || !m_uiPlayerGUID || !m_pQuestForEscort) + if (!HasEscortState(STATE_ESCORT_ESCORTING) || !_playerGUID || !_escortQuest) return; if (Player* player = GetPlayerForEscort()) @@ -140,24 +110,26 @@ void npc_escortAI::JustDied(Unit* /*killer*/) if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) + { if (Player* member = groupRef->GetSource()) if (member->IsInMap(player)) - member->FailQuest(m_pQuestForEscort->GetQuestId()); + member->FailQuest(_escortQuest->GetQuestId()); + } } else - player->FailQuest(m_pQuestForEscort->GetQuestId()); + player->FailQuest(_escortQuest->GetQuestId()); } } -void npc_escortAI::JustRespawned() +void EscortAI::JustAppeared() { - m_uiEscortState = STATE_ESCORT_NONE; + _escortState = STATE_ESCORT_NONE; if (!IsCombatMovementAllowed()) SetCombatMovement(true); - //add a small delay before going to first waypoint, normal in near all cases - m_uiWPWaitTimer = 2500; + // add a small delay before going to first waypoint, normal in near all cases + _pauseTimer = 2000; if (me->GetFaction() != me->GetCreatureTemplate()->faction) me->RestoreFaction(); @@ -165,14 +137,12 @@ void npc_escortAI::JustRespawned() Reset(); } -void npc_escortAI::ReturnToLastPoint() +void EscortAI::ReturnToLastPoint() { - float x, y, z, o; - me->GetHomePosition(x, y, z, o); - me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, x, y, z); + me->GetMotionMaster()->MovePoint(POINT_LAST_POINT, me->GetHomePosition()); } -void npc_escortAI::EnterEvadeMode(EvadeReason /*why*/) +void EscortAI::EnterEvadeMode(EvadeReason /*why*/) { me->RemoveAllAuras(); me->GetThreatManager().ClearAllThreat(); @@ -183,27 +153,29 @@ void npc_escortAI::EnterEvadeMode(EvadeReason /*why*/) { AddEscortState(STATE_ESCORT_RETURNING); ReturnToLastPoint(); - TC_LOG_DEBUG("scripts", "EscortAI has left combat and is now returning to last point"); + TC_LOG_DEBUG("scripts", "EscortAI::EnterEvadeMode: left combat and is now returning to last point"); } else { me->GetMotionMaster()->MoveTargetedHome(); - if (HasImmuneToNPCFlags) + if (_hasImmuneToNPCFlags) me->SetImmuneToNPC(true); Reset(); } } -bool npc_escortAI::IsPlayerOrGroupInRange() +bool EscortAI::IsPlayerOrGroupInRange() { if (Player* player = GetPlayerForEscort()) { if (Group* group = player->GetGroup()) { for (GroupReference* groupRef = group->GetFirstMember(); groupRef != nullptr; groupRef = groupRef->next()) + { if (Player* member = groupRef->GetSource()) if (me->IsWithinDistInMap(member, GetMaxPlayerDistance())) return true; + } } else if (me->IsWithinDistInMap(player, GetMaxPlayerDistance())) return true; @@ -212,95 +184,94 @@ bool npc_escortAI::IsPlayerOrGroupInRange() return false; } -void npc_escortAI::UpdateAI(uint32 diff) +void EscortAI::UpdateAI(uint32 diff) { - //Waypoint Updating - if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->GetVictim() && m_uiWPWaitTimer && !HasEscortState(STATE_ESCORT_RETURNING)) + // Waypoint Updating + if (HasEscortState(STATE_ESCORT_ESCORTING) && !me->IsEngaged() && !HasEscortState(STATE_ESCORT_RETURNING)) { - if (m_uiWPWaitTimer <= diff) + if (_pauseTimer <= diff) { - //End of the line - if (CurrentWP == WaypointList.end()) + if (!HasEscortState(STATE_ESCORT_PAUSED)) { - if (DespawnAtEnd) - { - TC_LOG_DEBUG("scripts", "EscortAI reached end of waypoints"); - - if (m_bCanReturnToStart) - { - float fRetX, fRetY, fRetZ; - me->GetRespawnPosition(fRetX, fRetY, fRetZ); - - me->GetMotionMaster()->MovePoint(POINT_HOME, fRetX, fRetY, fRetZ); + _pauseTimer = 0; - m_uiWPWaitTimer = 0; - - TC_LOG_DEBUG("scripts", "EscortAI are returning home to spawn location: %u, %f, %f, %f", POINT_HOME, fRetX, fRetY, fRetZ); - return; - } + if (_ended) + { + _ended = false; + me->GetMotionMaster()->MoveIdle(); - if (m_bCanInstantRespawn) + if (_despawnAtEnd) { - me->setDeathState(JUST_DIED); - me->Respawn(); + TC_LOG_DEBUG("scripts", "EscortAI::UpdateAI: reached end of waypoints, despawning at end"); + if (_returnToStart) + { + Position respawnPosition; + float orientation = 0.f; + me->GetRespawnPosition(respawnPosition.m_positionX, respawnPosition.m_positionY, respawnPosition.m_positionZ, &orientation); + respawnPosition.SetOrientation(orientation); + me->GetMotionMaster()->MovePoint(POINT_HOME, respawnPosition); + TC_LOG_DEBUG("scripts", "EscortAI::UpdateAI: returning to spawn location: %s", respawnPosition.ToString().c_str()); + } + else if (_instantRespawn) + me->Respawn(true); + else + me->DespawnOrUnsummon(); } - else - me->DespawnOrUnsummon(); - + TC_LOG_DEBUG("scripts", "EscortAI::UpdateAI: reached end of waypoints"); + RemoveEscortState(STATE_ESCORT_ESCORTING); return; } - else - { - TC_LOG_DEBUG("scripts", "EscortAI reached end of waypoints with Despawn off"); - return; + if (!_started) + { + _started = true; + me->GetMotionMaster()->MovePath(_path, false); + } + else if (_resume) + { + _resume = false; + if (MovementGenerator* movementGenerator = me->GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE)) + movementGenerator->Resume(0); } - } - - if (!HasEscortState(STATE_ESCORT_PAUSED)) - { - me->GetMotionMaster()->MovePoint(CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); - TC_LOG_DEBUG("scripts", "EscortAI start waypoint %u (%f, %f, %f).", CurrentWP->id, CurrentWP->x, CurrentWP->y, CurrentWP->z); - - WaypointStart(CurrentWP->id); - - m_uiWPWaitTimer = 0; } } else - m_uiWPWaitTimer -= diff; + _pauseTimer -= diff; } - //Check if player or any member of his group is within range - if (HasEscortState(STATE_ESCORT_ESCORTING) && m_uiPlayerGUID && !me->GetVictim() && !HasEscortState(STATE_ESCORT_RETURNING)) + // Check if player or any member of his group is within range + if (_despawnAtFar && HasEscortState(STATE_ESCORT_ESCORTING) && _playerGUID && !me->GetVictim() && !HasEscortState(STATE_ESCORT_RETURNING)) { - if (m_uiPlayerCheckTimer <= diff) + if (_playerCheckTimer <= diff) { - if (DespawnAtFar && !IsPlayerOrGroupInRange()) + if (!IsPlayerOrGroupInRange()) { - TC_LOG_DEBUG("scripts", "EscortAI failed because player/group was to far away or not found"); + TC_LOG_DEBUG("scripts", "EscortAI::UpdateAI: failed because player/group was to far away or not found"); - if (m_bCanInstantRespawn) - { - me->setDeathState(JUST_DIED); - me->Respawn(); - } + bool isEscort = false; + if (CreatureData const* creatureData = me->GetCreatureData()) + isEscort = (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (creatureData->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC)); + + if (_instantRespawn && !isEscort) + me->DespawnOrUnsummon(0, Seconds(1)); + else if (_instantRespawn && isEscort) + me->GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, me->GetSpawnId(), true); else me->DespawnOrUnsummon(); return; } - m_uiPlayerCheckTimer = 1000; + _playerCheckTimer = 1000; } else - m_uiPlayerCheckTimer -= diff; + _playerCheckTimer -= diff; } UpdateEscortAI(diff); } -void npc_escortAI::UpdateEscortAI(uint32 /*diff*/) +void EscortAI::UpdateEscortAI(uint32 /*diff*/) { if (!UpdateVictim()) return; @@ -308,262 +279,197 @@ void npc_escortAI::UpdateEscortAI(uint32 /*diff*/) DoMeleeAttackIfReady(); } -void npc_escortAI::MovementInform(uint32 moveType, uint32 pointId) +void EscortAI::MovementInform(uint32 type, uint32 id) { - if (moveType != POINT_MOTION_TYPE || !HasEscortState(STATE_ESCORT_ESCORTING)) + // no action allowed if there is no escort + if (!HasEscortState(STATE_ESCORT_ESCORTING)) return; - //Combat start position reached, continue waypoint movement - if (pointId == POINT_LAST_POINT) + if (type == POINT_MOTION_TYPE) { - TC_LOG_DEBUG("scripts", "EscortAI has returned to original position before combat"); + if (!_pauseTimer) + _pauseTimer = 2000; - me->SetWalk(!m_bIsRunning); - RemoveEscortState(STATE_ESCORT_RETURNING); - - if (!m_uiWPWaitTimer) - m_uiWPWaitTimer = 1; + // continue waypoint movement + if (id == POINT_LAST_POINT) + { + TC_LOG_DEBUG("scripts", "EscortAI::MovementInform: returned to before combat position"); + me->SetWalk(!_running); + RemoveEscortState(STATE_ESCORT_RETURNING); + } + else if (id == POINT_HOME) + { + TC_LOG_DEBUG("scripts", "EscortAI::MovementInform: returned to home location and restarting waypoint path"); + _started = false; + } } - else if (pointId == POINT_HOME) + else if (type == WAYPOINT_MOTION_TYPE) { - TC_LOG_DEBUG("scripts", "EscortAI has returned to original home location and will continue from beginning of waypoint list."); + ASSERT(id < _path.nodes.size(), "EscortAI::MovementInform: referenced movement id (%u) points to non-existing node in loaded path", id); + WaypointNode waypoint = _path.nodes[id]; - CurrentWP = WaypointList.begin(); - m_uiWPWaitTimer = 1; - } - else if (CurrentWP != WaypointList.end()) - { - //Make sure that we are still on the right waypoint - if (CurrentWP->id != pointId) + TC_LOG_DEBUG("scripts", "EscortAI::MovementInform: waypoint node %u reached", waypoint.id); + + // last point + if (id == _path.nodes.size() - 1) { - TC_LOG_ERROR("misc", "TSCR ERROR: EscortAI reached waypoint out of order %u, expected %u, creature entry %u", pointId, CurrentWP->id, me->GetEntry()); - return; + _started = false; + _ended = true; + _pauseTimer = 1000; } - - TC_LOG_DEBUG("scripts", "EscortAI Waypoint %u reached", CurrentWP->id); - - //Call WP function - WaypointReached(CurrentWP->id); - - m_uiWPWaitTimer = CurrentWP->WaitTimeMs + 1; - - ++CurrentWP; } } +///@todo investigate whether if its necessary to handle anything on charm /* -void npc_escortAI::OnPossess(bool apply) +void EscortAI::OnCharmed(bool apply) { - // We got possessed in the middle of being escorted, store the point - // where we left off to come back to when possess is removed - if (HasEscortState(STATE_ESCORT_ESCORTING)) - { - if (apply) - me->GetPosition(LastPos.x, LastPos.y, LastPos.z); - else - { - Returning = true; - me->GetMotionMaster()->MovementExpired(); - me->GetMotionMaster()->MovePoint(WP_LAST_POINT, LastPos.x, LastPos.y, LastPos.z); - } - } } */ -void npc_escortAI::AddWaypoint(uint32 id, float x, float y, float z, uint32 waitTime) +void EscortAI::AddWaypoint(uint32 id, float x, float y, float z, float orientation/* = 0*/, uint32 waitTime/* = 0*/) { - Escort_Waypoint t(id, x, y, z, waitTime); - - WaypointList.push_back(t); - - // i think SD2 no longer uses this function - ScriptWP = true; - /*PointMovement wp; - wp.m_uiCreatureEntry = me->GetEntry(); - wp.m_uiPointId = id; - wp.m_fX = x; - wp.m_fY = y; - wp.m_fZ = z; - wp.m_uiWaitTime = WaitTimeMs; - PointMovementMap[wp.m_uiCreatureEntry].push_back(wp);*/ + Trinity::NormalizeMapCoord(x); + Trinity::NormalizeMapCoord(y); + + WaypointNode waypoint; + waypoint.id = id; + waypoint.x = x; + waypoint.y = y; + waypoint.z = z; + waypoint.orientation = orientation; + waypoint.moveType = _running ? WAYPOINT_MOVE_TYPE_RUN : WAYPOINT_MOVE_TYPE_WALK; + waypoint.delay = waitTime; + waypoint.eventId = 0; + waypoint.eventChance = 100; + _path.nodes.push_back(std::move(waypoint)); + + _manualPath = true; } -void npc_escortAI::FillPointMovementListForCreature() +void EscortAI::FillPointMovementListForCreature() { - ScriptPointVector const* movePoints = sScriptSystemMgr->GetPointMoveList(me->GetEntry()); - if (!movePoints) + WaypointPath const* path = sScriptSystemMgr->GetPath(me->GetEntry()); + if (!path) return; - for (ScriptPointVector::const_iterator itr = movePoints->begin(); itr != movePoints->end(); ++itr) + for (WaypointNode const& value : path->nodes) { - Escort_Waypoint point(itr->uiPointId, itr->fX, itr->fY, itr->fZ, itr->uiWaitTime); - WaypointList.push_back(point); + WaypointNode node = value; + Trinity::NormalizeMapCoord(node.x); + Trinity::NormalizeMapCoord(node.y); + node.moveType = _running ? WAYPOINT_MOVE_TYPE_RUN : WAYPOINT_MOVE_TYPE_WALK; + + _path.nodes.push_back(std::move(node)); } } -void npc_escortAI::SetRun(bool on) +void EscortAI::SetRun(bool on) { - if (on) - { - if (!m_bIsRunning) - me->SetWalk(false); - else - TC_LOG_DEBUG("scripts", "EscortAI attempt to set run mode, but is already running."); - } - else - { - if (m_bIsRunning) - me->SetWalk(true); - else - TC_LOG_DEBUG("scripts", "EscortAI attempt to set walk mode, but is already walking."); - } + if (on && !_running) + me->SetWalk(false); + else if (!on && _running) + me->SetWalk(true); - m_bIsRunning = on; + _running = on; } /// @todo get rid of this many variables passed in function. -void npc_escortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */, ObjectGuid playerGUID /* = 0 */, Quest const* quest /* = nullptr */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */) +void EscortAI::Start(bool isActiveAttacker /* = true*/, bool run /* = false */, ObjectGuid playerGUID /* = 0 */, Quest const* quest /* = nullptr */, bool instantRespawn /* = false */, bool canLoopPath /* = false */, bool resetWaypoints /* = true */) { + // Queue respawn from the point it starts + if (Map* map = me->GetMap()) + { + if (CreatureData const* cdata = me->GetCreatureData()) + { + if (SpawnGroupTemplateData const* groupdata = cdata->spawnGroupData) + { + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && (groupdata->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) && !map->GetCreatureRespawnTime(me->GetSpawnId())) + { + me->SetRespawnTime(me->GetRespawnDelay()); + me->SaveRespawnTime(); + } + } + } + } + if (me->GetVictim()) { - TC_LOG_ERROR("scripts.escortai", "TSCR ERROR: EscortAI (script: %s, creature entry: %u) attempts to Start while in combat", me->GetScriptName().c_str(), me->GetEntry()); + TC_LOG_ERROR("scripts", "EscortAI::Start: (script: %s, creature entry: %u) attempts to Start while in combat", me->GetScriptName().c_str(), me->GetEntry()); return; } if (HasEscortState(STATE_ESCORT_ESCORTING)) { - TC_LOG_ERROR("scripts.escortai", "EscortAI (script: %s, creature entry: %u) attempts to Start while already escorting", me->GetScriptName().c_str(), me->GetEntry()); + TC_LOG_ERROR("scripts", "EscortAI::Start: (script: %s, creature entry: %u) attempts to Start while already escorting", me->GetScriptName().c_str(), me->GetEntry()); return; } - if (!ScriptWP && resetWaypoints) // sd2 never adds wp in script, but tc does - { - if (!WaypointList.empty()) - WaypointList.clear(); + if (!_manualPath && resetWaypoints) FillPointMovementListForCreature(); - } - if (WaypointList.empty()) + if (_path.nodes.empty()) { - TC_LOG_ERROR("scripts", "EscortAI (script: %s, creature entry: %u) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).", - me->GetScriptName().c_str(), me->GetEntry(), quest ? quest->GetQuestId() : 0); + TC_LOG_ERROR("scripts", "EscortAI::Start: (script: %s, creature entry: %u) starts with 0 waypoints (possible missing entry in script_waypoint. Quest: %u).", me->GetScriptName().c_str(), me->GetEntry(), quest ? quest->GetQuestId() : 0); return; } - //set variables - m_bIsActiveAttacker = isActiveAttacker; - m_bIsRunning = run; - - m_uiPlayerGUID = playerGUID; - m_pQuestForEscort = quest; + // set variables + _activeAttacker = isActiveAttacker; + _running = run; + _playerGUID = playerGUID; + _escortQuest = quest; + _instantRespawn = instantRespawn; + _returnToStart = canLoopPath; - m_bCanInstantRespawn = instantRespawn; - m_bCanReturnToStart = canLoopPath; + if (_returnToStart && _instantRespawn) + TC_LOG_DEBUG("scripts", "EscortAI::Start: (script: %s, creature entry: %u) is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn.", me->GetScriptName().c_str(), me->GetEntry()); - if (m_bCanReturnToStart && m_bCanInstantRespawn) - TC_LOG_DEBUG("scripts", "EscortAI is set to return home after waypoint end and instant respawn at waypoint end. Creature will never despawn."); + me->GetMotionMaster()->MoveIdle(); + me->GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) - { - me->GetMotionMaster()->MovementExpired(); - me->GetMotionMaster()->MoveIdle(); - TC_LOG_DEBUG("scripts", "EscortAI start with WAYPOINT_MOTION_TYPE, changed to MoveIdle."); - } - - //disable npcflags + // disable npcflags me->SetUInt32Value(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_NONE); if (me->IsImmuneToNPC()) { - HasImmuneToNPCFlags = true; + _hasImmuneToNPCFlags = true; me->SetImmuneToNPC(false); } - TC_LOG_DEBUG("scripts", "EscortAI started with " UI64FMTD " waypoints. ActiveAttacker = %d, Run = %d, %s", uint64(WaypointList.size()), m_bIsActiveAttacker, m_bIsRunning, m_uiPlayerGUID.ToString().c_str()); + TC_LOG_DEBUG("scripts", "EscortAI::Start: (script: %s, creature entry: %u) started with %u waypoints. ActiveAttacker = %d, Run = %d, Player = %s", me->GetScriptName().c_str(), me->GetEntry(), uint32(_path.nodes.size()), _activeAttacker, _running, _playerGUID.ToString().c_str()); - CurrentWP = WaypointList.begin(); - - //Set initial speed - if (m_bIsRunning) - me->SetWalk(false); - else - me->SetWalk(true); + // set initial speed + me->SetWalk(!_running); + _started = false; AddEscortState(STATE_ESCORT_ESCORTING); } -void npc_escortAI::SetEscortPaused(bool on) +void EscortAI::SetEscortPaused(bool on) { if (!HasEscortState(STATE_ESCORT_ESCORTING)) return; if (on) - AddEscortState(STATE_ESCORT_PAUSED); - else - RemoveEscortState(STATE_ESCORT_PAUSED); -} - -bool npc_escortAI::SetNextWaypoint(uint32 pointId, float x, float y, float z, float orientation) -{ - me->UpdatePosition(x, y, z, orientation); - return SetNextWaypoint(pointId, false, true); -} - -bool npc_escortAI::SetNextWaypoint(uint32 pointId, bool setPosition, bool resetWaypointsOnFail) -{ - if (!WaypointList.empty()) - WaypointList.clear(); - - FillPointMovementListForCreature(); - - if (WaypointList.empty()) - return false; - - size_t const size = WaypointList.size(); - Escort_Waypoint waypoint(0, 0, 0, 0, 0); - do { - waypoint = WaypointList.front(); - WaypointList.pop_front(); - if (waypoint.id == pointId) - { - if (setPosition) - me->UpdatePosition(waypoint.x, waypoint.y, waypoint.z, me->GetOrientation()); - - CurrentWP = WaypointList.begin(); - return true; - } + AddEscortState(STATE_ESCORT_PAUSED); + if (MovementGenerator* movementGenerator = me->GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE)) + movementGenerator->Pause(0); } - while (!WaypointList.empty()); - - // we failed. - // we reset the waypoints in the start; if we pulled any, reset it again - if (resetWaypointsOnFail && size != WaypointList.size()) + else { - if (!WaypointList.empty()) - WaypointList.clear(); - - FillPointMovementListForCreature(); + RemoveEscortState(STATE_ESCORT_PAUSED); + _resume = true; } - - return false; } -bool npc_escortAI::GetWaypointPosition(uint32 pointId, float& x, float& y, float& z) +bool EscortAI::IsEscortNPC(bool onlyIfActive) const { - ScriptPointVector const* waypoints = sScriptSystemMgr->GetPointMoveList(me->GetEntry()); - if (!waypoints) - return false; + if (!onlyIfActive) + return true; - for (ScriptPointVector::const_iterator itr = waypoints->begin(); itr != waypoints->end(); ++itr) - { - if (itr->uiPointId == pointId) - { - x = itr->fX; - y = itr->fY; - z = itr->fZ; - return true; - } - } + if (GetEventStarterGUID()) + return true; return false; } diff --git a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h index 754d96dced9..4a514af33cc 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedEscortAI.h @@ -20,93 +20,60 @@ #define SC_ESCORTAI_H #include "ScriptedCreature.h" -#include "ScriptSystem.h" +#include "WaypointDefines.h" class Quest; #define DEFAULT_MAX_PLAYER_DISTANCE 50 -struct Escort_Waypoint +enum EscortState : uint32 { - Escort_Waypoint(uint32 _id, float _x, float _y, float _z, uint32 _w) - { - id = _id; - x = _x; - y = _y; - z = _z; - WaitTimeMs = _w; - } - - uint32 id; - float x; - float y; - float z; - uint32 WaitTimeMs; + STATE_ESCORT_NONE = 0x00, // nothing in progress + STATE_ESCORT_ESCORTING = 0x01, // escort is in progress + STATE_ESCORT_RETURNING = 0x02, // escort is returning after being in combat + STATE_ESCORT_PAUSED = 0x04 // escort is paused, wont continue with next waypoint }; -enum eEscortState -{ - STATE_ESCORT_NONE = 0x000, //nothing in progress - STATE_ESCORT_ESCORTING = 0x001, //escort are in progress - STATE_ESCORT_RETURNING = 0x002, //escort is returning after being in combat - STATE_ESCORT_PAUSED = 0x004 //will not proceed with waypoints before state is removed -}; - -struct TC_GAME_API npc_escortAI : public ScriptedAI +struct TC_GAME_API EscortAI : public ScriptedAI { public: - explicit npc_escortAI(Creature* creature); - ~npc_escortAI() { } - - // CreatureAI functions - void AttackStart(Unit* who) override; + explicit EscortAI(Creature* creature); + ~EscortAI() { } + void UpdateAI(uint32 diff) override; // the "internal" update, calls UpdateEscortAI() void MoveInLineOfSight(Unit* who) override; - void JustDied(Unit*) override; - - void JustRespawned() override; - + void JustAppeared() override; void ReturnToLastPoint(); - void EnterEvadeMode(EvadeReason /*why*/ = EVADE_REASON_OTHER) override; - - void UpdateAI(uint32 diff) override; // the "internal" update, calls UpdateEscortAI() - virtual void UpdateEscortAI(uint32 diff); // used when it's needed to add code in update (abilities, scripted events, etc) - void MovementInform(uint32, uint32) override; - // EscortAI functions - void AddWaypoint(uint32 id, float x, float y, float z, uint32 waitTime = 0); // waitTime is in ms - - //this will set the current position to x/y/z/o, and the current WP to pointId. - bool SetNextWaypoint(uint32 pointId, float x, float y, float z, float orientation); + virtual void UpdateEscortAI(uint32 diff); // used when it's needed to add code in update (abilities, scripted events, etc) - //this will set the current position to WP start position (if setPosition == true), - //and the current WP to pointId - bool SetNextWaypoint(uint32 pointId, bool setPosition = true, bool resetWaypointsOnFail = true); - - bool GetWaypointPosition(uint32 pointId, float& x, float& y, float& z); - - virtual void WaypointReached(uint32 pointId) = 0; - virtual void WaypointStart(uint32 /*pointId*/) { } + void AddWaypoint(uint32 id, float x, float y, float z, float orientation = 0.f, uint32 waitTime = 0); // waitTime is in ms void Start(bool isActiveAttacker = true, bool run = false, ObjectGuid playerGUID = ObjectGuid::Empty, Quest const* quest = nullptr, bool instantRespawn = false, bool canLoopPath = false, bool resetWaypoints = true); void SetRun(bool on = true); + void SetEscortPaused(bool on); + void SetPauseTimer(uint32 Timer) { _pauseTimer = Timer; } + + bool HasEscortState(uint32 escortState) { return (_escortState & escortState) != 0; } + virtual bool IsEscorted() const override { return (_escortState & STATE_ESCORT_ESCORTING); } + + void SetMaxPlayerDistance(float newMax) { _maxPlayerDistance = newMax; } + float GetMaxPlayerDistance() const { return _maxPlayerDistance; } - bool HasEscortState(uint32 escortState) { return (m_uiEscortState & escortState) != 0; } - virtual bool IsEscorted() const override { return (m_uiEscortState & STATE_ESCORT_ESCORTING); } + void SetDespawnAtEnd(bool despawn) { _despawnAtEnd = despawn; } + void SetDespawnAtFar(bool despawn) { _despawnAtFar = despawn; } - void SetMaxPlayerDistance(float newMax) { MaxPlayerDistance = newMax; } - float GetMaxPlayerDistance() const { return MaxPlayerDistance; } + bool GetAttack() const { return _activeAttacker; } // used in EnterEvadeMode override + void SetCanAttack(bool attack) { _activeAttacker = attack; } - void SetDespawnAtEnd(bool despawn) { DespawnAtEnd = despawn; } - void SetDespawnAtFar(bool despawn) { DespawnAtFar = despawn; } - bool GetAttack() const { return m_bIsActiveAttacker; }//used in EnterEvadeMode override - void SetCanAttack(bool attack) { m_bIsActiveAttacker = attack; } - ObjectGuid GetEventStarterGUID() const { return m_uiPlayerGUID; } + ObjectGuid GetEventStarterGUID() const { return _playerGUID; } + + virtual bool IsEscortNPC(bool isEscorting) const override; protected: Player* GetPlayerForEscort(); @@ -116,27 +83,29 @@ struct TC_GAME_API npc_escortAI : public ScriptedAI bool IsPlayerOrGroupInRange(); void FillPointMovementListForCreature(); - void AddEscortState(uint32 escortState) { m_uiEscortState |= escortState; } - void RemoveEscortState(uint32 escortState) { m_uiEscortState &= ~escortState; } - - ObjectGuid m_uiPlayerGUID; - uint32 m_uiWPWaitTimer; - uint32 m_uiPlayerCheckTimer; - uint32 m_uiEscortState; - float MaxPlayerDistance; - - Quest const* m_pQuestForEscort; //generally passed in Start() when regular escort script. - - std::list<Escort_Waypoint> WaypointList; - std::list<Escort_Waypoint>::iterator CurrentWP; - - bool m_bIsActiveAttacker; //obsolete, determined by faction. - bool m_bIsRunning; //all creatures are walking by default (has flag MOVEMENTFLAG_WALK) - bool m_bCanInstantRespawn; //if creature should respawn instantly after escort over (if not, database respawntime are used) - bool m_bCanReturnToStart; //if creature can walk same path (loop) without despawn. Not for regular escort quests. - bool DespawnAtEnd; - bool DespawnAtFar; - bool ScriptWP; - bool HasImmuneToNPCFlags; + void AddEscortState(uint32 escortState) { _escortState |= escortState; } + void RemoveEscortState(uint32 escortState) { _escortState &= ~escortState; } + + ObjectGuid _playerGUID; + uint32 _pauseTimer; + uint32 _playerCheckTimer; + uint32 _escortState; + float _maxPlayerDistance; + + Quest const* _escortQuest; // generally passed in Start() when regular escort script. + + WaypointPath _path; + + bool _activeAttacker; // obsolete, determined by faction. + bool _running; // all creatures are walking by default (has flag MOVEMENTFLAG_WALK) + bool _instantRespawn; // if creature should respawn instantly after escort over (if not, database respawntime are used) + bool _returnToStart; // if creature can walk same path (loop) without despawn. Not for regular escort quests. + bool _despawnAtEnd; + bool _despawnAtFar; + bool _manualPath; + bool _hasImmuneToNPCFlags; + bool _started; + bool _ended; + bool _resume; }; #endif diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp index b0b332afecd..6f50cdaac10 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.cpp @@ -145,7 +145,7 @@ void FollowerAI::JustDied(Unit* /*killer*/) } } -void FollowerAI::JustRespawned() +void FollowerAI::JustAppeared() { m_uiFollowState = STATE_FOLLOW_NONE; diff --git a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h index 7fe877a7589..1b13db8f7a3 100644 --- a/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h +++ b/src/server/game/AI/ScriptedAI/ScriptedFollowerAI.h @@ -41,8 +41,6 @@ class TC_GAME_API FollowerAI : public ScriptedAI explicit FollowerAI(Creature* creature); ~FollowerAI() { } - //virtual void WaypointReached(uint32 uiPointId) = 0; - void MovementInform(uint32 motionType, uint32 pointId) override; void AttackStart(Unit*) override; @@ -53,7 +51,7 @@ class TC_GAME_API FollowerAI : public ScriptedAI void JustDied(Unit*) override; - void JustRespawned() override; + void JustAppeared() override; void UpdateAI(uint32) override; //the "internal" update, calls UpdateFollowerAI() virtual void UpdateFollowerAI(uint32); //used when it's needed to add code in update (abilities, scripted events, etc) diff --git a/src/server/game/AI/SmartScripts/SmartAI.cpp b/src/server/game/AI/SmartScripts/SmartAI.cpp index 5aa2550a704..24719902659 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.cpp +++ b/src/server/game/AI/SmartScripts/SmartAI.cpp @@ -17,6 +17,7 @@ #include "SmartAI.h" #include "Creature.h" +#include "CreatureGroups.h" #include "DBCStructure.h" #include "GameObject.h" #include "Group.h" @@ -28,54 +29,12 @@ #include "ScriptMgr.h" #include "Vehicle.h" -SmartAI::SmartAI(Creature* c) : CreatureAI(c) +SmartAI::SmartAI(Creature* creature) : CreatureAI(creature), mIsCharmed(false), mFollowCreditType(0), mFollowArrivedTimer(0), mFollowCredit(0), mFollowArrivedEntry(0), mFollowDist(0.f), mFollowAngle(0.f), + _escortState(SMART_ESCORT_NONE), _escortNPCFlags(0), _escortInvokerCheckTimer(1000), _currentWaypointNode(0), _waypointReached(false), _waypointPauseTimer(0), _waypointPauseForced(false), _repeatWaypointPath(false), + _OOCReached(false), _waypointPathEnded(false), mRun(true), mEvadeDisabled(false), mCanAutoAttack(true), mCanCombatMove(true), mInvincibilityHpLevel(0), mDespawnTime(0), mDespawnState(0), mConditionsTimer(0), + _gossipReturn(false), mEscortQuestID(0) { - mIsCharmed = false; - // copy script to local (protection for table reload) - - mWayPoints = nullptr; - mEscortState = SMART_ESCORT_NONE; - mCurrentWPID = 0;//first wp id is 1 !! - mWPReached = false; - mWPPauseTimer = 0; - mEscortNPCFlags = 0; - mLastWP = nullptr; - - mCanRepeatPath = false; - - // Spawn in run mode - me->SetWalk(false); - mRun = false; - mEvadeDisabled = false; - - mLastOOCPos = me->GetPosition(); - - mCanAutoAttack = true; - mCanCombatMove = true; - - mForcedPaused = false; - mLastWPIDReached = 0; - - mEscortQuestID = 0; - - mDespawnTime = 0; - mDespawnState = 0; - - mEscortInvokerCheckTimer = 1000; - mFollowGuid.Clear(); - mFollowDist = 0; - mFollowAngle = 0; - mFollowCredit = 0; - mFollowArrivedEntry = 0; - mFollowCreditType = 0; - mFollowArrivedTimer = 0; - mInvincibilityHpLevel = 0; - - mJustReset = false; - mConditionsTimer = 0; - mHasConditions = sConditionMgr->HasConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, c->GetEntry()); - - _gossipReturn = false; + mHasConditions = sConditionMgr->HasConditionsForNotGroupedEntry(CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE, creature->GetEntry()); } bool SmartAI::IsAIControlled() const @@ -83,92 +42,68 @@ bool SmartAI::IsAIControlled() const return !mIsCharmed; } -void SmartAI::UpdateDespawn(uint32 diff) -{ - if (mDespawnState <= 1 || mDespawnState > 3) - return; - - if (mDespawnTime < diff) - { - if (mDespawnState == 2) - { - me->SetVisible(false); - mDespawnTime = 5000; - mDespawnState++; - } - else - me->DespawnOrUnsummon(); - } else mDespawnTime -= diff; -} - -WayPoint* SmartAI::GetNextWayPoint() -{ - if (!mWayPoints || mWayPoints->empty()) - return nullptr; - - mCurrentWPID++; - WPPath::const_iterator itr = mWayPoints->find(mCurrentWPID); - if (itr != mWayPoints->end()) - { - mLastWP = (*itr).second; - if (mLastWP->id != mCurrentWPID) - { - TC_LOG_ERROR("misc", "SmartAI::GetNextWayPoint: Got not expected waypoint id %u, expected %u", mLastWP->id, mCurrentWPID); - } - return (*itr).second; - } - return nullptr; -} - -void SmartAI::StartPath(bool run, uint32 path, bool repeat, Unit* invoker) +void SmartAI::StartPath(bool run/* = false*/, uint32 pathId/* = 0*/, bool repeat/* = false*/, Unit* invoker/* = nullptr*/, uint32 nodeId/* = 1*/) { - if (me->IsInCombat())// no wp movement in combat + if (me->IsInCombat()) // no wp movement in combat { - TC_LOG_ERROR("misc", "SmartAI::StartPath: Creature entry %u wanted to start waypoint movement while in combat, ignoring.", me->GetEntry()); + TC_LOG_ERROR("misc", "SmartAI::StartPath: Creature entry %u wanted to start waypoint movement (%u) while in combat, ignoring.", me->GetEntry(), pathId); return; } if (HasEscortState(SMART_ESCORT_ESCORTING)) StopPath(); - if (path) + SetRun(run); + + if (pathId) { - if (!LoadPath(path)) + if (!LoadPath(pathId)) return; } - if (!mWayPoints || mWayPoints->empty()) + if (_path.nodes.empty()) return; - if (WayPoint* wp = GetNextWayPoint()) - { - AddEscortState(SMART_ESCORT_ESCORTING); - mCanRepeatPath = repeat; + _currentWaypointNode = nodeId; + _waypointPathEnded = false; - SetRun(run); + _repeatWaypointPath = repeat; - if (invoker && invoker->GetTypeId() == TYPEID_PLAYER) - { - mEscortNPCFlags = me->GetUInt32Value(UNIT_NPC_FLAGS); - me->SetUInt32Value(UNIT_NPC_FLAGS, 0); - } + // Do not use AddEscortState, removing everything from previous + _escortState = SMART_ESCORT_ESCORTING; - mLastOOCPos = me->GetPosition(); - me->GetMotionMaster()->MovePoint(wp->id, wp->x, wp->y, wp->z); - GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_START, nullptr, wp->id, GetScript()->GetPathId()); + if (invoker && invoker->GetTypeId() == TYPEID_PLAYER) + { + _escortNPCFlags = me->GetUInt32Value(UNIT_NPC_FLAGS); + me->SetFlag(UNIT_NPC_FLAGS, 0); } + + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_START, nullptr, _currentWaypointNode, GetScript()->GetPathId()); + + me->GetMotionMaster()->MovePath(_path, _repeatWaypointPath); } bool SmartAI::LoadPath(uint32 entry) { if (HasEscortState(SMART_ESCORT_ESCORTING)) return false; - mWayPoints = sSmartWaypointMgr->GetPath(entry); - if (!mWayPoints) + + WaypointPath const* path = sSmartWaypointMgr->GetPath(entry); + if (!path || path->nodes.empty()) { GetScript()->SetPathId(0); return false; } + + _path.id = path->id; + _path.nodes = path->nodes; + for (WaypointNode& waypoint : _path.nodes) + { + Trinity::NormalizeMapCoord(waypoint.x); + Trinity::NormalizeMapCoord(waypoint.y); + waypoint.moveType = mRun ? WAYPOINT_MOVE_TYPE_RUN : WAYPOINT_MOVE_TYPE_WALK; + } + GetScript()->SetPathId(entry); return true; } @@ -176,65 +111,88 @@ bool SmartAI::LoadPath(uint32 entry) void SmartAI::PausePath(uint32 delay, bool forced) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + me->PauseMovement(delay, MOTION_SLOT_IDLE, forced); + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + { + std::pair<uint32, uint32> waypointInfo = me->GetCurrentWaypointInfo(); + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_PAUSED, nullptr, waypointInfo.first, waypointInfo.second); + } return; + } + if (HasEscortState(SMART_ESCORT_PAUSED)) { - TC_LOG_ERROR("misc", "SmartAI::PausePath: Creature entry %u wanted to pause waypoint movement while already paused, ignoring.", me->GetEntry()); + TC_LOG_ERROR("misc", "SmartAI::PausePath: Creature entry %u wanted to pause waypoint (current waypoint: %u) movement while already paused, ignoring.", me->GetEntry(), _currentWaypointNode); return; } - mForcedPaused = forced; - mLastOOCPos = me->GetPosition(); - AddEscortState(SMART_ESCORT_PAUSED); - mWPPauseTimer = delay; + + _waypointPauseTimer = delay; + if (forced) { + _waypointPauseForced = forced; SetRun(mRun); - me->StopMoving();//force stop - me->GetMotionMaster()->MoveIdle();//force stop + me->PauseMovement(); + me->SetHomePosition(me->GetPosition()); } - GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_PAUSED, nullptr, mLastWP->id, GetScript()->GetPathId()); + else + _waypointReached = false; + + AddEscortState(SMART_ESCORT_PAUSED); + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_PAUSED, nullptr, _currentWaypointNode, GetScript()->GetPathId()); } void SmartAI::StopPath(uint32 DespawnTime, uint32 quest, bool fail) { if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + std::pair<uint32, uint32> waypointInfo = { 0, 0 }; + if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + waypointInfo = me->GetCurrentWaypointInfo(); + + if (mDespawnState != 2) + SetDespawnTime(DespawnTime); + + me->GetMotionMaster()->MoveIdle(); + + if (waypointInfo.first) + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_STOPPED, nullptr, waypointInfo.first, waypointInfo.second); + + if (!fail) + { + if (waypointInfo.first) + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, waypointInfo.first, waypointInfo.second); + if (mDespawnState == 1) + StartDespawn(); + } return; + } if (quest) mEscortQuestID = quest; - SetDespawnTime(DespawnTime); - //mDespawnTime = DespawnTime; - mLastOOCPos = me->GetPosition(); - me->StopMoving();//force stop + if (mDespawnState != 2) + SetDespawnTime(DespawnTime); + me->GetMotionMaster()->MoveIdle(); - GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_STOPPED, nullptr, mLastWP->id, GetScript()->GetPathId()); + + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_STOPPED, nullptr, _currentWaypointNode, GetScript()->GetPathId()); + EndPath(fail); } void SmartAI::EndPath(bool fail) { - GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, mLastWP->id, GetScript()->GetPathId()); - RemoveEscortState(SMART_ESCORT_ESCORTING | SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING); - mWayPoints = nullptr; - mCurrentWPID = 0; - mWPPauseTimer = 0; - mLastWP = nullptr; - - if (mEscortNPCFlags) - { - me->SetUInt32Value(UNIT_NPC_FLAGS, mEscortNPCFlags); - mEscortNPCFlags = 0; - } + _path.nodes.clear(); + _waypointPauseTimer = 0; - if (mCanRepeatPath) + if (_escortNPCFlags) { - if (IsAIControlled()) - StartPath(mRun, GetScript()->GetPathId(), true); + me->SetFlag(UNIT_NPC_FLAGS, _escortNPCFlags); + _escortNPCFlags = 0; } - else - GetScript()->SetPathId(0); ObjectVector const* targets = GetScript()->GetStoredTargetVector(SMART_ESCORT_TARGETS, *me); if (targets && mEscortQuestID) @@ -279,126 +237,57 @@ void SmartAI::EndPath(bool fail) } } + // End Path events should be only processed if it was SUCCESSFUL stop or stop called by SMART_ACTION_WAYPOINT_STOP + if (fail) + return; + + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, _currentWaypointNode, GetScript()->GetPathId()); + + if (_repeatWaypointPath) + { + if (IsAIControlled()) + StartPath(mRun, GetScript()->GetPathId(), _repeatWaypointPath); + } + else + GetScript()->SetPathId(0); + if (mDespawnState == 1) StartDespawn(); } void SmartAI::ResumePath() { - SetRun(mRun); - if (mLastWP) - me->GetMotionMaster()->MovePoint(mLastWP->id, mLastWP->x, mLastWP->y, mLastWP->z); -} + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_RESUMED, nullptr, _currentWaypointNode, GetScript()->GetPathId()); -void SmartAI::ReturnToLastOOCPos() -{ - if (!IsAIControlled()) - return; + RemoveEscortState(SMART_ESCORT_PAUSED); + + _waypointPauseForced = false; + _waypointReached = false; + _waypointPauseTimer = 0; SetRun(mRun); - me->GetMotionMaster()->MovePoint(SMART_ESCORT_LAST_OOC_POINT, mLastOOCPos); + me->ResumeMovement(); } -void SmartAI::UpdatePath(const uint32 diff) +void SmartAI::ReturnToLastOOCPos() { - if (!HasEscortState(SMART_ESCORT_ESCORTING)) - return; - if (mEscortInvokerCheckTimer < diff) - { - // Escort failed, no players in range - if (!IsEscortInvokerInRange()) - { - StopPath(0, mEscortQuestID, true); - - // allow to properly hook out of range despawn action, which in most cases should perform the same operation as dying - GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, me); - me->DespawnOrUnsummon(1); - return; - } - mEscortInvokerCheckTimer = 1000; - } - else - mEscortInvokerCheckTimer -= diff; - - // handle pause - if (HasEscortState(SMART_ESCORT_PAUSED)) - { - if (mWPPauseTimer < diff) - { - if (!me->IsInCombat() && !HasEscortState(SMART_ESCORT_RETURNING) && (mWPReached || mLastWPIDReached == SMART_ESCORT_LAST_OOC_POINT || mForcedPaused)) - { - GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_RESUMED, nullptr, mLastWP->id, GetScript()->GetPathId()); - RemoveEscortState(SMART_ESCORT_PAUSED); - if (mForcedPaused)// if paused between 2 wps resend movement - { - ResumePath(); - mWPReached = false; - mForcedPaused = false; - } - if (mLastWPIDReached == SMART_ESCORT_LAST_OOC_POINT) - mWPReached = true; - } - - mWPPauseTimer = 0; - } - else - mWPPauseTimer -= diff; - } - - if (HasEscortState(SMART_ESCORT_RETURNING)) - { - if (mWPReached)//reached OOC WP - { - RemoveEscortState(SMART_ESCORT_RETURNING); - if (!HasEscortState(SMART_ESCORT_PAUSED)) - ResumePath(); - mWPReached = false; - } - } - - if ((!me->HasReactState(REACT_PASSIVE) && me->IsInCombat()) || HasEscortState(SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING)) + if (!IsAIControlled()) return; - // handle next wp - if (mWPReached)//reached WP - { - mWPReached = false; - if (mCurrentWPID == GetWPCount()) - { - EndPath(); - } - else if (WayPoint* wp = GetNextWayPoint()) - { - SetRun(mRun); - me->GetMotionMaster()->MovePoint(wp->id, wp->x, wp->y, wp->z); - } - } + me->SetWalk(false); + me->GetMotionMaster()->MovePoint(SMART_ESCORT_LAST_OOC_POINT, me->GetHomePosition()); } void SmartAI::UpdateAI(uint32 diff) { CheckConditions(diff); + GetScript()->OnUpdate(diff); + UpdatePath(diff); + UpdateFollow(diff); UpdateDespawn(diff); - /// @todo move to void - if (mFollowGuid) - { - if (mFollowArrivedTimer < diff) - { - if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, true)) - { - StopFollow(true); - return; - } - - mFollowArrivedTimer = 1000; - } - else - mFollowArrivedTimer -= diff; - } - if (!IsAIControlled()) return; @@ -450,24 +339,70 @@ bool SmartAI::IsEscortInvokerInRange() return true; } -void SmartAI::MovepointReached(uint32 id) +///@todo move escort related logic +void SmartAI::WaypointPathStarted(uint32 nodeId, uint32 pathId) +{ + if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_START, nullptr, nodeId, pathId); + return; + } +} + +///@todo Implement new smart event SMART_EVENT_WAYPOINT_STARTED +void SmartAI::WaypointStarted(uint32 /*nodeId*/, uint32 /*pathId*/) +{ +} + +void SmartAI::WaypointReached(uint32 nodeId, uint32 pathId) { - if (id != SMART_ESCORT_LAST_OOC_POINT && mLastWPIDReached != id) - GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, id); + if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, nodeId, pathId); + return; + } + + _currentWaypointNode = nodeId; + + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_REACHED, nullptr, _currentWaypointNode, pathId); + + if (_waypointPauseTimer && !_waypointPauseForced) + { + _waypointReached = true; + me->PauseMovement(); + me->SetHomePosition(me->GetPosition()); + } + else if (HasEscortState(SMART_ESCORT_ESCORTING) && me->GetMotionMaster()->GetCurrentMovementGeneratorType() == WAYPOINT_MOTION_TYPE) + { + if (_currentWaypointNode == _path.nodes.size()) + _waypointPathEnded = true; + else + SetRun(mRun); + } +} - mLastWPIDReached = id; - mWPReached = true; +///@todo move escort related logic +void SmartAI::WaypointPathEnded(uint32 nodeId, uint32 pathId) +{ + if (!HasEscortState(SMART_ESCORT_ESCORTING)) + { + GetScript()->ProcessEventsFor(SMART_EVENT_WAYPOINT_ENDED, nullptr, nodeId, pathId); + return; + } } -void SmartAI::MovementInform(uint32 MovementType, uint32 Data) +void SmartAI::MovementInform(uint32 type, uint32 id) { - if ((MovementType == POINT_MOTION_TYPE && Data == SMART_ESCORT_LAST_OOC_POINT) || MovementType == FOLLOW_MOTION_TYPE) + if (type == POINT_MOTION_TYPE && id == SMART_ESCORT_LAST_OOC_POINT) me->ClearUnitState(UNIT_STATE_EVADE); - GetScript()->ProcessEventsFor(SMART_EVENT_MOVEMENTINFORM, nullptr, MovementType, Data); - if (MovementType != POINT_MOTION_TYPE || !HasEscortState(SMART_ESCORT_ESCORTING)) + GetScript()->ProcessEventsFor(SMART_EVENT_MOVEMENTINFORM, nullptr, type, id); + + if (!HasEscortState(SMART_ESCORT_ESCORTING)) return; - MovepointReached(Data); + + if (type == POINT_MOTION_TYPE && id == SMART_ESCORT_LAST_OOC_POINT) + _OOCReached = true; } void SmartAI::EnterEvadeMode(EvadeReason /*why*/) @@ -489,10 +424,16 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) me->AddUnitState(UNIT_STATE_EVADE); - GetScript()->ProcessEventsFor(SMART_EVENT_EVADE);//must be after aura clear so we can cast spells from db + GetScript()->ProcessEventsFor(SMART_EVENT_EVADE); // must be after _EnterEvadeMode (spells, auras, ...) SetRun(mRun); - if (HasEscortState(SMART_ESCORT_ESCORTING)) + + if (Unit* owner = me->GetCharmerOrOwner()) + { + me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); + me->ClearUnitState(UNIT_STATE_EVADE); + } + else if (HasEscortState(SMART_ESCORT_ESCORTING)) { AddEscortState(SMART_ESCORT_RETURNING); ReturnToLastOOCPos(); @@ -503,16 +444,11 @@ void SmartAI::EnterEvadeMode(EvadeReason /*why*/) // evade is not cleared in MoveFollow, so we can't keep it me->ClearUnitState(UNIT_STATE_EVADE); } - else if (Unit* owner = me->GetCharmerOrOwner()) - { - me->GetMotionMaster()->MoveFollow(owner, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); - me->ClearUnitState(UNIT_STATE_EVADE); - } else me->GetMotionMaster()->MoveTargetedHome(); - if (!HasEscortState(SMART_ESCORT_ESCORTING)) //dont mess up escort movement after combat - SetRun(mRun); + if (!me->HasUnitState(UNIT_STATE_EVADE)) + GetScript()->OnReset(); } void SmartAI::MoveInLineOfSight(Unit* who) @@ -525,7 +461,7 @@ void SmartAI::MoveInLineOfSight(Unit* who) if (!IsAIControlled()) return; - if (AssistPlayerInCombatAgainst(who)) + if (HasEscortState(SMART_ESCORT_ESCORTING) && AssistPlayerInCombatAgainst(who)) return; CreatureAI::MoveInLineOfSight(who); @@ -544,19 +480,32 @@ bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) if (!who || !who->GetVictim()) return false; - //experimental (unknown) flag not present + // experimental (unknown) flag not present if (!(me->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST)) return false; - //not a player + // not a player if (!who->EnsureVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()) return false; - //never attack friendly + if (!who->isInAccessiblePlaceFor(me)) + return false; + + if (!CanAIAttack(who)) + return false; + + // we cannot attack in evade mode + if (me->IsInEvadeMode()) + return false; + + // or if enemy is in evade mode + if (who->GetTypeId() == TYPEID_UNIT && who->ToCreature()->IsInEvadeMode()) + return false; + if (!me->IsValidAssistTarget(who->GetVictim())) return false; - //too far away and no free sight? + // too far away and no free sight if (me->IsWithinDistInMap(who, SMART_MAX_AID_DIST) && me->IsWithinLOSInMap(who)) { me->EngageWithTarget(who); @@ -566,18 +515,21 @@ bool SmartAI::AssistPlayerInCombatAgainst(Unit* who) return false; } -void SmartAI::JustRespawned() +void SmartAI::JustAppeared() { mDespawnTime = 0; mDespawnState = 0; - mEscortState = SMART_ESCORT_NONE; + _escortState = SMART_ESCORT_NONE; + me->SetVisible(true); + if (me->GetFaction() != me->GetCreatureTemplate()->faction) me->RestoreFaction(); - mJustReset = true; - JustReachedHome(); + + GetScript()->OnReset(); GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); - mFollowGuid.Clear();//do not reset follower on Reset(), we need it after combat evade + + mFollowGuid.Clear(); // do not reset follower on Reset(), we need it after combat evade mFollowDist = 0; mFollowAngle = 0; mFollowCredit = 0; @@ -589,16 +541,18 @@ void SmartAI::JustRespawned() void SmartAI::JustReachedHome() { GetScript()->OnReset(); + GetScript()->ProcessEventsFor(SMART_EVENT_REACHED_HOME); - if (!mJustReset) + CreatureGroup* formation = me->GetFormation(); + if (!formation || formation->getLeader() == me || !formation->isFormed()) { - GetScript()->ProcessEventsFor(SMART_EVENT_REACHED_HOME); - - if (!UpdateVictim() && me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE && me->GetWaypointPath()) + if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_IDLE) != WAYPOINT_MOTION_TYPE && me->GetWaypointPath()) me->GetMotionMaster()->MovePath(me->GetWaypointPath(), true); + else + me->ResumeMovement(); } - - mJustReset = false; + else if (formation->isFormed()) + me->GetMotionMaster()->MoveIdle(); // wait the order of leader } void SmartAI::EnterCombat(Unit* enemy) @@ -607,24 +561,14 @@ void SmartAI::EnterCombat(Unit* enemy) me->InterruptNonMeleeSpells(false); // must be before ProcessEvents GetScript()->ProcessEventsFor(SMART_EVENT_AGGRO, enemy); - - if (!IsAIControlled()) - return; - mLastOOCPos = me->GetPosition(); - SetRun(mRun); - if (me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == POINT_MOTION_TYPE) - me->GetMotionMaster()->MovementExpired(); } void SmartAI::JustDied(Unit* killer) { - GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, killer); if (HasEscortState(SMART_ESCORT_ESCORTING)) - { EndPath(true); - me->StopMoving();//force stop - me->GetMotionMaster()->MoveIdle(); - } + + GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, killer); } void SmartAI::KilledUnit(Unit* victim) @@ -642,15 +586,21 @@ void SmartAI::AttackStart(Unit* who) // dont allow charmed npcs to act on their own if (!IsAIControlled()) { - if (who && mCanAutoAttack) - me->Attack(who, true); + if (who) + me->Attack(who, mCanAutoAttack); return; } - if (who && me->Attack(who, me->IsWithinMeleeRange(who))) + if (who && me->Attack(who, mCanAutoAttack)) { + me->GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); + me->PauseMovement(); + if (mCanCombatMove) + { + SetRun(mRun); me->GetMotionMaster()->MoveChase(who); + } } } @@ -713,12 +663,9 @@ void SmartAI::PassengerBoarded(Unit* who, int8 seatId, bool apply) void SmartAI::InitializeAI() { GetScript()->OnInitialize(me); + if (!me->isDead()) - { - mJustReset = true; - JustReachedHome(); - GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); - } + GetScript()->OnReset(); } void SmartAI::OnCharmed(bool apply) @@ -727,13 +674,13 @@ void SmartAI::OnCharmed(bool apply) { if (HasEscortState(SMART_ESCORT_ESCORTING | SMART_ESCORT_PAUSED | SMART_ESCORT_RETURNING)) EndPath(true); - me->StopMoving(); } + mIsCharmed = apply; if (!apply && !me->IsInEvadeMode()) { - if (mCanRepeatPath) + if (_repeatWaypointPath) StartPath(mRun, GetScript()->GetPathId(), true); else me->SetWalk(!mRun); @@ -826,30 +773,21 @@ void SmartAI::SetCombatMove(bool on) { if (mCanCombatMove == on) return; + mCanCombatMove = on; + if (!IsAIControlled()) return; - if (!HasEscortState(SMART_ESCORT_ESCORTING)) + + if (me->IsEngaged()) { - if (on && me->GetVictim()) + if (on && !me->HasReactState(REACT_PASSIVE) && me->GetVictim() && me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == MAX_MOTION_TYPE) { - if (me->GetMotionMaster()->GetCurrentMovementGeneratorType() == IDLE_MOTION_TYPE) - { - SetRun(mRun); - me->GetMotionMaster()->MoveChase(me->GetVictim()); - me->CastStop(); - } - } - else - { - if (me->HasUnitState(UNIT_STATE_CONFUSED_MOVE | UNIT_STATE_FLEEING_MOVE)) - return; - - me->GetMotionMaster()->MovementExpired(); - me->GetMotionMaster()->Clear(true); - me->StopMoving(); - me->GetMotionMaster()->MoveIdle(); + SetRun(mRun); + me->GetMotionMaster()->MoveChase(me->GetVictim()); } + else if (!on && me->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == CHASE_MOTION_TYPE) + me->GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); } } @@ -881,7 +819,6 @@ void SmartAI::StopFollow(bool complete) mFollowArrivedTimer = 1000; mFollowArrivedEntry = 0; mFollowCreditType = 0; - me->StopMoving(); me->GetMotionMaster()->MoveIdle(); if (!complete) @@ -949,6 +886,96 @@ void SmartAI::CheckConditions(uint32 diff) mConditionsTimer -= diff; } +void SmartAI::UpdatePath(uint32 diff) +{ + if (!HasEscortState(SMART_ESCORT_ESCORTING)) + return; + + if (_escortInvokerCheckTimer < diff) + { + if (!IsEscortInvokerInRange()) + { + StopPath(0, mEscortQuestID, true); + + // allow to properly hook out of range despawn action, which in most cases should perform the same operation as dying + GetScript()->ProcessEventsFor(SMART_EVENT_DEATH, me); + me->DespawnOrUnsummon(); + return; + } + _escortInvokerCheckTimer = 1000; + } + else + _escortInvokerCheckTimer -= diff; + + // handle pause + if (HasEscortState(SMART_ESCORT_PAUSED) && (_waypointReached || _waypointPauseForced)) + { + if (_waypointPauseTimer <= diff) + { + if (!me->IsInCombat() && !HasEscortState(SMART_ESCORT_RETURNING)) + ResumePath(); + } + else + _waypointPauseTimer -= diff; + } + else if (_waypointPathEnded) // end path + { + _waypointPathEnded = false; + StopPath(); + return; + } + + if (HasEscortState(SMART_ESCORT_RETURNING)) + { + if (_OOCReached) // reached OOC WP + { + _OOCReached = false; + RemoveEscortState(SMART_ESCORT_RETURNING); + if (!HasEscortState(SMART_ESCORT_PAUSED)) + ResumePath(); + } + } +} + +void SmartAI::UpdateFollow(uint32 diff) +{ + if (mFollowGuid) + { + if (mFollowArrivedTimer < diff) + { + if (me->FindNearestCreature(mFollowArrivedEntry, INTERACTION_DISTANCE, true)) + { + StopFollow(true); + return; + } + + mFollowArrivedTimer = 1000; + } + else + mFollowArrivedTimer -= diff; + } +} + +void SmartAI::UpdateDespawn(uint32 diff) +{ + if (mDespawnState <= 1 || mDespawnState > 3) + return; + + if (mDespawnTime < diff) + { + if (mDespawnState == 2) + { + me->SetVisible(false); + mDespawnTime = 5000; + mDespawnState++; + } + else + me->DespawnOrUnsummon(); + } + else + mDespawnTime -= diff; +} + void SmartGameObjectAI::UpdateAI(uint32 diff) { GetScript()->OnUpdate(diff); @@ -957,10 +984,6 @@ void SmartGameObjectAI::UpdateAI(uint32 diff) void SmartGameObjectAI::InitializeAI() { GetScript()->OnInitialize(me); - - // do not call respawn event if go is not spawned - if (me->isSpawned()) - GetScript()->ProcessEventsFor(SMART_EVENT_RESPAWN); //Reset(); } diff --git a/src/server/game/AI/SmartScripts/SmartAI.h b/src/server/game/AI/SmartScripts/SmartAI.h index b5ed74c4edb..ecf9aaeb97a 100644 --- a/src/server/game/AI/SmartScripts/SmartAI.h +++ b/src/server/game/AI/SmartScripts/SmartAI.h @@ -23,8 +23,7 @@ #include "GameObjectAI.h" #include "Position.h" #include "SmartScript.h" - -struct WayPoint; +#include "WaypointDefines.h" enum SmartEscortState { @@ -43,35 +42,42 @@ enum SmartEscortVars class TC_GAME_API SmartAI : public CreatureAI { public: - ~SmartAI(){ } + ~SmartAI() { } explicit SmartAI(Creature* c); + // core related + static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; } + // Check whether we are currently permitted to make the creature take action bool IsAIControlled() const; // Start moving to the desired MovePoint - void StartPath(bool run = false, uint32 path = 0, bool repeat = false, Unit* invoker = nullptr); + void StartPath(bool run = false, uint32 pathId = 0, bool repeat = false, Unit* invoker = nullptr, uint32 nodeId = 1); bool LoadPath(uint32 entry); void PausePath(uint32 delay, bool forced = false); void StopPath(uint32 DespawnTime = 0, uint32 quest = 0, bool fail = false); void EndPath(bool fail = false); void ResumePath(); - WayPoint* GetNextWayPoint(); - bool HasEscortState(uint32 uiEscortState) const { return (mEscortState & uiEscortState) != 0; } - void AddEscortState(uint32 uiEscortState) { mEscortState |= uiEscortState; } - void RemoveEscortState(uint32 uiEscortState) { mEscortState &= ~uiEscortState; } + bool HasEscortState(uint32 uiEscortState) const { return (_escortState & uiEscortState) != 0; } + void AddEscortState(uint32 uiEscortState) { _escortState |= uiEscortState; } + void RemoveEscortState(uint32 uiEscortState) { _escortState &= ~uiEscortState; } void SetAutoAttack(bool on) { mCanAutoAttack = on; } void SetCombatMove(bool on); bool CanCombatMove() { return mCanCombatMove; } void SetFollow(Unit* target, float dist = 0.0f, float angle = 0.0f, uint32 credit = 0, uint32 end = 0, uint32 creditType = 0); void StopFollow(bool complete); + bool IsEscortInvokerInRange(); + + void WaypointPathStarted(uint32 nodeId, uint32 pathId) override; + void WaypointStarted(uint32 nodeId, uint32 pathId) override; + void WaypointReached(uint32 nodeId, uint32 pathId) override; + void WaypointPathEnded(uint32 nodeId, uint32 pathId) override; void SetScript9(SmartScriptHolder& e, uint32 entry, Unit* invoker); SmartScript* GetScript() { return &mScript; } - bool IsEscortInvokerInRange(); // Called when creature is spawned or respawned - void JustRespawned() override; + void JustAppeared() override; // Called at reaching home after evade, InitializeAI(), EnterEvadeMode() for resetting variables void JustReachedHome() override; @@ -157,12 +163,6 @@ class TC_GAME_API SmartAI : public CreatureAI // Used in scripts to share variables ObjectGuid GetGUID(int32 id = 0) const override; - //core related - static int32 Permissible(Creature const* /*creature*/) { return PERMIT_BASE_NO; } - - // Called at movepoint reached - void MovepointReached(uint32 id); - // Makes the creature run/walk void SetRun(bool run = true); @@ -183,8 +183,6 @@ class TC_GAME_API SmartAI : public CreatureAI void QuestReward(Player* player, Quest const* quest, uint32 opt) override; void OnGameEvent(bool start, uint16 eventId) override; - uint32 mEscortQuestID; - void SetDespawnTime (uint32 t) { mDespawnTime = t; @@ -194,11 +192,22 @@ class TC_GAME_API SmartAI : public CreatureAI void OnSpellClick(Unit* clicker, bool& result) override; - void SetWPPauseTimer(uint32 time) { mWPPauseTimer = time; } + void SetWPPauseTimer(uint32 time) { _waypointPauseTimer = time; } void SetGossipReturn(bool val) { _gossipReturn = val; } + void SetEscortQuest(uint32 questID) { mEscortQuestID = questID; } + private: + bool AssistPlayerInCombatAgainst(Unit* who); + void ReturnToLastOOCPos(); + void CheckConditions(uint32 diff); + void UpdatePath(uint32 diff); + void UpdateFollow(uint32 diff); + void UpdateDespawn(uint32 diff); + + SmartScript mScript; + bool mIsCharmed; uint32 mFollowCreditType; uint32 mFollowArrivedTimer; @@ -208,41 +217,35 @@ class TC_GAME_API SmartAI : public CreatureAI float mFollowDist; float mFollowAngle; - void ReturnToLastOOCPos(); - void UpdatePath(const uint32 diff); - SmartScript mScript; - WPPath* mWayPoints; - uint32 mEscortState; - uint32 mCurrentWPID; - uint32 mLastWPIDReached; - bool mWPReached; - uint32 mWPPauseTimer; - uint32 mEscortNPCFlags; - WayPoint* mLastWP; - Position mLastOOCPos;//set on enter combat - uint32 GetWPCount() const { return mWayPoints ? uint32(mWayPoints->size()) : 0; } - bool mCanRepeatPath; + uint32 _escortState; + uint32 _escortNPCFlags; + uint32 _escortInvokerCheckTimer; + WaypointPath _path; + uint32 _currentWaypointNode; + bool _waypointReached; + uint32 _waypointPauseTimer; + bool _waypointPauseForced; + bool _repeatWaypointPath; + bool _OOCReached; + bool _waypointPathEnded; + bool mRun; bool mEvadeDisabled; bool mCanAutoAttack; bool mCanCombatMove; - bool mForcedPaused; uint32 mInvincibilityHpLevel; - bool AssistPlayerInCombatAgainst(Unit* who); uint32 mDespawnTime; uint32 mDespawnState; - void UpdateDespawn(uint32 diff); - uint32 mEscortInvokerCheckTimer; - bool mJustReset; // Vehicle conditions - void CheckConditions(uint32 diff); bool mHasConditions; uint32 mConditionsTimer; // Gossip bool _gossipReturn; + + uint32 mEscortQuestID; }; class TC_GAME_API SmartGameObjectAI : public GameObjectAI diff --git a/src/server/game/AI/SmartScripts/SmartScript.cpp b/src/server/game/AI/SmartScripts/SmartScript.cpp index 7b7bb00416d..30fa5deadc3 100644 --- a/src/server/game/AI/SmartScripts/SmartScript.cpp +++ b/src/server/game/AI/SmartScripts/SmartScript.cpp @@ -39,6 +39,7 @@ #include "SpellMgr.h" #include "TemporarySummon.h" #include "Vehicle.h" +#include "WaypointDefines.h" #include <G3D/Quat.h> SmartScript::SmartScript() @@ -309,7 +310,11 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { if (IsUnit(target)) { - target->PlayDirectSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr); + if (e.action.sound.distance == 1) + target->PlayDistanceSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr); + else + target->PlayDirectSound(e.action.sound.sound, e.action.sound.onlySelf ? target->ToPlayer() : nullptr); + TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_SOUND: target: %s (GuidLow: %u), sound: %u, onlyself: %u", target->GetName().c_str(), target->GetGUID().GetCounter(), e.action.sound.sound, e.action.sound.onlySelf); } @@ -936,7 +941,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u case 1: instance->SetBossState(e.action.setInstanceData.field, static_cast<EncounterState>(e.action.setInstanceData.data)); TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction: SMART_ACTION_SET_INST_DATA: SetBossState BossId: %u, State: %u (%s)", - e.action.setInstanceData.field, e.action.setInstanceData.data, InstanceScript::GetBossStateName(e.action.setInstanceData.data).c_str()); + e.action.setInstanceData.field, e.action.setInstanceData.data, InstanceScript::GetBossStateName(e.action.setInstanceData.data)); break; default: // Static analysis break; @@ -1039,8 +1044,8 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u else creature->DespawnOrUnsummon(respawnDelay); } - else if (GameObject* go = target->ToGameObject()) - go->SetRespawnTime(respawnDelay); + else if (GameObject* goTarget = target->ToGameObject()) + goTarget->SetRespawnTime(respawnDelay); } break; } @@ -1331,7 +1336,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u uint32 quest = e.action.wpStart.quest; uint32 DespawnTime = e.action.wpStart.despawnTime; - ENSURE_AI(SmartAI, me->AI())->mEscortQuestID = quest; + ENSURE_AI(SmartAI, me->AI())->SetEscortQuest(quest); ENSURE_AI(SmartAI, me->AI())->SetDespawnTime(DespawnTime); break; } @@ -1588,10 +1593,10 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsSmart(creature)) ENSURE_AI(SmartAI, creature->AI())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } - else if (GameObject* go = target->ToGameObject()) + else if (GameObject* goTarget = target->ToGameObject()) { - if (IsSmartGO(go)) - ENSURE_AI(SmartGameObjectAI, go->AI())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); + if (IsSmartGO(goTarget)) + ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetScript9(e, e.action.timedActionList.id, GetLastInvoker()); } } break; @@ -1675,10 +1680,10 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsSmart(creature)) ENSURE_AI(SmartAI, creature->AI())->SetScript9(e, id, GetLastInvoker()); } - else if (GameObject* go = target->ToGameObject()) + else if (GameObject* goTarget = target->ToGameObject()) { - if (IsSmartGO(go)) - ENSURE_AI(SmartGameObjectAI, go->AI())->SetScript9(e, id, GetLastInvoker()); + if (IsSmartGO(goTarget)) + ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetScript9(e, id, GetLastInvoker()); } } break; @@ -1699,10 +1704,10 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsSmart(creature)) ENSURE_AI(SmartAI, creature->AI())->SetScript9(e, id, GetLastInvoker()); } - else if (GameObject* go = target->ToGameObject()) + else if (GameObject* goTarget = target->ToGameObject()) { - if (IsSmartGO(go)) - ENSURE_AI(SmartGameObjectAI, go->AI())->SetScript9(e, id, GetLastInvoker()); + if (IsSmartGO(goTarget)) + ENSURE_AI(SmartGameObjectAI, goTarget->AI())->SetScript9(e, id, GetLastInvoker()); } } break; @@ -1899,7 +1904,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { for (WorldObject* target : targets) if (IsCreature(target)) - target->ToCreature()->setRegeneratingHealth(e.action.setHealthRegen.regenHealth != 0); + target->ToCreature()->SetRegenerateHealth(e.action.setHealthRegen.regenHealth != 0); break; } case SMART_ACTION_SET_ROOT: @@ -1990,7 +1995,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u std::back_inserter(waypoints), [](uint32 wp) { return wp != 0; }); float distanceToClosest = std::numeric_limits<float>::max(); - WayPoint* closestWp = nullptr; + std::pair<uint32, uint32> closest = { 0, 0 }; for (WorldObject* target : targets) { @@ -1998,29 +2003,27 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u { if (IsSmart(creature)) { - for (uint32 wp : waypoints) + for (uint32 pathId : waypoints) { - WPPath* path = sSmartWaypointMgr->GetPath(wp); - if (!path || path->empty()) + WaypointPath const* path = sSmartWaypointMgr->GetPath(pathId); + if (!path || path->nodes.empty()) continue; - auto itrWp = path->find(0); - if (itrWp != path->end()) + for (auto itr = path->nodes.begin(); itr != path->nodes.end(); ++itr) { - if (WayPoint* wp = itrWp->second) + WaypointNode const waypoint = *itr; + float distamceToThisNode = creature->GetDistance(waypoint.x, waypoint.y, waypoint.z); + if (distamceToThisNode < distanceToClosest) { - float distToThisPath = creature->GetDistance(wp->x, wp->y, wp->z); - if (distToThisPath < distanceToClosest) - { - distanceToClosest = distToThisPath; - closestWp = wp; - } + distanceToClosest = distamceToThisNode; + closest.first = pathId; + closest.second = waypoint.id; } } } - if (closestWp) - CAST_AI(SmartAI, creature->AI())->StartPath(false, closestWp->id, true); + if (closest.first != 0) + ENSURE_AI(SmartAI, creature->AI())->StartPath(false, closest.first, true, nullptr, closest.second); } } } @@ -2038,7 +2041,12 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsUnit(target)) { uint32 sound = Trinity::Containers::SelectRandomContainerElement(sounds); - target->PlayDirectSound(sound, onlySelf ? target->ToPlayer() : nullptr); + + if (e.action.randomSound.distance == 1) + target->PlayDistanceSound(sound, onlySelf ? target->ToPlayer() : nullptr); + else + target->PlayDirectSound(sound, onlySelf ? target->ToPlayer() : nullptr); + TC_LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_RANDOM_SOUND: target: %s (%s), sound: %u, onlyself: %s", target->GetName().c_str(), target->GetGUID().ToString().c_str(), sound, onlySelf ? "true" : "false"); } @@ -2052,6 +2060,92 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u if (IsCreature(target)) target->ToCreature()->SetCorpseDelay(e.action.corpseDelay.timer); } + + break; + } + case SMART_ACTION_SPAWN_SPAWNGROUP: + { + if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) + { + bool const ignoreRespawn = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_IGNORE_RESPAWN) != 0); + bool const force = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_FORCE_SPAWN) != 0); + + // Instant spawn + GetBaseObject()->GetMap()->SpawnGroupSpawn(e.action.groupSpawn.groupId, ignoreRespawn, force); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = SmartEvent(); + ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + + SmartAction ac = SmartAction(); + ac.type = (SMART_ACTION)SMART_ACTION_SPAWN_SPAWNGROUP; + ac.groupSpawn.groupId = e.action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; + ac.timeEvent.id = e.action.timeEvent.id; + + SmartScriptHolder ev = SmartScriptHolder(); + ev.event = ne; + ev.event_id = e.event_id; + ev.target = e.target; + ev.action = ac; + InitTimer(ev); + mStoredEvents.push_back(ev); + } + break; + } + case SMART_ACTION_DESPAWN_SPAWNGROUP: + { + if (e.action.groupSpawn.minDelay == 0 && e.action.groupSpawn.maxDelay == 0) + { + bool const deleteRespawnTimes = ((e.action.groupSpawn.spawnflags & SMARTAI_SPAWN_FLAGS::SMARTAI_SPAWN_FLAG_NOSAVE_RESPAWN) != 0); + + // Instant spawn + GetBaseObject()->GetMap()->SpawnGroupDespawn(e.action.groupSpawn.groupId, deleteRespawnTimes); + } + else + { + // Delayed spawn (use values from parameter to schedule event to call us back + SmartEvent ne = SmartEvent(); + ne.type = (SMART_EVENT)SMART_EVENT_UPDATE; + ne.event_chance = 100; + + ne.minMaxRepeat.min = e.action.groupSpawn.minDelay; + ne.minMaxRepeat.max = e.action.groupSpawn.maxDelay; + ne.minMaxRepeat.repeatMin = 0; + ne.minMaxRepeat.repeatMax = 0; + + ne.event_flags = 0; + ne.event_flags |= SMART_EVENT_FLAG_NOT_REPEATABLE; + + SmartAction ac = SmartAction(); + ac.type = (SMART_ACTION)SMART_ACTION_DESPAWN_SPAWNGROUP; + ac.groupSpawn.groupId = e.action.groupSpawn.groupId; + ac.groupSpawn.minDelay = 0; + ac.groupSpawn.maxDelay = 0; + ac.groupSpawn.spawnflags = e.action.groupSpawn.spawnflags; + ac.timeEvent.id = e.action.timeEvent.id; + + SmartScriptHolder ev = SmartScriptHolder(); + ev.event = ne; + ev.event_id = e.event_id; + ev.target = e.target; + ev.action = ac; + InitTimer(ev); + mStoredEvents.push_back(ev); + } break; } case SMART_ACTION_DISABLE_EVADE: @@ -2947,7 +3041,7 @@ void SmartScript::ProcessEvent(SmartScriptHolder& e, Unit* unit, uint32 var0, ui case SMART_EVENT_WAYPOINT_STOPPED: case SMART_EVENT_WAYPOINT_ENDED: { - if (!me || (e.event.waypoint.pointID && var0 != e.event.waypoint.pointID) || (e.event.waypoint.pathID && GetPathId() != e.event.waypoint.pathID)) + if (!me || (e.event.waypoint.pointID && var0 != e.event.waypoint.pointID) || (e.event.waypoint.pathID && var1 != e.event.waypoint.pathID)) return; ProcessAction(e, unit); break; diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp index 31282a934ac..b4713b7ed4d 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.cpp @@ -29,6 +29,7 @@ #include "SpellMgr.h" #include "Timer.h" #include "UnitDefines.h" +#include "WaypointDefines.h" SmartWaypointMgr* SmartWaypointMgr::instance() { @@ -40,15 +41,7 @@ void SmartWaypointMgr::LoadFromDB() { uint32 oldMSTime = getMSTime(); - for (std::unordered_map<uint32, WPPath*>::iterator itr = waypoint_map.begin(); itr != waypoint_map.end(); ++itr) - { - for (WPPath::iterator pathItr = itr->second->begin(); pathItr != itr->second->end(); ++pathItr) - delete pathItr->second; - - delete itr->second; - } - - waypoint_map.clear(); + _waypointStore.clear(); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_SMARTAI_WP); PreparedQueryResult result = WorldDatabase.Query(stmt); @@ -62,50 +55,47 @@ void SmartWaypointMgr::LoadFromDB() uint32 count = 0; uint32 total = 0; - uint32 last_entry = 0; - uint32 last_id = 1; + uint32 lastEntry = 0; + uint32 lastId = 1; do { Field* fields = result->Fetch(); uint32 entry = fields[0].GetUInt32(); uint32 id = fields[1].GetUInt32(); - float x, y, z; - x = fields[2].GetFloat(); - y = fields[3].GetFloat(); - z = fields[4].GetFloat(); + float x = fields[2].GetFloat(); + float y = fields[3].GetFloat(); + float z = fields[4].GetFloat(); - if (last_entry != entry) + if (lastEntry != entry) { - waypoint_map[entry] = new WPPath(); - last_id = 1; - count++; + lastId = 1; + ++count; } - if (last_id != id) - TC_LOG_ERROR("sql.sql", "SmartWaypointMgr::LoadFromDB: Path entry %u, unexpected point id %u, expected %u.", entry, id, last_id); + if (lastId != id) + TC_LOG_ERROR("sql.sql", "SmartWaypointMgr::LoadFromDB: Path entry %u, unexpected point id %u, expected %u.", entry, id, lastId); - last_id++; - (*waypoint_map[entry])[id] = new WayPoint(id, x, y, z); + ++lastId; + WaypointPath& path = _waypointStore[entry]; + path.id = entry; + path.nodes.emplace_back(id, x, y, z); - last_entry = entry; - total++; + lastEntry = entry; + ++total; } while (result->NextRow()); TC_LOG_INFO("server.loading", ">> Loaded %u SmartAI waypoint paths (total %u waypoints) in %u ms", count, total, GetMSTimeDiffToNow(oldMSTime)); } -SmartWaypointMgr::~SmartWaypointMgr() +WaypointPath const* SmartWaypointMgr::GetPath(uint32 id) { - for (std::unordered_map<uint32, WPPath*>::iterator itr = waypoint_map.begin(); itr != waypoint_map.end(); ++itr) - { - for (WPPath::iterator pathItr = itr->second->begin(); pathItr != itr->second->end(); ++pathItr) - delete pathItr->second; - - delete itr->second; - } + auto itr = _waypointStore.find(id); + if (itr != _waypointStore.end()) + return &itr->second; + return nullptr; } SmartAIMgr* SmartAIMgr::instance() @@ -228,7 +218,7 @@ void SmartAIMgr::LoadSmartAIFromDB() } case SMART_SCRIPT_TYPE_GAMEOBJECT: { - GameObjectData const* gameObject = sObjectMgr->GetGOData(uint32(std::abs(temp.entryOrGuid))); + GameObjectData const* gameObject = sObjectMgr->GetGameObjectData(uint32(std::abs(temp.entryOrGuid))); if (!gameObject) { TC_LOG_ERROR("sql.sql", "SmartAIMgr::LoadSmartAIFromDB: GameObject guid (%u) does not exist, skipped loading.", uint32(std::abs(temp.entryOrGuid))); @@ -932,7 +922,7 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) return false; } - if (e.event.distance.guid != 0 && !sObjectMgr->GetGOData(e.event.distance.guid)) + if (e.event.distance.guid != 0 && !sObjectMgr->GetGameObjectData(e.event.distance.guid)) { TC_LOG_ERROR("sql.sql", "SmartAIMgr: Event SMART_EVENT_DISTANCE_GAMEOBJECT using invalid gameobject guid %u, skipped.", e.event.distance.guid); return false; @@ -1314,7 +1304,8 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) break; case SMART_ACTION_WP_START: { - if (!sSmartWaypointMgr->GetPath(e.action.wpStart.pathID)) + WaypointPath const* path = sSmartWaypointMgr->GetPath(e.action.wpStart.pathID); + if (!path || path->nodes.empty()) { TC_LOG_ERROR("sql.sql", "SmartAIMgr: Creature %d Event %u Action %u uses non-existent WaypointPath id %u, skipped.", e.entryOrGuid, e.event_id, e.GetActionType(), e.action.wpStart.pathID); return false; @@ -1508,6 +1499,8 @@ bool SmartAIMgr::IsEventValid(SmartScriptHolder& e) case SMART_ACTION_TRIGGER_RANDOM_TIMED_EVENT: case SMART_ACTION_SET_COUNTER: case SMART_ACTION_REMOVE_ALL_GAMEOBJECTS: + case SMART_ACTION_SPAWN_SPAWNGROUP: + case SMART_ACTION_DESPAWN_SPAWNGROUP: break; default: TC_LOG_ERROR("sql.sql", "SmartAIMgr: Not handled action_type(%u), event_type(%u), Entry %d SourceType %u Event %u, skipped.", e.GetActionType(), e.GetEventType(), e.entryOrGuid, e.GetScriptType(), e.event_id); diff --git a/src/server/game/AI/SmartScripts/SmartScriptMgr.h b/src/server/game/AI/SmartScripts/SmartScriptMgr.h index 7a1433bc7fb..499b1ad458e 100644 --- a/src/server/game/AI/SmartScripts/SmartScriptMgr.h +++ b/src/server/game/AI/SmartScripts/SmartScriptMgr.h @@ -20,6 +20,7 @@ #include "Define.h" #include "ObjectGuid.h" +#include "WaypointDefines.h" #include <map> #include <string> #include <unordered_map> @@ -27,22 +28,6 @@ class WorldObject; enum SpellEffIndex : uint8; -struct WayPoint -{ - WayPoint(uint32 _id, float _x, float _y, float _z) - { - id = _id; - x = _x; - y = _y; - z = _z; - } - - uint32 id; - float x; - float y; - float z; -}; - enum eSmartAI { SMART_EVENT_PARAM_COUNT = 4, @@ -589,8 +574,12 @@ enum SMART_ACTION SMART_ACTION_REMOVE_ALL_GAMEOBJECTS = 126, SMART_ACTION_STOP_MOTION = 127, // stopMoving, movementExpired SMART_ACTION_PLAY_ANIMKIT = 128, // don't use on 3.3.5a + SMART_ACTION_SCENE_PLAY = 129, // don't use on 3.3.5a + SMART_ACTION_SCENE_CANCEL = 130, // don't use on 3.3.5a + SMART_ACTION_SPAWN_SPAWNGROUP = 131, // Group ID, min secs, max secs, spawnflags + SMART_ACTION_DESPAWN_SPAWNGROUP = 132, // Group ID, min secs, max secs, spawnflags - SMART_ACTION_END = 129 + SMART_ACTION_END = 133 }; struct SmartAction @@ -621,6 +610,7 @@ struct SmartAction { uint32 sound; uint32 onlySelf; + uint32 distance; } sound; struct @@ -1081,8 +1071,9 @@ struct SmartAction struct { - uint32 sounds[SMART_ACTION_PARAM_COUNT - 1]; + uint32 sounds[SMART_ACTION_PARAM_COUNT - 2]; uint32 onlySelf; + uint32 distance; } randomSound; struct @@ -1094,6 +1085,13 @@ struct SmartAction { uint32 disable; } disableEvade; + struct + { + uint32 groupId; + uint32 minDelay; + uint32 maxDelay; + uint32 spawnflags; + } groupSpawn; struct { @@ -1138,6 +1136,14 @@ struct SmartAction }; }; +enum SMARTAI_SPAWN_FLAGS +{ + SMARTAI_SPAWN_FLAG_NONE = 0x00, + SMARTAI_SPAWN_FLAG_IGNORE_RESPAWN = 0x01, + SMARTAI_SPAWN_FLAG_FORCE_SPAWN = 0x02, + SMARTAI_SPAWN_FLAG_NOSAVE_RESPAWN = 0x04, +}; + enum SMARTAI_TEMPLATE { SMARTAI_TEMPLATE_BASIC = 0, //nothing is preset @@ -1495,8 +1501,6 @@ struct SmartScriptHolder operator bool() const { return entryOrGuid != 0; } }; -typedef std::unordered_map<uint32, WayPoint*> WPPath; - typedef std::vector<WorldObject*> ObjectVector; class ObjectGuidVector @@ -1523,26 +1527,22 @@ typedef std::unordered_map<uint32, ObjectGuidVector> ObjectVectorMap; class TC_GAME_API SmartWaypointMgr { - private: - SmartWaypointMgr() { } - ~SmartWaypointMgr(); - public: static SmartWaypointMgr* instance(); void LoadFromDB(); - WPPath* GetPath(uint32 id) - { - if (waypoint_map.find(id) != waypoint_map.end()) - return waypoint_map[id]; - else return nullptr; - } + WaypointPath const* GetPath(uint32 id); private: - std::unordered_map<uint32, WPPath*> waypoint_map; + SmartWaypointMgr() { } + ~SmartWaypointMgr() { } + + std::unordered_map<uint32, WaypointPath> _waypointStore; }; +#define sSmartWaypointMgr SmartWaypointMgr::instance() + // all events for a single entry typedef std::vector<SmartScriptHolder> SmartAIEventList; typedef std::vector<SmartScriptHolder> SmartAIEventStoredList; @@ -1608,5 +1608,5 @@ class TC_GAME_API SmartAIMgr }; #define sSmartScriptMgr SmartAIMgr::instance() -#define sSmartWaypointMgr SmartWaypointMgr::instance() + #endif diff --git a/src/server/game/Accounts/RBAC.h b/src/server/game/Accounts/RBAC.h index 8fc5f220d43..0f8e031ba20 100644 --- a/src/server/game/Accounts/RBAC.h +++ b/src/server/game/Accounts/RBAC.h @@ -57,7 +57,7 @@ enum RBACPermissions RBAC_PERM_JOIN_RANDOM_BG = 4, RBAC_PERM_JOIN_ARENAS = 5, RBAC_PERM_JOIN_DUNGEON_FINDER = 6, - // 7 - reuse + RBAC_PERM_IGNORE_IDLE_CONNECTION = 7, // 8 - reuse // 9 - reuse RBAC_PERM_USE_CHARACTER_TEMPLATES = 10, // not on 3.3.5a @@ -748,7 +748,7 @@ enum RBACPermissions RBAC_PERM_COMMAND_SERVER_RESTART_FORCE = 840, RBAC_PERM_COMMAND_NEARGRAVEYARD = 841, RBAC_PERM_COMMAND_RELOAD_CHARACTER_TEMPLATE = 842, // not on 3.3.5a - RBAC_PERM_COMMAND_RELOAD_QUEST_GREETING = 843, // not on 3.3.5a + RBAC_PERM_COMMAND_RELOAD_QUEST_GREETING = 843, RBAC_PERM_COMMAND_SCENE = 844, // not on 3.3.5a RBAC_PERM_COMMAND_SCENE_DEBUG = 845, // not on 3.3.5a RBAC_PERM_COMMAND_SCENE_PLAY = 846, // not on 3.3.5a @@ -761,16 +761,18 @@ enum RBACPermissions RBAC_PERM_COMMAND_RELOAD_CONVERSATION_TEMPLATE = 853, // not on 3.3.5a RBAC_PERM_COMMAND_DEBUG_CONVERSATION = 854, // not on 3.3.5a RBAC_PERM_COMMAND_DEBUG_PLAY_MUSIC = 855, - RBAC_PERM_COMMAND_NPC_SPAWNGROUP = 856, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_NPC_DESPAWNGROUP = 857, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_GOBJECT_SPAWNGROUP = 858, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_GOBJECT_DESPAWNGROUP = 859, // reserved for dynamic_spawning - RBAC_PERM_COMMAND_LIST_RESPAWNS = 860, // reserved for dynamic_spawning + RBAC_PERM_COMMAND_NPC_SPAWNGROUP = 856, + RBAC_PERM_COMMAND_NPC_DESPAWNGROUP = 857, + RBAC_PERM_COMMAND_GOBJECT_SPAWNGROUP = 858, + RBAC_PERM_COMMAND_GOBJECT_DESPAWNGROUP = 859, + RBAC_PERM_COMMAND_LIST_RESPAWNS = 860, RBAC_PERM_COMMAND_GROUP_SET = 861, RBAC_PERM_COMMAND_GROUP_ASSISTANT = 862, RBAC_PERM_COMMAND_GROUP_MAINTANK = 863, RBAC_PERM_COMMAND_GROUP_MAINASSIST = 864, RBAC_PERM_COMMAND_NPC_SHOWLOOT = 865, + RBAC_PERM_COMMAND_LIST_SPAWNPOINTS = 866, + RBAC_PERM_COMMAND_RELOAD_QUEST_GREETING_LOCALE = 867, // custom permissions 1000+ RBAC_PERM_MAX diff --git a/src/server/game/Achievements/AchievementMgr.cpp b/src/server/game/Achievements/AchievementMgr.cpp index a43ef238521..f904614960a 100644 --- a/src/server/game/Achievements/AchievementMgr.cpp +++ b/src/server/game/Achievements/AchievementMgr.cpp @@ -2287,7 +2287,7 @@ void AchievementGlobalMgr::LoadAchievementCriteriaList() if (sAchievementCriteriaStore.GetNumRows() == 0) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 achievement criteria."); + TC_LOG_INFO("server.loading", ">> Loaded 0 achievement criteria."); return; } @@ -2574,7 +2574,7 @@ void AchievementGlobalMgr::LoadRewards() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 achievement rewards. DB table `achievement_reward` is empty."); return; } diff --git a/src/server/game/Battlefield/Battlefield.cpp b/src/server/game/Battlefield/Battlefield.cpp index 27e83185e27..92b78216cb0 100644 --- a/src/server/game/Battlefield/Battlefield.cpp +++ b/src/server/game/Battlefield/Battlefield.cpp @@ -776,18 +776,15 @@ Creature* Battlefield::SpawnCreature(uint32 entry, Position const& pos) return nullptr; } - float x, y, z, o; - pos.GetPosition(x, y, z, o); - Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map, PHASEMASK_NORMAL, entry, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map, PHASEMASK_NORMAL, entry, pos)) { TC_LOG_ERROR("bg.battlefield", "Battlefield::SpawnCreature: Can't create creature entry: %u", entry); delete creature; return nullptr; } - creature->SetHomePosition(x, y, z, o); + creature->SetHomePosition(pos); // Set creature in world map->AddToMap(creature); diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp index 41591c419e9..e184c22354e 100644 --- a/src/server/game/Battlegrounds/Battleground.cpp +++ b/src/server/game/Battlegrounds/Battleground.cpp @@ -1515,7 +1515,7 @@ Creature* Battleground::AddCreature(uint32 entry, uint32 type, float x, float y, Creature* creature = new Creature(); - if (!creature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map, PHASEMASK_NORMAL, entry, x, y, z, o)) + if (!creature->Create(map->GenerateLowGuid<HighGuid::Unit>(), map, PHASEMASK_NORMAL, entry, { x, y, z, o })) { TC_LOG_ERROR("bg.battleground", "Battleground::AddCreature: cannot create creature (entry: %u) for BG (map: %u, instance id: %u)!", entry, m_MapId, m_InstanceID); diff --git a/src/server/game/Battlegrounds/BattlegroundMgr.cpp b/src/server/game/Battlegrounds/BattlegroundMgr.cpp index 1b8fb8ee907..76e3d7ea08c 100644 --- a/src/server/game/Battlegrounds/BattlegroundMgr.cpp +++ b/src/server/game/Battlegrounds/BattlegroundMgr.cpp @@ -535,7 +535,7 @@ void BattlegroundMgr::LoadBattlegroundTemplates() QueryResult result = WorldDatabase.Query("SELECT ID, MinPlayersPerTeam, MaxPlayersPerTeam, MinLvl, MaxLvl, AllianceStartLoc, AllianceStartO, HordeStartLoc, HordeStartO, StartMaxDist, Weight, ScriptName FROM battleground_template"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 battlegrounds. DB table `battleground_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 battlegrounds. DB table `battleground_template` is empty."); return; } @@ -973,7 +973,6 @@ BattlegroundTypeId BattlegroundMgr::GetRandomBG(BattlegroundTypeId bgTypeId) { if (BattlegroundTemplate const* bgTemplate = GetBattlegroundTemplateByTypeId(bgTypeId)) { - BattlegroundSelectionWeightMap selectionWeights; std::vector<BattlegroundTypeId> ids; ids.reserve(16); std::vector<double> weights; diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp index 6ef3bbcc3c3..3ebca71a326 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.cpp @@ -176,7 +176,7 @@ void BattlegroundAB::PostUpdateImpl(uint32 diff) if (team == TEAM_ALLIANCE) UpdateWorldState(BG_AB_OP_RESOURCES_ALLY, m_TeamScores[team]); - else if (team == TEAM_HORDE) + else UpdateWorldState(BG_AB_OP_RESOURCES_HORDE, m_TeamScores[team]); // update achievement flags // we increased m_TeamScores[team] so we just need to check if it is 500 more than other teams resources @@ -418,12 +418,12 @@ void BattlegroundAB::_NodeOccupied(uint8 node, Team team) void BattlegroundAB::_NodeDeOccupied(uint8 node) { + //only dynamic nodes, no start points if (node >= BG_AB_DYNAMIC_NODES_COUNT) return; //remove bonus honor aura trigger creature when node is lost - if (node < BG_AB_DYNAMIC_NODES_COUNT)//only dynamic nodes, no start points - DelCreature(node+7);//NULL checks are in DelCreature! 0-6 spirit guides + DelCreature(node+7);//NULL checks are in DelCreature! 0-6 spirit guides RelocateDeadPlayers(BgCreatures[node]); diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h index 7f68ef3eb0d..f7215989093 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAB.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAB.h @@ -212,11 +212,11 @@ const uint32 BG_AB_GraveyardIds[BG_AB_ALL_NODES_COUNT] = {895, 894, 893, 897, 89 // x, y, z, o const float BG_AB_BuffPositions[BG_AB_DYNAMIC_NODES_COUNT][4] = { - {1185.71f, 1185.24f, -56.36f, 2.56f}, // stables - {990.75f, 1008.18f, -42.60f, 2.43f}, // blacksmith - {817.66f, 843.34f, -56.54f, 3.01f}, // farm - {807.46f, 1189.16f, 11.92f, 5.44f}, // lumber mill - {1146.62f, 816.94f, -98.49f, 6.14f} // gold mine + {1185.566f, 1184.629f, -56.36329f, 2.303831f}, // stables + {990.1131f, 1008.73f, -42.60328f, 0.8203033f}, // blacksmith + {818.0089f, 842.3543f, -56.54062f, 3.176533f}, // farm + {808.8463f, 1185.417f, 11.92161f, 5.619962f}, // lumber mill + {1147.091f, 816.8362f, -98.39896f, 6.056293f} // gold mine }; Position const BG_AB_SpiritGuidePos[BG_AB_ALL_NODES_COUNT] = diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp index fc2bee156ee..506e4d2dea3 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.cpp @@ -314,6 +314,7 @@ Creature* BattlegroundAV::AddAVCreature(uint16 cinfoid, uint16 type) || (cinfoid >= AV_NPC_H_GRAVEDEFENSE0 && cinfoid <= AV_NPC_H_GRAVEDEFENSE3))) { CreatureData &data = sObjectMgr->NewOrExistCreatureData(creature->GetSpawnId()); + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); data.spawndist = 5; } //else spawndist will be 15, so creatures move maximum=10 diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h index 9adaea025d3..7336caaf0d7 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundAV.h +++ b/src/server/game/Battlegrounds/Zones/BattlegroundAV.h @@ -987,19 +987,19 @@ Position const BG_AV_CreaturePos[AV_CPLACE_MAX] = enum BG_AV_CreatureIds { - AV_NPC_A_TOWERDEFENSE = 0, // stormpike bowman - AV_NPC_A_GRAVEDEFENSE0 = 1, // stormpike Defender - AV_NPC_A_GRAVEDEFENSE1 = 2, // seasoned defender - AV_NPC_A_GRAVEDEFENSE2 = 3, // veteran defender - AV_NPC_A_GRAVEDEFENSE3 = 4, // champion defender + AV_NPC_A_GRAVEDEFENSE0 = 0, // stormpike Defender + AV_NPC_A_GRAVEDEFENSE1 = 1, // seasoned defender + AV_NPC_A_GRAVEDEFENSE2 = 2, // veteran defender + AV_NPC_A_GRAVEDEFENSE3 = 3, // champion defender + AV_NPC_A_TOWERDEFENSE = 4, // stormpike bowman AV_NPC_A_CAPTAIN = 5, // balinda AV_NPC_A_BOSS = 6, // vanndar - AV_NPC_H_TOWERDEFENSE = 7, // frostwolf bowman - AV_NPC_H_GRAVEDEFENSE0 = 8, // frostwolf guardian - AV_NPC_H_GRAVEDEFENSE1 = 9, // seasoned guardian - AV_NPC_H_GRAVEDEFENSE2 = 10, // veteran guardian - AV_NPC_H_GRAVEDEFENSE3 = 11, // champion guardian + AV_NPC_H_GRAVEDEFENSE0 = 7, // frostwolf guardian + AV_NPC_H_GRAVEDEFENSE1 = 8, // seasoned guardian + AV_NPC_H_GRAVEDEFENSE2 = 9, // veteran guardian + AV_NPC_H_GRAVEDEFENSE3 = 10, // champion guardian + AV_NPC_H_TOWERDEFENSE = 11, // frostwolf bowman AV_NPC_H_CAPTAIN = 12, // galvangar AV_NPC_H_BOSS = 13, // drek thar diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp index e1d1394cb05..a686d52cda2 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundEY.cpp @@ -529,8 +529,7 @@ bool BattlegroundEY::SetupBattleground() TC_LOG_ERROR("bg.battleground", "BattlegroundEY: Could not spawn Speedbuff Fel Reaver."); } - WorldSafeLocsEntry const* sg = nullptr; - sg = sWorldSafeLocsStore.LookupEntry(EY_GRAVEYARD_MAIN_ALLIANCE); + WorldSafeLocsEntry const* sg = sWorldSafeLocsStore.LookupEntry(EY_GRAVEYARD_MAIN_ALLIANCE); if (!sg || !AddSpiritGuide(EY_SPIRIT_MAIN_ALLIANCE, sg->x, sg->y, sg->z, 3.124139f, TEAM_ALLIANCE)) { TC_LOG_ERROR("sql.sql", "BatteGroundEY: Failed to spawn spirit guide. The battleground was not created."); @@ -774,8 +773,7 @@ void BattlegroundEY::EventTeamCapturedPoint(Player* player, uint32 Point) if (BgCreatures[Point]) DelCreature(Point); - WorldSafeLocsEntry const* sg = nullptr; - sg = sWorldSafeLocsStore.LookupEntry(m_CapturingPointTypes[Point].GraveyardId); + WorldSafeLocsEntry const* sg = sWorldSafeLocsStore.LookupEntry(m_CapturingPointTypes[Point].GraveyardId); if (!sg || !AddSpiritGuide(Point, sg->x, sg->y, sg->z, 3.124139f, GetTeamIndexByTeamId(Team))) TC_LOG_ERROR("bg.battleground", "BatteGroundEY: Failed to spawn spirit guide. point: %u, team: %u, graveyard_id: %u", Point, Team, m_CapturingPointTypes[Point].GraveyardId); diff --git a/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp b/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp index b28f31cd09c..90fd95b5780 100644 --- a/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp +++ b/src/server/game/Battlegrounds/Zones/BattlegroundSA.cpp @@ -198,8 +198,7 @@ bool BattlegroundSA::ResetObjs() //Graveyards for (uint8 i = 0; i < BG_SA_MAX_GY; i++) { - WorldSafeLocsEntry const* sg = nullptr; - sg = sWorldSafeLocsStore.LookupEntry(BG_SA_GYEntries[i]); + WorldSafeLocsEntry const* sg = sWorldSafeLocsStore.LookupEntry(BG_SA_GYEntries[i]); if (!sg) { diff --git a/src/server/game/Calendar/CalendarMgr.cpp b/src/server/game/Calendar/CalendarMgr.cpp index 08a406c4f15..d93786d66f1 100644 --- a/src/server/game/Calendar/CalendarMgr.cpp +++ b/src/server/game/Calendar/CalendarMgr.cpp @@ -57,6 +57,8 @@ CalendarMgr* CalendarMgr::instance() void CalendarMgr::LoadFromDB() { + uint32 oldMSTime = getMSTime(); + uint32 count = 0; _maxEventId = 0; _maxInviteId = 0; @@ -90,7 +92,7 @@ void CalendarMgr::LoadFromDB() } while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %u calendar events", count); + TC_LOG_INFO("server.loading", ">> Loaded %u calendar events in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); count = 0; // 0 1 2 3 4 5 6 7 @@ -117,7 +119,7 @@ void CalendarMgr::LoadFromDB() } while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %u calendar invites", count); + TC_LOG_INFO("server.loading", ">> Loaded %u calendar invites in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); for (uint64 i = 1; i < _maxEventId; ++i) if (!GetEvent(i)) diff --git a/src/server/game/Chat/Chat.cpp b/src/server/game/Chat/Chat.cpp index 3e15ecc9319..46c6a26760c 100644 --- a/src/server/game/Chat/Chat.cpp +++ b/src/server/game/Chat/Chat.cpp @@ -33,6 +33,7 @@ #include "Realm.h" #include "ScriptMgr.h" #include "World.h" +#include <boost/algorithm/string/replace.hpp> ChatCommand::ChatCommand(char const* name, uint32 permission, bool allowConsole, pHandler handler, std::string help, std::vector<ChatCommand> childCommands /*= std::vector<ChatCommand>()*/) : Name(ASSERT_NOTNULL(name)), Permission(permission), AllowConsole(allowConsole), Handler(handler), Help(std::move(help)), ChildCommands(std::move(childCommands)) @@ -347,6 +348,7 @@ bool ChatHandler::ExecuteCommandInTable(std::vector<ChatCommand> const& table, c SendSysMessage(table[i].Help.c_str()); else SendSysMessage(LANG_CMD_SYNTAX); + SetSentErrorMessage(true); } return true; @@ -410,41 +412,39 @@ bool ChatHandler::SetDataForCommandInTable(std::vector<ChatCommand>& table, char return false; } +bool ChatHandler::_ParseCommands(char const* text) +{ + if (ExecuteCommandInTable(getCommandTable(), text, text)) + return true; + + // Pretend commands don't exist for regular players + if (m_session && !m_session->HasPermission(rbac::RBAC_PERM_COMMANDS_NOTIFY_COMMAND_NOT_FOUND_ERROR)) + return false; + + // Send error message for GMs + SendSysMessage(LANG_NO_CMD); + SetSentErrorMessage(true); + return true; +} + bool ChatHandler::ParseCommands(char const* text) { ASSERT(text); ASSERT(*text); - std::string fullcmd = text; - /// chat case (.command or !command format) - if (m_session) - { - if (text[0] != '!' && text[0] != '.') - return false; - } + if (text[0] != '!' && text[0] != '.') + return false; /// ignore single . and ! in line - if (strlen(text) < 2) + if (!text[1]) return false; - // original `text` can't be used. It content destroyed in command code processing. /// ignore messages staring from many dots. - if ((text[0] == '.' && text[1] == '.') || (text[0] == '!' && text[1] == '!')) + if (text[1] == '!' || text[1] == '.') return false; - /// skip first . or ! (in console allowed use command with . and ! and without its) - if (text[0] == '!' || text[0] == '.') - ++text; - - if (!ExecuteCommandInTable(getCommandTable(), text, fullcmd)) - { - if (m_session && !m_session->HasPermission(rbac::RBAC_PERM_COMMANDS_NOTIFY_COMMAND_NOT_FOUND_ERROR)) - return false; - - SendSysMessage(LANG_NO_CMD); - } - return true; + return _ParseCommands(text+1); } bool ChatHandler::isValidChatMessage(char const* message) @@ -1232,6 +1232,16 @@ void CliHandler::SendSysMessage(const char *str, bool /*escapeCharacters*/) m_print(m_callbackArg, "\r\n"); } +bool CliHandler::ParseCommands(char const* str) +{ + if (!str[0]) + return false; + // Console allows using commands both with and without leading indicator + if (str[0] == '.' || str[0] == '!') + ++str; + return _ParseCommands(str); +} + std::string CliHandler::GetNameLink() const { return GetTrinityString(LANG_CONSOLE_COMMAND); @@ -1295,3 +1305,102 @@ int CliHandler::GetSessionDbLocaleIndex() const { return sObjectMgr->GetDBCLocaleIndex(); } + +bool AddonChannelCommandHandler::ParseCommands(char const* str) +{ + if (memcmp(str, "TrinityCore\t", 12)) + return false; + char opcode = str[12]; + if (!opcode) // str[12] is opcode + return false; + if (!str[13] || !str[14] || !str[15] || !str[16]) // str[13] through str[16] is 4-character command counter + return false; + echo = str+13; + + switch (opcode) + { + case 'p': // p Ping + SendAck(); + return true; + case 'h': // h Issue human-readable command + case 'i': // i Issue command + if (!str[17]) + return false; + humanReadable = (opcode == 'h'); + if (_ParseCommands(str + 17)) // actual command starts at str[17] + { + if (!hadAck) + SendAck(); + if (HasSentErrorMessage()) + SendFailed(); + else + SendOK(); + } + else + { + SendSysMessage(LANG_NO_CMD); + SendFailed(); + } + return true; + default: + return false; + } +} + +void AddonChannelCommandHandler::Send(std::string const& msg) +{ + WorldPacket data; + ChatHandler::BuildChatPacket(data, CHAT_MSG_WHISPER, LANG_ADDON, GetSession()->GetPlayer(), GetSession()->GetPlayer(), msg); + GetSession()->SendPacket(&data); +} + +void AddonChannelCommandHandler::SendAck() // a Command acknowledged, no body +{ + ASSERT(echo); + char ack[18] = "TrinityCore\ta"; + memcpy(ack+13, echo, 4); + ack[17] = '\0'; + Send(ack); + hadAck = true; +} + +void AddonChannelCommandHandler::SendOK() // o Command OK, no body +{ + ASSERT(echo); + char ok[18] = "TrinityCore\to"; + memcpy(ok+13, echo, 4); + ok[17] = '\0'; + Send(ok); +} + +void AddonChannelCommandHandler::SendFailed() // f Command failed, no body +{ + ASSERT(echo); + char fail[18] = "TrinityCore\tf"; + memcpy(fail + 13, echo, 4); + fail[17] = '\0'; + Send(fail); +} + +// m Command message, message in body +void AddonChannelCommandHandler::SendSysMessage(char const* str, bool escapeCharacters) +{ + ASSERT(echo); + if (!hadAck) + SendAck(); + + std::string msg = "TrinityCore\tm"; + msg.append(echo, 4); + std::string body(str); + if (escapeCharacters) + boost::replace_all(body, "|", "||"); + size_t pos, lastpos; + for (lastpos = 0, pos = body.find('\n', lastpos); pos != std::string::npos; lastpos = pos + 1, pos = body.find('\n', lastpos)) + { + std::string line(msg); + line.append(body, lastpos, pos - lastpos); + Send(line); + } + msg.append(body, lastpos, pos - lastpos); + Send(msg); +} diff --git a/src/server/game/Chat/Chat.h b/src/server/game/Chat/Chat.h index f2ab3772a0d..8bbcf49c7a1 100644 --- a/src/server/game/Chat/Chat.h +++ b/src/server/game/Chat/Chat.h @@ -93,7 +93,8 @@ class TC_GAME_API ChatHandler return Trinity::StringFormat(GetTrinityString(entry), std::forward<Args>(args)...); } - bool ParseCommands(char const* text); + bool _ParseCommands(char const* text); + virtual bool ParseCommands(char const* text); static std::vector<ChatCommand> const& getCommandTable(); static void invalidateCommandTable(); @@ -105,6 +106,7 @@ class TC_GAME_API ChatHandler // function with different implementation for chat/console virtual bool isAvailable(ChatCommand const& cmd) const; + virtual bool IsHumanReadable() const { return true; } virtual bool HasPermission(uint32 permission) const; virtual std::string GetNameLink() const; virtual bool needReportToTarget(Player* chr) const; @@ -171,6 +173,7 @@ class TC_GAME_API CliHandler : public ChatHandler bool isAvailable(ChatCommand const& cmd) const override; bool HasPermission(uint32 /*permission*/) const override { return true; } void SendSysMessage(const char *str, bool escapeCharacters) override; + bool ParseCommands(char const* str) override; std::string GetNameLink() const override; bool needReportToTarget(Player* chr) const override; LocaleConstant GetSessionDbcLocale() const override; @@ -181,4 +184,24 @@ class TC_GAME_API CliHandler : public ChatHandler Print* m_print; }; +class TC_GAME_API AddonChannelCommandHandler : public ChatHandler +{ + public: + using ChatHandler::ChatHandler; + bool ParseCommands(char const* str) override; + void SendSysMessage(char const* str, bool escapeCharacters) override; + using ChatHandler::SendSysMessage; + bool IsHumanReadable() const override { return humanReadable; } + + private: + void Send(std::string const& msg); + void SendAck(); + void SendOK(); + void SendFailed(); + + char const* echo = nullptr; + bool hadAck = false; + bool humanReadable = false; +}; + #endif diff --git a/src/server/game/Chat/ChatLink.cpp b/src/server/game/Chat/ChatLink.cpp index db07e9e2efd..e7fd0b97703 100644 --- a/src/server/game/Chat/ChatLink.cpp +++ b/src/server/game/Chat/ChatLink.cpp @@ -153,7 +153,7 @@ bool ItemChatLink::Initialize(std::istringstream& iss) return false; } } - else if (id < 0) + else { _suffix = sItemRandomSuffixStore.LookupEntry(-id); if (!_suffix) diff --git a/src/server/game/Combat/ThreatManager.h b/src/server/game/Combat/ThreatManager.h index a6432d62216..21d975ccea1 100644 --- a/src/server/game/Combat/ThreatManager.h +++ b/src/server/game/Combat/ThreatManager.h @@ -20,11 +20,11 @@ #define _THREATMANAGER #include "Common.h" +#include "IteratorPair.h" #include "SharedDefines.h" #include "LinkedReference/Reference.h" #include "UnitEvents.h" #include "ObjectGuid.h" -#include "Containers.h" #include <list> @@ -220,8 +220,8 @@ class TC_GAME_API ThreatManager { public: // -- compatibility layer for combat rewrite (PR #19930) - Trinity::Containers::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.getThreatList(); return { list.cbegin(), list.cend() }; } - Trinity::Containers::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); } + Trinity::IteratorPair<std::list<ThreatReference*>::const_iterator> GetSortedThreatList() const { auto& list = iThreatContainer.getThreatList(); return { list.cbegin(), list.cend() }; } + Trinity::IteratorPair<std::list<ThreatReference*>::const_iterator> GetUnsortedThreatList() const { return GetSortedThreatList(); } std::list<ThreatReference*> GetModifiableThreatList() const { return iThreatContainer.getThreatList(); } Unit* SelectVictim() { return getHostilTarget(); } Unit* GetCurrentVictim() const { if (ThreatReference* ref = getCurrentVictim()) return ref->GetVictim(); else return nullptr; } diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp index 146d2cb3bde..5b7e85d6d3e 100644 --- a/src/server/game/Conditions/ConditionMgr.cpp +++ b/src/server/game/Conditions/ConditionMgr.cpp @@ -1036,7 +1036,7 @@ void ConditionMgr::LoadConditions(bool isReload) if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 conditions. DB table `conditions` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 conditions. DB table `conditions` is empty!"); return; } @@ -1677,7 +1677,7 @@ bool ConditionMgr::isSourceTypeValid(Condition* cond) const } break; } - case CONDITION_SOURCE_TYPE_QUEST_ACCEPT: + case CONDITION_SOURCE_TYPE_QUEST_AVAILABLE: if (!sObjectMgr->GetQuestTemplate(cond->SourceEntry)) { TC_LOG_ERROR("sql.sql", "%s SourceEntry specifies non-existing quest, skipped.", cond->ToString().c_str()); @@ -2011,7 +2011,7 @@ bool ConditionMgr::isConditionTypeValid(Condition* cond) const } if (cond->ConditionValue3) { - if (GameObjectData const* goData = sObjectMgr->GetGOData(cond->ConditionValue3)) + if (GameObjectData const* goData = sObjectMgr->GetGameObjectData(cond->ConditionValue3)) { if (cond->ConditionValue2 && goData->id != cond->ConditionValue2) { diff --git a/src/server/game/Conditions/ConditionMgr.h b/src/server/game/Conditions/ConditionMgr.h index ff3f096abae..4f6c98e87d9 100644 --- a/src/server/game/Conditions/ConditionMgr.h +++ b/src/server/game/Conditions/ConditionMgr.h @@ -133,7 +133,7 @@ enum ConditionSourceType CONDITION_SOURCE_TYPE_CREATURE_TEMPLATE_VEHICLE = 16, CONDITION_SOURCE_TYPE_SPELL = 17, CONDITION_SOURCE_TYPE_SPELL_CLICK_EVENT = 18, - CONDITION_SOURCE_TYPE_QUEST_ACCEPT = 19, + CONDITION_SOURCE_TYPE_QUEST_AVAILABLE = 19, // Condition source type 20 unused CONDITION_SOURCE_TYPE_VEHICLE_SPELL = 21, CONDITION_SOURCE_TYPE_SMART_EVENT = 22, diff --git a/src/server/game/DataStores/DBCEnums.h b/src/server/game/DataStores/DBCEnums.h index f7d3fe325dc..923ed40b93a 100644 --- a/src/server/game/DataStores/DBCEnums.h +++ b/src/server/game/DataStores/DBCEnums.h @@ -248,7 +248,7 @@ enum AreaFlags AREA_FLAG_UNK0 = 0x00000001, // Unknown AREA_FLAG_UNK1 = 0x00000002, // Razorfen Downs, Naxxramas and Acherus: The Ebon Hold (3.3.5a) AREA_FLAG_UNK2 = 0x00000004, // Only used for areas on map 571 (development before) - AREA_FLAG_SLAVE_CAPITAL = 0x00000008, // city and city subsones + AREA_FLAG_SLAVE_CAPITAL = 0x00000008, // city and city subzones AREA_FLAG_UNK3 = 0x00000010, // can't find common meaning AREA_FLAG_SLAVE_CAPITAL2 = 0x00000020, // slave capital city flag? AREA_FLAG_ALLOW_DUELS = 0x00000040, // allow to duel here diff --git a/src/server/game/DataStores/DBCStores.cpp b/src/server/game/DataStores/DBCStores.cpp index e72408205c0..f41e9b4e205 100644 --- a/src/server/game/DataStores/DBCStores.cpp +++ b/src/server/game/DataStores/DBCStores.cpp @@ -239,7 +239,7 @@ inline void LoadDBC(uint32& availableDbcLocales, StoreProblemList& errors, DBCSt localizedName.push_back('/'); localizedName.append(filename); - if (!storage.LoadStringsFrom(localizedName.c_str())) + if (!storage.LoadStringsFrom(localizedName)) availableDbcLocales &= ~(1 << i); // mark as not available for speedup next checks } @@ -435,10 +435,10 @@ void LoadDBCStores(const std::string& dataPath) ASSERT(Utf8toWStr(namesProfanity->Name, wname)); if (namesProfanity->Language != -1) - NamesProfaneValidators[namesProfanity->Language].emplace_back(wname, Trinity::regex::icase | Trinity::regex::optimize); + NamesProfaneValidators[namesProfanity->Language].emplace_back(wname, Trinity::regex::perl | Trinity::regex::icase | Trinity::regex::optimize); else for (uint32 i = 0; i < TOTAL_LOCALES; ++i) - NamesProfaneValidators[i].emplace_back(wname, Trinity::regex::icase | Trinity::regex::optimize); + NamesProfaneValidators[i].emplace_back(wname, Trinity::regex::perl | Trinity::regex::icase | Trinity::regex::optimize); } for (NamesReservedEntry const* namesReserved : sNamesReservedStore) @@ -448,10 +448,10 @@ void LoadDBCStores(const std::string& dataPath) ASSERT(Utf8toWStr(namesReserved->Name, wname)); if (namesReserved->Language != -1) - NamesReservedValidators[namesReserved->Language].emplace_back(wname, Trinity::regex::icase | Trinity::regex::optimize); + NamesReservedValidators[namesReserved->Language].emplace_back(wname, Trinity::regex::perl | Trinity::regex::icase | Trinity::regex::optimize); else for (uint32 i = 0; i < TOTAL_LOCALES; ++i) - NamesReservedValidators[i].emplace_back(wname, Trinity::regex::icase | Trinity::regex::optimize); + NamesReservedValidators[i].emplace_back(wname, Trinity::regex::perl | Trinity::regex::icase | Trinity::regex::optimize); } for (PvPDifficultyEntry const* entry : sPvPDifficultyStore) diff --git a/src/server/game/DungeonFinding/LFGMgr.cpp b/src/server/game/DungeonFinding/LFGMgr.cpp index 39d30a28c4f..344759a2022 100644 --- a/src/server/game/DungeonFinding/LFGMgr.cpp +++ b/src/server/game/DungeonFinding/LFGMgr.cpp @@ -129,7 +129,7 @@ void LFGMgr::LoadRewards() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 lfg dungeon rewards. DB table `lfg_dungeon_rewards` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 lfg dungeon rewards. DB table `lfg_dungeon_rewards` is empty!"); return; } @@ -215,7 +215,7 @@ void LFGMgr::LoadLFGDungeons(bool reload /* = false */) if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 lfg entrance positions. DB table `lfg_dungeon_template` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 lfg entrance positions. DB table `lfg_dungeon_template` is empty!"); return; } diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index b11a5a8018c..27ea877ca08 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -233,14 +233,12 @@ bool ForcedDespawnDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) return true; } -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_respawnradius(0.0f), m_boundaryCheckTime(2500), m_combatPulseTime(0), m_combatPulseDelay(0), 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_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), -m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), m_waypointID(0), m_path_id(0), m_formation(nullptr), m_focusSpell(nullptr), m_focusDelay(0), m_shouldReacquireTarget(false), m_suppressedOrientation(0.0f), -_lastDamagedTime(0) +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_respawnradius(0.0f), m_boundaryCheckTime(2500), m_combatPulseTime(0), m_combatPulseDelay(0), 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_cannotReachTarget(false), m_cannotReachTimer(0), + m_AI_locked(false), m_meleeDamageSchoolMask(SPELL_SCHOOL_MASK_NORMAL), m_originalEntry(0), m_homePosition(), m_transportHomePosition(), m_creatureInfo(nullptr), m_creatureData(nullptr), _waypointPathId(0), _currentWaypointNodeInfo(0, 0), + m_formation(nullptr), m_triggerJustAppeared(true), m_respawnCompatibilityMode(false), m_focusSpell(nullptr), m_focusDelay(0), m_shouldReacquireTarget(false), m_suppressedOrientation(0.0f), _lastDamagedTime(0), + _regenerateHealth(true), _regenerateHealthLock(false) { m_regenTimer = CREATURE_REGEN_INTERVAL; m_valuesCount = UNIT_END; @@ -254,7 +252,6 @@ _lastDamagedTime(0) m_CombatDistance = 0;//MELEE_RANGE; ResetLootMode(); // restore default loot mode - m_TriggerJustRespawned = false; m_isTempWorldObject = false; } @@ -314,6 +311,14 @@ void Creature::DisappearAndDie() ForcedDespawn(0); } +bool Creature::IsReturningHome() const +{ + if (GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_ACTIVE) == HOME_MOTION_TYPE) + return true; + + return false; +} + void Creature::SearchFormation() { if (IsSummon()) @@ -328,48 +333,96 @@ void Creature::SearchFormation() sFormationMgr->AddCreatureToGroup(frmdata->second->leaderGUID, this); } +bool Creature::IsFormationLeader() const +{ + if (!m_formation) + return false; + + return m_formation->IsLeader(this); +} + +void Creature::SignalFormationMovement(Position const& destination, uint32 id/* = 0*/, uint32 moveType/* = 0*/, bool orientation/* = false*/) +{ + if (!m_formation) + return; + + if (!m_formation->IsLeader(this)) + return; + + m_formation->LeaderMoveTo(destination, id, moveType, orientation); +} + +bool Creature::IsFormationLeaderMoveAllowed() const +{ + if (!m_formation) + return false; + + return m_formation->CanLeaderStartMoving(); +} + void Creature::RemoveCorpse(bool setSpawnTime, bool destroyForNearbyPlayers) { if (getDeathState() != CORPSE) return; - m_corpseRemoveTime = time(nullptr); - setDeathState(DEAD); - RemoveAllAuras(); - loot.clear(); - uint32 respawnDelay = m_respawnDelay; - if (IsAIEnabled) - AI()->CorpseRemoved(respawnDelay); + if (m_respawnCompatibilityMode) + { + m_corpseRemoveTime = time(nullptr); + setDeathState(DEAD); + RemoveAllAuras(); + loot.clear(); + uint32 respawnDelay = m_respawnDelay; + if (IsAIEnabled) + AI()->CorpseRemoved(respawnDelay); - if (destroyForNearbyPlayers) - DestroyForNearbyPlayers(); + if (destroyForNearbyPlayers) + DestroyForNearbyPlayers(); - // Should get removed later, just keep "compatibility" with scripts - if (setSpawnTime) - m_respawnTime = std::max<time_t>(time(nullptr) + respawnDelay, m_respawnTime); + // Should get removed later, just keep "compatibility" with scripts + if (setSpawnTime) + m_respawnTime = std::max<time_t>(time(nullptr) + respawnDelay, m_respawnTime); - // if corpse was removed during falling, the falling will continue and override relocation to respawn position - if (IsFalling()) - StopMoving(); + // if corpse was removed during falling, the falling will continue and override relocation to respawn position + if (IsFalling()) + StopMoving(); - float x, y, z, o; - GetRespawnPosition(x, y, z, &o); + float x, y, z, o; + GetRespawnPosition(x, y, z, &o); - // We were spawned on transport, calculate real position - if (IsSpawnedOnTransport()) - { - Position& pos = m_movementInfo.transport.pos; - pos.m_positionX = x; - pos.m_positionY = y; - pos.m_positionZ = z; - pos.SetOrientation(o); + // We were spawned on transport, calculate real position + if (IsSpawnedOnTransport()) + { + Position& pos = m_movementInfo.transport.pos; + pos.m_positionX = x; + pos.m_positionY = y; + pos.m_positionZ = z; + pos.SetOrientation(o); + + if (TransportBase* transport = GetDirectTransport()) + transport->CalculatePassengerPosition(x, y, z, &o); + } - if (TransportBase* transport = GetDirectTransport()) - transport->CalculatePassengerPosition(x, y, z, &o); + SetHomePosition(x, y, z, o); + GetMap()->CreatureRelocation(this, x, y, z, o); } + else + { + // In case this is called directly and normal respawn timer not set + // Since this timer will be longer than the already present time it + // will be ignored if the correct place added a respawn timer + if (setSpawnTime) + { + uint32 respawnDelay = m_respawnDelay; + m_respawnTime = std::max<time_t>(time(NULL) + respawnDelay, m_respawnTime); + + SaveRespawnTime(0, false); + } - SetHomePosition(x, y, z, o); - GetMap()->CreatureRelocation(this, x, y, z, o); + if (TempSummon* summon = ToTempSummon()) + summon->UnSummon(); + else + AddObjectToRemoveList(); + } } /** @@ -478,7 +531,7 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/, CreatureTemplate const* cInfo = GetCreatureTemplate(); - m_regenHealth = cInfo->RegenHealth; + _regenerateHealth = cInfo->RegenHealth; // creatures always have melee weapon ready if any unless specified otherwise if (!GetCreatureAddon()) @@ -561,12 +614,12 @@ bool Creature::UpdateEntry(uint32 entry, CreatureData const* data /*= nullptr*/, void Creature::Update(uint32 diff) { - if (IsAIEnabled && m_TriggerJustRespawned) + if (IsAIEnabled && m_triggerJustAppeared && m_deathState == ALIVE) { - m_TriggerJustRespawned = false; - AI()->JustRespawned(); - if (m_vehicleKit) + if (m_respawnCompatibilityMode && m_vehicleKit) m_vehicleKit->Reset(); + m_triggerJustAppeared = false; + AI()->JustAppeared(); } UpdateMovementFlags(); @@ -579,30 +632,34 @@ void Creature::Update(uint32 diff) break; case JUST_DIED: // Must not be called, see Creature::setDeathState JUST_DIED -> CORPSE promoting. - TC_LOG_ERROR("entities.unit", "Creature (GUID: %u Entry: %u) in wrong state: JUST_DEAD (1)", GetGUID().GetCounter(), GetEntry()); + TC_LOG_ERROR("entities.unit", "Creature (GUID: %u Entry: %u) in wrong state: JUST_DIED (1)", GetGUID().GetCounter(), GetEntry()); break; case DEAD: { time_t now = time(nullptr); if (m_respawnTime <= now) { + // First check if there are any scripts that object to us respawning - if (!sScriptMgr->CanSpawn(GetSpawnId(), GetEntry(), GetCreatureTemplate(), GetCreatureData(), GetMap())) - break; // Will be rechecked on next Update call + if (!sScriptMgr->CanSpawn(GetSpawnId(), GetEntry(), GetCreatureData(), GetMap())) + { + m_respawnTime = now + urand(4,7); + break; // Will be rechecked on next Update call after delay expires + } ObjectGuid dbtableHighGuid(HighGuid::Unit, GetEntry(), m_spawnId); - time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); - if (!linkedRespawntime) // Can respawn + time_t linkedRespawnTime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (!linkedRespawnTime) // Can respawn Respawn(); else // the master is dead { ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); - if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(DAY); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn + SetRespawnTime(WEEK); else { // else copy time from master and add a little - time_t baseRespawnTime = std::max(linkedRespawntime, now); + time_t baseRespawnTime = std::max(linkedRespawnTime, now); time_t const offset = urand(5, MINUTE); // linked guid can be a boss, uses std::numeric_limits<time_t>::max to never respawn in that instance @@ -742,7 +799,7 @@ void Creature::Update(uint32 diff) if (m_regenTimer == 0) { - bool bInCombat = IsInCombat() && (!GetVictim() || // if IsInCombat() is true and this has no victim + bool bInCombat = IsInCombat() && (!GetVictim() || // if IsInCombat() is true and this has no victim !EnsureVictim()->GetCharmerOrOwnerPlayerOrPlayerItself() || // or the victim/owner/charmer is not a player !EnsureVictim()->GetCharmerOrOwnerPlayerOrPlayerItself()->IsGameMaster()); // or the victim/owner/charmer is not a GameMaster @@ -830,7 +887,7 @@ void Creature::Regenerate(Powers power) void Creature::RegenerateHealth() { - if (!isRegeneratingHealth()) + if (!CanRegenerateHealth()) return; uint32 curValue = GetHealth(); @@ -947,12 +1004,16 @@ void Creature::Motion_Initialize() GetMotionMaster()->Initialize(); } -bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, float x, float y, float z, float ang, CreatureData const* data /*= nullptr*/, uint32 vehId /*= 0*/) +bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, Position const& pos, CreatureData const* data /*= nullptr*/, uint32 vehId /*= 0*/, bool dynamic) { ASSERT(map); SetMap(map); SetPhaseMask(phaseMask, false); + // Set if this creature can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + CreatureTemplate const* cinfo = sObjectMgr->GetCreatureTemplate(entry); if (!cinfo) { @@ -962,15 +1023,16 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u //! Relocate before CreateFromProto, to initialize coords and allow //! returning correct zone id for selecting OutdoorPvP/Battlefield script - Relocate(x, y, z, ang); + Relocate(pos); // Check if the position is valid before calling CreateFromProto(), otherwise we might add Auras to Creatures at // invalid position, triggering a crash about Auras not removed in the destructor if (!IsPositionValid()) { - TC_LOG_ERROR("entities.unit", "Creature::Create(): given coordinates for creature (guidlow %d, entry %d) are not valid (X: %f, Y: %f, Z: %f, O: %f)", guidlow, entry, x, y, z, ang); + TC_LOG_ERROR("entities.unit", "Creature::Create(): given coordinates for creature (guidlow %d, entry %d) are not valid (X: %f, Y: %f, Z: %f, O: %f)", guidlow, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation()); return false; } + UpdatePositionData(); // Allow players to see those units while dead, do it here (mayby altered by addon auras) if (cinfo->type_flags & CREATURE_TYPE_FLAG_GHOST_VISIBLE) @@ -1004,10 +1066,8 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u //! Need to be called after LoadCreaturesAddon - MOVEMENTFLAG_HOVER is set there if (HasUnitMovementFlag(MOVEMENTFLAG_HOVER)) { - z += GetFloatValue(UNIT_FIELD_HOVERHEIGHT); - //! Relocate again with updated Z coord - Relocate(x, y, z, ang); + m_positionZ += GetFloatValue(UNIT_FIELD_HOVERHEIGHT); } LastUsedScriptID = GetScriptId(); @@ -1187,27 +1247,17 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) dynamicflags = 0; } - // data->guid = guid must not be updated at save + if (!data.spawnId) + data.spawnId = m_spawnId; + ASSERT(data.spawnId == m_spawnId); data.id = GetEntry(); - data.mapid = mapid; data.phaseMask = phaseMask; data.displayid = displayId; data.equipmentId = GetCurrentEquipmentId(); if (!GetTransport()) - { - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZMinusOffset(); - data.orientation = GetOrientation(); - } + data.spawnPoint.WorldRelocate(this); else - { - data.posX = GetTransOffsetX(); - data.posY = GetTransOffsetY(); - data.posZ = GetTransOffsetZ(); - data.orientation = GetTransOffsetO(); - } - + data.spawnPoint.WorldRelocate(mapid, GetTransOffsetX(), GetTransOffsetY(), GetTransOffsetZ(), GetTransOffsetO()); data.spawntimesecs = m_respawnDelay; // prevent add data integrity problems data.spawndist = GetDefaultMovementType() == IDLE_MOTION_TYPE ? 0.0f : m_respawnradius; @@ -1221,6 +1271,8 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) data.npcflag = npcflag; data.unit_flags = unit_flags; data.dynamicflags = dynamicflags; + if (!data.spawnGroupData) + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); // update in DB SQLTransaction trans = WorldDatabase.BeginTransaction(); @@ -1425,7 +1477,7 @@ bool Creature::CreateFromProto(ObjectGuid::LowType guidlow, uint32 entry, Creatu return true; } -bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate) +bool Creature::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate) { if (!allowDuplicate) { @@ -1466,31 +1518,41 @@ bool Creature::LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool ad } m_spawnId = spawnId; + + m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); m_creatureData = data; m_respawnradius = data->spawndist; m_respawnDelay = data->spawntimesecs; - if (!Create(map->GenerateLowGuid<HighGuid::Unit>(), map, data->phaseMask, data->id, data->posX, data->posY, data->posZ, data->orientation, data)) + + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (!m_respawnCompatibilityMode && !m_respawnTime && !sScriptMgr->CanSpawn(spawnId, data->id, data, map)) + { + SaveRespawnTime(urand(4,7)); + return false; + } + + if (!Create(map->GenerateLowGuid<HighGuid::Unit>(), map, data->phaseMask, data->id, data->spawnPoint, data, 0U , !m_respawnCompatibilityMode)) return false; //We should set first home position, because then AI calls home movement - SetHomePosition(data->posX, data->posY, data->posZ, data->orientation); + SetHomePosition(data->spawnPoint); m_deathState = ALIVE; m_respawnTime = GetMap()->GetCreatureRespawnTime(m_spawnId); - // Is the creature script objecting to us spawning? If yes, delay by one second (then re-check in ::Update) - if (!m_respawnTime && !sScriptMgr->CanSpawn(spawnId, GetEntry(), GetCreatureTemplate(), GetCreatureData(), map)) - m_respawnTime = time(nullptr)+1; + // Is the creature script objecting to us spawning? If yes, delay by a little bit (then re-check in ::Update) + if (m_respawnCompatibilityMode && !m_respawnTime && !sScriptMgr->CanSpawn(spawnId, GetEntry(), GetCreatureData(), map)) + m_respawnTime = time(nullptr)+urand(4,7); if (m_respawnTime) // respawn on Update { m_deathState = DEAD; if (CanFly()) { - float tz = map->GetHeight(GetPhaseMask(), data->posX, data->posY, data->posZ, true, MAX_FALL_DISTANCE); - if (data->posZ - tz > 0.1f && Trinity::IsValidMapCoord(tz)) - Relocate(data->posX, data->posY, tz); + float tz = map->GetHeight(GetPhaseMask(), data->spawnPoint, true, MAX_FALL_DISTANCE); + if (data->spawnPoint.GetPositionZ() - tz > 0.1f && Trinity::IsValidMapCoord(tz)) + Relocate(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY(), tz); } } @@ -1535,8 +1597,11 @@ void Creature::LoadEquipment(int8 id, bool force /*= true*/) void Creature::SetSpawnHealth() { + if (_regenerateHealthLock) + return; + uint32 curhealth; - if (m_creatureData && !m_regenHealth) + if (m_creatureData && !_regenerateHealth) { curhealth = m_creatureData->curhealth; if (curhealth) @@ -1586,15 +1651,24 @@ void Creature::DeleteFromDB() return; } - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + // remove any scheduled respawns + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); + + // delete data from memory sObjectMgr->DeleteCreatureData(m_spawnId); + // delete data and all its associations from DB SQLTransaction trans = WorldDatabase.BeginTransaction(); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE); stmt->setUInt32(0, m_spawnId); trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); + stmt->setUInt8(0, uint8(SPAWN_TYPE_CREATURE)); + stmt->setUInt32(1, m_spawnId); + trans->Append(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_CREATURE_ADDON); stmt->setUInt32(0, m_spawnId); trans->Append(stmt); @@ -1608,6 +1682,11 @@ void Creature::DeleteFromDB() trans->Append(stmt); WorldDatabase.CommitTransaction(trans); + + // then delete any active instances of the creature + auto const& spawnMap = GetMap()->GetCreatureBySpawnIdStore(); + for (auto it = spawnMap.find(m_spawnId); it != spawnMap.end(); it = spawnMap.find(m_spawnId)) + it->second->AddObjectToRemoveList(); } bool Creature::IsInvisibleDueToDespawn() const @@ -1734,14 +1813,31 @@ void Creature::setDeathState(DeathState s) if (s == JUST_DIED) { m_corpseRemoveTime = time(nullptr) + m_corpseDelay; - if (IsDungeonBoss() && !m_respawnDelay) - m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance + + uint32 respawnDelay = m_respawnDelay; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, m_spawnId, respawnDelay, scalingMode); + // @todo remove the boss respawn time hack in a dynspawn follow-up once we have creature groups in instances + if (m_respawnCompatibilityMode) + { + if (IsDungeonBoss() && !m_respawnDelay) + m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance + else + m_respawnTime = time(nullptr) + respawnDelay + m_corpseDelay; + } else - m_respawnTime = time(nullptr) + m_respawnDelay + m_corpseDelay; + { + if (IsDungeonBoss() && !m_respawnDelay) + m_respawnTime = std::numeric_limits<time_t>::max(); // never respawn in this instance + else + m_respawnTime = time(nullptr) + respawnDelay; + } // always save boss respawn time at death to prevent crash cheating if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY) || isWorldBoss()) SaveRespawnTime(); + else if (!m_respawnCompatibilityMode) + SaveRespawnTime(0, false); ReleaseFocus(nullptr, false); // remove spellcast focus DoNotReacquireTarget(); // cancel delayed re-target @@ -1809,8 +1905,6 @@ void Creature::setDeathState(DeathState s) void Creature::Respawn(bool force) { - DestroyForNearbyPlayers(); - if (force) { if (IsAlive()) @@ -1819,51 +1913,59 @@ void Creature::Respawn(bool force) setDeathState(CORPSE); } - RemoveCorpse(false, false); - - if (getDeathState() == DEAD) + if (m_respawnCompatibilityMode) { - if (m_spawnId) - GetMap()->RemoveCreatureRespawnTime(m_spawnId); + DestroyForNearbyPlayers(); + RemoveCorpse(false, false); - TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", - GetName().c_str(), GetGUID().ToString().c_str()); - m_respawnTime = 0; - ResetPickPocketRefillTimer(); - loot.clear(); + if (getDeathState() == DEAD) + { + if (m_spawnId) + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId); - if (m_originalEntry != GetEntry()) - UpdateEntry(m_originalEntry); + TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", GetName().c_str(), GetGUID().ToString().c_str()); + m_respawnTime = 0; + ResetPickPocketRefillTimer(); + loot.clear(); - SelectLevel(); + if (m_originalEntry != GetEntry()) + UpdateEntry(m_originalEntry); - setDeathState(JUST_RESPAWNED); + SelectLevel(); - uint32 displayID = GetNativeDisplayId(); - if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) - { - SetDisplayId(displayID); - SetNativeDisplayId(displayID); - } + setDeathState(JUST_RESPAWNED); - GetMotionMaster()->InitDefault(); - //Re-initialize reactstate that could be altered by movementgenerators - InitializeReactState(); + uint32 displayID = GetNativeDisplayId(); + if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) + { + SetDisplayId(displayID); + SetNativeDisplayId(displayID); + } - //Call AI respawn virtual function - if (IsAIEnabled) - { - //reset the AI to be sure no dirty or uninitialized values will be used till next tick - AI()->Reset(); - m_TriggerJustRespawned = true;//delay event to next tick so all creatures are created on the map before processing - } + GetMotionMaster()->InitDefault(); + //Re-initialize reactstate that could be altered by movementgenerators + InitializeReactState(); - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<Creature>(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool<Creature>(poolid, GetSpawnId()); + if (IsAIEnabled) // reset the AI to be sure no dirty or uninitialized values will be used till next tick + AI()->Reset(); + + m_triggerJustAppeared = true; + + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<Creature>(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool<Creature>(poolid, GetSpawnId()); + } + UpdateObjectVisibility(); + } + else + { + if (m_spawnId) + GetMap()->RemoveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId, true); } - UpdateObjectVisibility(); + TC_LOG_DEBUG("entities.unit", "Respawning creature %s (%s)", + GetName().c_str(), GetGUID().ToString().c_str()); + } void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds const& forceRespawnTimer) @@ -1874,30 +1976,48 @@ void Creature::ForcedDespawn(uint32 timeMSToDespawn, Seconds const& forceRespawn return; } - uint32 corpseDelay = GetCorpseDelay(); - uint32 respawnDelay = GetRespawnDelay(); + if (m_respawnCompatibilityMode) + { + uint32 corpseDelay = GetCorpseDelay(); + uint32 respawnDelay = GetRespawnDelay(); + + // do it before killing creature + DestroyForNearbyPlayers(); - // do it before killing creature - DestroyForNearbyPlayers(); + bool overrideRespawnTime = false; + if (IsAlive()) + { + if (forceRespawnTimer > Seconds::zero()) + { + SetCorpseDelay(0); + SetRespawnDelay(forceRespawnTimer.count()); + overrideRespawnTime = true; + } - bool overrideRespawnTime = true; - if (IsAlive()) + setDeathState(JUST_DIED); + } + + // Skip corpse decay time + RemoveCorpse(!overrideRespawnTime, false); + + SetCorpseDelay(corpseDelay); + SetRespawnDelay(respawnDelay); + } + else { if (forceRespawnTimer > Seconds::zero()) + SaveRespawnTime(forceRespawnTimer.count()); + else { - SetCorpseDelay(0); - SetRespawnDelay(forceRespawnTimer.count()); - overrideRespawnTime = false; + uint32 respawnDelay = m_respawnDelay; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, m_spawnId, respawnDelay, scalingMode); + m_respawnTime = time(NULL) + respawnDelay; + SaveRespawnTime(); } - setDeathState(JUST_DIED); + AddObjectToRemoveList(); } - - // Skip corpse decay time - RemoveCorpse(overrideRespawnTime, false); - - SetCorpseDelay(corpseDelay); - SetRespawnDelay(respawnDelay); } void Creature::DespawnOrUnsummon(uint32 msTimeToDespawn /*= 0*/, Seconds const& forceRespawnTimer /*= 0*/) @@ -2156,9 +2276,6 @@ void Creature::CallForHelp(float radius) bool Creature::CanAssistTo(Unit const* u, Unit const* enemy, bool checkfaction /*= true*/) const { - if (IsInEvadeMode()) - return false; - // is it true? if (!HasReactState(REACT_AGGRESSIVE)) return false; @@ -2244,12 +2361,19 @@ bool Creature::_IsTargetAcceptable(Unit const* target) const return false; } -void Creature::SaveRespawnTime() +void Creature::SaveRespawnTime(uint32 forceDelay, bool savetodb) { if (IsSummon() || !m_spawnId || (m_creatureData && !m_creatureData->dbData)) return; - GetMap()->SaveCreatureRespawnTime(m_spawnId, m_respawnTime); + if (m_respawnCompatibilityMode) + { + GetMap()->SaveRespawnTimeDB(SPAWN_TYPE_CREATURE, m_spawnId, m_respawnTime); + return; + } + + time_t thisRespawnTime = forceDelay ? time(NULL) + forceDelay : m_respawnTime; + GetMap()->SaveRespawnTime(SPAWN_TYPE_CREATURE, m_spawnId, GetEntry(), thisRespawnTime, GetMap()->GetZoneId(GetHomePosition()), Trinity::ComputeGridCoord(GetHomePosition().GetPositionX(), GetHomePosition().GetPositionY()).GetId(), savetodb && m_creatureData && m_creatureData->dbData); } // this should not be called by petAI or @@ -2365,9 +2489,9 @@ bool Creature::LoadCreaturesAddon() if (cainfo->emote != 0) SetUInt32Value(UNIT_NPC_EMOTESTATE, cainfo->emote); - //Load Path + // Load Path if (cainfo->path_id != 0) - m_path_id = cainfo->path_id; + _waypointPathId = cainfo->path_id; if (!cainfo->auras.empty()) { @@ -2461,33 +2585,26 @@ time_t Creature::GetRespawnTimeEx() const void Creature::GetRespawnPosition(float &x, float &y, float &z, float* ori, float* dist) const { - if (m_spawnId) + if (m_creatureData) { - // for npcs on transport, this will return transport offset - if (CreatureData const* data = sObjectMgr->GetCreatureData(GetSpawnId())) - { - x = data->posX; - y = data->posY; - z = data->posZ; - if (ori) - *ori = data->orientation; - if (dist) - *dist = data->spawndist; + if (ori) + m_creatureData->spawnPoint.GetPosition(x, y, z, *ori); + else + m_creatureData->spawnPoint.GetPosition(x, y, z); - return; - } + if (dist) + *dist = m_creatureData->spawndist; + } + else + { + Position const& homePos = GetHomePosition(); + if (ori) + homePos.GetPosition(x, y, z, *ori); + else + homePos.GetPosition(x, y, z); + if (dist) + *dist = 0; } - - // changed this from current position to home position, fixes world summons with infinite duration (wg npcs for example) - Position homePos = GetHomePosition(); - x = homePos.GetPositionX(); - y = homePos.GetPositionY(); - z = homePos.GetPositionZ(); - if (ori) - *ori = homePos.GetOrientation(); - - if (dist) - *dist = 0; } void Creature::AllLootRemovedFromCorpse() @@ -2538,7 +2655,7 @@ std::string Creature::GetScriptName() const uint32 Creature::GetScriptId() const { if (CreatureData const* creatureData = GetCreatureData()) - if (uint32 scriptId = creatureData->ScriptId) + if (uint32 scriptId = creatureData->scriptId) return scriptId; return sObjectMgr->GetCreatureTemplate(GetEntry())->ScriptID; @@ -3081,3 +3198,11 @@ bool Creature::CanGiveExperience() const && !IsTotem() && !(GetCreatureTemplate()->flags_extra & CREATURE_FLAG_EXTRA_NO_XP_AT_KILL); } + +bool Creature::IsEscortNPC(bool onlyIfActive) +{ + if (!IsAIEnabled) + return false; + + return AI()->IsEscortNPC(onlyIfActive); +} diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index cc12efbd7eb..4a7c3362f30 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -35,6 +35,7 @@ class Quest; class Player; class SpellInfo; class WorldSession; + enum MovementGeneratorType : uint8; struct VendorItemCount @@ -72,7 +73,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void DisappearAndDie(); - bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, float x, float y, float z, float ang, CreatureData const* data = nullptr, uint32 vehId = 0); + bool Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, uint32 entry, Position const& pos, CreatureData const* data = nullptr, uint32 vehId = 0, bool dynamic = false); bool LoadCreaturesAddon(); void SelectLevel(); void UpdateLevelDependantStats(); @@ -83,7 +84,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void Update(uint32 time) override; // overwrited Unit::Update 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(); } + bool IsSpawnedOnTransport() const { return m_creatureData && m_creatureData->spawnPoint.GetMapId() != GetMapId(); } void SetCorpseDelay(uint32 delay) { m_corpseDelay = delay; } uint32 GetCorpseDelay() const { return m_corpseDelay; } @@ -176,8 +177,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void setDeathState(DeathState s) override; // override virtual Unit::setDeathState - bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadCreatureFromDB(spawnId, map, false); } - bool LoadCreatureFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true, bool allowDuplicate = false); + bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool allowDuplicate); void SaveToDB(); // overriden in Pet virtual void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask); @@ -239,7 +239,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma time_t GetRespawnTimeEx() const; void SetRespawnTime(uint32 respawn) { m_respawnTime = respawn ? time(nullptr) + respawn : 0; } void Respawn(bool force = false); - void SaveRespawnTime() override; + void SaveRespawnTime(uint32 forceDelay = 0, bool savetodb = true) override; uint32 GetRespawnDelay() const { return m_respawnDelay; } void SetRespawnDelay(uint32 delay) { m_respawnDelay = delay; } @@ -266,8 +266,8 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma bool hasQuest(uint32 quest_id) const override; bool hasInvolvedQuest(uint32 quest_id) const override; - bool isRegeneratingHealth() { return m_regenHealth; } - void setRegeneratingHealth(bool regenHealth) { m_regenHealth = regenHealth; } + bool CanRegenerateHealth() { return !_regenerateHealthLock && _regenerateHealth; } + void SetRegenerateHealth(bool value) { _regenerateHealthLock = !value; } virtual uint8 GetPetAutoSpellSize() const { return MAX_SPELL_CHARM; } virtual uint32 GetPetAutoSpellOnPos(uint8 pos) const; float GetPetChaseDistance() const; @@ -291,15 +291,21 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void GetTransportHomePosition(float& x, float& y, float& z, float& ori) const { m_transportHomePosition.GetPosition(x, y, z, ori); } Position const& GetTransportHomePosition() const { return m_transportHomePosition; } - uint32 GetWaypointPath() const { return m_path_id; } - void LoadPath(uint32 pathid) { m_path_id = pathid; } + uint32 GetWaypointPath() const { return _waypointPathId; } + void LoadPath(uint32 pathid) { _waypointPathId = pathid; } + + // nodeId, pathId + std::pair<uint32, uint32> GetCurrentWaypointInfo() const { return _currentWaypointNodeInfo; } + void UpdateCurrentWaypointInfo(uint32 nodeId, uint32 pathId) { _currentWaypointNodeInfo = { nodeId, pathId }; } - uint32 GetCurrentWaypointID() const { return m_waypointID; } - void UpdateWaypointID(uint32 wpID) { m_waypointID = wpID; } + bool IsReturningHome() const; void SearchFormation(); CreatureGroup* GetFormation() { return m_formation; } void SetFormation(CreatureGroup* formation) { m_formation = formation; } + bool IsFormationLeader() const; + void SignalFormationMovement(Position const& destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false); + bool IsFormationLeaderMoveAllowed() const; Unit* SelectVictim(); @@ -313,6 +319,10 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma uint32 GetOriginalEntry() const { return m_originalEntry; } void SetOriginalEntry(uint32 entry) { m_originalEntry = entry; } + // There's many places not ready for dynamic spawns. This allows them to live on for now. + void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; } + bool GetRespawnCompatibilityMode() { return m_respawnCompatibilityMode; } + static float _GetDamageMod(int32 Rank); float m_SightDistance, m_CombatDistance; @@ -336,6 +346,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma CreatureTextRepeatIds GetTextRepeatGroup(uint8 textGroup); void SetTextRepeatId(uint8 textGroup, uint8 id); void ClearTextRepeatGroup(uint8 textGroup); + bool IsEscortNPC(bool onlyIfActive = true); bool CanGiveExperience() const; @@ -372,7 +383,6 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma bool m_AlreadyCallAssistance; bool m_AlreadySearchedAssistance; - bool m_regenHealth; bool m_cannotReachTarget; uint32 m_cannotReachTimer; bool m_AI_locked; @@ -397,13 +407,14 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma void ForcedDespawn(uint32 timeMSToDespawn = 0, Seconds const& forceRespawnTimer = Seconds(0)); bool CheckNoGrayAggroConfig(uint32 playerLevel, uint32 creatureLevel) const; // No aggro from gray creatures - //WaypointMovementGenerator vars - uint32 m_waypointID; - uint32 m_path_id; + // Waypoint path + uint32 _waypointPathId; + std::pair<uint32/*nodeId*/, uint32/*pathId*/> _currentWaypointNodeInfo; //Formation var CreatureGroup* m_formation; - bool m_TriggerJustRespawned; + bool m_triggerJustAppeared; + bool m_respawnCompatibilityMode; /* Spell focus system */ Spell const* m_focusSpell; // Locks the target during spell cast for proper facing @@ -414,6 +425,10 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma time_t _lastDamagedTime; // Part of Evade mechanics CreatureTextRepeatGroup m_textRepeat; + + // Regenerate health + bool _regenerateHealth; // Set on creation + bool _regenerateHealthLock; // Dynamically set }; class TC_GAME_API AssistDelayEvent : public BasicEvent diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index 754963f9812..de169c97136 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -20,6 +20,7 @@ #include "DBCEnums.h" #include "SharedDefines.h" +#include "SpawnData.h" #include "UnitDefines.h" #include "WorldPacket.h" #include <string> @@ -235,33 +236,19 @@ struct EquipmentInfo }; // from `creature` table -struct CreatureData +struct CreatureData : public SpawnData { - CreatureData() : id(0), mapid(0), phaseMask(0), displayid(0), equipmentId(0), - posX(0.0f), posY(0.0f), posZ(0.0f), orientation(0.0f), spawntimesecs(0), - spawndist(0.0f), currentwaypoint(0), curhealth(0), curmana(0), movementType(0), - spawnMask(0), npcflag(0), unit_flags(0), dynamicflags(0), ScriptId(0), dbData(true) { } - uint32 id; // entry in creature_template - uint16 mapid; - uint32 phaseMask; - uint32 displayid; - int8 equipmentId; - float posX; - float posY; - float posZ; - float orientation; - uint32 spawntimesecs; - float spawndist; - uint32 currentwaypoint; - uint32 curhealth; - uint32 curmana; - uint8 movementType; - uint8 spawnMask; - uint32 npcflag; - uint32 unit_flags; // enum UnitFlags mask values - uint32 dynamicflags; - uint32 ScriptId; - bool dbData; + CreatureData() : SpawnData(SPAWN_TYPE_CREATURE) { } + uint32 displayid = 0; + int8 equipmentId = 0; + float spawndist = 0.0f; + uint32 currentwaypoint = 0; + uint32 curhealth = 0; + uint32 curmana = 0; + uint8 movementType = 0; + uint32 npcflag = 0; + uint32 unit_flags = 0; + uint32 dynamicflags = 0; }; struct CreatureModelInfo diff --git a/src/server/game/Entities/Creature/CreatureGroups.cpp b/src/server/game/Entities/Creature/CreatureGroups.cpp index 3984a81cf71..4297a745ccc 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.cpp +++ b/src/server/game/Entities/Creature/CreatureGroups.cpp @@ -93,7 +93,7 @@ void FormationMgr::LoadCreatureFormations() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 creatures in formations. DB table `creature_formations` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 creatures in formations. DB table `creature_formations` is empty!"); return; } @@ -236,7 +236,7 @@ void CreatureGroup::LeaderMoveTo(Position const& destination, uint32 id /*= 0*/, continue; if (itr->second->point_1) - if (m_leader->GetCurrentWaypointID() == itr->second->point_1 - 1 || m_leader->GetCurrentWaypointID() == itr->second->point_2 - 1) + if (m_leader->GetCurrentWaypointInfo().first == itr->second->point_1 || m_leader->GetCurrentWaypointInfo().first == itr->second->point_2) itr->second->follow_angle = float(M_PI) * 2 - itr->second->follow_angle; float angle = itr->second->follow_angle; @@ -258,3 +258,17 @@ void CreatureGroup::LeaderMoveTo(Position const& destination, uint32 id /*= 0*/, member->SetHomePosition(dx, dy, dz, pathangle); } } + +bool CreatureGroup::CanLeaderStartMoving() const +{ + for (auto itr = m_members.begin(); itr != m_members.end(); ++itr) + { + if (itr->first != m_leader && itr->first->IsAlive()) + { + if (itr->first->IsEngaged() || itr->first->IsReturningHome()) + return false; + } + } + + return true; +} diff --git a/src/server/game/Entities/Creature/CreatureGroups.h b/src/server/game/Entities/Creature/CreatureGroups.h index b36144db42f..52eb4eddf2a 100644 --- a/src/server/game/Entities/Creature/CreatureGroups.h +++ b/src/server/game/Entities/Creature/CreatureGroups.h @@ -84,6 +84,7 @@ class TC_GAME_API CreatureGroup uint32 GetId() const { return m_groupID; } bool isEmpty() const { return m_members.empty(); } bool isFormed() const { return m_Formed; } + bool IsLeader(Creature const* creature) const { return m_leader == creature; } void AddMember(Creature* member); void RemoveMember(Creature* member); @@ -91,6 +92,7 @@ class TC_GAME_API CreatureGroup void LeaderMoveTo(Position const& destination, uint32 id = 0, uint32 moveType = 0, bool orientation = false); void MemberEngagingTarget(Creature* member, Unit* target); + bool CanLeaderStartMoving() const; }; #define sFormationMgr FormationMgr::instance() diff --git a/src/server/game/Entities/Creature/GossipDef.cpp b/src/server/game/Entities/Creature/GossipDef.cpp index caa700f6672..4a39c233fc2 100644 --- a/src/server/game/Entities/Creature/GossipDef.cpp +++ b/src/server/game/Entities/Creature/GossipDef.cpp @@ -323,16 +323,33 @@ void QuestMenu::ClearMenu() _questMenuItems.clear(); } -void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string& Title, ObjectGuid npcGUID) +void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string& Title, ObjectGuid guid) { WorldPacket data(SMSG_QUESTGIVER_QUEST_LIST, 100); // guess size - data << uint64(npcGUID); - data << Title; - data << uint32(eEmote._Delay); // player emote - data << uint32(eEmote._Emote); // NPC emote + data << uint64(guid); + + if (QuestGreeting const* questGreeting = sObjectMgr->GetQuestGreeting(guid)) + { + std::string strGreeting = questGreeting->greeting; + + LocaleConstant localeConstant = _session->GetSessionDbLocaleIndex(); + if (localeConstant != LOCALE_enUS) + if (QuestGreetingLocale const* questGreetingLocale = sObjectMgr->GetQuestGreetingLocale(MAKE_PAIR32(guid.GetEntry(), guid.GetTypeId()))) + ObjectMgr::GetLocaleString(questGreetingLocale->greeting, localeConstant, strGreeting); + + data << strGreeting; + data << uint32(questGreeting->greetEmoteDelay); + data << uint32(questGreeting->greetEmoteType); + } + else + { + data << Title; + data << uint32(eEmote._Delay); // player emote + data << uint32(eEmote._Emote); // NPC emote + } size_t count_pos = data.wpos(); - data << uint8 (0); + data << uint8(0); uint32 count = 0; // Store this instead of checking the Singleton every loop iteration @@ -340,9 +357,9 @@ void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string for (uint32 i = 0; i < _questMenu.GetMenuItemCount(); ++i) { - QuestMenuItem const& qmi = _questMenu.GetItem(i); + QuestMenuItem const& questMenuItem = _questMenu.GetItem(i); - uint32 questID = qmi.QuestId; + uint32 questID = questMenuItem.QuestId; if (Quest const* quest = sObjectMgr->GetQuestTemplate(questID)) { @@ -351,14 +368,14 @@ void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string LocaleConstant localeConstant = _session->GetSessionDbLocaleIndex(); if (localeConstant != LOCALE_enUS) - if (QuestLocale const* localeData = sObjectMgr->GetQuestLocale(questID)) - ObjectMgr::GetLocaleString(localeData->Title, localeConstant, title); + if (QuestLocale const* questTemplateLocaleData = sObjectMgr->GetQuestLocale(questID)) + ObjectMgr::GetLocaleString(questTemplateLocaleData->Title, localeConstant, title); if (questLevelInTitle) Quest::AddQuestLevelToTitle(title, quest->GetQuestLevel()); data << uint32(questID); - data << uint32(qmi.QuestIcon); + data << uint32(questMenuItem.QuestIcon); data << int32(quest->GetQuestLevel()); data << uint32(quest->GetFlags()); // 3.3.3 quest flags data << uint8(0); // 3.3.3 changes icon: blue question or yellow exclamation @@ -368,7 +385,8 @@ void PlayerMenu::SendQuestGiverQuestList(QEmote const& eEmote, const std::string data.put<uint8>(count_pos, count); _session->SendPacket(&data); - TC_LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_LIST NPC=%s", npcGUID.ToString().c_str()); + + TC_LOG_DEBUG("network", "WORLD: Sent SMSG_QUESTGIVER_QUEST_LIST (QuestGiver: %s)", guid.ToString().c_str()); } void PlayerMenu::SendQuestGiverStatus(uint8 questStatus, ObjectGuid npcGUID) const diff --git a/src/server/game/Entities/Creature/TemporarySummon.cpp b/src/server/game/Entities/Creature/TemporarySummon.cpp index 7e9cf6bf7a3..70a7e3446d4 100644 --- a/src/server/game/Entities/Creature/TemporarySummon.cpp +++ b/src/server/game/Entities/Creature/TemporarySummon.cpp @@ -57,6 +57,7 @@ void TempSummon::Update(uint32 diff) switch (m_type) { case TEMPSUMMON_MANUAL_DESPAWN: + case TEMPSUMMON_DEAD_DESPAWN: break; case TEMPSUMMON_TIMED_DESPAWN: { @@ -104,7 +105,7 @@ void TempSummon::Update(uint32 diff) case TEMPSUMMON_CORPSE_DESPAWN: { // if m_deathState is DEAD, CORPSE was skipped - if (m_deathState == CORPSE || m_deathState == DEAD) + if (m_deathState == CORPSE) { UnSummon(); return; @@ -112,19 +113,9 @@ void TempSummon::Update(uint32 diff) break; } - case TEMPSUMMON_DEAD_DESPAWN: - { - if (m_deathState == DEAD) - { - UnSummon(); - return; - } - break; - } case TEMPSUMMON_TIMED_OR_CORPSE_DESPAWN: { - // if m_deathState is DEAD, CORPSE was skipped - if (m_deathState == CORPSE || m_deathState == DEAD) + if (m_deathState == CORPSE) { UnSummon(); return; @@ -146,13 +137,6 @@ void TempSummon::Update(uint32 diff) } case TEMPSUMMON_TIMED_OR_DEAD_DESPAWN: { - // if m_deathState is DEAD, CORPSE was skipped - if (m_deathState == DEAD) - { - UnSummon(); - return; - } - if (!IsInCombat() && IsAlive()) { if (m_timer <= diff) diff --git a/src/server/game/Entities/GameObject/GameObject.cpp b/src/server/game/Entities/GameObject/GameObject.cpp index be0406be5b0..ce9658ef37e 100644 --- a/src/server/game/Entities/GameObject/GameObject.cpp +++ b/src/server/game/Entities/GameObject/GameObject.cpp @@ -101,7 +101,7 @@ QuaternionData QuaternionData::fromEulerAnglesZYX(float Z, float Y, float X) } GameObject::GameObject() : WorldObject(false), MapObject(), - m_model(nullptr), m_goValue(), m_AI(nullptr) + m_model(nullptr), m_goValue(), m_AI(nullptr), m_respawnCompatibilityMode(false) { m_objectType |= TYPEMASK_GAMEOBJECT; m_objectTypeId = TYPEID_GAMEOBJECT; @@ -240,7 +240,7 @@ void GameObject::RemoveFromWorld() } } -bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit /*= 0*/) +bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit /*= 0*/, bool dynamic, ObjectGuid::LowType spawnid) { ASSERT(map); SetMap(map); @@ -253,7 +253,12 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u return false; } + // Set if this object can handle dynamic spawns + if (!dynamic) + SetRespawnCompatibilityMode(); + SetPhaseMask(phaseMask, false); + UpdatePositionData(); SetZoneScript(); if (m_zoneScript) @@ -375,6 +380,9 @@ bool GameObject::Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, u if (map->Is25ManRaid()) loot.maxDuplicates = 3; + if (spawnid) + m_spawnId = spawnid; + if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry()) { GameObject* linkedGO = new GameObject(); @@ -486,83 +494,90 @@ void GameObject::Update(uint32 diff) } case GO_READY: { - if (m_respawnTime > 0) // timer on + if (m_respawnCompatibilityMode) { - time_t now = time(nullptr); - if (m_respawnTime <= now) // timer expired + if (m_respawnTime > 0) // timer on { - ObjectGuid dbtableHighGuid(HighGuid::GameObject, GetEntry(), m_spawnId); - time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); - if (linkedRespawntime) // Can't respawn, the master is dead + time_t now = time(nullptr); + if (m_respawnTime <= now) // timer expired { - ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); - if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day) - SetRespawnTime(DAY); - else - m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little - SaveRespawnTime(); // also save to DB immediately - return; - } + ObjectGuid dbtableHighGuid(HighGuid::GameObject, GetEntry(), m_spawnId); + time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid); + if (linkedRespawntime) // Can't respawn, the master is dead + { + ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid); + if (targetGuid == dbtableHighGuid) // if linking self, never respawn + SetRespawnTime(WEEK); + else + m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little + SaveRespawnTime(); // also save to DB immediately + return; + } - m_respawnTime = 0; - m_SkillupList.clear(); - m_usetimes = 0; + m_respawnTime = 0; + m_SkillupList.clear(); + m_usetimes = 0; - // If nearby linked trap exists, respawn it - if (GameObject* linkedTrap = GetLinkedTrap()) - linkedTrap->SetLootState(GO_READY); + // If nearby linked trap exists, respawn it + if (GameObject* linkedTrap = GetLinkedTrap()) + linkedTrap->SetLootState(GO_READY); - switch (GetGoType()) - { - case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now + switch (GetGoType()) { - Unit* caster = GetOwner(); - if (caster && caster->GetTypeId() == TYPEID_PLAYER) + case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now { - caster->ToPlayer()->RemoveGameObject(this, false); - - WorldPacket data(SMSG_FISH_ESCAPED, 0); - caster->ToPlayer()->SendDirectMessage(&data); + Unit* caster = GetOwner(); + if (caster && caster->GetTypeId() == TYPEID_PLAYER) + { + caster->ToPlayer()->RemoveGameObject(this, false); + + WorldPacket data(SMSG_FISH_ESCAPED, 0); + caster->ToPlayer()->SendDirectMessage(&data); + } + // can be delete + m_lootState = GO_JUST_DEACTIVATED; + return; } - // can be delete - m_lootState = GO_JUST_DEACTIVATED; - return; + case GAMEOBJECT_TYPE_DOOR: + case GAMEOBJECT_TYPE_BUTTON: + // We need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds) + if (GetGoState() != GO_STATE_READY) + ResetDoorOrButton(); + break; + case GAMEOBJECT_TYPE_FISHINGHOLE: + // Initialize a new max fish count on respawn + m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishinghole.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens); + break; + default: + break; } - case GAMEOBJECT_TYPE_DOOR: - case GAMEOBJECT_TYPE_BUTTON: - // We need to open doors if they are closed (add there another condition if this code breaks some usage, but it need to be here for battlegrounds) - if (GetGoState() != GO_STATE_READY) - ResetDoorOrButton(); - break; - case GAMEOBJECT_TYPE_FISHINGHOLE: - // Initialize a new max fish count on respawn - m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishinghole.minSuccessOpens, GetGOInfo()->fishinghole.maxSuccessOpens); - break; - default: - break; - } - // Despawn timer - if (!m_spawnedByDefault) - { - // Can be despawned or destroyed - SetLootState(GO_JUST_DEACTIVATED); - return; - } + // Despawn timer + if (!m_spawnedByDefault) + { + // Can be despawned or destroyed + SetLootState(GO_JUST_DEACTIVATED); + return; + } - // Call AI Reset (required for example in SmartAI to clear one time events) - if (AI()) - AI()->Reset(); + // Call AI Reset (required for example in SmartAI to clear one time events) + if (AI()) + AI()->Reset(); - // Respawn timer - uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0; - if (poolid) - sPoolMgr->UpdatePool<GameObject>(poolid, GetSpawnId()); - else - GetMap()->AddToMap(this); + // Respawn timer + uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0; + if (poolid) + sPoolMgr->UpdatePool<GameObject>(poolid, GetSpawnId()); + else + GetMap()->AddToMap(this); + } } } + // Set respawn timer + if (!m_respawnCompatibilityMode && m_respawnTime > 0) + SaveRespawnTime(0, false); + if (isSpawned()) { GameObjectTemplate const* goInfo = GetGOInfo(); @@ -750,6 +765,7 @@ void GameObject::Update(uint32 diff) if (!m_respawnDelayTime) return; + // ToDo: Decide if we should properly despawn these. Maybe they expect to be able to manually respawn from script? if (!m_spawnedByDefault) { m_respawnTime = 0; @@ -757,12 +773,28 @@ void GameObject::Update(uint32 diff) return; } - m_respawnTime = time(nullptr) + m_respawnDelayTime; + uint32 respawnDelay = m_respawnDelayTime; + if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE)) + GetMap()->ApplyDynamicModeRespawnScaling(this, this->m_spawnId, respawnDelay, scalingMode); + m_respawnTime = time(nullptr) + respawnDelay; // if option not set then object will be saved at grid unload + // Otherwise just save respawn time to map object memory if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY)) SaveRespawnTime(); + if (!m_respawnCompatibilityMode) + { + // Respawn time was just saved if set to save to DB + // If not, we save only to map memory + if (!sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY)) + SaveRespawnTime(0, false); + + // Then despawn + AddObjectToRemoveList(); + return; + } + DestroyForNearbyPlayers(); // old UpdateObjectVisibility() break; } @@ -848,7 +880,7 @@ void GameObject::SaveToDB() { // this should only be used when the gameobject has already been loaded // preferably after adding to map, because mapid may not be valid otherwise - GameObjectData const* data = sObjectMgr->GetGOData(m_spawnId); + GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId); if (!data) { TC_LOG_ERROR("misc", "GameObject::SaveToDB failed, cannot get gameobject data!"); @@ -868,22 +900,22 @@ void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) m_spawnId = sObjectMgr->GenerateGameObjectSpawnId(); // update in loaded data (changing data only in this place) - GameObjectData& data = sObjectMgr->NewGOData(m_spawnId); + GameObjectData& data = sObjectMgr->NewOrExistGameObjectData(m_spawnId); - // data->guid = guid must not be updated at save + if (!data.spawnId) + data.spawnId = m_spawnId; + ASSERT(data.spawnId == m_spawnId); data.id = GetEntry(); - data.mapid = mapid; + data.spawnPoint.WorldRelocate(this); data.phaseMask = phaseMask; - data.posX = GetPositionX(); - data.posY = GetPositionY(); - data.posZ = GetPositionZ(); - data.orientation = GetOrientation(); data.rotation = m_worldRotation; data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime; data.animprogress = GetGoAnimProgress(); - data.go_state = GetGoState(); + data.goState = GetGoState(); data.spawnMask = spawnMask; data.artKit = GetGoArtKit(); + if (!data.spawnGroupData) + data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup(); // Update in DB SQLTransaction trans = WorldDatabase.BeginTransaction(); @@ -916,9 +948,9 @@ void GameObject::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask) WorldDatabase.CommitTransaction(trans); } -bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap) +bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool) { - GameObjectData const* data = sObjectMgr->GetGOData(spawnId); + GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId); if (!data) { @@ -929,14 +961,14 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo uint32 entry = data->id; //uint32 map_id = data->mapid; // already used before call uint32 phaseMask = data->phaseMask; - Position pos(data->posX, data->posY, data->posZ, data->orientation); uint32 animprogress = data->animprogress; - GOState go_state = data->go_state; + GOState go_state = data->goState; uint32 artKit = data->artKit; m_spawnId = spawnId; - if (!Create(map->GenerateLowGuid<HighGuid::GameObject>(), entry, map, phaseMask, pos, data->rotation, animprogress, go_state, artKit)) + m_respawnCompatibilityMode = ((data->spawnGroupData->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) != 0); + if (!Create(map->GenerateLowGuid<HighGuid::GameObject>(), entry, map, phaseMask, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode)) return false; if (data->spawntimesecs >= 0) @@ -958,7 +990,7 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo if (m_respawnTime && m_respawnTime <= time(nullptr)) { m_respawnTime = 0; - GetMap()->RemoveGORespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); } } } @@ -979,20 +1011,25 @@ bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, boo void GameObject::DeleteFromDB() { - GetMap()->RemoveGORespawnTime(m_spawnId); - sObjectMgr->DeleteGOData(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId); + sObjectMgr->DeleteGameObjectData(m_spawnId); - PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); + SQLTransaction trans = WorldDatabase.BeginTransaction(); + PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT); stmt->setUInt32(0, m_spawnId); + trans->Append(stmt); - WorldDatabase.Execute(stmt); + stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER); + stmt->setUInt8(0, uint8(SPAWN_TYPE_GAMEOBJECT)); + stmt->setUInt32(1, m_spawnId); + trans->Append(stmt); stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT); - stmt->setUInt32(0, m_spawnId); + trans->Append(stmt); - WorldDatabase.Execute(stmt); + WorldDatabase.CommitTransaction(trans); } /*********************************************************/ @@ -1055,10 +1092,19 @@ Unit* GameObject::GetOwner() const return ObjectAccessor::GetUnit(*this, GetOwnerGUID()); } -void GameObject::SaveRespawnTime() +void GameObject::SaveRespawnTime(uint32 forceDelay, bool savetodb) { - if (m_goData && m_goData->dbData && m_respawnTime > time(nullptr) && m_spawnedByDefault) - GetMap()->SaveGORespawnTime(m_spawnId, m_respawnTime); + if (m_goData && m_respawnTime > time(nullptr) && m_spawnedByDefault) + { + if (m_respawnCompatibilityMode) + { + GetMap()->SaveRespawnTimeDB(SPAWN_TYPE_GAMEOBJECT, m_spawnId, m_respawnTime); + return; + } + + uint32 thisRespawnTime = forceDelay ? time(nullptr) + forceDelay : m_respawnTime; + GetMap()->SaveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, GetEntry(), thisRespawnTime, GetZoneId(), Trinity::ComputeGridCoord(GetPositionX(), GetPositionY()).GetId(), m_goData->dbData ? savetodb : false); + } } bool GameObject::IsNeverVisible() const @@ -1123,7 +1169,7 @@ void GameObject::Respawn() if (m_spawnedByDefault && m_respawnTime > 0) { m_respawnTime = time(nullptr); - GetMap()->RemoveGORespawnTime(m_spawnId); + GetMap()->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, m_spawnId, true); } } @@ -1227,7 +1273,7 @@ void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = f void GameObject::SetGoArtKit(uint8 kit) { SetByteValue(GAMEOBJECT_BYTES_1, 2, kit); - GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGOData(m_spawnId)); + GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGameObjectData(m_spawnId)); if (data) data->artKit = kit; } @@ -1238,10 +1284,10 @@ void GameObject::SetGoArtKit(uint8 artkit, GameObject* go, ObjectGuid::LowType l if (go) { go->SetGoArtKit(artkit); - data = go->GetGOData(); + data = go->GetGameObjectData(); } else if (lowguid) - data = sObjectMgr->GetGOData(lowguid); + data = sObjectMgr->GetGameObjectData(lowguid); if (data) const_cast<GameObjectData*>(data)->artKit = artkit; @@ -1971,8 +2017,8 @@ void GameObject::EventInform(uint32 eventId, WorldObject* invoker /*= nullptr*/) uint32 GameObject::GetScriptId() const { - if (GameObjectData const* gameObjectData = GetGOData()) - if (uint32 scriptId = gameObjectData->ScriptId) + if (GameObjectData const* gameObjectData = GetGameObjectData()) + if (uint32 scriptId = gameObjectData->scriptId) return scriptId; return GetGOInfo()->ScriptId; @@ -2410,24 +2456,20 @@ void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* t void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const { - if (m_spawnId) + if (m_goData) { - if (GameObjectData const* data = sObjectMgr->GetGOData(GetSpawnId())) - { - x = data->posX; - y = data->posY; - z = data->posZ; - if (ori) - *ori = data->orientation; - return; - } + if (ori) + m_goData->spawnPoint.GetPosition(x, y, z, *ori); + else + m_goData->spawnPoint.GetPosition(x, y, z); + } + else + { + if (ori) + GetPosition(x, y, z, *ori); + else + GetPosition(x, y, z); } - - x = GetPositionX(); - y = GetPositionY(); - z = GetPositionZ(); - if (ori) - *ori = GetOrientation(); } float GameObject::GetInteractionDistance() const diff --git a/src/server/game/Entities/GameObject/GameObject.h b/src/server/game/Entities/GameObject/GameObject.h index 3080bd94860..784af8fda27 100644 --- a/src/server/game/Entities/GameObject/GameObject.h +++ b/src/server/game/Entities/GameObject/GameObject.h @@ -89,11 +89,11 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void RemoveFromWorld() override; void CleanupsBeforeDelete(bool finalCleanup = true) override; - bool Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit = 0); + bool Create(ObjectGuid::LowType guidlow, uint32 name_id, Map* map, uint32 phaseMask, Position const& pos, QuaternionData const& rotation, uint32 animprogress, GOState go_state, uint32 artKit = 0, bool dynamic = false, ObjectGuid::LowType spawnid = 0); void Update(uint32 p_time) override; GameObjectTemplate const* GetGOInfo() const { return m_goInfo; } GameObjectTemplateAddon const* GetTemplateAddon() const { return m_goTemplateAddon; } - GameObjectData const* GetGOData() const { return m_goData; } + GameObjectData const* GetGameObjectData() const { return m_goData; } GameObjectValue const* GetGOValue() const { return &m_goValue; } bool IsTransport() const; @@ -113,8 +113,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void SaveToDB(); void SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask); - bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map) { return LoadGameObjectFromDB(spawnId, map, false); } - bool LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap = true); + bool LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool = true); // arg4 is unused, only present to match the signature on Creature void DeleteFromDB(); void SetOwnerGUID(ObjectGuid owner) @@ -212,7 +211,7 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> uint32 GetUseCount() const { return m_usetimes; } uint32 GetUniqueUseCount() const { return m_unique_users.size(); } - void SaveRespawnTime() override; + void SaveRespawnTime(uint32 forceDelay = 0, bool savetodb = true) override; Loot loot; @@ -264,6 +263,10 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> void EventInform(uint32 eventId, WorldObject* invoker = nullptr); + // There's many places not ready for dynamic spawns. This allows them to live on for now. + void SetRespawnCompatibilityMode(bool mode = true) { m_respawnCompatibilityMode = mode; } + bool GetRespawnCompatibilityMode() {return m_respawnCompatibilityMode; } + uint32 GetScriptId() const; GameObjectAI* AI() const { return m_AI; } @@ -344,5 +347,6 @@ class TC_GAME_API GameObject : public WorldObject, public GridObject<GameObject> return IsInRange(obj->GetPositionX(), obj->GetPositionY(), obj->GetPositionZ(), dist2compare); } GameObjectAI* m_AI; + bool m_respawnCompatibilityMode; }; #endif diff --git a/src/server/game/Entities/GameObject/GameObjectData.h b/src/server/game/Entities/GameObject/GameObjectData.h index e2d2f27e5b5..6e1a07786f9 100644 --- a/src/server/game/Entities/GameObject/GameObjectData.h +++ b/src/server/game/Entities/GameObject/GameObjectData.h @@ -20,6 +20,7 @@ #include "Common.h" #include "SharedDefines.h" +#include "SpawnData.h" #include "WorldPacket.h" #include <string> #include <vector> @@ -593,26 +594,14 @@ struct GameObjectAddon uint32 InvisibilityValue; }; -// from `gameobject` -struct GameObjectData +// `gameobject` table +struct GameObjectData : public SpawnData { - explicit GameObjectData() : id(0), mapid(0), phaseMask(0), posX(0.0f), posY(0.0f), posZ(0.0f), orientation(0.0f), spawntimesecs(0), - animprogress(0), go_state(GO_STATE_ACTIVE), spawnMask(0), artKit(0), ScriptId(0), dbData(true) { } - uint32 id; // entry in gamobject_template - uint16 mapid; - uint32 phaseMask; - float posX; - float posY; - float posZ; - float orientation; + GameObjectData() : SpawnData(SPAWN_TYPE_GAMEOBJECT) { } QuaternionData rotation; - int32 spawntimesecs; - uint32 animprogress; - GOState go_state; - uint8 spawnMask; - uint8 artKit; - uint32 ScriptId; - bool dbData; + uint32 animprogress = 0; + GOState goState = GO_STATE_ACTIVE; + uint8 artKit = 0; }; #endif // GameObjectData_h__ diff --git a/src/server/game/Entities/Item/ItemEnchantmentMgr.cpp b/src/server/game/Entities/Item/ItemEnchantmentMgr.cpp index 50e0805797c..1468d168ffa 100644 --- a/src/server/game/Entities/Item/ItemEnchantmentMgr.cpp +++ b/src/server/game/Entities/Item/ItemEnchantmentMgr.cpp @@ -76,7 +76,7 @@ void LoadRandomEnchantmentsTable() TC_LOG_INFO("server.loading", ">> Loaded %u Item Enchantment definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } else - TC_LOG_ERROR("server.loading", ">> Loaded 0 Item Enchantment definitions. DB table `item_enchantment_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 Item Enchantment definitions. DB table `item_enchantment_template` is empty."); } uint32 GetItemEnchantMod(int32 entry) diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp index 508a89a86c8..9e2546f2df7 100644 --- a/src/server/game/Entities/Object/Object.cpp +++ b/src/server/game/Entities/Object/Object.cpp @@ -1105,7 +1105,7 @@ void WorldObject::RemoveFromWorld() Object::RemoveFromWorld(); } -InstanceScript* WorldObject::GetInstanceScript() +InstanceScript* WorldObject::GetInstanceScript() const { Map* map = GetMap(); return map->IsDungeon() ? ((InstanceMap*)map)->GetInstanceScript() : nullptr; @@ -1908,7 +1908,7 @@ TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropert break; } - if (!summon->Create(GenerateLowGuid<HighGuid::Unit>(), this, phase, entry, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation(), nullptr, vehId)) + if (!summon->Create(GenerateLowGuid<HighGuid::Unit>(), this, phase, entry, pos, nullptr, vehId, true)) { delete summon; return nullptr; diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h index ca1b547f68b..b6cee547126 100644 --- a/src/server/game/Entities/Object/Object.h +++ b/src/server/game/Entities/Object/Object.h @@ -291,7 +291,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation uint32 GetAreaId() const { return m_areaId; } void GetZoneAndAreaId(uint32& zoneid, uint32& areaid) const { zoneid = m_zoneId, areaid = m_areaId; } - InstanceScript* GetInstanceScript(); + InstanceScript* GetInstanceScript() const; std::string const& GetName() const { return m_name; } void SetName(std::string const& newname) { m_name = newname; } @@ -342,7 +342,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation void SendObjectDeSpawnAnim(ObjectGuid guid); - virtual void SaveRespawnTime() { } + virtual void SaveRespawnTime(uint32 /*forceDelay*/ = 0, bool /*saveToDB*/ = true) { } void AddObjectToRemoveList(); float GetGridActivationRange() const; diff --git a/src/server/game/Entities/Object/ObjectGuid.cpp b/src/server/game/Entities/Object/ObjectGuid.cpp index 3a26dfd0d7f..71837474ca9 100644 --- a/src/server/game/Entities/Object/ObjectGuid.cpp +++ b/src/server/game/Entities/Object/ObjectGuid.cpp @@ -98,6 +98,14 @@ void ObjectGuidGeneratorBase::HandleCounterOverflow(HighGuid high) World::StopNow(ERROR_EXIT_CODE); } +void ObjectGuidGeneratorBase::CheckGuidTrigger(ObjectGuid::LowType guidlow) +{ + if (!sWorld->IsGuidAlert() && guidlow > sWorld->getIntConfig(CONFIG_RESPAWN_GUIDALERTLEVEL)) + sWorld->TriggerGuidAlert(); + else if (!sWorld->IsGuidWarning() && guidlow > sWorld->getIntConfig(CONFIG_RESPAWN_GUIDWARNLEVEL)) + sWorld->TriggerGuidWarning(); +} + #define GUID_TRAIT_INSTANTIATE_GUID( HIGH_GUID ) \ template class TC_GAME_API ObjectGuidGenerator< HIGH_GUID >; diff --git a/src/server/game/Entities/Object/ObjectGuid.h b/src/server/game/Entities/Object/ObjectGuid.h index 75410aa4f28..d1e34d3a38c 100644 --- a/src/server/game/Entities/Object/ObjectGuid.h +++ b/src/server/game/Entities/Object/ObjectGuid.h @@ -287,6 +287,7 @@ public: protected: static void HandleCounterOverflow(HighGuid high); + static void CheckGuidTrigger(ObjectGuid::LowType guid); ObjectGuid::LowType _nextGuid; }; @@ -300,6 +301,10 @@ public: { if (_nextGuid >= ObjectGuid::GetMaxCounter(high) - 1) HandleCounterOverflow(high); + + if (high == HighGuid::Unit || high == HighGuid::GameObject) + CheckGuidTrigger(_nextGuid); + return _nextGuid++; } }; diff --git a/src/server/game/Entities/Object/Position.h b/src/server/game/Entities/Object/Position.h index 98cb82df2e1..11867c6fed6 100644 --- a/src/server/game/Entities/Object/Position.h +++ b/src/server/game/Entities/Object/Position.h @@ -207,15 +207,9 @@ public: return GetExactDist2dSq(pos) < dist * dist; } - bool IsInDist(float x, float y, float z, float dist) const - { - return GetExactDistSq(x, y, z) < dist * dist; - } - - bool IsInDist(Position const* pos, float dist) const - { - return GetExactDistSq(pos) < dist * dist; - } + bool IsInDist(float x, float y, float z, float dist) const { return GetExactDistSq(x, y, z) < dist * dist; } + bool IsInDist(Position const& pos, float dist) const { return GetExactDistSq(pos) < dist * dist; } + bool IsInDist(Position const* pos, float dist) const { return GetExactDistSq(pos) < dist * dist; } bool IsWithinBox(Position const& center, float xradius, float yradius, float zradius) const; @@ -245,15 +239,12 @@ class WorldLocation : public Position WorldLocation(WorldLocation const& loc) : Position(loc), m_mapId(loc.GetMapId()) { } - void WorldRelocate(WorldLocation const& loc) - { - m_mapId = loc.GetMapId(); - Relocate(loc); - } - - void WorldRelocate(uint32 _mapId = MAPID_INVALID, float x = 0.f, float y = 0.f, float z = 0.f, float o = 0.f) + void WorldRelocate(WorldLocation const& loc) { m_mapId = loc.GetMapId(); Relocate(loc); } + void WorldRelocate(WorldLocation const* loc) { m_mapId = loc->GetMapId(); Relocate(loc); } + void WorldRelocate(uint32 mapId, Position const& pos) { m_mapId = mapId; Relocate(pos); } + void WorldRelocate(uint32 mapId = MAPID_INVALID, float x = 0.f, float y = 0.f, float z = 0.f, float o = 0.f) { - m_mapId = _mapId; + m_mapId = mapId; Relocate(x, y, z, o); } diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 20f9e57b666..6890113178a 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -1306,24 +1306,22 @@ bool Pet::addSpell(uint32 spellId, ActiveStates active /*= ACT_DECIDE*/, PetSpel if (itr != m_spells.end()) { if (itr->second.state == PETSPELL_REMOVED) - { - m_spells.erase(itr); state = PETSPELL_CHANGED; - } - else if (state == PETSPELL_UNCHANGED && itr->second.state != PETSPELL_UNCHANGED) + else { - // can be in case spell loading but learned at some previous spell loading - itr->second.state = PETSPELL_UNCHANGED; + if (state == PETSPELL_UNCHANGED && itr->second.state != PETSPELL_UNCHANGED) + { + // can be in case spell loading but learned at some previous spell loading + itr->second.state = PETSPELL_UNCHANGED; - if (active == ACT_ENABLED) - ToggleAutocast(spellInfo, true); - else if (active == ACT_DISABLED) - ToggleAutocast(spellInfo, false); + if (active == ACT_ENABLED) + ToggleAutocast(spellInfo, true); + else if (active == ACT_DISABLED) + ToggleAutocast(spellInfo, false); + } return false; } - else - return false; } PetSpell newspell; @@ -1520,7 +1518,7 @@ bool Pet::removeSpell(uint32 spell_id, bool learn_prev, bool clear_ab) } // if remove last rank or non-ranked then update action bar at server and client if need - if (m_charmInfo->RemoveSpellFromActionBar(spell_id) && !learn_prev && clear_ab) + if (clear_ab && !learn_prev && m_charmInfo->RemoveSpellFromActionBar(spell_id)) { if (!m_loading) GetOwner()->PetSpellInitialize(); // need update action bar for last removed rank diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 032c02f2288..7bf4e185efd 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -176,6 +176,7 @@ Player::Player(WorldSession* session): Unit(true) m_session = session; m_ingametime = 0; + m_sharedQuestId = 0; m_ExtraFlags = 0; @@ -490,6 +491,7 @@ bool Player::Create(ObjectGuid::LowType guidlow, CharacterCreateInfo* createInfo } SetMap(sMapMgr->CreateMap(info->mapId, this)); + UpdatePositionData(); uint8 powertype = cEntry->powerType; @@ -6444,6 +6446,12 @@ void Player::CheckAreaExploreAndOutdoor() XP = uint32(sObjectMgr->GetBaseXP(areaEntry->area_level)*sWorld->getRate(RATE_XP_EXPLORE)); } + if (sWorld->getIntConfig(CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO)) + { + uint32 minScaledXP = uint32(sObjectMgr->GetBaseXP(areaEntry->area_level)*sWorld->getRate(RATE_XP_EXPLORE)) * sWorld->getIntConfig(CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO) / 100; + XP = std::max(minScaledXP, XP); + } + GiveXP(XP, nullptr); SendExplorationExperience(areaId, XP); } @@ -6993,24 +7001,25 @@ void Player::UpdateArea(uint32 newArea) void Player::UpdateZone(uint32 newZone, uint32 newArea) { - if (m_zoneUpdateId != newZone) + if (!IsInWorld()) + return; + uint32 const oldZone = m_zoneUpdateId; + m_zoneUpdateId = newZone; + m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL; + + GetMap()->UpdatePlayerZoneStats(oldZone, newZone); + + // call leave script hooks immedately (before updating flags) + if (oldZone != newZone) { sOutdoorPvPMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId); - sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone); sBattlefieldMgr->HandlePlayerLeaveZone(this, m_zoneUpdateId); - sBattlefieldMgr->HandlePlayerEnterZone(this, newZone); - SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange... - if (Guild* guild = GetGuild()) - guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone); } // group update if (GetGroup()) SetGroupUpdateFlag(GROUP_UPDATE_FULL); - m_zoneUpdateId = newZone; - m_zoneUpdateTimer = ZONE_UPDATE_INTERVAL; - // zone changed, so area changed as well, update it UpdateArea(newArea); @@ -7027,8 +7036,6 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) WeatherMgr::SendFineWeatherUpdateToPlayer(this); } - sScriptMgr->OnPlayerUpdateZone(this, newZone, newArea); - // in PvP, any not controlled zone (except zone->team == 6, default case) // in PvE, only opposition team capital switch (zone->team) @@ -7075,6 +7082,17 @@ void Player::UpdateZone(uint32 newZone, uint32 newArea) UpdateLocalChannels(newZone); UpdateZoneDependentAuras(newZone); + + // call enter script hooks after everyting else has processed + sScriptMgr->OnPlayerUpdateZone(this, newZone, newArea); + if (oldZone != newZone) + { + sOutdoorPvPMgr->HandlePlayerEnterZone(this, newZone); + sBattlefieldMgr->HandlePlayerEnterZone(this, newZone); + SendInitWorldStates(newZone, newArea); // only if really enters to new zone, not just area change, works strange... + if (Guild* guild = GetGuild()) + guild->UpdateMemberData(this, GUILD_MEMBER_DATA_ZONEID, newZone); + } } //If players are too far away from the duel flag... they lose the duel @@ -11194,6 +11212,9 @@ InventoryResult Player::CanEquipItem(uint8 slot, uint16 &dest, Item* pItem, bool if (HasUnitState(UNIT_STATE_STUNNED)) return EQUIP_ERR_YOU_ARE_STUNNED; + if (IsCharmed()) + return EQUIP_ERR_CANT_DO_RIGHT_NOW; // @todo is this the correct error? + // do not allow equipping gear except weapons, offhands, projectiles, relics in // - combat // - in-progress arenas @@ -11351,6 +11372,9 @@ InventoryResult Player::CanUnequipItem(uint16 pos, bool swap) const if (pItem->m_lootGenerated) return EQUIP_ERR_ALREADY_LOOTED; + if (IsCharmed()) + return EQUIP_ERR_CANT_DO_RIGHT_NOW; // @todo is this the correct error? + // do not allow unequipping gear except weapons, offhands, projectiles, relics in // - combat // - in-progress arenas @@ -11775,14 +11799,11 @@ void Player::SetAmmo(uint32 item) return; // check ammo - if (item) + InventoryResult msg = CanUseAmmo(item); + if (msg != EQUIP_ERR_OK) { - InventoryResult msg = CanUseAmmo(item); - if (msg != EQUIP_ERR_OK) - { - SendEquipError(msg, nullptr, nullptr, item); - return; - } + SendEquipError(msg, nullptr, nullptr, item); + return; } SetUInt32Value(PLAYER_AMMO_ID, item); @@ -13984,7 +14005,7 @@ void Player::SendItemDurations() } } -void Player::SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast) +void Player::SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast, bool sendChatMessage) { if (!item) // prevent crash return; @@ -13994,7 +14015,7 @@ void Player::SendNewItem(Item* item, uint32 count, bool received, bool created, data << uint64(GetGUID()); // player GUID data << uint32(received); // 0=looted, 1=from npc data << uint32(created); // 0=received, 1=created - data << uint32(1); // bool print error to chat + data << uint32(sendChatMessage); // bool print message to chat data << uint8(item->GetBagSlot()); // bagslot // item slot, but when added to stack: 0xFFFFFFFF data << uint32((item->GetCount() == count) ? item->GetSlot() : -1); @@ -15009,7 +15030,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, quest->RewardItemIdCount[i]) == EQUIP_ERR_OK) { Item* item = StoreNewItem(dest, itemId, true, GenerateItemRandomPropertyId(itemId)); - SendNewItem(item, quest->RewardItemIdCount[i], true, false); + SendNewItem(item, quest->RewardItemIdCount[i], true, false, false, false); } else if (quest->IsDFQuest()) SendItemRetrievalMail(itemId, quest->RewardItemIdCount[i]); @@ -15025,7 +15046,7 @@ void Player::RewardQuest(Quest const* quest, uint32 reward, Object* questGiver, if (CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemId, quest->RewardChoiceItemCount[reward]) == EQUIP_ERR_OK) { Item* item = StoreNewItem(dest, itemId, true, GenerateItemRandomPropertyId(itemId)); - SendNewItem(item, quest->RewardChoiceItemCount[reward], true, false); + SendNewItem(item, quest->RewardChoiceItemCount[reward], true, false, false, false); } } } @@ -15502,7 +15523,7 @@ bool Player::SatisfyQuestStatus(Quest const* qInfo, bool msg) const bool Player::SatisfyQuestConditions(Quest const* qInfo, bool msg) { - if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_ACCEPT, qInfo->GetQuestId(), this)) + if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, qInfo->GetQuestId(), this)) { if (msg) { @@ -15901,7 +15922,7 @@ QuestGiverStatus Player::GetQuestDialogStatus(Object* questgiver) if (!quest) continue; - if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_ACCEPT, quest->GetQuestId(), this)) + if (!sConditionMgr->IsObjectMeetingNotGroupedConditions(CONDITION_SOURCE_TYPE_QUEST_AVAILABLE, quest->GetQuestId(), this)) continue; QuestStatus status = GetQuestStatus(questId); @@ -17148,7 +17169,6 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) //join player to battleground group currentBg->EventPlayerLoggedIn(this); - currentBg->AddOrSetPlayerToCorrectBgGroup(this, m_bgData.bgTeam); SetInviteForBattlegroundQueueType(bgQueueTypeId, currentBg->GetInstanceID()); } @@ -17373,6 +17393,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder) SetMap(map); StoreRaidMapDifficulty(); + UpdatePositionData(); // now that map position is determined, check instance validity if (!CheckInstanceValidity(true) && !IsInstanceLoginGameMasterException()) @@ -21217,7 +21238,14 @@ bool Player::ActivateTaxiPathTo(std::vector<uint32> const& nodes, Creature* npc uint32 money = GetMoney(); if (npc) - totalcost = (uint32)ceil(totalcost*GetReputationPriceDiscount(npc)); + { + float discount = GetReputationPriceDiscount(npc); + totalcost = uint32(ceil(totalcost * discount)); + firstcost = uint32(ceil(firstcost * discount)); + m_taxi.SetFlightMasterFactionTemplateId(npc->GetFaction()); + } + else + m_taxi.SetFlightMasterFactionTemplateId(0); if (money < totalcost) { @@ -21325,6 +21353,27 @@ void Player::ContinueTaxiFlight() const GetSession()->SendDoFlight(mountDisplayId, path, startNode); } +void Player::SendTaxiNodeStatusMultiple() +{ + for (auto itr = m_clientGUIDs.begin(); itr != m_clientGUIDs.end(); ++itr) + { + if (!itr->IsCreature()) + continue; + Creature* creature = ObjectAccessor::GetCreature(*this, *itr); + if (!creature || creature->IsHostileTo(this)) + continue; + if (!creature->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_FLIGHTMASTER)) + continue; + uint32 nearestNode = sObjectMgr->GetNearestTaxiNode(creature->GetPositionX(), creature->GetPositionY(), creature->GetPositionZ(), creature->GetMapId(), GetTeam()); + if (!nearestNode) + continue; + WorldPacket data(SMSG_TAXINODE_STATUS, 9); + data << *itr; + data << uint8(m_taxi.IsTaximaskNodeKnown(nearestNode) ? 1 : 0); + SendDirectMessage(&data); + } +} + void Player::InitDataForForm(bool reapplyMods) { ShapeshiftForm form = GetShapeshiftForm(); @@ -22517,6 +22566,8 @@ void Player::SendInitialPacketsAfterAddToMap() SendAurasForTarget(this); SendEnchantmentDurations(); // must be after add to map SendItemDurations(); // must be after add to map + SendQuestGiverStatusMultiple(); + SendTaxiNodeStatusMultiple(); // raid downscaling - send difficulty to player if (GetMap()->IsRaid()) @@ -23168,11 +23219,15 @@ bool Player::GetBGAccessByLevel(BattlegroundTypeId bgTypeId) const float Player::GetReputationPriceDiscount(Creature const* creature) const { - FactionTemplateEntry const* vendor_faction = creature->GetFactionTemplateEntry(); - if (!vendor_faction || !vendor_faction->faction) + return GetReputationPriceDiscount(creature->GetFactionTemplateEntry()); +} + +float Player::GetReputationPriceDiscount(FactionTemplateEntry const* factionTemplate) const +{ + if (!factionTemplate || !factionTemplate->faction) return 1.0f; - ReputationRank rank = GetReputationRank(vendor_faction->faction); + ReputationRank rank = GetReputationRank(factionTemplate->faction); if (rank <= REP_NEUTRAL) return 1.0f; @@ -23557,7 +23612,7 @@ bool Player::isHonorOrXPTarget(Unit* victim) const uint8 k_grey = Trinity::XP::GetGrayLevel(getLevel()); // Victim level less gray level - if (v_level <= k_grey) + if (v_level <= k_grey && !sWorld->getIntConfig(CONFIG_MIN_CREATURE_SCALED_XP_RATIO)) return false; if (Creature const* creature = victim->ToCreature()) @@ -24030,7 +24085,7 @@ void Player::ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidDat else m_MirrorTimerFlags &= ~UNDERWATER_INLAVA; } - + // Slime state (any contact) if (liquidData->type_flags & MAP_LIQUID_TYPE_SLIME) { @@ -26240,9 +26295,6 @@ bool Player::SetDisableGravity(bool disable, bool packetOnly /*= false*/) bool Player::SetCanFly(bool apply, bool packetOnly /*= false*/) { - if (!packetOnly && !Unit::SetCanFly(apply)) - return false; - if (!apply) SetFallInformation(0, GetPositionZ()); @@ -26251,11 +26303,16 @@ bool Player::SetCanFly(bool apply, bool packetOnly /*= false*/) data << uint32(0); //! movement counter SendDirectMessage(&data); - data.Initialize(MSG_MOVE_UPDATE_CAN_FLY, 64); - data << GetPackGUID(); - BuildMovementPacket(&data); - SendMessageToSet(&data, false); - return true; + if (packetOnly || Unit::SetCanFly(apply)) + { + data.Initialize(MSG_MOVE_UPDATE_CAN_FLY, 64); + data << GetPackGUID(); + BuildMovementPacket(&data); + SendMessageToSet(&data, false); + return true; + } + else + return false; } bool Player::SetHover(bool apply, bool packetOnly /*= false*/) diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 4a2a82000b4..bc72066307f 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -946,6 +946,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> bool ActivateTaxiPathTo(uint32 taxi_path_id, uint32 spellid = 0); void CleanupAfterTaxiFlight(); void ContinueTaxiFlight() const; + void SendTaxiNodeStatusMultiple(); // mount_id can be used in scripting calls bool isAcceptWhispers() const { return (m_ExtraFlags & PLAYER_EXTRA_ACCEPT_WHISPERS) != 0; } void SetAcceptWhispers(bool on) { if (on) m_ExtraFlags |= PLAYER_EXTRA_ACCEPT_WHISPERS; else m_ExtraFlags &= ~PLAYER_EXTRA_ACCEPT_WHISPERS; } @@ -1120,11 +1121,12 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> bool IsUseEquipedWeapon(bool mainhand) const; bool IsTwoHandUsed() const; bool IsUsingTwoHandedWeaponInOneHand() const; - void SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast = false); + void SendNewItem(Item* item, uint32 count, bool received, bool created, bool broadcast = false, bool sendChatMessage = true); bool BuyItemFromVendorSlot(ObjectGuid vendorguid, uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot); bool _StoreOrEquipNewItem(uint32 vendorslot, uint32 item, uint8 count, uint8 bag, uint8 slot, int32 price, ItemTemplate const* pProto, Creature* pVendor, VendorItem const* crItem, bool bStore); float GetReputationPriceDiscount(Creature const* creature) const; + float GetReputationPriceDiscount(FactionTemplateEntry const* factionTemplate) const; Player* GetTrader() const; TradeData* GetTradeData() const { return m_trade; } diff --git a/src/server/game/Entities/Player/PlayerTaxi.cpp b/src/server/game/Entities/Player/PlayerTaxi.cpp index 9a53838e4a9..74a11230d4e 100644 --- a/src/server/game/Entities/Player/PlayerTaxi.cpp +++ b/src/server/game/Entities/Player/PlayerTaxi.cpp @@ -91,9 +91,13 @@ bool PlayerTaxi::LoadTaxiDestinationsFromString(const std::string& values, uint3 { ClearTaxiDestinations(); - Tokenizer Tokenizer(values, ' '); + Tokenizer tokens(values, ' '); + auto iter = tokens.begin(); + if (iter != tokens.end()) + m_flightMasterFactionId = atoul(*iter); - for (Tokenizer::const_iterator iter = Tokenizer.begin(); iter != Tokenizer.end(); ++iter) + ++iter; + for (; iter != tokens.end(); ++iter) { uint32 node = atoul(*iter); AddTaxiDestination(node); @@ -128,6 +132,7 @@ std::string PlayerTaxi::SaveTaxiDestinationsToString() return ""; std::ostringstream ss; + ss << m_flightMasterFactionId << ' '; for (size_t i = 0; i < m_TaxiDestinations.size(); ++i) ss << m_TaxiDestinations[i] << ' '; @@ -154,3 +159,8 @@ std::ostringstream& operator<<(std::ostringstream& ss, PlayerTaxi const& taxi) ss << taxi.m_taximask[i] << ' '; return ss; } + +FactionTemplateEntry const* PlayerTaxi::GetFlightMasterFactionTemplate() const +{ + return sFactionTemplateStore.LookupEntry(m_flightMasterFactionId); +} diff --git a/src/server/game/Entities/Player/PlayerTaxi.h b/src/server/game/Entities/Player/PlayerTaxi.h index ae5052b3e7a..a4791c0ff4e 100644 --- a/src/server/game/Entities/Player/PlayerTaxi.h +++ b/src/server/game/Entities/Player/PlayerTaxi.h @@ -24,11 +24,12 @@ #include <iosfwd> class ByteBuffer; +struct FactionTemplateEntry; class TC_GAME_API PlayerTaxi { public: - PlayerTaxi() { m_taximask.fill(0); } + PlayerTaxi() : m_flightMasterFactionId(0) { m_taximask.fill(0); } ~PlayerTaxi() { } // Nodes void InitTaxiNodesForLevel(uint32 race, uint32 chrClass, uint8 level); @@ -71,11 +72,14 @@ class TC_GAME_API PlayerTaxi std::deque<uint32> const& GetPath() const { return m_TaxiDestinations; } bool empty() const { return m_TaxiDestinations.empty(); } + FactionTemplateEntry const* GetFlightMasterFactionTemplate() const; + void SetFlightMasterFactionTemplateId(uint32 factionTemplateId) { m_flightMasterFactionId = factionTemplateId; } friend std::ostringstream& operator<<(std::ostringstream& ss, PlayerTaxi const& taxi); private: TaxiMask m_taximask; std::deque<uint32> m_TaxiDestinations; + uint32 m_flightMasterFactionId; }; std::ostringstream& operator<<(std::ostringstream& ss, PlayerTaxi const& taxi); diff --git a/src/server/game/Entities/Transport/Transport.cpp b/src/server/game/Entities/Transport/Transport.cpp index 6d36095ee7d..2b9b82b392b 100644 --- a/src/server/game/Entities/Transport/Transport.cpp +++ b/src/server/game/Entities/Transport/Transport.cpp @@ -302,16 +302,14 @@ Creature* Transport::CreateNPCPassenger(ObjectGuid::LowType guid, CreatureData c Map* map = GetMap(); Creature* creature = new Creature(); - if (!creature->LoadCreatureFromDB(guid, map, false)) + if (!creature->LoadFromDB(guid, map, false, true)) { delete creature; return nullptr; } - float x = data->posX; - float y = data->posY; - float z = data->posZ; - float o = data->orientation; + float x, y, z, o; + data->spawnPoint.GetPosition(x, y, z, o); creature->SetTransport(this); creature->AddUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT); @@ -349,7 +347,7 @@ GameObject* Transport::CreateGOPassenger(ObjectGuid::LowType guid, GameObjectDat Map* map = GetMap(); GameObject* go = new GameObject(); - if (!go->LoadGameObjectFromDB(guid, map, false)) + if (!go->LoadFromDB(guid, map, false)) { delete go; return nullptr; @@ -357,10 +355,8 @@ GameObject* Transport::CreateGOPassenger(ObjectGuid::LowType guid, GameObjectDat ASSERT(data); - float x = data->posX; - float y = data->posY; - float z = data->posZ; - float o = data->orientation; + float x, y, z, o; + data->spawnPoint.GetPosition(x, y, z, o); go->SetTransport(this); go->m_movementInfo.transport.guid = GetGUID(); @@ -468,7 +464,7 @@ TempSummon* Transport::SummonPassenger(uint32 entry, Position const& pos, TempSu pos.GetPosition(x, y, z, o); CalculatePassengerPosition(x, y, z, &o); - if (!summon->Create(map->GenerateLowGuid<HighGuid::Unit>(), map, phase, entry, x, y, z, o, nullptr, vehId)) + if (!summon->Create(map->GenerateLowGuid<HighGuid::Unit>(), map, phase, entry, { x, y, z, o }, nullptr, vehId)) { delete summon; return nullptr; @@ -545,7 +541,7 @@ void Transport::LoadStaticPassengers() // GameObjects on transport guidEnd = cellItr->second.gameobjects.end(); for (CellGuidSet::const_iterator guidItr = cellItr->second.gameobjects.begin(); guidItr != guidEnd; ++guidItr) - CreateGOPassenger(*guidItr, sObjectMgr->GetGOData(*guidItr)); + CreateGOPassenger(*guidItr, sObjectMgr->GetGameObjectData(*guidItr)); } } } diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 733c3231915..bd4c71c06e6 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -40,6 +40,7 @@ #include "Log.h" #include "LootMgr.h" #include "MotionMaster.h" +#include "MovementGenerator.h" #include "MoveSpline.h" #include "MoveSplineInit.h" #include "ObjectAccessor.h" @@ -2607,7 +2608,7 @@ SpellMissInfo Unit::MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const { // Can`t miss on dead target (on skinning for example) - if ((!victim->IsAlive() && victim->GetTypeId() != TYPEID_PLAYER)) + if (!victim->IsAlive() && victim->GetTypeId() != TYPEID_PLAYER) return SPELL_MISS_NONE; SpellSchoolMask schoolMask = spellInfo->GetSchoolMask(); @@ -3391,7 +3392,8 @@ void Unit::ProcessTerrainStatusUpdate(ZLiquidStatus status, Optional<LiquidData> { if (_lastLiquid && _lastLiquid->SpellId) RemoveAurasDueToSpell(_lastLiquid->SpellId); - if (curLiquid && curLiquid->SpellId) + Player* player = GetCharmerOrOwnerPlayerOrPlayerItself(); + if (curLiquid && curLiquid->SpellId && (!player || !player->IsGameMaster())) CastSpell(this, curLiquid->SpellId, true); _lastLiquid = curLiquid; } @@ -5675,8 +5677,6 @@ ReputationRank Unit::GetFactionReactionTo(FactionTemplateEntry const* factionTem return REP_NEUTRAL; FactionTemplateEntry const* targetFactionTemplateEntry = target->GetFactionTemplateEntry(); - if (!targetFactionTemplateEntry) - return REP_NEUTRAL; if (Player const* targetPlayerOwner = target->GetAffectingPlayer()) { @@ -5728,7 +5728,7 @@ bool Unit::IsFriendlyTo(Unit const* unit) const bool Unit::IsHostileToPlayers() const { FactionTemplateEntry const* my_faction = GetFactionTemplateEntry(); - if (!my_faction || !my_faction->faction) + if (!my_faction->faction) return false; FactionEntry const* raw_faction = sFactionStore.LookupEntry(my_faction->faction); @@ -5741,7 +5741,7 @@ bool Unit::IsHostileToPlayers() const bool Unit::IsNeutralToAll() const { FactionTemplateEntry const* my_faction = GetFactionTemplateEntry(); - if (!my_faction || !my_faction->faction) + if (!my_faction->faction) return true; FactionEntry const* raw_faction = sFactionStore.LookupEntry(my_faction->faction); @@ -9683,11 +9683,10 @@ Unit* Creature::SelectVictim() // last case when creature must not go to evade mode: // it in combat but attacker not make any damage and not enter to aggro radius to have record in threat list - // for example at owner command to pet attack some far away creature // Note: creature does not have targeted movement generator but has attacker in this case for (AttackerSet::const_iterator itr = m_attackers.begin(); itr != m_attackers.end(); ++itr) { - if ((*itr) && !CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER + if ((*itr) && CanCreatureAttack(*itr) && (*itr)->GetTypeId() != TYPEID_PLAYER && !(*itr)->ToCreature()->HasUnitTypeMask(UNIT_MASK_CONTROLABLE_GUARDIAN)) return nullptr; } @@ -11407,6 +11406,27 @@ void Unit::StopMoving() init.Stop(); } +void Unit::PauseMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/, bool forced/* = true*/) +{ + if (slot >= MAX_MOTION_SLOT) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Pause(timer); + + if (forced) + StopMoving(); +} + +void Unit::ResumeMovement(uint32 timer/* = 0*/, uint8 slot/* = 0*/) +{ + if (slot >= MAX_MOTION_SLOT) + return; + + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MovementSlot(slot))) + movementGenerator->Resume(timer); +} + void Unit::SendMovementFlagUpdate(bool self /* = false */) { WorldPacket data; @@ -12597,8 +12617,14 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au if (GetTypeId() == TYPEID_UNIT) { + if (MovementGenerator* movementGenerator = GetMotionMaster()->GetMotionSlot(MOTION_SLOT_IDLE)) + movementGenerator->Pause(0); + + GetMotionMaster()->Clear(MOTION_SLOT_ACTIVE); + + StopMoving(); + ToCreature()->AI()->OnCharmed(true); - GetMotionMaster()->MoveIdle(); } else if (Player* player = ToPlayer()) { @@ -12712,6 +12738,7 @@ void Unit::RemoveCharmedBy(Unit* charmer) else RestoreFaction(); + ///@todo Handle SLOT_IDLE motion resume GetMotionMaster()->InitDefault(); if (Creature* creature = ToCreature()) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 4fc9a43e464..cfe2f840915 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1279,7 +1279,9 @@ class TC_GAME_API Unit : public WorldObject void CastSpell(SpellCastTargets const& targets, SpellInfo const* spellInfo, CustomSpellValues const* value, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); void CastSpell(Unit* victim, uint32 spellId, bool triggered, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); + void CastSpell(std::nullptr_t, uint32 spellId, bool triggered, Item* castItem = nullptr, AuraEffect* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty) { CastSpell((Unit*)nullptr, spellId, triggered, castItem, triggeredByAura, originalCaster); } void CastSpell(Unit* victim, uint32 spellId, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); + void CastSpell(std::nullptr_t, uint32 spellId, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty) { CastSpell((Unit*)nullptr, spellId, triggerFlags, castItem, triggeredByAura, originalCaster); } void CastSpell(Unit* victim, SpellInfo const* spellInfo, bool triggered, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); void CastSpell(Unit* victim, SpellInfo const* spellInfo, TriggerCastFlags triggerFlags = TRIGGERED_NONE, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); void CastSpell(float x, float y, float z, uint32 spellId, bool triggered, Item* castItem = nullptr, AuraEffect const* triggeredByAura = nullptr, ObjectGuid originalCaster = ObjectGuid::Empty); @@ -1792,6 +1794,8 @@ class TC_GAME_API Unit : public WorldObject bool IsStopped() const { return !(HasUnitState(UNIT_STATE_MOVING)); } void StopMoving(); + void PauseMovement(uint32 timer = 0, uint8 slot = 0, bool forced = true); // timer in ms + void ResumeMovement(uint32 timer = 0, uint8 slot = 0); // timer in ms void AddUnitMovementFlag(uint32 f) { m_movementInfo.flags |= f; } void RemoveUnitMovementFlag(uint32 f) { m_movementInfo.flags &= ~f; } diff --git a/src/server/game/Entities/Unit/UnitDefines.h b/src/server/game/Entities/Unit/UnitDefines.h index 47f0ed28923..04d3521e40c 100644 --- a/src/server/game/Entities/Unit/UnitDefines.h +++ b/src/server/game/Entities/Unit/UnitDefines.h @@ -131,7 +131,7 @@ enum UnitFlags : uint32 UNIT_FLAG_IMMUNE_TO_PC = 0x00000100, // disables combat/assistance with PlayerCharacters (PC) - see Unit::_IsValidAttackTarget, Unit::_IsValidAssistTarget UNIT_FLAG_IMMUNE_TO_NPC = 0x00000200, // disables combat/assistance with NonPlayerCharacters (NPC) - see Unit::_IsValidAttackTarget, Unit::_IsValidAssistTarget UNIT_FLAG_LOOTING = 0x00000400, // loot animation - UNIT_FLAG_PET_IN_COMBAT = 0x00000800, // in combat?, 2.0.8 + UNIT_FLAG_PET_IN_COMBAT = 0x00000800, // on player pets: whether the pet is chasing a target to attack || on other units: whether any of the unit's minions is in combat UNIT_FLAG_PVP = 0x00001000, // changed in 3.0.3 UNIT_FLAG_SILENCED = 0x00002000, // silenced, 2.1.1 UNIT_FLAG_CANNOT_SWIM = 0x00004000, // 2.0.8 diff --git a/src/server/game/Entities/Vehicle/Vehicle.cpp b/src/server/game/Entities/Vehicle/Vehicle.cpp index 357cceb99b6..8b259438efc 100755 --- a/src/server/game/Entities/Vehicle/Vehicle.cpp +++ b/src/server/game/Entities/Vehicle/Vehicle.cpp @@ -148,7 +148,8 @@ void Vehicle::Reset(bool evading /*= false*/) TC_LOG_DEBUG("entities.vehicle", "Vehicle::Reset (Entry: %u, GuidLow: %u, DBGuid: %u)", GetCreatureEntry(), _me->GetGUID().GetCounter(), _me->ToCreature()->GetSpawnId()); ApplyAllImmunities(); - InstallAllAccessories(evading); + if (GetBase()->IsAlive()) + InstallAllAccessories(evading); sScriptMgr->OnReset(this); } @@ -401,7 +402,7 @@ void Vehicle::InstallAccessory(uint32 entry, int8 seatId, bool minion, uint8 typ * @author Machiavelli * @date 17-2-2013 * - * @param [in, out] The prospective passenger. + * @param unit The prospective passenger. * @param seatId Identifier for the seat. Value of -1 indicates the next available seat. * * @return true if it succeeds, false if it fails. @@ -772,6 +773,13 @@ bool VehicleJoinEvent::Execute(uint64, uint32) Target->RemovePendingEventsForSeat(Seat->first); Target->RemovePendingEventsForPassenger(Passenger); + // Passenger might've died in the meantime - abort if this is the case + if (!Passenger->IsAlive()) + { + Abort(0); + return true; + } + Passenger->SetVehicle(Target); Seat->second.Passenger.Guid = Passenger->GetGUID(); Seat->second.Passenger.IsUnselectable = Passenger->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_NOT_SELECTABLE); diff --git a/src/server/game/Events/GameEventMgr.cpp b/src/server/game/Events/GameEventMgr.cpp index fc00ce5ee9e..14fdccce85e 100644 --- a/src/server/game/Events/GameEventMgr.cpp +++ b/src/server/game/Events/GameEventMgr.cpp @@ -218,12 +218,12 @@ void GameEventMgr::LoadFromDB() { { uint32 oldMSTime = getMSTime(); - // 0 1 2 3 4 5 6 7 8 - QueryResult result = WorldDatabase.Query("SELECT eventEntry, UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), occurence, length, holiday, description, world_event, announce FROM game_event"); + // 0 1 2 3 4 5 6 7 8 9 + QueryResult result = WorldDatabase.Query("SELECT eventEntry, UNIX_TIMESTAMP(start_time), UNIX_TIMESTAMP(end_time), occurence, length, holiday, holidayStage, description, world_event, announce FROM game_event"); if (!result) { mGameEvent.clear(); - TC_LOG_ERROR("server.loading", ">> Loaded 0 game events. DB table `game_event` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 game events. DB table `game_event` is empty."); return; } @@ -247,10 +247,13 @@ void GameEventMgr::LoadFromDB() pGameEvent.occurence = fields[3].GetUInt64(); pGameEvent.length = fields[4].GetUInt64(); pGameEvent.holiday_id = HolidayIds(fields[5].GetUInt32()); - - pGameEvent.state = (GameEventState)(fields[7].GetUInt8()); + pGameEvent.holidayStage = fields[6].GetUInt8(); + pGameEvent.description = fields[7].GetString(); + pGameEvent.state = (GameEventState)(fields[8].GetUInt8()); + pGameEvent.announce = fields[9].GetUInt8(); pGameEvent.nextstart = 0; - pGameEvent.announce = fields[8].GetUInt8(); + + ++count; if (pGameEvent.length == 0 && pGameEvent.state == GAMEEVENT_NORMAL) // length>0 is validity check { @@ -264,12 +267,18 @@ void GameEventMgr::LoadFromDB() { TC_LOG_ERROR("sql.sql", "`game_event`: game event id (%i) contains nonexisting holiday id %u.", event_id, pGameEvent.holiday_id); pGameEvent.holiday_id = HOLIDAY_NONE; + continue; + } + if (pGameEvent.holidayStage > MAX_HOLIDAY_DURATIONS) + { + TC_LOG_ERROR("sql.sql", "`game_event` game event id (%i) has out of range holidayStage %u.", event_id, pGameEvent.holidayStage); + pGameEvent.holidayStage = 0; + continue; } - } - pGameEvent.description = fields[6].GetString(); + SetHolidayEventTime(pGameEvent); + } - ++count; } while (result->NextRow()); @@ -436,7 +445,7 @@ void GameEventMgr::LoadFromDB() int32 internal_event_id = mGameEvent.size() + event_id - 1; - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) { TC_LOG_ERROR("sql.sql", "`game_event_gameobject` contains gameobject (GUID: %u) not found in `gameobject` table.", guid); @@ -929,6 +938,45 @@ void GameEventMgr::LoadFromDB() } } +void GameEventMgr::LoadHolidayDates() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT id, date_id, date_value FROM holiday_dates"); + + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 holiday dates. DB table `holiday_dates` is empty."); + return; + } + + uint32 count = 0; + do + { + Field* fields = result->Fetch(); + uint32 holidayId = fields[0].GetUInt32(); + HolidaysEntry* entry = const_cast<HolidaysEntry*>(sHolidaysStore.LookupEntry(holidayId)); + if (!entry) + { + TC_LOG_ERROR("sql.sql", "holiday_dates entry has invalid holiday id %u.", holidayId); + continue; + } + uint8 dateId = fields[1].GetUInt8(); + if (dateId >= MAX_HOLIDAY_DATES) + { + TC_LOG_ERROR("sql.sql", "holiday_dates entry has out of range date_id %u.", dateId); + continue; + } + entry->Date[dateId] = fields[2].GetUInt32(); + modifiedHolidays.insert(entry->Id); + ++count; + + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u holiday dates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + uint32 GameEventMgr::GetNPCFlag(Creature* cr) { uint32 mask = 0; @@ -1140,7 +1188,7 @@ void GameEventMgr::UpdateEventNPCFlags(uint16 event_id) for (NPCFlagList::iterator itr = mGameEventNPCFlags[event_id].begin(); itr != mGameEventNPCFlags[event_id].end(); ++itr) // get the creature data from the low guid to get the entry, to be able to find out the whole guid if (CreatureData const* data = sObjectMgr->GetCreatureData(itr->first)) - creaturesByMap[data->mapid].insert(itr->first); + creaturesByMap[data->spawnPoint.GetMapId()].insert(itr->first); for (auto const& p : creaturesByMap) { @@ -1203,13 +1251,13 @@ void GameEventMgr::GameEventSpawn(int16 event_id) sObjectMgr->AddCreatureToGrid(*itr, data); // Spawn if necessary (loaded grids only) - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { Creature* creature = new Creature(); //TC_LOG_DEBUG("misc", "Spawning creature %u", *itr); - if (!creature->LoadCreatureFromDB(*itr, map)) + if (!creature->LoadFromDB(*itr, map, true, false)) delete creature; } } @@ -1225,19 +1273,19 @@ void GameEventMgr::GameEventSpawn(int16 event_id) for (GuidList::iterator itr = mGameEventGameobjectGuids[internal_event_id].begin(); itr != mGameEventGameobjectGuids[internal_event_id].end(); ++itr) { // Add to correct cell - if (GameObjectData const* data = sObjectMgr->GetGOData(*itr)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(*itr)) { sObjectMgr->AddGameobjectToGrid(*itr, data); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { GameObject* pGameobject = new GameObject; //TC_LOG_DEBUG("misc", "Spawning gameobject %u", *itr); /// @todo find out when it is add to map - if (!pGameobject->LoadGameObjectFromDB(*itr, map, false)) + if (!pGameobject->LoadFromDB(*itr, map, false)) delete pGameobject; else { @@ -1280,7 +1328,7 @@ void GameEventMgr::GameEventUnspawn(int16 event_id) { sObjectMgr->RemoveCreatureFromGrid(*itr, data); - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr](Map* map) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(*itr); for (auto itr2 = creatureBounds.first; itr2 != creatureBounds.second;) @@ -1306,11 +1354,11 @@ void GameEventMgr::GameEventUnspawn(int16 event_id) if (event_id >0 && hasGameObjectActiveEventExcept(*itr, event_id)) continue; // Remove the gameobject from grid - if (GameObjectData const* data = sObjectMgr->GetGOData(*itr)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(*itr)) { sObjectMgr->RemoveGameobjectFromGrid(*itr, data); - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr](Map* map) { auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(*itr); for (auto itr2 = gameobjectBounds.first; itr2 != gameobjectBounds.second;) @@ -1344,7 +1392,7 @@ void GameEventMgr::ChangeEquipOrModel(int16 event_id, bool activate) continue; // Update if spawned - sMapMgr->DoForAllMapsWithMapId(data->mapid, [&itr, activate](Map* map) + sMapMgr->DoForAllMapsWithMapId(data->spawnPoint.GetMapId(), [&itr, activate](Map* map) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(itr->first); @@ -1667,6 +1715,84 @@ void GameEventMgr::RunSmartAIScripts(uint16 event_id, bool activate) }); } +void GameEventMgr::SetHolidayEventTime(GameEventData& event) +{ + if (!event.holidayStage) // Ignore holiday + return; + + const HolidaysEntry* holiday = sHolidaysStore.LookupEntry(event.holiday_id); + + if (!holiday->Date[0] || !holiday->Duration[0]) // Invalid definitions + { + TC_LOG_ERROR("sql.sql", "Missing date or duration for holiday %u.", event.holiday_id); + return; + } + + uint8 stageIndex = event.holidayStage - 1; + event.length = holiday->Duration[stageIndex] * HOUR / MINUTE; + + time_t stageOffset = 0; + for (int i = 0; i < stageIndex; ++i) + stageOffset += holiday->Duration[i] * HOUR; + + switch (holiday->CalendarFilterType) + { + case -1: // Yearly + event.occurence = YEAR / MINUTE; // Not all too useful + break; + case 0: // Weekly + event.occurence = WEEK / MINUTE; + break; + case 1: // Defined dates only (Darkmoon Faire) + break; + case 2: // Only used for looping events (Call to Arms) + break; + } + + if (holiday->Looping) + { + event.occurence = 0; + for (int i = 0; i < MAX_HOLIDAY_DURATIONS && holiday->Duration[i]; ++i) + event.occurence += holiday->Duration[i] * HOUR / MINUTE; + } + + bool singleDate = ((holiday->Date[0] >> 24) & 0x1F) == 31; // Events with fixed date within year have - 1 + + time_t curTime = time(NULL); + for (int i = 0; i < MAX_HOLIDAY_DATES && holiday->Date[i]; ++i) + { + uint32 date = holiday->Date[i]; + + tm timeInfo; + if (singleDate) + timeInfo.tm_year = localtime(&curTime)->tm_year - 1; // First try last year (event active through New Year) + else + timeInfo.tm_year = ((date >> 24) & 0x1F) + 100; + + timeInfo.tm_mon = (date >> 20) & 0xF; + timeInfo.tm_mday = ((date >> 14) & 0x3F) + 1; + timeInfo.tm_hour = (date >> 6) & 0x1F; + timeInfo.tm_min = date & 0x3F; + timeInfo.tm_sec = 0; + timeInfo.tm_isdst = -1; + tm tmCopy = timeInfo; + + time_t startTime = mktime(&timeInfo); + if (curTime < startTime + event.length * MINUTE) + { + event.start = startTime + stageOffset; + return; + } + else if (singleDate) + { + tmCopy.tm_year = localtime(&curTime)->tm_year; // This year + event.start = mktime(&tmCopy) + stageOffset; + return; + } + } + TC_LOG_ERROR("sql.sql", "No suitable start date found for holiday %u.", event.holiday_id); +} + bool IsHolidayActive(HolidayIds id) { if (id == HOLIDAY_NONE) diff --git a/src/server/game/Events/GameEventMgr.h b/src/server/game/Events/GameEventMgr.h index bee6dfc8fb3..4d1ce718422 100644 --- a/src/server/game/Events/GameEventMgr.h +++ b/src/server/game/Events/GameEventMgr.h @@ -60,7 +60,7 @@ typedef std::map<uint32 /*condition id*/, GameEventFinishCondition> GameEventCon struct GameEventData { - GameEventData() : start(1), end(0), nextstart(0), occurence(0), length(0), holiday_id(HOLIDAY_NONE), state(GAMEEVENT_NORMAL), + GameEventData() : start(1), end(0), nextstart(0), occurence(0), length(0), holiday_id(HOLIDAY_NONE), holidayStage(0), state(GAMEEVENT_NORMAL), announce(0) { } time_t start; // occurs after this time time_t end; // occurs before this time @@ -68,6 +68,7 @@ struct GameEventData uint32 occurence; // time between end and start uint32 length; // length of the event (minutes) after finishing all conditions HolidayIds holiday_id; + uint8 holidayStage; GameEventState state; // state of the game event, these are saved into the game_event table on change! GameEventConditionMap conditions; // conditions to finish std::set<uint16 /*gameevent id*/> prerequisite_events; // events that must be completed before starting this event @@ -114,6 +115,7 @@ class TC_GAME_API GameEventMgr bool CheckOneGameEvent(uint16 entry) const; uint32 NextCheck(uint16 entry) const; void LoadFromDB(); + void LoadHolidayDates(); uint32 Update(); bool IsActiveEvent(uint16 event_id) { return (m_ActiveEvents.find(event_id) != m_ActiveEvents.end()); } uint32 StartSystem(); @@ -147,6 +149,7 @@ class TC_GAME_API GameEventMgr bool hasGameObjectQuestActiveEventExcept(uint32 quest_id, uint16 event_id); bool hasCreatureActiveEventExcept(ObjectGuid::LowType creature_guid, uint16 event_id); bool hasGameObjectActiveEventExcept(ObjectGuid::LowType go_guid, uint16 event_id); + void SetHolidayEventTime(GameEventData& event); typedef std::list<ObjectGuid::LowType> GuidList; typedef std::list<uint32> IdList; @@ -181,6 +184,7 @@ class TC_GAME_API GameEventMgr public: GameEventGuidMap mGameEventCreatureGuids; GameEventGuidMap mGameEventGameobjectGuids; + std::set<uint32> modifiedHolidays; }; #define sGameEventMgr GameEventMgr::instance() diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index 1b7713ab363..2f1e4fa48f3 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -30,6 +30,7 @@ #include "GroupMgr.h" #include "GuildMgr.h" #include "InstanceSaveMgr.h" +#include "InstanceScript.h" #include "Language.h" #include "LFGMgr.h" #include "Log.h" @@ -831,7 +832,7 @@ void ObjectMgr::CheckCreatureTemplate(CreatureTemplate const* cInfo) TC_LOG_ERROR("sql.sql", "Creature (Entry: %u) lists non-existing Modelid1 id (%u), this can crash the client.", cInfo->Entry, cInfo->Modelid1); const_cast<CreatureTemplate*>(cInfo)->Modelid1 = 0; } - else if (!displayScaleEntry) + else displayScaleEntry = displayEntry; CreatureModelInfo const* modelInfo = GetCreatureModelInfo(cInfo->Modelid1); @@ -1123,7 +1124,7 @@ void ObjectMgr::LoadGameObjectAddons() ObjectGuid::LowType guid = fields[0].GetUInt32(); - GameObjectData const* goData = GetGOData(guid); + GameObjectData const* goData = GetGameObjectData(guid); if (!goData) { TC_LOG_ERROR("sql.sql", "GameObject (GUID: %u) does not exist but has a record in `gameobject_addon`", guid); @@ -1427,7 +1428,7 @@ void ObjectMgr::LoadLinkedRespawn() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 linked respawns. DB table `linked_respawn` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 linked respawns. DB table `linked_respawn` is empty."); return; } @@ -1461,8 +1462,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to Creature '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1490,7 +1491,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - GameObjectData const* master = GetGOData(linkedGuidLow); + GameObjectData const* master = GetGameObjectData(linkedGuidLow); if (!master) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '%u' not found in gameobject table", linkedGuidLow); @@ -1498,8 +1499,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Creature '%u' linking to Gameobject '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1519,7 +1520,7 @@ void ObjectMgr::LoadLinkedRespawn() } case GO_TO_GO: { - GameObjectData const* slave = GetGOData(guidLow); + GameObjectData const* slave = GetGameObjectData(guidLow); if (!slave) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '%u' not found in gameobject table", guidLow); @@ -1527,7 +1528,7 @@ void ObjectMgr::LoadLinkedRespawn() break; } - GameObjectData const* master = GetGOData(linkedGuidLow); + GameObjectData const* master = GetGameObjectData(linkedGuidLow); if (!master) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (linkedGuid) '%u' not found in gameobject table", linkedGuidLow); @@ -1535,8 +1536,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '%u' linking to Gameobject '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1556,7 +1557,7 @@ void ObjectMgr::LoadLinkedRespawn() } case GO_TO_CREATURE: { - GameObjectData const* slave = GetGOData(guidLow); + GameObjectData const* slave = GetGameObjectData(guidLow); if (!slave) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject (guid) '%u' not found in gameobject table", guidLow); @@ -1572,8 +1573,8 @@ void ObjectMgr::LoadLinkedRespawn() break; } - MapEntry const* const map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* const map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "LinkedRespawn: Gameobject '%u' linking to Creature '%u' on an unpermitted map.", guidLow, linkedGuidLow); error = true; @@ -1626,8 +1627,8 @@ bool ObjectMgr::SetCreatureLinkedRespawn(ObjectGuid::LowType guidLow, ObjectGuid return false; } - MapEntry const* map = sMapStore.LookupEntry(master->mapid); - if (!map || !map->Instanceable() || (master->mapid != slave->mapid)) + MapEntry const* map = sMapStore.LookupEntry(master->spawnPoint.GetMapId()); + if (!map || !map->Instanceable() || (master->spawnPoint.GetMapId() != slave->spawnPoint.GetMapId())) { TC_LOG_ERROR("sql.sql", "Creature '%u' linking to '%u' on an unpermitted map.", guidLow, linkedGuidLow); return false; @@ -1741,8 +1742,8 @@ void ObjectMgr::LoadCreatures() { uint32 oldMSTime = getMSTime(); - // 0 1 2 3 4 5 6 7 8 9 10 - QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, modelid, equipment_id, position_x, position_y, position_z, orientation, spawntimesecs, spawndist, " + // 0 1 2 3 4 5 6 7 8 9 10 + QueryResult result = WorldDatabase.Query("SELECT creature.guid, id, map, position_x, position_y, position_z, orientation, modelid, equipment_id, spawntimesecs, spawndist, " // 11 12 13 14 15 16 17 18 19 20 21 "currentwaypoint, curhealth, curmana, MovementType, spawnMask, phaseMask, eventEntry, pool_entry, creature.npcflag, creature.unit_flags, creature.dynamicflags, " // 22 @@ -1753,7 +1754,7 @@ void ObjectMgr::LoadCreatures() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 creatures. DB table `creature` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 creatures. DB table `creature` is empty."); return; } @@ -1782,14 +1783,11 @@ void ObjectMgr::LoadCreatures() } CreatureData& data = _creatureDataStore[guid]; + data.spawnId = guid; data.id = entry; - data.mapid = fields[2].GetUInt16(); - data.displayid = fields[3].GetUInt32(); - data.equipmentId = fields[4].GetInt8(); - data.posX = fields[5].GetFloat(); - data.posY = fields[6].GetFloat(); - data.posZ = fields[7].GetFloat(); - data.orientation = fields[8].GetFloat(); + data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat()); + data.displayid = fields[7].GetUInt32(); + data.equipmentId = fields[8].GetInt8(); data.spawntimesecs = fields[9].GetUInt32(); data.spawndist = fields[10].GetFloat(); data.currentwaypoint= fields[11].GetUInt32(); @@ -1803,18 +1801,19 @@ void ObjectMgr::LoadCreatures() data.npcflag = fields[19].GetUInt32(); data.unit_flags = fields[20].GetUInt32(); data.dynamicflags = fields[21].GetUInt32(); - data.ScriptId = GetScriptId(fields[22].GetString()); + data.scriptId = GetScriptId(fields[22].GetString()); + data.spawnGroupData = &_spawnGroupDataStore[0]; - MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId()); if (!mapEntry) { - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that spawned at nonexistent map (Id: %u), skipped.", guid, data.mapid); + TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that spawned at nonexistent map (Id: %u), skipped.", guid, data.spawnPoint.GetMapId()); continue; } // Skip spawnMask check for transport maps - if (!IsTransportMap(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid]) - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that have wrong spawn mask %u including unsupported difficulty modes for map (Id: %u).", guid, data.spawnMask, data.mapid); + if (!IsTransportMap(data.spawnPoint.GetMapId()) && data.spawnMask & ~spawnMasks[data.spawnPoint.GetMapId()]) + TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u) that have wrong spawn mask %u including unsupported difficulty modes for map (Id: %u).", guid, data.spawnMask, data.spawnPoint.GetMapId()); bool ok = true; for (uint32 diff = 0; diff < MAX_DIFFICULTY - 1 && ok; ++diff) @@ -1879,17 +1878,11 @@ void ObjectMgr::LoadCreatures() data.phaseMask = 1; } - if (std::abs(data.orientation) > 2 * float(M_PI)) - { - TC_LOG_ERROR("sql.sql", "Table `creature` has creature (GUID: %u Entry: %u) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position::NormalizeOrientation(data.orientation); - } - if (sWorld->getBoolConfig(CONFIG_CALCULATE_CREATURE_ZONE_AREA_DATA)) { uint32 zoneId = 0; uint32 areaId = 0; - sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.mapid, data.posX, data.posY, data.posZ); + sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.spawnPoint); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_CREATURE_ZONE_AREA_DATA); @@ -1916,8 +1909,8 @@ void ObjectMgr::AddCreatureToGrid(ObjectGuid::LowType guid, CreatureData const* { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.creatures.insert(guid); } } @@ -1930,14 +1923,14 @@ void ObjectMgr::RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData co { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.creatures.erase(guid); } } } -ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay /*= 0*/) +ObjectGuid::LowType ObjectMgr::AddGameObjectData(uint32 entry, uint32 mapId, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay /*= 0*/) { GameObjectTemplate const* goinfo = GetGameObjectTemplate(entry); if (!goinfo) @@ -1947,41 +1940,40 @@ ObjectGuid::LowType ObjectMgr::AddGOData(uint32 entry, uint32 mapId, Position co if (!map) return 0; - ObjectGuid::LowType guid = GenerateGameObjectSpawnId(); + ObjectGuid::LowType spawnId = GenerateGameObjectSpawnId(); - GameObjectData& data = NewGOData(guid); + GameObjectData& data = NewOrExistGameObjectData(spawnId); + data.spawnId = spawnId; data.id = entry; - data.mapid = mapId; - - pos.GetPosition(data.posX, data.posY, data.posZ, data.orientation); - + data.spawnPoint.WorldRelocate(mapId,pos); data.rotation = rot; data.spawntimesecs = spawntimedelay; data.animprogress = 100; data.spawnMask = 1; - data.go_state = GO_STATE_READY; + data.goState = GO_STATE_READY; data.phaseMask = PHASEMASK_NORMAL; data.artKit = goinfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT ? 21 : 0; - data.dbData = false; + data.dbData = false; + data.spawnGroupData = GetLegacySpawnGroup(); - AddGameobjectToGrid(guid, &data); + AddGameobjectToGrid(spawnId, &data); // Spawn if necessary (loaded grids only) // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data.posX, data.posY)) + if (!map->Instanceable() && map->IsGridLoaded(data.spawnPoint)) { GameObject* go = new GameObject; - if (!go->LoadGameObjectFromDB(guid, map)) + if (!go->LoadFromDB(spawnId, map, true)) { - TC_LOG_ERROR("misc", "AddGOData: cannot add gameobject entry %u to map", entry); + TC_LOG_ERROR("misc", "AddGameObjectData: cannot add gameobject entry %u to map", entry); delete go; return 0; } } - TC_LOG_DEBUG("maps", "AddGOData: dbguid %u entry %u map %u x %f y %f z %f o %f", guid, entry, mapId, data.posX, data.posY, data.posZ, data.orientation); + TC_LOG_DEBUG("maps", "AddGameObjectData: dbguid %u entry %u map %u pos %s", spawnId, entry, mapId, data.spawnPoint.ToString().c_str()); - return guid; + return spawnId; } @@ -1997,15 +1989,13 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit if (!map) return 0; - ObjectGuid::LowType guid = GenerateCreatureSpawnId(); - CreatureData& data = NewOrExistCreatureData(guid); + ObjectGuid::LowType spawnId = GenerateCreatureSpawnId(); + CreatureData& data = NewOrExistCreatureData(spawnId); + data.spawnId = spawnId; data.id = entry; - data.mapid = mapId; + data.spawnPoint.WorldRelocate(mapId, pos); data.displayid = 0; data.equipmentId = 0; - - pos.GetPosition(data.posX, data.posY, data.posZ, data.orientation); - data.spawntimesecs = spawntimedelay; data.spawndist = 0; data.currentwaypoint = 0; @@ -2018,14 +2008,15 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit data.npcflag = cInfo->npcflag; data.unit_flags = cInfo->unit_flags; data.dynamicflags = cInfo->dynamicflags; + data.spawnGroupData = GetLegacySpawnGroup(); - AddCreatureToGrid(guid, &data); + AddCreatureToGrid(spawnId, &data); // We use spawn coords to spawn - if (!map->Instanceable() && !map->IsRemovalGrid(data.posX, data.posY)) + if (!map->Instanceable() && !map->IsRemovalGrid(data.spawnPoint)) { Creature* creature = new Creature(); - if (!creature->LoadCreatureFromDB(guid, map)) + if (!creature->LoadFromDB(spawnId, map, true, true)) { TC_LOG_ERROR("misc", "AddCreature: Cannot add creature entry %u to map", entry); delete creature; @@ -2033,10 +2024,10 @@ ObjectGuid::LowType ObjectMgr::AddCreatureData(uint32 entry, uint32 mapId, Posit } } - return guid; + return spawnId; } -void ObjectMgr::LoadGameobjects() +void ObjectMgr::LoadGameObjects() { uint32 oldMSTime = getMSTime(); @@ -2051,7 +2042,7 @@ void ObjectMgr::LoadGameobjects() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 gameobjects. DB table `gameobject` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 gameobjects. DB table `gameobject` is empty."); return; } @@ -2100,22 +2091,20 @@ void ObjectMgr::LoadGameobjects() GameObjectData& data = _gameObjectDataStore[guid]; + data.spawnId = guid; data.id = entry; - data.mapid = fields[2].GetUInt16(); - data.posX = fields[3].GetFloat(); - data.posY = fields[4].GetFloat(); - data.posZ = fields[5].GetFloat(); - data.orientation = fields[6].GetFloat(); + data.spawnPoint.WorldRelocate(fields[2].GetUInt16(), fields[3].GetFloat(), fields[4].GetFloat(), fields[5].GetFloat(), fields[6].GetFloat()); data.rotation.x = fields[7].GetFloat(); data.rotation.y = fields[8].GetFloat(); data.rotation.z = fields[9].GetFloat(); data.rotation.w = fields[10].GetFloat(); data.spawntimesecs = fields[11].GetInt32(); + data.spawnGroupData = &_spawnGroupDataStore[0]; - MapEntry const* mapEntry = sMapStore.LookupEntry(data.mapid); + MapEntry const* mapEntry = sMapStore.LookupEntry(data.spawnPoint.GetMapId()); if (!mapEntry) { - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) spawned on a non-existed map (Id: %u), skip", guid, data.id, data.mapid); + TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) spawned on a non-existed map (Id: %u), skip", guid, data.id, data.spawnPoint.GetMapId()); continue; } @@ -2133,24 +2122,18 @@ void ObjectMgr::LoadGameobjects() TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid `state` (%u) value, skip", guid, data.id, go_state); continue; } - data.go_state = GOState(go_state); + data.goState = GOState(go_state); data.spawnMask = fields[14].GetUInt8(); - if (!IsTransportMap(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid]) - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) that has wrong spawn mask %u including unsupported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.mapid); + if (!IsTransportMap(data.spawnPoint.GetMapId()) && data.spawnMask & ~spawnMasks[data.spawnPoint.GetMapId()]) + TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) that has wrong spawn mask %u including unsupported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.spawnPoint.GetMapId()); data.phaseMask = fields[15].GetUInt32(); int16 gameEvent = fields[16].GetInt8(); uint32 PoolId = fields[17].GetUInt32(); - data.ScriptId = GetScriptId(fields[18].GetString()); - - if (std::abs(data.orientation) > 2 * float(M_PI)) - { - TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with abs(`orientation`) > 2*PI (orientation is expressed in radians), normalized.", guid, data.id); - data.orientation = Position::NormalizeOrientation(data.orientation); - } + data.scriptId = GetScriptId(fields[18].GetString()); if (data.rotation.x < -1.0f || data.rotation.x > 1.0f) { @@ -2176,7 +2159,7 @@ void ObjectMgr::LoadGameobjects() continue; } - if (!MapManager::IsValidMapCoord(data.mapid, data.posX, data.posY, data.posZ, data.orientation)) + if (!MapManager::IsValidMapCoord(data.spawnPoint)) { TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) with invalid coordinates, skip", guid, data.id); continue; @@ -2192,7 +2175,7 @@ void ObjectMgr::LoadGameobjects() { uint32 zoneId = 0; uint32 areaId = 0; - sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.mapid, data.posX, data.posY, data.posZ); + sMapMgr->GetZoneAndAreaId(zoneId, areaId, data.spawnPoint); PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_UPD_GAMEOBJECT_ZONE_AREA_DATA); @@ -2211,6 +2194,207 @@ void ObjectMgr::LoadGameobjects() TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " gameobjects in %u ms", _gameObjectDataStore.size(), GetMSTimeDiffToNow(oldMSTime)); } +void ObjectMgr::LoadSpawnGroupTemplates() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT groupId, groupName, groupFlags FROM spawn_group_template"); + + if (result) + { + do + { + Field* fields = result->Fetch(); + uint32 groupId = fields[0].GetUInt32(); + SpawnGroupTemplateData& group = _spawnGroupDataStore[groupId]; + group.groupId = groupId; + group.name = fields[1].GetString(); + group.mapId = SPAWNGROUP_MAP_UNSET; + uint32 flags = fields[2].GetUInt32(); + if (flags & ~SPAWNGROUP_FLAGS_ALL) + { + flags &= SPAWNGROUP_FLAGS_ALL; + TC_LOG_ERROR("sql.sql", "Invalid spawn group flag %u on group ID %u (%s), reduced to valid flag %u.", flags, groupId, group.name.c_str(), uint32(group.flags)); + } + if (flags & SPAWNGROUP_FLAG_SYSTEM && flags & SPAWNGROUP_FLAG_MANUAL_SPAWN) + { + flags &= ~SPAWNGROUP_FLAG_MANUAL_SPAWN; + TC_LOG_ERROR("sql.sql", "System spawn group %u (%s) has invalid manual spawn flag. Ignored.", groupId, group.name.c_str()); + } + group.flags = SpawnGroupFlags(flags); + } while (result->NextRow()); + } + + if (_spawnGroupDataStore.find(0) == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("sql.sql", "Default spawn group (index 0) is missing from DB! Manually inserted."); + SpawnGroupTemplateData& data = _spawnGroupDataStore[0]; + data.groupId = 0; + data.name = "Default Group"; + data.mapId = 0; + data.flags = SPAWNGROUP_FLAG_SYSTEM; + } + if (_spawnGroupDataStore.find(1) == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("sql.sql", "Default legacy spawn group (index 1) is missing from DB! Manually inserted."); + SpawnGroupTemplateData&data = _spawnGroupDataStore[1]; + data.groupId = 1; + data.name = "Legacy Group"; + data.mapId = 0; + data.flags = SpawnGroupFlags(SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE); + } + + if (result) + TC_LOG_INFO("server.loading", ">> Loaded " SZFMTD " spawn group templates in %u ms", _spawnGroupDataStore.size(), GetMSTimeDiffToNow(oldMSTime)); + else + TC_LOG_INFO("server.loading", ">> Loaded 0 spawn group templates. DB table `spawn_group_template` is empty."); + + return; +} + +void ObjectMgr::LoadSpawnGroups() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 + QueryResult result = WorldDatabase.Query("SELECT groupId, spawnType, spawnId FROM spawn_group"); + + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 spawn group members. DB table `spawn_group` is empty."); + return; + } + + uint32 numMembers = 0; + do + { + Field* fields = result->Fetch(); + uint32 groupId = fields[0].GetUInt32(); + SpawnObjectType spawnType; + { + uint32 type = fields[1].GetUInt8(); + if (type >= SPAWN_TYPE_MAX) + { + TC_LOG_ERROR("sql.sql", "Spawn data with invalid type %u listed for spawn group %u. Skipped.", type, groupId); + continue; + } + spawnType = SpawnObjectType(type); + } + ObjectGuid::LowType spawnId = fields[2].GetUInt32(); + + SpawnData const* data = GetSpawnData(spawnType, spawnId); + if (!data) + { + TC_LOG_ERROR("sql.sql", "Spawn data with ID (%u,%u) not found, but is listed as a member of spawn group %u!", uint32(spawnType), spawnId, groupId); + continue; + } + else if (data->spawnGroupData->groupId) + { + TC_LOG_ERROR("sql.sql", "Spawn with ID (%u,%u) is listed as a member of spawn group %u, but is already a member of spawn group %u. Skipping.", uint32(spawnType), spawnId, groupId, data->spawnGroupData->groupId); + continue; + } + auto it = _spawnGroupDataStore.find(groupId); + if (it == _spawnGroupDataStore.end()) + { + TC_LOG_ERROR("sql.sql", "Spawn group %u assigned to spawn ID (%u,%u), but group is found!", groupId, uint32(spawnType), spawnId); + continue; + } + else + { + SpawnGroupTemplateData& groupTemplate = it->second; + if (groupTemplate.mapId == SPAWNGROUP_MAP_UNSET) + groupTemplate.mapId = data->spawnPoint.GetMapId(); + else if (groupTemplate.mapId != data->spawnPoint.GetMapId() && !(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM)) + { + TC_LOG_ERROR("sql.sql", "Spawn group %u has map ID %u, but spawn (%u,%u) has map id %u - spawn NOT added to group!", groupId, groupTemplate.mapId, uint32(spawnType), spawnId, data->spawnPoint.GetMapId()); + continue; + } + const_cast<SpawnData*>(data)->spawnGroupData = &groupTemplate; + if (!(groupTemplate.flags & SPAWNGROUP_FLAG_SYSTEM)) + _spawnGroupMapStore.emplace(groupId, data); + ++numMembers; + } + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u spawn group members in %u ms", numMembers, GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::LoadInstanceSpawnGroups() +{ + uint32 oldMSTime = getMSTime(); + + // 0 1 2 3 4 + QueryResult result = WorldDatabase.Query("SELECT instanceMapId, bossStateId, bossStates, spawnGroupId, flags FROM instance_spawn_groups"); + + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 instance spawn groups. DB table `instance_spawn_groups` is empty."); + return; + } + + uint32 n = 0; + do + { + Field* fields = result->Fetch(); + uint32 const spawnGroupId = fields[3].GetUInt32(); + auto it = _spawnGroupDataStore.find(spawnGroupId); + if (it == _spawnGroupDataStore.end() || (it->second.flags & SPAWNGROUP_FLAG_SYSTEM)) + { + TC_LOG_ERROR("sql.sql", "Invalid spawn group %u specified for instance %u. Skipped.", spawnGroupId, fields[0].GetUInt16()); + continue; + } + + uint16 const instanceMapId = fields[0].GetUInt16(); + auto& vector = _instanceSpawnGroupStore[instanceMapId]; + vector.emplace_back(); + InstanceSpawnGroupInfo& info = vector.back(); + info.SpawnGroupId = spawnGroupId; + info.BossStateId = fields[1].GetUInt8(); + + uint8 const ALL_STATES = (1 << TO_BE_DECIDED) - 1; + uint8 const states = fields[2].GetUInt8(); + if (states & ~ALL_STATES) + { + info.BossStates = states & ALL_STATES; + TC_LOG_ERROR("sql.sql", "Instance spawn group (%u,%u) had invalid boss state mask %u - truncated to %u.", instanceMapId, spawnGroupId, states, info.BossStates); + } + else + info.BossStates = states; + + uint8 const flags = fields[4].GetUInt8(); + if (flags & ~InstanceSpawnGroupInfo::FLAG_ALL) + { + info.Flags = flags & InstanceSpawnGroupInfo::FLAG_ALL; + TC_LOG_ERROR("sql.sql", "Instance spawn group (%u,%u) had invalid flags %u - truncated to %u.", instanceMapId, spawnGroupId, flags, info.Flags); + } + else + info.Flags = flags; + + ++n; + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u instance spawn groups in %u ms", n, GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::OnDeleteSpawnData(SpawnData const* data) +{ + auto templateIt = _spawnGroupDataStore.find(data->spawnGroupData->groupId); + ASSERT(templateIt != _spawnGroupDataStore.end(), "Creature data for (%u,%u) is being deleted and has invalid spawn group index %u!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId); + if (templateIt->second.flags & SPAWNGROUP_FLAG_SYSTEM) // system groups don't store their members in the map + return; + + auto pair = _spawnGroupMapStore.equal_range(data->spawnGroupData->groupId); + for (auto it = pair.first; it != pair.second; ++it) + { + if (it->second != data) + continue; + _spawnGroupMapStore.erase(it); + return; + } + ASSERT(false, "Spawn data (%u,%u) being removed is member of spawn group %u, but not actually listed in the lookup table for that group!", uint32(data->type), data->spawnId, data->spawnGroupData->groupId); +} + void ObjectMgr::AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData const* data) { uint8 mask = data->spawnMask; @@ -2218,8 +2402,8 @@ void ObjectMgr::AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData con { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.gameobjects.insert(guid); } } @@ -2232,8 +2416,8 @@ void ObjectMgr::RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectDat { if (mask & 1) { - CellCoord cellCoord = Trinity::ComputeCellCoord(data->posX, data->posY); - CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->mapid, i)][cellCoord.GetId()]; + CellCoord cellCoord = Trinity::ComputeCellCoord(data->spawnPoint.GetPositionX(), data->spawnPoint.GetPositionY()); + CellObjectGuids& cell_guids = _mapObjectGuidsStore[MAKE_PAIR32(data->spawnPoint.GetMapId(), i)][cellCoord.GetId()]; cell_guids.gameobjects.erase(guid); } } @@ -3011,7 +3195,7 @@ void ObjectMgr::LoadVehicleTemplateAccessories() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 vehicle template accessories. DB table `vehicle_template_accessory` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 vehicle template accessories. DB table `vehicle_template_accessory` is empty."); return; } @@ -3105,7 +3289,7 @@ void ObjectMgr::LoadPetLevelInfo() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 level pet stats definitions. DB table `pet_levelstats` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 level pet stats definitions. DB table `pet_levelstats` is empty."); return; } @@ -3399,7 +3583,7 @@ void ObjectMgr::LoadPlayerInfo() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 player create skills. DB table `playercreateinfo_skills` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 player create skills. DB table `playercreateinfo_skills` is empty."); } else { @@ -3473,7 +3657,7 @@ void ObjectMgr::LoadPlayerInfo() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 player create spells. DB table `playercreateinfo_spell_custom` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 player create spells. DB table `playercreateinfo_spell_custom` is empty."); } else { @@ -3534,7 +3718,7 @@ void ObjectMgr::LoadPlayerInfo() QueryResult result = WorldDatabase.PQuery("SELECT raceMask, classMask, spell FROM playercreateinfo_cast_spell"); if (!result) - TC_LOG_ERROR("server.loading", ">> Loaded 0 player create cast spells. DB table `playercreateinfo_cast_spell` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 player create cast spells. DB table `playercreateinfo_cast_spell` is empty."); else { uint32 count = 0; @@ -3591,7 +3775,7 @@ void ObjectMgr::LoadPlayerInfo() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 player create actions. DB table `playercreateinfo_action` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 player create actions. DB table `playercreateinfo_action` is empty."); } else { @@ -4019,7 +4203,7 @@ void ObjectMgr::LoadQuests() " FROM quest_template"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 quests definitions. DB table `quest_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 quests definitions. DB table `quest_template` is empty."); return; } @@ -4070,7 +4254,7 @@ void ObjectMgr::LoadQuests() { QueryResult result = WorldDatabase.PQuery("SELECT %s FROM %s", loader.QueryFields, loader.TableName); if (!result) - TC_LOG_ERROR("server.loading", ">> Loaded 0 quest %s. DB table `%s` is empty.", loader.TableDesc, loader.TableName); + TC_LOG_INFO("server.loading", ">> Loaded 0 quest %s. DB table `%s` is empty.", loader.TableDesc, loader.TableName); else { do @@ -4881,7 +5065,7 @@ void ObjectMgr::LoadScripts(ScriptsType type) case SCRIPT_COMMAND_RESPAWN_GAMEOBJECT: { - GameObjectData const* data = GetGOData(tmp.RespawnGameobject.GOGuid); + GameObjectData const* data = GetGameObjectData(tmp.RespawnGameobject.GOGuid); if (!data) { TC_LOG_ERROR("sql.sql", "Table `%s` has invalid gameobject (GUID: %u) in SCRIPT_COMMAND_RESPAWN_GAMEOBJECT for script id %u", @@ -4931,7 +5115,7 @@ void ObjectMgr::LoadScripts(ScriptsType type) case SCRIPT_COMMAND_OPEN_DOOR: case SCRIPT_COMMAND_CLOSE_DOOR: { - GameObjectData const* data = GetGOData(tmp.ToggleDoor.GOGuid); + GameObjectData const* data = GetGameObjectData(tmp.ToggleDoor.GOGuid); if (!data) { TC_LOG_ERROR("sql.sql", "Table `%s` has invalid gameobject (GUID: %u) in %s for script id %u", @@ -5409,7 +5593,7 @@ void ObjectMgr::LoadInstanceEncounters() QueryResult result = WorldDatabase.Query("SELECT entry, creditType, creditEntry, lastEncounterDungeon FROM instance_encounters"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 instance encounters, table is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 instance encounters, table is empty!"); return; } @@ -5695,6 +5879,10 @@ void ObjectMgr::ReturnOrDeleteOldMails(bool serverUp) stmt->setUInt32(0, itr2->item_guid); CharacterDatabase.Execute(stmt); } + + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_MAIL_ITEM_BY_ID); + stmt->setUInt32(0, m->messageID); + CharacterDatabase.Execute(stmt); } else { @@ -5794,6 +5982,134 @@ void ObjectMgr::LoadQuestAreaTriggers() TC_LOG_INFO("server.loading", ">> Loaded %u quest trigger points in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } +QuestGreeting const* ObjectMgr::GetQuestGreeting(ObjectGuid guid) const +{ + auto itr = _questGreetingStore.find(guid.GetTypeId()); + if (itr == _questGreetingStore.end()) + return nullptr; + + auto questItr = itr->second.find(guid.GetEntry()); + if (questItr == itr->second.end()) + return nullptr; + + return questItr->second; +} + +void ObjectMgr::LoadQuestGreetings() +{ + uint32 oldMSTime = getMSTime(); + + _questGreetingStore.clear(); // need for reload case + + // 0 1 2 3 4 + QueryResult result = WorldDatabase.Query("SELECT ID, Type, GreetEmoteType, GreetEmoteDelay, Greeting FROM quest_greeting"); + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 quest greetings. DB table `quest_greeting` is empty."); + return; + } + + _questGreetingStore.rehash(result->GetRowCount()); + + uint32 count = 0; + + do + { + Field* fields = result->Fetch(); + + uint32 id = fields[0].GetUInt32(); + uint8 type = fields[1].GetUInt8(); + // overwrite + switch (type) + { + case 0: // Creature + type = TYPEID_UNIT; + if (!sObjectMgr->GetCreatureTemplate(id)) + { + TC_LOG_ERROR("sql.sql", "Table `quest_greeting`: creature template (entry: %u) does not exist.", id); + continue; + } + break; + case 1: // GameObject + type = TYPEID_GAMEOBJECT; + if (!sObjectMgr->GetGameObjectTemplate(id)) + { + TC_LOG_ERROR("sql.sql", "Table `quest_greeting`: gameobject template (entry: %u) does not exist.", id); + continue; + } + break; + default: + TC_LOG_ERROR("sql.sql", "Table `quest_greeting`: unknown type = %u for entry = %u. Skipped.", type, id); + continue; + } + + uint16 greetEmoteType = fields[2].GetUInt16(); + + if (greetEmoteType > 0 && !sEmotesStore.LookupEntry(greetEmoteType)) + { + TC_LOG_DEBUG("sql.sql", "Table `quest_greeting`: entry %u has greetEmoteType = %u but emote does not exist. Set to 0.", id, greetEmoteType); + greetEmoteType = 0; + } + + uint32 greetEmoteDelay = fields[3].GetUInt32(); + std::string greeting = fields[4].GetString(); + + _questGreetingStore[type][id] = new QuestGreeting(greetEmoteType, greetEmoteDelay, greeting); + + ++count; + } + while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u quest_greeting in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); +} + +void ObjectMgr::LoadQuestGreetingsLocales() +{ + uint32 oldMSTime = getMSTime(); + + _questGreetingLocaleStore.clear(); // need for reload case + + // 0 1 2 3 + QueryResult result = WorldDatabase.Query("SELECT ID, Type, Locale, Greeting FROM quest_greeting_locale"); + if (!result) + { + TC_LOG_INFO("server.loading", ">> Loaded 0 quest_greeting locales. DB table `quest_greeting_locale` is empty."); + return; + } + + do + { + Field* fields = result->Fetch(); + + uint32 id = fields[0].GetUInt32(); + uint8 type = fields[1].GetUInt8(); + // overwrite + switch (type) + { + case 0: // Creature + type = TYPEID_UNIT; + break; + case 1: // GameObject + type = TYPEID_GAMEOBJECT; + break; + default: + break; + } + + std::string localeName = fields[2].GetString(); + std::string greeting = fields[3].GetString(); + + QuestGreetingLocale& data = _questGreetingLocaleStore[MAKE_PAIR32(id, type)]; + LocaleConstant locale = GetLocaleByName(localeName); + if (locale == LOCALE_enUS) + continue; + + AddLocaleString(greeting, locale, data.greeting); + } while (result->NextRow()); + + TC_LOG_INFO("server.loading", ">> Loaded %u quest greeting locale strings in %u ms", uint32(_questGreetingLocaleStore.size()), GetMSTimeDiffToNow(oldMSTime)); +} + void ObjectMgr::LoadTavernAreaTriggers() { uint32 oldMSTime = getMSTime(); @@ -6586,7 +6902,7 @@ uint32 ObjectMgr::GenerateGameObjectSpawnId() { if (_gameObjectSpawnId >= uint32(0xFFFFFF)) { - TC_LOG_ERROR("misc", "Creature spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. "); + TC_LOG_ERROR("misc", "GameObject spawn id overflow!! Can't continue, shutting down server. Search on forum for TCE00007 for more info. "); World::StopNow(ERROR_EXIT_CODE); } return _gameObjectSpawnId++; @@ -6937,7 +7253,7 @@ void ObjectMgr::LoadExplorationBaseXP() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 BaseXP definitions. DB table `exploration_basexp` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 BaseXP definitions. DB table `exploration_basexp` is empty."); return; } @@ -7044,7 +7360,7 @@ void ObjectMgr::LoadReputationRewardRate() QueryResult result = WorldDatabase.Query("SELECT faction, quest_rate, quest_daily_rate, quest_weekly_rate, quest_monthly_rate, quest_repeatable_rate, creature_rate, spell_rate FROM reputation_reward_rate"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded `reputation_reward_rate`, table is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded `reputation_reward_rate`, table is empty!"); return; } @@ -7139,7 +7455,7 @@ void ObjectMgr::LoadReputationOnKill() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 creature award reputation definitions. DB table `creature_onkill_reputation` is empty."); return; } @@ -7299,7 +7615,7 @@ void ObjectMgr::LoadPointsOfInterest() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 Points of Interest definitions. DB table `points_of_interest` is empty."); return; } @@ -7344,7 +7660,7 @@ void ObjectMgr::LoadQuestPOI() QueryResult result = WorldDatabase.Query("SELECT QuestID, id, ObjectiveIndex, MapID, WorldMapAreaId, Floor, Priority, Flags FROM quest_poi order by QuestID"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 quest POI definitions. DB table `quest_poi` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 quest POI definitions. DB table `quest_poi` is empty."); return; } @@ -7436,7 +7752,7 @@ void ObjectMgr::LoadNPCSpellClickSpells() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 spellclick spells. DB table `npc_spellclick_spells` is empty."); return; } @@ -7497,17 +7813,23 @@ void ObjectMgr::DeleteCreatureData(ObjectGuid::LowType guid) // remove mapid*cellid -> guid_set map CreatureData const* data = GetCreatureData(guid); if (data) + { RemoveCreatureFromGrid(guid, data); + OnDeleteSpawnData(data); + } _creatureDataStore.erase(guid); } -void ObjectMgr::DeleteGOData(ObjectGuid::LowType guid) +void ObjectMgr::DeleteGameObjectData(ObjectGuid::LowType guid) { // remove mapid*cellid -> guid_set map - GameObjectData const* data = GetGOData(guid); + GameObjectData const* data = GetGameObjectData(guid); if (data) + { RemoveGameobjectFromGrid(guid, data); + OnDeleteSpawnData(data); + } _gameObjectDataStore.erase(guid); } @@ -7524,7 +7846,7 @@ void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReve if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 quest relations from `%s`, table is empty.", table.c_str()); + TC_LOG_INFO("server.loading", ">> Loaded 0 quest relations from `%s`, table is empty.", table.c_str()); return; } @@ -7550,7 +7872,7 @@ void ObjectMgr::LoadQuestRelationsHelper(QuestRelations& map, QuestRelationsReve if (reverseMap) reverseMap->insert(QuestRelationsReverse::value_type(quest, id)); } - else if (starter) + else poolRelationMap->insert(PooledQuestRelation::value_type(quest, id)); ++count; @@ -7884,7 +8206,7 @@ bool ObjectMgr::LoadTrinityStrings() QueryResult result = WorldDatabase.Query("SELECT entry, content_default, content_loc1, content_loc2, content_loc3, content_loc4, content_loc5, content_loc6, content_loc7, content_loc8 FROM trinity_string"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 trinity strings. DB table `trinity_string` is empty. You have imported an incorrect database for more info search for TCE00003 on forum."); + TC_LOG_INFO("server.loading", ">> Loaded 0 trinity strings. DB table `trinity_string` is empty. You have imported an incorrect database for more info search for TCE00003 on forum."); return false; } @@ -7930,7 +8252,7 @@ void ObjectMgr::LoadFishingBaseSkillLevel() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 areas for fishing base skill level. DB table `skill_fishing_base_level` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 areas for fishing base skill level. DB table `skill_fishing_base_level` is empty."); return; } @@ -8049,7 +8371,7 @@ void ObjectMgr::LoadGameTele() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 GameTeleports. DB table `game_tele` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 GameTeleports. DB table `game_tele` is empty!"); return; } @@ -8207,7 +8529,7 @@ void ObjectMgr::LoadMailLevelRewards() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 level dependent mail rewards. DB table `mail_level_reward` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 level dependent mail rewards. DB table `mail_level_reward` is empty."); return; } @@ -8474,7 +8796,7 @@ void ObjectMgr::LoadGossipMenu() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 gossip_menu IDs. DB table `gossip_menu` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 gossip_menu IDs. DB table `gossip_menu` is empty!"); return; } @@ -8512,7 +8834,7 @@ void ObjectMgr::LoadGossipMenuItems() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 gossip_menu_option IDs. DB table `gossip_menu_option` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 gossip_menu_option IDs. DB table `gossip_menu_option` is empty!"); return; } @@ -8733,7 +9055,7 @@ void ObjectMgr::LoadScriptNames() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded empty set of Script Names!"); + TC_LOG_INFO("server.loading", ">> Loaded empty set of Script Names!"); return; } @@ -9002,7 +9324,7 @@ void ObjectMgr::LoadFactionChangeAchievements() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 faction change achievement pairs. DB table `player_factionchange_achievement` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 faction change achievement pairs. DB table `player_factionchange_achievement` is empty."); return; } @@ -9072,7 +9394,7 @@ void ObjectMgr::LoadFactionChangeQuests() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 faction change quest pairs. DB table `player_factionchange_quests` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 faction change quest pairs. DB table `player_factionchange_quests` is empty."); return; } @@ -9142,7 +9464,7 @@ void ObjectMgr::LoadFactionChangeSpells() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 faction change spell pairs. DB table `player_factionchange_spells` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 faction change spell pairs. DB table `player_factionchange_spells` is empty."); return; } diff --git a/src/server/game/Globals/ObjectMgr.h b/src/server/game/Globals/ObjectMgr.h index 95e7019dacf..8a07723a34e 100644 --- a/src/server/game/Globals/ObjectMgr.h +++ b/src/server/game/Globals/ObjectMgr.h @@ -23,8 +23,10 @@ #include "ConditionMgr.h" #include "CreatureData.h" #include "DatabaseEnvFwd.h" +#include "Errors.h" #include "GameObjectData.h" #include "ItemTemplate.h" +#include "IteratorPair.h" #include "NPCHandler.h" #include "ObjectDefines.h" #include "ObjectGuid.h" @@ -38,6 +40,7 @@ class Item; class Unit; class Vehicle; +class Map; struct AccessRequirement; struct DeclinedName; struct DungeonEncounterEntry; @@ -98,7 +101,7 @@ struct TempSummonData // DB scripting commands enum ScriptCommands { - SCRIPT_COMMAND_TALK = 0, // source/target = Creature, target = any, datalong = talk type (0=say, 1=whisper, 2=yell, 3=emote text, 4=boss emote text), datalong2 & 1 = player talk (instead of creature), dataint = string_id + SCRIPT_COMMAND_TALK = 0, // source/target = Creature, target = any, datalong = talk type (see ChatType enum), datalong2 & 1 = player talk (instead of creature), dataint = string_id SCRIPT_COMMAND_EMOTE = 1, // source/target = Creature, datalong = emote id, datalong2 = 0: set emote state; > 0: play emote state SCRIPT_COMMAND_FIELD_SET = 2, // source/target = Creature, datalong = field id, datalog2 = value SCRIPT_COMMAND_MOVE_TO = 3, // source/target = Creature, datalong2 = time to reach, x/y/z = destination @@ -419,6 +422,21 @@ std::string GetScriptsTableNameByType(ScriptsType type); ScriptMapMap* GetScriptsMapByType(ScriptsType type); std::string GetScriptCommandName(ScriptCommands command); +struct TC_GAME_API InstanceSpawnGroupInfo +{ + enum + { + FLAG_ACTIVATE_SPAWN = 0x01, + FLAG_BLOCK_SPAWN = 0x02, + + FLAG_ALL = (FLAG_ACTIVATE_SPAWN | FLAG_BLOCK_SPAWN) + }; + uint8 BossStateId; + uint8 BossStates; + uint32 SpawnGroupId; + uint8 Flags; +}; + struct TC_GAME_API SpellClickInfo { uint32 spellId; @@ -514,6 +532,11 @@ struct TrinityString std::vector<std::string> Content; }; +struct QuestGreetingLocale +{ + std::vector<std::string> greeting; +}; + typedef std::map<ObjectGuid, ObjectGuid> LinkedRespawnContainer; typedef std::unordered_map<uint32, CreatureTemplate> CreatureTemplateContainer; typedef std::unordered_map<uint32, CreatureAddon> CreatureTemplateAddonContainer; @@ -529,6 +552,9 @@ typedef std::unordered_map<uint32, GameObjectTemplateAddon> GameObjectTemplateAd typedef std::unordered_map<ObjectGuid::LowType, GameObjectData> GameObjectDataContainer; typedef std::unordered_map<ObjectGuid::LowType, GameObjectAddon> GameObjectAddonContainer; typedef std::unordered_map<uint32, std::vector<uint32>> GameObjectQuestItemMap; +typedef std::unordered_map<uint32, SpawnGroupTemplateData> SpawnGroupDataContainer; +typedef std::multimap<uint32, SpawnData const*> SpawnGroupLinkContainer; +typedef std::unordered_map<uint16, std::vector<InstanceSpawnGroupInfo>> InstanceSpawnGroupContainer; typedef std::map<TempSummonGroupKey, std::vector<TempSummonData>> TempSummonDataContainer; typedef std::unordered_map<uint32, CreatureLocale> CreatureLocaleContainer; typedef std::unordered_map<uint32, GameObjectLocale> GameObjectLocaleContainer; @@ -553,6 +579,7 @@ struct PointOfInterestLocale }; typedef std::unordered_map<uint32, PointOfInterestLocale> PointOfInterestLocaleContainer; +typedef std::unordered_map<uint32, QuestGreetingLocale> QuestGreetingLocaleContainer; typedef std::unordered_map<uint32, TrinityString> TrinityStringContainer; @@ -773,6 +800,19 @@ struct QuestPOIWrapper typedef std::unordered_map<uint32, QuestPOIWrapper> QuestPOIContainer; +struct QuestGreeting +{ + uint16 greetEmoteType; + uint32 greetEmoteDelay; + std::string greeting; + + QuestGreeting() : greetEmoteType(0), greetEmoteDelay(0) { } + QuestGreeting(uint16 _greetEmoteType, uint32 _greetEmoteDelay, std::string _greeting) + : greetEmoteType(_greetEmoteType), greetEmoteDelay(_greetEmoteDelay), greeting(_greeting) { } +}; + +typedef std::unordered_map<uint8, std::unordered_map<uint32, QuestGreeting const*>> QuestGreetingContainer; + struct GraveyardData { uint32 safeLocId; @@ -803,6 +843,7 @@ SkillRangeType GetSkillRangeType(SkillRaceClassInfoEntry const* rcEntry); #define MAX_CHARTER_NAME 24 // max allowed by client name length TC_GAME_API bool normalizePlayerName(std::string& name); +#define SPAWNGROUP_MAP_UNSET 0xFFFFFFFF struct LanguageDesc { @@ -976,6 +1017,7 @@ class TC_GAME_API ObjectMgr } GossipText const* GetGossipText(uint32 Text_ID) const; + QuestGreeting const* GetQuestGreeting(ObjectGuid guid) const; WorldSafeLocsEntry const* GetDefaultGraveyard(uint32 team) const; WorldSafeLocsEntry const* GetClosestGraveyard(float x, float y, float z, uint32 MapId, uint32 team) const; @@ -1120,7 +1162,10 @@ class TC_GAME_API ObjectMgr void LoadCreatureModelInfo(); void LoadEquipmentTemplates(); void LoadGameObjectLocales(); - void LoadGameobjects(); + void LoadGameObjects(); + void LoadSpawnGroupTemplates(); + void LoadSpawnGroups(); + void LoadInstanceSpawnGroups(); void LoadItemTemplates(); void LoadItemLocales(); void LoadItemSetNames(); @@ -1130,6 +1175,7 @@ class TC_GAME_API ObjectMgr void LoadPageTextLocales(); void LoadGossipMenuItemsLocales(); void LoadPointOfInterestLocales(); + void LoadQuestGreetingsLocales(); void LoadInstanceTemplate(); void LoadInstanceEncounters(); void LoadMailLevelRewards(); @@ -1141,6 +1187,7 @@ class TC_GAME_API ObjectMgr void LoadAreaTriggerTeleports(); void LoadAccessRequirements(); void LoadQuestAreaTriggers(); + void LoadQuestGreetings(); void LoadAreaTriggerScripts(); void LoadTavernAreaTriggers(); void LoadGameObjectForQuests(); @@ -1202,8 +1249,14 @@ class TC_GAME_API ObjectMgr uint64 GenerateEquipmentSetGuid(); uint32 GenerateMailID(); uint32 GeneratePetNumber(); - uint32 GenerateCreatureSpawnId(); - uint32 GenerateGameObjectSpawnId(); + ObjectGuid::LowType GenerateCreatureSpawnId(); + ObjectGuid::LowType GenerateGameObjectSpawnId(); + + SpawnGroupTemplateData const* GetSpawnGroupData(uint32 groupId) const { auto it = _spawnGroupDataStore.find(groupId); return it != _spawnGroupDataStore.end() ? &it->second : nullptr; } + SpawnGroupTemplateData const* GetDefaultSpawnGroup() const { return &_spawnGroupDataStore.at(0); } + SpawnGroupTemplateData const* GetLegacySpawnGroup() const { return &_spawnGroupDataStore.at(1); } + Trinity::IteratorPair<SpawnGroupLinkContainer::const_iterator> GetSpawnDataForGroup(uint32 groupId) const { return Trinity::Containers::MapEqualRange(_spawnGroupMapStore, groupId); } + std::vector<InstanceSpawnGroupInfo> const* GetSpawnGroupsForInstance(uint32 instanceId) const { auto it = _instanceSpawnGroupStore.find(instanceId); return it != _instanceSpawnGroupStore.end() ? &it->second : nullptr; } MailLevelReward const* GetMailLevelReward(uint32 level, uint32 raceMask) const { @@ -1254,6 +1307,18 @@ class TC_GAME_API ObjectMgr return nullptr; } + SpawnData const* GetSpawnData(SpawnObjectType type, ObjectGuid::LowType guid) + { + if (type == SPAWN_TYPE_CREATURE) + return GetCreatureData(guid); + else if (type == SPAWN_TYPE_GAMEOBJECT) + return GetGameObjectData(guid); + else + ASSERT(false, "Invalid spawn object type %u", uint32(type)); + return nullptr; + } + void OnDeleteSpawnData(SpawnData const* data); + CreatureDataContainer const& GetAllCreatureData() const { return _creatureDataStore; } CreatureData const* GetCreatureData(ObjectGuid::LowType guid) const { CreatureDataContainer::const_iterator itr = _creatureDataStore.find(guid); @@ -1274,6 +1339,15 @@ class TC_GAME_API ObjectMgr if (itr == _creatureLocaleStore.end()) return nullptr; return &itr->second; } + GameObjectDataContainer const& GetAllGameObjectData() const { return _gameObjectDataStore; } + GameObjectData const* GetGameObjectData(ObjectGuid::LowType guid) const + { + GameObjectDataContainer::const_iterator itr = _gameObjectDataStore.find(guid); + if (itr == _gameObjectDataStore.end()) return nullptr; + return &itr->second; + } + GameObjectData& NewOrExistGameObjectData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } + void DeleteGameObjectData(ObjectGuid::LowType guid); GameObjectLocale const* GetGameObjectLocale(uint32 entry) const { GameObjectLocaleContainer::const_iterator itr = _gameObjectLocaleStore.find(entry); @@ -1322,15 +1396,12 @@ class TC_GAME_API ObjectMgr if (itr == _pointOfInterestLocaleStore.end()) return nullptr; return &itr->second; } - - GameObjectData const* GetGOData(ObjectGuid::LowType guid) const + QuestGreetingLocale const* GetQuestGreetingLocale(uint32 id) const { - GameObjectDataContainer::const_iterator itr = _gameObjectDataStore.find(guid); - if (itr == _gameObjectDataStore.end()) return nullptr; + QuestGreetingLocaleContainer::const_iterator itr = _questGreetingLocaleStore.find(id); + if (itr == _questGreetingLocaleStore.end()) return nullptr; return &itr->second; } - GameObjectData& NewGOData(ObjectGuid::LowType guid) { return _gameObjectDataStore[guid]; } - void DeleteGOData(ObjectGuid::LowType guid); TrinityString const* GetTrinityString(uint32 entry) const { @@ -1349,7 +1420,7 @@ class TC_GAME_API ObjectMgr void RemoveCreatureFromGrid(ObjectGuid::LowType guid, CreatureData const* data); void AddGameobjectToGrid(ObjectGuid::LowType guid, GameObjectData const* data); void RemoveGameobjectFromGrid(ObjectGuid::LowType guid, GameObjectData const* data); - ObjectGuid::LowType AddGOData(uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay = 0); + ObjectGuid::LowType AddGameObjectData(uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot, uint32 spawntimedelay = 0); ObjectGuid::LowType AddCreatureData(uint32 entry, uint32 map, Position const& pos, uint32 spawntimedelay = 0); // reserved names @@ -1458,8 +1529,8 @@ class TC_GAME_API ObjectMgr std::atomic<uint32> _mailId; std::atomic<uint32> _hiPetNumber; - uint32 _creatureSpawnId; - uint32 _gameObjectSpawnId; + ObjectGuid::LowType _creatureSpawnId; + ObjectGuid::LowType _gameObjectSpawnId; // first free low guid for selected guid type template<HighGuid high> @@ -1484,6 +1555,7 @@ class TC_GAME_API ObjectMgr TavernAreaTriggerContainer _tavernAreaTriggerStore; GameObjectForQuestContainer _gameObjectForQuestStore; GossipTextContainer _gossipTextStore; + QuestGreetingContainer _questGreetingStore; AreaTriggerContainer _areaTriggerStore; AreaTriggerScriptContainer _areaTriggerScriptStore; AccessRequirementContainer _accessRequirementStore; @@ -1579,6 +1651,9 @@ class TC_GAME_API ObjectMgr GameObjectLocaleContainer _gameObjectLocaleStore; GameObjectTemplateContainer _gameObjectTemplateStore; GameObjectTemplateAddonContainer _gameObjectTemplateAddonStore; + SpawnGroupDataContainer _spawnGroupDataStore; + SpawnGroupLinkContainer _spawnGroupMapStore; + InstanceSpawnGroupContainer _instanceSpawnGroupStore; /// Stores temp summon data grouped by summoner's entry, summoner's type and group id TempSummonDataContainer _tempSummonDataStore; @@ -1591,6 +1666,7 @@ class TC_GAME_API ObjectMgr PageTextLocaleContainer _pageTextLocaleStore; GossipMenuItemsLocaleContainer _gossipMenuItemsLocaleStore; PointOfInterestLocaleContainer _pointOfInterestLocaleStore; + QuestGreetingLocaleContainer _questGreetingLocaleStore; TrinityStringContainer _trinityStringStore; diff --git a/src/server/game/Grids/Notifiers/GridNotifiers.h b/src/server/game/Grids/Notifiers/GridNotifiers.h index 897e43d5e03..16583516243 100644 --- a/src/server/game/Grids/Notifiers/GridNotifiers.h +++ b/src/server/game/Grids/Notifiers/GridNotifiers.h @@ -552,6 +552,11 @@ namespace Trinity : ContainerInserter<Player*>(container), i_phaseMask(searcher->GetPhaseMask()), i_check(check) { } + template<typename Container> + PlayerListSearcher(uint32 phaseMask, Container& container, Check & check) + : ContainerInserter<Player*>(container), + i_phaseMask(phaseMask), i_check(check) { } + void Visit(PlayerMapType &m); template<class NOT_INTERESTED> void Visit(GridRefManager<NOT_INTERESTED> &) { } @@ -928,9 +933,9 @@ namespace Trinity return false; float searchRadius = i_range; - if (i_incOwnRadius) + if (i_incOwnRadius) searchRadius += i_obj->GetCombatReach(); - if (i_incTargetRadius) + if (i_incTargetRadius) searchRadius += u->GetCombatReach(); if (!u->IsInMap(i_obj) || !u->InSamePhase(i_obj) || !u->IsWithinDoubleVerticalCylinder(i_obj, searchRadius, searchRadius)) @@ -954,7 +959,7 @@ namespace Trinity class AnyGroupedUnitInObjectRangeCheck { public: - AnyGroupedUnitInObjectRangeCheck(WorldObject const* obj, Unit const* funit, float range, bool raid, bool playerOnly = false, bool incOwnRadius = true, bool incTargetRadius = true) + AnyGroupedUnitInObjectRangeCheck(WorldObject const* obj, Unit const* funit, float range, bool raid, bool playerOnly = false, bool incOwnRadius = true, bool incTargetRadius = true) : _source(obj), _refUnit(funit), _range(range), _raid(raid), _playerOnly(playerOnly), i_incOwnRadius(incOwnRadius), i_incTargetRadius(incTargetRadius) { } bool operator()(Unit* u) const @@ -977,9 +982,9 @@ namespace Trinity return false; float searchRadius = _range; - if (i_incOwnRadius) + if (i_incOwnRadius) searchRadius += _source->GetCombatReach(); - if (i_incTargetRadius) + if (i_incTargetRadius) searchRadius += u->GetCombatReach(); return u->IsInMap(_source) && u->InSamePhase(_source) && u->IsWithinDoubleVerticalCylinder(_source, searchRadius, searchRadius); @@ -1022,7 +1027,7 @@ namespace Trinity bool operator()(Unit* u) { if (u->isTargetableForAttack() && i_obj->IsWithinDistInMap(u, i_range) && - !i_funit->IsFriendlyTo(u) && i_funit->CanSeeOrDetect(u)) + (i_funit->IsInCombatWith(u) || i_funit->IsHostileTo(u)) && i_obj->CanSeeOrDetect(u)) { i_range = i_obj->GetDistance(u); // use found unit range as new range limit for next check return true; @@ -1064,9 +1069,9 @@ namespace Trinity return false; float searchRadius = i_range; - if (i_incOwnRadius) + if (i_incOwnRadius) searchRadius += i_obj->GetCombatReach(); - if (i_incTargetRadius) + if (i_incTargetRadius) searchRadius += u->GetCombatReach(); return u->IsInMap(i_obj) && u->InSamePhase(i_obj) && u->IsWithinDoubleVerticalCylinder(i_obj, searchRadius, searchRadius); @@ -1321,6 +1326,27 @@ namespace Trinity bool _reqAlive; }; + class AnyPlayerInPositionRangeCheck + { + public: + AnyPlayerInPositionRangeCheck(Position const* pos, float range, bool reqAlive = true) : _pos(pos), _range(range), _reqAlive(reqAlive) { } + bool operator()(Player* u) + { + if (_reqAlive && !u->IsAlive()) + return false; + + if (!u->IsWithinDist3d(_pos, _range)) + return false; + + return true; + } + + private: + Position const* _pos; + float _range; + bool _reqAlive; + }; + class NearestPlayerInObjectRangeCheck { public: diff --git a/src/server/game/Grids/ObjectGridLoader.cpp b/src/server/game/Grids/ObjectGridLoader.cpp index 653c9d51d11..4dfca6f98c0 100644 --- a/src/server/game/Grids/ObjectGridLoader.cpp +++ b/src/server/game/Grids/ObjectGridLoader.cpp @@ -27,6 +27,7 @@ #include "ObjectAccessor.h" #include "ObjectMgr.h" #include "World.h" +#include "ScriptMgr.h" void ObjectGridEvacuator::Visit(CreatureMapType &m) { @@ -119,15 +120,56 @@ void LoadHelper(CellGuidSet const& guid_set, CellCoord &cell, GridRefManager<T> for (CellGuidSet::const_iterator i_guid = guid_set.begin(); i_guid != guid_set.end(); ++i_guid) { T* obj = new T; - ObjectGuid::LowType guid = *i_guid; - //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); - if (!obj->LoadFromDB(guid, map)) + + // Don't spawn at all if there's a respawn time + if ((obj->GetTypeId() == TYPEID_UNIT && !map->GetCreatureRespawnTime(*i_guid)) || (obj->GetTypeId() == TYPEID_GAMEOBJECT && !map->GetGORespawnTime(*i_guid))) { - delete obj; - continue; - } + ObjectGuid::LowType guid = *i_guid; + //TC_LOG_INFO("misc", "DEBUG: LoadHelper from table: %s for (guid: %u) Loading", table, guid); - AddObjectHelper(cell, m, count, map, obj); + if (obj->GetTypeId() == TYPEID_UNIT) + { + CreatureData const* cdata = sObjectMgr->GetCreatureData(guid); + ASSERT(cdata, "Tried to load creature with spawnId %u, but no such creature exists.", guid); + SpawnGroupTemplateData const* const group = cdata->spawnGroupData; + // If creature in manual spawn group, don't spawn here, unless group is already active. + if (!(group->flags & SPAWNGROUP_FLAG_SYSTEM)) + if (!map->IsSpawnGroupActive(group->groupId)) + { + delete obj; + continue; + } + + // If script is blocking spawn, don't spawn but queue for a re-check in a little bit + if (!(group->flags & SPAWNGROUP_FLAG_COMPATIBILITY_MODE) && !sScriptMgr->CanSpawn(guid, cdata->id, cdata, map)) + { + map->SaveRespawnTime(SPAWN_TYPE_CREATURE, guid, cdata->id, time(NULL) + urand(4,7), map->GetZoneId(cdata->spawnPoint), Trinity::ComputeGridCoord(cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY()).GetId(), false); + delete obj; + continue; + } + } + else if (obj->GetTypeId() == TYPEID_GAMEOBJECT) + { + // If gameobject in manual spawn group, don't spawn here, unless group is already active. + GameObjectData const* godata = sObjectMgr->GetGameObjectData(guid); + ASSERT(godata, "Tried to load gameobject with spawnId %u, but no such object exists.", guid); + if (!(godata->spawnGroupData->flags & SPAWNGROUP_FLAG_SYSTEM)) + if (!map->IsSpawnGroupActive(godata->spawnGroupData->groupId)) + { + delete obj; + continue; + } + } + + if (!obj->LoadFromDB(guid, map, false, false)) + { + delete obj; + continue; + } + AddObjectHelper(cell, m, count, map, obj); + } + else + delete obj; } } diff --git a/src/server/game/Groups/Group.cpp b/src/server/game/Groups/Group.cpp index 76b7de68ea9..79df88b61a5 100644 --- a/src/server/game/Groups/Group.cpp +++ b/src/server/game/Groups/Group.cpp @@ -1534,8 +1534,7 @@ void Group::CountTheRoll(Rolls::iterator rollI, Map* allowedMap) // remove is_blocked so that the item is lootable by all players LootItem* item = &(roll->itemSlot >= roll->getLoot()->items.size() ? roll->getLoot()->quest_items[roll->itemSlot - roll->getLoot()->items.size()] : roll->getLoot()->items[roll->itemSlot]); - if (item) - item->is_blocked = false; + item->is_blocked = false; } RollId.erase(rollI); diff --git a/src/server/game/Handlers/BattleGroundHandler.cpp b/src/server/game/Handlers/BattleGroundHandler.cpp index 8b03ff91240..bb8f52b90fd 100644 --- a/src/server/game/Handlers/BattleGroundHandler.cpp +++ b/src/server/game/Handlers/BattleGroundHandler.cpp @@ -51,7 +51,8 @@ void WorldSession::HandleBattlemasterHelloOpcode(WorldPacket& recvData) return; // Stop the npc if moving - unit->StopMoving(); + unit->PauseMovement(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)); + unit->SetHomePosition(unit->GetPosition()); BattlegroundTypeId bgTypeId = sBattlegroundMgr->GetBattleMasterBG(unit->GetEntry()); diff --git a/src/server/game/Handlers/CalendarHandler.cpp b/src/server/game/Handlers/CalendarHandler.cpp index 6d884ca2ee9..4384ff82474 100644 --- a/src/server/game/Handlers/CalendarHandler.cpp +++ b/src/server/game/Handlers/CalendarHandler.cpp @@ -41,6 +41,7 @@ Copied events should probably have a new owner #include "CharacterCache.h" #include "DatabaseEnv.h" #include "DBCStores.h" +#include "GameEventMgr.h" #include "Guild.h" #include "GuildMgr.h" #include "InstanceSaveMgr.h" @@ -151,12 +152,10 @@ void WorldSession::HandleCalendarGetCalendar(WorldPacket& /*recvData*/) data << uint32(boundCounter); data.append(dataBuffer); - /// @todo Fix this, how we do know how many and what holidays to send? - uint32 holidayCount = 0; - data << uint32(holidayCount); - for (uint32 i = 0; i < holidayCount; ++i) + data << uint32(sGameEventMgr->modifiedHolidays.size()); + for (uint32 entry : sGameEventMgr->modifiedHolidays) { - HolidaysEntry const* holiday = sHolidaysStore.LookupEntry(666); + HolidaysEntry const* holiday = sHolidaysStore.LookupEntry(entry); data << uint32(holiday->Id); // m_ID data << uint32(holiday->Region); // m_region, might be looping diff --git a/src/server/game/Handlers/CharacterHandler.cpp b/src/server/game/Handlers/CharacterHandler.cpp index 9bdf294c9b3..df8f418c245 100644 --- a/src/server/game/Handlers/CharacterHandler.cpp +++ b/src/server/game/Handlers/CharacterHandler.cpp @@ -1560,6 +1560,9 @@ void WorldSession::HandleEquipmentSetUse(WorldPacket& recvData) InventoryResult msg = _player->CanStoreItem(NULL_BAG, NULL_SLOT, sDest, uItem, false); if (msg == EQUIP_ERR_OK) { + if (_player->CanEquipItem(NULL_SLOT, dstpos, uItem, false) != EQUIP_ERR_OK) + continue; + _player->RemoveItem(INVENTORY_SLOT_BAG_0, i, true); _player->StoreItem(sDest, uItem, true); } @@ -1572,6 +1575,9 @@ void WorldSession::HandleEquipmentSetUse(WorldPacket& recvData) if (item->GetPos() == dstpos) continue; + if (_player->CanUnequipItem(dstpos, true) != EQUIP_ERR_OK) + continue; + _player->SwapItem(item->GetPos(), dstpos); } @@ -2084,7 +2090,7 @@ void WorldSession::HandleCharFactionOrRaceChangeCallback(std::shared_ptr<Charact ss << knownTitles[index] << ' '; stmt = CharacterDatabase.GetPreparedStatement(CHAR_UPD_CHAR_TITLES_FACTION_CHANGE); - stmt->setString(0, ss.str().c_str()); + stmt->setString(0, ss.str()); stmt->setUInt32(1, lowGuid); trans->Append(stmt); diff --git a/src/server/game/Handlers/ChatHandler.cpp b/src/server/game/Handlers/ChatHandler.cpp index eb3200ea87d..259bc08838e 100644 --- a/src/server/game/Handlers/ChatHandler.cpp +++ b/src/server/game/Handlers/ChatHandler.cpp @@ -126,7 +126,7 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) // LANG_ADDON should not be changed nor be affected by flood control else { - // send in universal language if player in .gmon mode (ignore spell effects) + // send in universal language if player in .gm on mode (ignore spell effects) if (sender->IsGameMaster()) lang = LANG_UNIVERSAL; else @@ -216,11 +216,15 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) if (msg.empty()) return; - if (ChatHandler(this).ParseCommands(msg.c_str())) - return; - + if (lang == LANG_ADDON) + { + if (AddonChannelCommandHandler(this).ParseCommands(msg.c_str())) + return; + } if (lang != LANG_ADDON) { + if (ChatHandler(this).ParseCommands(msg.c_str())) + return; // Strip invisible characters for non-addon messages if (sWorld->getBoolConfig(CONFIG_CHAT_FAKE_MESSAGE_PREVENTING)) stripLineInvisibleChars(msg); @@ -466,7 +470,7 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recvData) if (Channel* chn = ChannelMgr::GetChannelForPlayerByNamePart(channel, sender)) { sScriptMgr->OnPlayerChat(sender, type, lang, msg, chn); - chn->Say(sender->GetGUID(), msg.c_str(), lang); + chn->Say(sender->GetGUID(), msg, lang); } break; } diff --git a/src/server/game/Handlers/ItemHandler.cpp b/src/server/game/Handlers/ItemHandler.cpp index 9f3e2a52efe..ee2666c9f96 100644 --- a/src/server/game/Handlers/ItemHandler.cpp +++ b/src/server/game/Handlers/ItemHandler.cpp @@ -615,8 +615,8 @@ void WorldSession::SendListInventory(ObjectGuid vendorGuid) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); // Stop the npc if moving - if (vendor->HasUnitState(UNIT_STATE_MOVING)) - vendor->StopMoving(); + vendor->PauseMovement(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)); + vendor->SetHomePosition(vendor->GetPosition()); VendorItemData const* items = vendor->GetVendorItems(); if (!items) diff --git a/src/server/game/Handlers/MiscHandler.cpp b/src/server/game/Handlers/MiscHandler.cpp index a632bf1979c..9a130fab45f 100644 --- a/src/server/game/Handlers/MiscHandler.cpp +++ b/src/server/game/Handlers/MiscHandler.cpp @@ -51,6 +51,7 @@ #include "WhoListStorage.h" #include "World.h" #include "WorldPacket.h" +#include <cstdarg> #include <zlib.h> void WorldSession::HandleRepopRequestOpcode(WorldPacket& recvData) diff --git a/src/server/game/Handlers/MovementHandler.cpp b/src/server/game/Handlers/MovementHandler.cpp index 8b64e0e08bb..a8b365e5db2 100644 --- a/src/server/game/Handlers/MovementHandler.cpp +++ b/src/server/game/Handlers/MovementHandler.cpp @@ -24,9 +24,10 @@ #include "Corpse.h" #include "Player.h" #include "MapManager.h" +#include "MotionMaster.h" +#include "MovementGenerator.h" #include "Transport.h" #include "Battleground.h" -#include "WaypointMovementGenerator.h" #include "InstanceSaveMgr.h" #include "ObjectMgr.h" #include "Vehicle.h" @@ -133,8 +134,8 @@ void WorldSession::HandleMoveWorldportAck() if (!_player->InBattleground()) { // short preparations to continue flight - FlightPathMovementGenerator* flight = (FlightPathMovementGenerator*)(GetPlayer()->GetMotionMaster()->top()); - flight->Initialize(GetPlayer()); + MovementGenerator* movementGenerator = GetPlayer()->GetMotionMaster()->top(); + movementGenerator->Initialize(GetPlayer()); return; } diff --git a/src/server/game/Handlers/NPCHandler.cpp b/src/server/game/Handlers/NPCHandler.cpp index ddde13ae860..59344509283 100644 --- a/src/server/game/Handlers/NPCHandler.cpp +++ b/src/server/game/Handlers/NPCHandler.cpp @@ -38,6 +38,7 @@ #include "ScriptMgr.h" #include "SpellInfo.h" #include "SpellMgr.h" +#include "World.h" #include "WorldPacket.h" enum StableResultCode @@ -322,11 +323,9 @@ void WorldSession::HandleGossipHelloOpcode(WorldPacket& recvData) //if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) // GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - // and if he has pure gossip or is banker and moves or is tabard designer? - //if (unit->IsArmorer() || unit->IsCivilian() || unit->IsQuestGiver() || unit->IsServiceProvider() || unit->IsGuard()) - { - unit->StopMoving(); - } + // Stop the npc if moving + unit->PauseMovement(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)); + unit->SetHomePosition(unit->GetPosition()); // If spiritguide, no need for gossip menu, just put player into resurrect queue if (unit->IsSpiritGuide()) diff --git a/src/server/game/Handlers/PetitionsHandler.cpp b/src/server/game/Handlers/PetitionsHandler.cpp index 4e47f260fff..03087ce5e7e 100644 --- a/src/server/game/Handlers/PetitionsHandler.cpp +++ b/src/server/game/Handlers/PetitionsHandler.cpp @@ -420,7 +420,7 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recvData) { if (_player->getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", _player->GetName().c_str(), ERR_ARENA_TEAM_TARGET_TOO_LOW_S); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, "", _player->GetName(), ERR_ARENA_TEAM_TARGET_TOO_LOW_S); return; } @@ -430,13 +430,13 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recvData) if (_player->GetArenaTeamId(slot)) { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", _player->GetName().c_str(), ERR_ALREADY_IN_ARENA_TEAM_S); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", _player->GetName(), ERR_ALREADY_IN_ARENA_TEAM_S); return; } if (_player->GetArenaTeamIdInvited()) { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", _player->GetName().c_str(), ERR_ALREADY_INVITED_TO_ARENA_TEAM_S); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", _player->GetName(), ERR_ALREADY_INVITED_TO_ARENA_TEAM_S); return; } } @@ -549,7 +549,7 @@ void WorldSession::HandleOfferPetitionOpcode(WorldPacket& recvData) if (player->getLevel() < sWorld->getIntConfig(CONFIG_MAX_PLAYER_LEVEL)) { // player is too low level to join an arena team - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, player->GetName().c_str(), "", ERR_ARENA_TEAM_TARGET_TOO_LOW_S); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, player->GetName(), "", ERR_ARENA_TEAM_TARGET_TOO_LOW_S); return; } @@ -560,13 +560,13 @@ void WorldSession::HandleOfferPetitionOpcode(WorldPacket& recvData) if (player->GetArenaTeamId(slot)) { // player is already in an arena team - SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, player->GetName().c_str(), "", ERR_ALREADY_IN_ARENA_TEAM_S); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_CREATE_S, player->GetName(), "", ERR_ALREADY_IN_ARENA_TEAM_S); return; } if (player->GetArenaTeamIdInvited()) { - SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", _player->GetName().c_str(), ERR_ALREADY_INVITED_TO_ARENA_TEAM_S); + SendArenaTeamCommandResult(ERR_ARENA_TEAM_INVITE_SS, "", _player->GetName(), ERR_ALREADY_INVITED_TO_ARENA_TEAM_S); return; } } diff --git a/src/server/game/Handlers/QuestHandler.cpp b/src/server/game/Handlers/QuestHandler.cpp index f95fb01ed36..23c0ac25524 100644 --- a/src/server/game/Handlers/QuestHandler.cpp +++ b/src/server/game/Handlers/QuestHandler.cpp @@ -91,8 +91,10 @@ void WorldSession::HandleQuestgiverHelloOpcode(WorldPacket& recvData) // remove fake death if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); + // Stop the npc if moving - creature->StopMoving(); + creature->PauseMovement(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)); + creature->SetHomePosition(creature->GetPosition()); _player->PlayerTalkClass->ClearMenus(); if (creature->AI()->GossipHello(_player)) diff --git a/src/server/game/Handlers/TaxiHandler.cpp b/src/server/game/Handlers/TaxiHandler.cpp index 3912707ea25..6adc2d04aed 100644 --- a/src/server/game/Handlers/TaxiHandler.cpp +++ b/src/server/game/Handlers/TaxiHandler.cpp @@ -18,9 +18,12 @@ #include "WorldSession.h" #include "Common.h" +#include "Creature.h" #include "DatabaseEnv.h" #include "DBCStores.h" #include "Log.h" +#include "MotionMaster.h" +#include "ObjectAccessor.h" #include "ObjectMgr.h" #include "Opcodes.h" #include "Player.h" @@ -39,25 +42,22 @@ void WorldSession::HandleTaxiNodeStatusQueryOpcode(WorldPacket& recvData) void WorldSession::SendTaxiStatus(ObjectGuid guid) { - // cheating checks - Creature* unit = GetPlayer()->GetNPCIfCanInteractWith(guid, UNIT_NPC_FLAG_FLIGHTMASTER); - if (!unit) + Player* const player = GetPlayer(); + Creature* unit = ObjectAccessor::GetCreature(*player, guid); + if (!unit || unit->IsHostileTo(player) || !unit->HasFlag(UNIT_NPC_FLAGS, UNIT_NPC_FLAG_FLIGHTMASTER)) { TC_LOG_DEBUG("network", "WorldSession::SendTaxiStatus - %s not found or you can't interact with him.", guid.ToString().c_str()); return; } - uint32 curloc = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), GetPlayer()->GetTeam()); - - // not found nearest - if (curloc == 0) + // find taxi node + uint32 nearest = sObjectMgr->GetNearestTaxiNode(unit->GetPositionX(), unit->GetPositionY(), unit->GetPositionZ(), unit->GetMapId(), player->GetTeam()); + if (!nearest) return; - TC_LOG_DEBUG("network", "WORLD: current location %u ", curloc); - WorldPacket data(SMSG_TAXINODE_STATUS, 9); data << guid; - data << uint8(GetPlayer()->m_taxi.IsTaximaskNodeKnown(curloc) ? 1 : 0); + data << uint8(player->m_taxi.IsTaximaskNodeKnown(nearest) ? 1 : 0); SendPacket(&data); } @@ -119,8 +119,7 @@ void WorldSession::SendDoFlight(uint32 mountDisplayId, uint32 path, uint32 pathN if (GetPlayer()->HasUnitState(UNIT_STATE_DIED)) GetPlayer()->RemoveAurasByType(SPELL_AURA_FEIGN_DEATH); - while (GetPlayer()->GetMotionMaster()->GetCurrentMovementGeneratorType() == FLIGHT_MOTION_TYPE) - GetPlayer()->GetMotionMaster()->MovementExpired(false); + GetPlayer()->GetMotionMaster()->Clear(MOTION_SLOT_CONTROLLED); if (mountDisplayId) GetPlayer()->Mount(mountDisplayId); diff --git a/src/server/game/Instances/InstanceScript.cpp b/src/server/game/Instances/InstanceScript.cpp index 3fa7eb52d6e..154254e25b2 100644 --- a/src/server/game/Instances/InstanceScript.cpp +++ b/src/server/game/Instances/InstanceScript.cpp @@ -46,7 +46,7 @@ BossBoundaryData::~BossBoundaryData() delete it->Boundary; } -InstanceScript::InstanceScript(Map* map) : instance(map), completedEncounters(0) +InstanceScript::InstanceScript(Map* map) : instance(map), completedEncounters(0), _instanceSpawnGroups(sObjectMgr->GetSpawnGroupsForInstance(map->GetId())) { #ifdef TRINITY_API_USE_DYNAMIC_LINKING uint32 scriptId = sObjectMgr->GetInstanceTemplate(map->GetId())->ScriptId; @@ -186,27 +186,6 @@ void InstanceScript::LoadObjectData(ObjectData const* data, ObjectInfoMap& objec } } -void InstanceScript::UpdateMinionState(Creature* minion, EncounterState state) -{ - switch (state) - { - case NOT_STARTED: - if (!minion->IsAlive()) - minion->Respawn(); - else if (minion->IsInCombat()) - minion->AI()->EnterEvadeMode(); - break; - case IN_PROGRESS: - if (!minion->IsAlive()) - minion->Respawn(); - else if (!minion->GetVictim()) - minion->AI()->DoZoneInCombat(); - break; - default: - break; - } -} - void InstanceScript::UpdateDoorState(GameObject* door) { DoorInfoMapBounds range = doors.equal_range(door->GetEntry()); @@ -236,6 +215,60 @@ void InstanceScript::UpdateDoorState(GameObject* door) door->SetGoState(open ? GO_STATE_ACTIVE : GO_STATE_READY); } +void InstanceScript::UpdateMinionState(Creature* minion, EncounterState state) +{ + switch (state) + { + case NOT_STARTED: + if (!minion->IsAlive()) + minion->Respawn(); + else if (minion->IsInCombat()) + minion->AI()->EnterEvadeMode(); + break; + case IN_PROGRESS: + if (!minion->IsAlive()) + minion->Respawn(); + else if (!minion->GetVictim()) + minion->AI()->DoZoneInCombat(); + break; + default: + break; + } +} + +void InstanceScript::UpdateSpawnGroups() +{ + if (!_instanceSpawnGroups) + return; + enum states { BLOCK, SPAWN, FORCEBLOCK }; + std::unordered_map<uint32, states> newStates; + for (auto it = _instanceSpawnGroups->begin(), end = _instanceSpawnGroups->end(); it != end; ++it) + { + InstanceSpawnGroupInfo const& info = *it; + states& curValue = newStates[info.SpawnGroupId]; // makes sure there's a BLOCK value in the map + if (curValue == FORCEBLOCK) // nothing will change this + continue; + if (!((1 << GetBossState(info.BossStateId)) & info.BossStates)) + continue; + if (info.Flags & InstanceSpawnGroupInfo::FLAG_BLOCK_SPAWN) + curValue = FORCEBLOCK; + else if (info.Flags & InstanceSpawnGroupInfo::FLAG_ACTIVATE_SPAWN) + curValue = SPAWN; + } + for (auto const& pair : newStates) + { + uint32 const groupId = pair.first; + bool const doSpawn = (pair.second == SPAWN); + if (instance->IsSpawnGroupActive(groupId) == doSpawn) + continue; // nothing to do here + // if we should spawn group, then spawn it... + if (doSpawn) + instance->SpawnGroupSpawn(groupId); + else // otherwise, set it as inactive so it no longer respawns (but don't despawn it) + instance->SetSpawnGroupActive(groupId, false); + } +} + BossInfo* InstanceScript::GetBossInfo(uint32 id) { ASSERT(id < bosses.size()); @@ -310,7 +343,7 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state) if (bossInfo->state == TO_BE_DECIDED) // loading { bossInfo->state = state; - //TC_LOG_ERROR("misc", "Inialize boss %u state as %u.", id, (uint32)state); + TC_LOG_DEBUG("scripts", "InstanceScript: Initialize boss %u state as %s (map %u, %u).", id, GetBossStateName(state), instance->GetId(), instance->GetInstanceId()); return false; } else @@ -318,6 +351,12 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state) if (bossInfo->state == state) return false; + if (bossInfo->state == DONE) + { + TC_LOG_ERROR("map", "InstanceScript: Tried to set instance state from %s back to %s for map %u, instance id %u. Blocked!", GetBossStateName(bossInfo->state), GetBossStateName(state), instance->GetId(), instance->GetInstanceId()); + return false; + } + if (state == DONE) for (GuidSet::iterator i = bossInfo->minion.begin(); i != bossInfo->minion.end(); ++i) if (Creature* minion = instance->GetCreature(*i)) @@ -337,6 +376,7 @@ bool InstanceScript::SetBossState(uint32 id, EncounterState state) if (Creature* minion = instance->GetCreature(*i)) UpdateMinionState(minion, state); + UpdateSpawnGroups(); return true; } return false; @@ -347,6 +387,13 @@ bool InstanceScript::_SkipCheckRequiredBosses(Player const* player /*= nullptr*/ return player && player->GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_INSTANCE_REQUIRED_BOSSES); } +void InstanceScript::Create() +{ + for (size_t i = 0; i < bosses.size(); ++i) + SetBossState(i, NOT_STARTED); + UpdateSpawnGroups(); +} + void InstanceScript::Load(char const* data) { if (!data) @@ -397,6 +444,7 @@ void InstanceScript::ReadSaveDataBossStates(std::istringstream& data) if (buff < TO_BE_DECIDED) SetBossState(bossId, EncounterState(buff)); } + UpdateSpawnGroups(); } std::string InstanceScript::GetSaveData() @@ -691,7 +739,7 @@ void InstanceScript::UpdateEncounterStateForSpellCast(uint32 spellId, Unit* sour UpdateEncounterState(ENCOUNTER_CREDIT_CAST_SPELL, spellId, source); } -std::string InstanceScript::GetBossStateName(uint8 state) +/*static*/ char const* InstanceScript::GetBossStateName(uint8 state) { // See enum EncounterState in InstanceScript.h switch (state) diff --git a/src/server/game/Instances/InstanceScript.h b/src/server/game/Instances/InstanceScript.h index 55452ebf3c1..d9802aa548b 100644 --- a/src/server/game/Instances/InstanceScript.h +++ b/src/server/game/Instances/InstanceScript.h @@ -34,6 +34,7 @@ class AreaBoundary; class Creature; class GameObject; +struct InstanceSpawnGroupInfo; class Map; class ModuleReference; class Player; @@ -156,7 +157,10 @@ class TC_GAME_API InstanceScript : public ZoneScript // KEEPING THIS METHOD ONLY FOR BACKWARD COMPATIBILITY !!! virtual void Initialize() { } - // On load + // On instance load, exactly ONE of these methods will ALWAYS be called: + // if we're starting without any saved instance data + virtual void Create(); + // if we're loading existing instance save data virtual void Load(char const* data); // When save is needed, this function generates the data @@ -223,7 +227,7 @@ class TC_GAME_API InstanceScript : public ZoneScript virtual bool SetBossState(uint32 id, EncounterState state); EncounterState GetBossState(uint32 id) const { return id < bosses.size() ? bosses[id].state : TO_BE_DECIDED; } - static std::string GetBossStateName(uint8 state); + static char const* GetBossStateName(uint8 state); CreatureBoundary const* GetBossBoundary(uint32 id) const { return id < bosses.size() ? &bosses[id].boundary : nullptr; } // Achievement criteria additional requirements check @@ -249,6 +253,11 @@ class TC_GAME_API InstanceScript : public ZoneScript uint32 GetEncounterCount() const { return bosses.size(); } + // Only used by areatriggers that inherit from OnlyOnceAreaTriggerScript + void MarkAreaTriggerDone(uint32 id) { _activatedAreaTriggers.insert(id); } + void ResetAreaTriggerDone(uint32 id) { _activatedAreaTriggers.erase(id); } + bool IsAreaTriggerDone(uint32 id) const { return _activatedAreaTriggers.find(id) != _activatedAreaTriggers.end(); } + protected: void SetHeaders(std::string const& dataHeaders); void SetBossNumber(uint32 number) { bosses.resize(number); } @@ -267,6 +276,8 @@ class TC_GAME_API InstanceScript : public ZoneScript virtual void UpdateDoorState(GameObject* door); void UpdateMinionState(Creature* minion, EncounterState state); + void UpdateSpawnGroups(); + // Exposes private data that should never be modified unless exceptional cases. // Pay very much attention at how the returned BossInfo data is modified to avoid issues. BossInfo* GetBossInfo(uint32 id); @@ -293,6 +304,8 @@ class TC_GAME_API InstanceScript : public ZoneScript ObjectInfoMap _gameObjectInfo; ObjectGuidMap _objectGuids; uint32 completedEncounters; // completed encounter mask, bit indexes are DungeonEncounter.dbc boss numbers, used for packets + std::vector<InstanceSpawnGroupInfo> const* const _instanceSpawnGroups; + std::unordered_set<uint32> _activatedAreaTriggers; #ifdef TRINITY_API_USE_DYNAMIC_LINKING // Strong reference to the associated script module diff --git a/src/server/game/Loot/LootMgr.cpp b/src/server/game/Loot/LootMgr.cpp index 7c6cc28fb48..f93ee0a91bc 100644 --- a/src/server/game/Loot/LootMgr.cpp +++ b/src/server/game/Loot/LootMgr.cpp @@ -812,7 +812,7 @@ void LoadLootTemplates_Creature() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u creature loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 creature loot templates. DB table `creature_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 creature loot templates. DB table `creature_loot_template` is empty"); } void LoadLootTemplates_Disenchant() @@ -845,7 +845,7 @@ void LoadLootTemplates_Disenchant() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u disenchanting loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 disenchanting loot templates. DB table `disenchant_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 disenchanting loot templates. DB table `disenchant_loot_template` is empty"); } void LoadLootTemplates_Fishing() @@ -868,7 +868,7 @@ void LoadLootTemplates_Fishing() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u fishing loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 fishing loot templates. DB table `fishing_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 fishing loot templates. DB table `fishing_loot_template` is empty"); } void LoadLootTemplates_Gameobject() @@ -902,7 +902,7 @@ void LoadLootTemplates_Gameobject() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u gameobject loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 gameobject loot templates. DB table `gameobject_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 gameobject loot templates. DB table `gameobject_loot_template` is empty"); } void LoadLootTemplates_Item() @@ -926,7 +926,7 @@ void LoadLootTemplates_Item() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u item loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 item loot templates. DB table `item_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 item loot templates. DB table `item_loot_template` is empty"); } void LoadLootTemplates_Milling() @@ -955,7 +955,7 @@ void LoadLootTemplates_Milling() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u milling loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 milling loot templates. DB table `milling_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 milling loot templates. DB table `milling_loot_template` is empty"); } void LoadLootTemplates_Pickpocketing() @@ -989,7 +989,7 @@ void LoadLootTemplates_Pickpocketing() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u pickpocketing loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 pickpocketing loot templates. DB table `pickpocketing_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 pickpocketing loot templates. DB table `pickpocketing_loot_template` is empty"); } void LoadLootTemplates_Prospecting() @@ -1018,7 +1018,7 @@ void LoadLootTemplates_Prospecting() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u prospecting loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 prospecting loot templates. DB table `prospecting_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 prospecting loot templates. DB table `prospecting_loot_template` is empty"); } void LoadLootTemplates_Mail() @@ -1042,7 +1042,7 @@ void LoadLootTemplates_Mail() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u mail loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 mail loot templates. DB table `mail_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 mail loot templates. DB table `mail_loot_template` is empty"); } void LoadLootTemplates_Skinning() @@ -1076,7 +1076,7 @@ void LoadLootTemplates_Skinning() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u skinning loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 skinning loot templates. DB table `skinning_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 skinning loot templates. DB table `skinning_loot_template` is empty"); } void LoadLootTemplates_Spell() @@ -1116,7 +1116,7 @@ void LoadLootTemplates_Spell() if (count) TC_LOG_INFO("server.loading", ">> Loaded %u spell loot templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); else - TC_LOG_ERROR("server.loading", ">> Loaded 0 spell loot templates. DB table `spell_loot_template` is empty"); + TC_LOG_INFO("server.loading", ">> Loaded 0 spell loot templates. DB table `spell_loot_template` is empty"); } void LoadLootTemplates_Reference() diff --git a/src/server/game/Maps/Map.cpp b/src/server/game/Maps/Map.cpp index 0793422f8ad..61f24f2b6e5 100644 --- a/src/server/game/Maps/Map.cpp +++ b/src/server/game/Maps/Map.cpp @@ -23,6 +23,7 @@ #include "DisableMgr.h" #include "DynamicTree.h" #include "GameObjectModel.h" +#include "GameTime.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "GridStates.h" @@ -37,6 +38,7 @@ #include "ObjectGridLoader.h" #include "ObjectMgr.h" #include "Pet.h" +#include "PoolMgr.h" #include "ScriptMgr.h" #include "Transport.h" #include "Vehicle.h" @@ -61,6 +63,10 @@ Map::~Map() sScriptMgr->OnDestroyMap(this); + // Delete all waiting spawns, else there will be a memory leak + // This doesn't delete from database. + DeleteRespawnInfo(); + while (!i_worldObjects.empty()) { WorldObject* obj = *i_worldObjects.begin(); @@ -253,7 +259,7 @@ m_unloadTimer(0), m_VisibleDistance(DEFAULT_VISIBILITY_DISTANCE), m_VisibilityNotifyPeriod(DEFAULT_VISIBILITY_NOTIFY_PERIOD), m_activeNonPlayersIter(m_activeNonPlayers.end()), _transportsUpdateIter(_transports.end()), i_gridExpiry(expiry), -i_scriptLock(false), _defaultLight(GetDefaultMapLight(id)) +i_scriptLock(false), _respawnCheckTimer(0), _defaultLight(GetDefaultMapLight(id)) { m_parentMap = (_parent ? _parent : this); for (unsigned int idx=0; idx < MAX_NUMBER_OF_GRIDS; ++idx) @@ -266,6 +272,8 @@ i_scriptLock(false), _defaultLight(GetDefaultMapLight(id)) } } + _zonePlayerCountMap.clear(); + //lets initialize visibility distance for map Map::InitVisibilityDistance(); @@ -522,6 +530,29 @@ bool Map::EnsureGridLoaded(Cell const& cell) return false; } +void Map::GridMarkNoUnload(uint32 x, uint32 y) +{ + // First make sure this grid is loaded + float gX = ((float(x) - 0.5f - CENTER_GRID_ID) * SIZE_OF_GRIDS) + (CENTER_GRID_OFFSET * 2); + float gY = ((float(y) - 0.5f - CENTER_GRID_ID) * SIZE_OF_GRIDS) + (CENTER_GRID_OFFSET * 2); + Cell cell = Cell(gX, gY); + EnsureGridLoaded(cell); + + // Mark as don't unload + NGridType* grid = getNGrid(x, y); + grid->setUnloadExplicitLock(true); +} + +void Map::GridUnmarkNoUnload(uint32 x, uint32 y) +{ + // If grid is loaded, clear unload lock + if (IsGridLoaded(GridCoord(x, y))) + { + NGridType* grid = getNGrid(x, y); + grid->setUnloadExplicitLock(false); + } +} + void Map::LoadGrid(float x, float y) { EnsureGridLoaded(Cell(x, y)); @@ -689,6 +720,21 @@ void Map::VisitNearbyCellsOf(WorldObject* obj, TypeContainerVisitor<Trinity::Obj } } +void Map::UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone) +{ + // Nothing to do if no change + if (oldZone == newZone) + return; + + if (oldZone != MAP_INVALID_ZONE) + { + uint32& oldZoneCount = _zonePlayerCountMap[oldZone]; + ASSERT(oldZoneCount, "A player left zone %u (went to %u) - but there were no players in the zone!", oldZone, newZone); + --oldZoneCount; + } + ++_zonePlayerCountMap[newZone]; +} + void Map::Update(uint32 t_diff) { _dynamicTree.update(t_diff); @@ -704,6 +750,16 @@ void Map::Update(uint32 t_diff) session->Update(t_diff, updater); } } + + /// process any due respawns + if (_respawnCheckTimer <= t_diff) + { + ProcessRespawns(); + _respawnCheckTimer = sWorld->getIntConfig(CONFIG_RESPAWN_MINCHECKINTERVALMS); + } + else + _respawnCheckTimer -= t_diff; + /// update active cells around players and active objects resetMarkedCells(); @@ -885,6 +941,8 @@ void Map::ProcessRelocationNotifies(const uint32 diff) void Map::RemovePlayerFromMap(Player* player, bool remove) { + // Before leaving map, update zone/area for stats + player->UpdateZone(MAP_INVALID_ZONE, 0); sScriptMgr->OnPlayerLeaveMap(this, player); player->getHostileRefManager().deleteReferences(); // multithreading crashfix @@ -2639,10 +2697,7 @@ void Map::GetFullTerrainStatusForPosition(float x, float y, float z, PositionFul else { data.floorZ = mapHeight; - if (gmap) - data.areaId = gmap->getArea(x, y); - else - data.areaId = 0; + data.areaId = gmap->getArea(x, y); if (!data.areaId) data.areaId = i_mapEntry->linked_zone; @@ -2744,11 +2799,6 @@ bool Map::getObjectHitPos(uint32 phasemask, float x1, float y1, float z1, float return result; } -float Map::GetHeight(uint32 phasemask, float x, float y, float z, bool vmap/*=true*/, float maxSearchDist/*=DEFAULT_HEIGHT_SEARCH*/) const -{ - return std::max<float>(GetHeight(x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phasemask, x, y, z, maxSearchDist)); -} - bool Map::IsInWater(float x, float y, float pZ, LiquidData* data) const { LiquidData liquid_status; @@ -2867,6 +2917,474 @@ void Map::SendObjectUpdates() } } +bool Map::CheckRespawn(RespawnInfo* info) +{ + uint32 poolId = info->spawnId ? sPoolMgr->IsPartOfAPool(info->type, info->spawnId) : 0; + // First, check if there's already an instance of this object that would block the respawn + // Only do this for unpooled spawns + if (!poolId) + { + bool doDelete = false; + switch (info->type) + { + case SPAWN_TYPE_CREATURE: + { + // escort check for creatures only (if the world config boolean is set) + bool isEscort = false; + if (sWorld->getBoolConfig(CONFIG_RESPAWN_DYNAMIC_ESCORTNPC) && info->type == SPAWN_TYPE_CREATURE) + if (CreatureData const* cdata = sObjectMgr->GetCreatureData(info->spawnId)) + if (cdata->spawnGroupData->flags & SPAWNGROUP_FLAG_ESCORTQUESTNPC) + isEscort = true; + + auto range = _creatureBySpawnIdStore.equal_range(info->spawnId); + for (auto it = range.first; it != range.second; ++it) + { + Creature* creature = it->second; + if (!creature->IsAlive()) + continue; + // escort NPCs are allowed to respawn as long as all other instances are already escorting + if (isEscort && creature->IsEscortNPC(true)) + continue; + doDelete = true; + break; + } + break; + } + case SPAWN_TYPE_GAMEOBJECT: + // gameobject check is simpler - they cannot be dead or escorting + if (_gameobjectBySpawnIdStore.find(info->spawnId) != _gameobjectBySpawnIdStore.end()) + doDelete = true; + break; + default: + ASSERT(false, "Invalid spawn type %u with spawnId %u on map %u", uint32(info->type), info->spawnId, GetId()); + return true; + } + if (doDelete) + { + info->respawnTime = 0; + return false; + } + } + + // next, check linked respawn time + ObjectGuid thisGUID = ObjectGuid((info->type == SPAWN_TYPE_GAMEOBJECT) ? HighGuid::GameObject : HighGuid::Unit, info->entry, info->spawnId); + if (time_t linkedTime = GetLinkedRespawnTime(thisGUID)) + { + time_t now = time(NULL); + time_t respawnTime; + if (linkedTime == std::numeric_limits<time_t>::max()) + respawnTime = linkedTime; + else if (sObjectMgr->GetLinkedRespawnGuid(thisGUID) == thisGUID) // never respawn, save "something" in DB + respawnTime = now + WEEK; + else // set us to check again shortly after linked unit + respawnTime = std::max<time_t>(now, linkedTime) + urand(5, 15); + info->respawnTime = respawnTime; + return false; + } + + // now, check if we're part of a pool + if (poolId) + { + // ok, part of a pool - hand off to pool logic to handle this, we're just going to remove the respawn and call it a day + if (info->type == SPAWN_TYPE_GAMEOBJECT) + sPoolMgr->UpdatePool<GameObject>(poolId, info->spawnId); + else if (info->type == SPAWN_TYPE_CREATURE) + sPoolMgr->UpdatePool<Creature>(poolId, info->spawnId); + else + ASSERT(false, "Invalid spawn type %u (spawnid %u) on map %u", uint32(info->type), info->spawnId, GetId()); + info->respawnTime = 0; + return false; + } + + // if we're a creature, see if the script objects to us spawning + if (info->type == SPAWN_TYPE_CREATURE) + { + if (!sScriptMgr->CanSpawn(info->spawnId, info->entry, sObjectMgr->GetCreatureData(info->spawnId), this)) + { // if a script blocks our respawn, schedule next check in a little bit + info->respawnTime = time(NULL) + urand(4, 7); + return false; + } + } + return true; +} + +void Map::DoRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 gridId) +{ + if (!IsGridLoaded(gridId)) // if grid isn't loaded, this will be processed in grid load handler + return; + + switch (type) + { + case SPAWN_TYPE_CREATURE: + { + Creature* obj = new Creature(); + if (!obj->LoadFromDB(spawnId, this, true, true)) + delete obj; + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + GameObject* obj = new GameObject(); + if (!obj->LoadFromDB(spawnId, this, true)) + delete obj; + break; + } + default: + ASSERT(false, "Invalid spawn type %u (spawnid %u) on map %u", uint32(type), spawnId, GetId()); + } +} + +void Map::Respawn(RespawnInfo* info, bool force, SQLTransaction dbTrans) +{ + if (!force && !CheckRespawn(info)) + { + if (info->respawnTime) + SaveRespawnTime(info->type, info->spawnId, info->entry, info->respawnTime, info->zoneId, info->gridId, true, true, dbTrans); + else + RemoveRespawnTime(info); + return; + } + + // remove the actual respawn record first - since this deletes it, we save what we need + SpawnObjectType const type = info->type; + uint32 const gridId = info->gridId; + ObjectGuid::LowType const spawnId = info->spawnId; + RemoveRespawnTime(info); + DoRespawn(type, spawnId, gridId); +} + +void Map::Respawn(RespawnVector& respawnData, bool force, SQLTransaction dbTrans) +{ + SQLTransaction trans = dbTrans ? dbTrans : CharacterDatabase.BeginTransaction(); + for (RespawnInfo* info : respawnData) + Respawn(info, force, trans); + if (!dbTrans) + CharacterDatabase.CommitTransaction(trans); +} + +void Map::AddRespawnInfo(RespawnInfo& info, bool replace) +{ + if (!info.spawnId) + return; + + RespawnInfoMap& bySpawnIdMap = GetRespawnMapForType(info.type); + + auto it = bySpawnIdMap.find(info.spawnId); + if (it != bySpawnIdMap.end()) // spawnid already has a respawn scheduled + { + RespawnInfo* const existing = it->second; + if (replace || info.respawnTime < existing->respawnTime) // delete existing in this case + DeleteRespawnInfo(existing); + else // don't delete existing, instead replace respawn time so caller saves the correct time + { + info.respawnTime = existing->respawnTime; + return; + } + } + + // if we get to this point, we should insert the respawninfo (there either was no prior entry, or it was deleted already) + RespawnInfo * ri = new RespawnInfo(info); + ri->handle = _respawnTimes.push(ri); + bool success = bySpawnIdMap.emplace(ri->spawnId, ri).second; + ASSERT(success, "Insertion of respawn info with id (%u,%u) into spawn id map failed - state desync.", uint32(ri->type), ri->spawnId); +} + +static void PushRespawnInfoFrom(RespawnVector& data, RespawnInfoMap const& map, uint32 zoneId) +{ + for (auto const& pair : map) + if (!zoneId || pair.second->zoneId == zoneId) + data.push_back(pair.second); +} +void Map::GetRespawnInfo(RespawnVector& respawnData, SpawnObjectTypeMask types, uint32 zoneId) const +{ + if (types & SPAWN_TYPEMASK_CREATURE) + PushRespawnInfoFrom(respawnData, _creatureRespawnTimesBySpawnId, zoneId); + if (types & SPAWN_TYPEMASK_GAMEOBJECT) + PushRespawnInfoFrom(respawnData, _gameObjectRespawnTimesBySpawnId, zoneId); +} + +RespawnInfo* Map::GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const +{ + RespawnInfoMap const& map = GetRespawnMapForType(type); + auto it = map.find(spawnId); + if (it == map.end()) + return nullptr; + return it->second; +} + +void Map::DeleteRespawnInfo() // delete everything +{ + for (RespawnInfo* info : _respawnTimes) + delete info; + _respawnTimes.clear(); + _creatureRespawnTimesBySpawnId.clear(); + _gameObjectRespawnTimesBySpawnId.clear(); +} + +void Map::DeleteRespawnInfo(RespawnInfo* info) +{ + // Delete from all relevant containers to ensure consistency + ASSERT(info); + + // spawnid store + size_t const n = GetRespawnMapForType(info->type).erase(info->spawnId); + ASSERT(n == 1, "Respawn stores inconsistent for map %u, spawnid %u (type %u)", GetId(), info->spawnId, uint32(info->type)); + + //respawn heap + _respawnTimes.erase(info->handle); + + // then cleanup the object + delete info; +} + +void Map::RemoveRespawnTime(RespawnInfo* info, bool doRespawn, SQLTransaction dbTrans) +{ + PreparedStatement* stmt; + switch (info->type) + { + case SPAWN_TYPE_CREATURE: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN); + break; + case SPAWN_TYPE_GAMEOBJECT: + stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN); + break; + default: + ASSERT(false, "Invalid respawninfo type %u for spawnid %u map %u", uint32(info->type), info->spawnId, GetId()); + return; + } + stmt->setUInt32(0, info->spawnId); + stmt->setUInt16(1, GetId()); + stmt->setUInt32(2, GetInstanceId()); + CharacterDatabase.ExecuteOrAppend(dbTrans, stmt); + + if (doRespawn) + Respawn(info); + else + DeleteRespawnInfo(info); +} + +void Map::RemoveRespawnTime(RespawnVector& respawnData, bool doRespawn, SQLTransaction dbTrans) +{ + SQLTransaction trans = dbTrans ? dbTrans : CharacterDatabase.BeginTransaction(); + for (RespawnInfo* info : respawnData) + RemoveRespawnTime(info, doRespawn, trans); + if (!dbTrans) + CharacterDatabase.CommitTransaction(trans); +} + +void Map::ProcessRespawns() +{ + time_t now = time(NULL); + while (!_respawnTimes.empty()) + { + RespawnInfo* next = _respawnTimes.top(); + if (now < next->respawnTime) // done for this tick + break; + if (CheckRespawn(next)) // see if we're allowed to respawn + { + // ok, respawn + _respawnTimes.pop(); + GetRespawnMapForType(next->type).erase(next->spawnId); + DoRespawn(next->type, next->spawnId, next->gridId); + delete next; + } + else if (!next->respawnTime) // just remove respawn entry without rescheduling + { + _respawnTimes.pop(); + GetRespawnMapForType(next->type).erase(next->spawnId); + delete next; + } + else // value changed, update heap position + { + ASSERT(now < next->respawnTime); // infinite loop guard + _respawnTimes.decrease(next->handle); + } + } +} + +void Map::ApplyDynamicModeRespawnScaling(WorldObject const* obj, ObjectGuid::LowType spawnId, uint32& respawnDelay, uint32 mode) const +{ + ASSERT(mode == 1); + ASSERT(obj->GetMap() == this); + + if (IsBattlegroundOrArena()) + return; + + SpawnObjectType type; + switch (obj->GetTypeId()) + { + case TYPEID_UNIT: + type = SPAWN_TYPE_CREATURE; + break; + case TYPEID_GAMEOBJECT: + type = SPAWN_TYPE_GAMEOBJECT; + break; + default: + return; + } + + SpawnData const* data = sObjectMgr->GetSpawnData(type, spawnId); + if (!data || !data->spawnGroupData || !(data->spawnGroupData->flags & SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE)) + return; + + auto it = _zonePlayerCountMap.find(obj->GetZoneId()); + if (it == _zonePlayerCountMap.end()) + return; + uint32 const playerCount = it->second; + if (!playerCount) + return; + double const adjustFactor = sWorld->getFloatConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICRATE_CREATURE) / playerCount; + if (adjustFactor >= 1.0) // nothing to do here + return; + uint32 const timeMinimum = sWorld->getIntConfig(type == SPAWN_TYPE_GAMEOBJECT ? CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT : CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE); + if (respawnDelay <= timeMinimum) + return; + + respawnDelay = std::max<uint32>(ceil(respawnDelay * adjustFactor), timeMinimum); +} + +SpawnGroupTemplateData const* Map::GetSpawnGroupData(uint32 groupId) const +{ + SpawnGroupTemplateData const* data = sObjectMgr->GetSpawnGroupData(groupId); + if (data && data->mapId == GetId()) + return data; + return nullptr; +} + +bool Map::SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn, bool force, std::vector<WorldObject*>* spawnedObjects) +{ + SpawnGroupTemplateData const* groupData = GetSpawnGroupData(groupId); + if (!groupData || groupData->flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to spawn non-existing (or system) spawn group %u on map %u. Blocked.", groupId, GetId()); + return false; + } + + for (auto& pair : sObjectMgr->GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + ASSERT(groupData->mapId == data->spawnPoint.GetMapId()); + // Check if there's already an instance spawned + if (!force) + if (WorldObject* obj = GetWorldObjectBySpawnId(data->type, data->spawnId)) + if ((data->type != SPAWN_TYPE_CREATURE) || obj->ToCreature()->IsAlive()) + continue; + + time_t respawnTime = GetRespawnTime(data->type, data->spawnId); + if (respawnTime && respawnTime > time(NULL)) + { + if (!force && !ignoreRespawn) + continue; + + // we need to remove the respawn time, otherwise we'd end up double spawning + RemoveRespawnTime(data->type, data->spawnId, false); + } + + // don't spawn if the grid isn't loaded (will be handled in grid loader) + if (!IsGridLoaded(data->spawnPoint)) + continue; + + // Everything OK, now do the actual (re)spawn + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + Creature* creature = new Creature(); + if (!creature->LoadFromDB(data->spawnId, this, true, force)) + delete creature; + else if (spawnedObjects) + spawnedObjects->push_back(creature); + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + GameObject* gameobject = new GameObject(); + if (!gameobject->LoadFromDB(data->spawnId, this, true)) + delete gameobject; + else if (spawnedObjects) + spawnedObjects->push_back(gameobject); + break; + } + default: + ASSERT(false, "Invalid spawn type %u with spawnId %u", uint32(data->type), data->spawnId); + return false; + } + } + SetSpawnGroupActive(groupId, true); // start processing respawns for the group + return true; +} + +bool Map::SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes) +{ + SpawnGroupTemplateData const* groupData = GetSpawnGroupData(groupId); + if (!groupData || groupData->flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to despawn non-existing (or system) spawn group %u on map %u. Blocked.", groupId, GetId()); + return false; + } + + std::vector<WorldObject*> toUnload; // unload after iterating, otherwise iterator invalidation + for (auto const& pair : sObjectMgr->GetSpawnDataForGroup(groupId)) + { + SpawnData const* data = pair.second; + ASSERT(groupData->mapId == data->spawnPoint.GetMapId()); + if (deleteRespawnTimes) + RemoveRespawnTime(data->type, data->spawnId); + switch (data->type) + { + case SPAWN_TYPE_CREATURE: + { + auto bounds = GetCreatureBySpawnIdStore().equal_range(data->spawnId); + for (auto it = bounds.first; it != bounds.second; ++it) + toUnload.emplace_back(it->second); + break; + } + case SPAWN_TYPE_GAMEOBJECT: + { + auto bounds = GetGameObjectBySpawnIdStore().equal_range(data->spawnId); + for (auto it = bounds.first; it != bounds.second; ++it) + toUnload.emplace_back(it->second); + break; + } + default: + ASSERT(false, "Invalid spawn type %u in spawn data with spawnId %u.", uint32(data->type), data->spawnId); + return false; + } + } + // now do the actual despawning + for (WorldObject* obj : toUnload) + obj->AddObjectToRemoveList(); + SetSpawnGroupActive(groupId, false); // stop processing respawns for the group, too + return true; +} + +void Map::SetSpawnGroupActive(uint32 groupId, bool state) +{ + SpawnGroupTemplateData const* const data = GetSpawnGroupData(groupId); + if (!data || data->flags & SPAWNGROUP_FLAG_SYSTEM) + { + TC_LOG_ERROR("maps", "Tried to set non-existing (or system) spawn group %u to %s on map %u. Blocked.", groupId, state ? "active" : "inactive", GetId()); + return; + } + if (state != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN)) // toggled + _toggledSpawnGroupIds.insert(groupId); + else + _toggledSpawnGroupIds.erase(groupId); +} + +bool Map::IsSpawnGroupActive(uint32 groupId) const +{ + SpawnGroupTemplateData const* const data = GetSpawnGroupData(groupId); + if (!data) + { + TC_LOG_WARN("maps", "Tried to query state of non-existing spawn group %u on map %u.", groupId, GetId()); + return false; + } + if (data->flags & SPAWNGROUP_FLAG_SYSTEM) + return true; + return (_toggledSpawnGroupIds.find(groupId) != _toggledSpawnGroupIds.end()) != !(data->flags & SPAWNGROUP_FLAG_MANUAL_SPAWN); +} + void Map::DelayedUpdate(uint32 t_diff) { for (_transportsUpdateIter = _transports.begin(); _transportsUpdateIter != _transports.end();) @@ -3364,6 +3882,8 @@ void InstanceMap::CreateInstanceData(bool load) } } } + else + i_data->Create(); } /* @@ -3693,6 +4213,34 @@ Creature* Map::GetCreature(ObjectGuid const& guid) return _objectsStore.Find<Creature>(guid); } +Creature* Map::GetCreatureBySpawnId(ObjectGuid::LowType spawnId) const +{ + auto const bounds = GetCreatureBySpawnIdStore().equal_range(spawnId); + if (bounds.first == bounds.second) + return nullptr; + + std::unordered_multimap<uint32, Creature*>::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::CreatureBySpawnIdContainer::value_type const& pair) + { + return pair.second->IsAlive(); + }); + + return creatureItr != bounds.second ? creatureItr->second : bounds.first->second; +} + +GameObject* Map::GetGameObjectBySpawnId(ObjectGuid::LowType spawnId) const +{ + auto const bounds = GetGameObjectBySpawnIdStore().equal_range(spawnId); + if (bounds.first == bounds.second) + return nullptr; + + std::unordered_multimap<uint32, GameObject*>::const_iterator creatureItr = std::find_if(bounds.first, bounds.second, [](Map::GameObjectBySpawnIdContainer::value_type const& pair) + { + return pair.second->isSpawned(); + }); + + return creatureItr != bounds.second ? creatureItr->second : bounds.first->second; +} + GameObject* Map::GetGameObject(ObjectGuid const& guid) { return _objectsStore.Find<GameObject>(guid); @@ -3723,64 +4271,37 @@ void Map::UpdateIteratorBack(Player* player) m_mapRefIter = m_mapRefIter->nocheck_prev(); } -void Map::SaveCreatureRespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime) +void Map::SaveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 entry, time_t respawnTime, uint32 zoneId, uint32 gridId, bool writeDB, bool replace, SQLTransaction dbTrans) { if (!respawnTime) { // Delete only - RemoveCreatureRespawnTime(dbGuid); + RemoveRespawnTime(type, spawnId, false, dbTrans); return; } - _creatureRespawnTimes[dbGuid] = respawnTime; + RespawnInfo ri; + ri.type = type; + ri.spawnId = spawnId; + ri.entry = entry; + ri.respawnTime = respawnTime; + ri.gridId = gridId; + ri.zoneId = zoneId; + AddRespawnInfo(ri, replace); - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_CREATURE_RESPAWN); - stmt->setUInt32(0, dbGuid); - stmt->setUInt64(1, uint64(respawnTime)); - stmt->setUInt16(2, GetId()); - stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); + if (writeDB) + SaveRespawnTimeDB(type, spawnId, ri.respawnTime, dbTrans); // might be different from original respawn time if we didn't replace } -void Map::RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid) +void Map::SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, SQLTransaction dbTrans) { - _creatureRespawnTimes.erase(dbGuid); - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN); - stmt->setUInt32(0, dbGuid); - stmt->setUInt16(1, GetId()); - stmt->setUInt32(2, GetInstanceId()); - CharacterDatabase.Execute(stmt); -} - -void Map::SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime) -{ - if (!respawnTime) - { - // Delete only - RemoveGORespawnTime(dbGuid); - return; - } - - _goRespawnTimes[dbGuid] = respawnTime; - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_REP_GO_RESPAWN); - stmt->setUInt32(0, dbGuid); + // Just here for support of compatibility mode + PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement((type == SPAWN_TYPE_GAMEOBJECT) ? CHAR_REP_GO_RESPAWN : CHAR_REP_CREATURE_RESPAWN); + stmt->setUInt32(0, spawnId); stmt->setUInt64(1, uint64(respawnTime)); stmt->setUInt16(2, GetId()); stmt->setUInt32(3, GetInstanceId()); - CharacterDatabase.Execute(stmt); -} - -void Map::RemoveGORespawnTime(ObjectGuid::LowType dbGuid) -{ - _goRespawnTimes.erase(dbGuid); - - PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_GO_RESPAWN); - stmt->setUInt32(0, dbGuid); - stmt->setUInt16(1, GetId()); - stmt->setUInt32(2, GetInstanceId()); - CharacterDatabase.Execute(stmt); + CharacterDatabase.ExecuteOrAppend(dbTrans, stmt); } void Map::LoadRespawnTimes() @@ -3796,7 +4317,9 @@ void Map::LoadRespawnTimes() ObjectGuid::LowType loguid = fields[0].GetUInt32(); uint64 respawnTime = fields[1].GetUInt64(); - _creatureRespawnTimes[loguid] = time_t(respawnTime); + if (CreatureData const* cdata = sObjectMgr->GetCreatureData(loguid)) + SaveRespawnTime(SPAWN_TYPE_CREATURE, loguid, cdata->id, time_t(respawnTime), GetZoneId(cdata->spawnPoint), Trinity::ComputeGridCoord(cdata->spawnPoint.GetPositionX(), cdata->spawnPoint.GetPositionY()).GetId(), false); + } while (result->NextRow()); } @@ -3811,19 +4334,13 @@ void Map::LoadRespawnTimes() ObjectGuid::LowType loguid = fields[0].GetUInt32(); uint64 respawnTime = fields[1].GetUInt64(); - _goRespawnTimes[loguid] = time_t(respawnTime); + if (GameObjectData const* godata = sObjectMgr->GetGameObjectData(loguid)) + SaveRespawnTime(SPAWN_TYPE_GAMEOBJECT, loguid, godata->id, time_t(respawnTime), GetZoneId(godata->spawnPoint), Trinity::ComputeGridCoord(godata->spawnPoint.GetPositionX(), godata->spawnPoint.GetPositionY()).GetId(), false); + } while (result->NextRow()); } } -void Map::DeleteRespawnTimes() -{ - _creatureRespawnTimes.clear(); - _goRespawnTimes.clear(); - - DeleteRespawnTimesInDB(GetId(), GetInstanceId()); -} - void Map::DeleteRespawnTimesInDB(uint16 mapId, uint32 instanceId) { PreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CREATURE_RESPAWN_BY_INSTANCE); diff --git a/src/server/game/Maps/Map.h b/src/server/game/Maps/Map.h index dd0e43158c1..3d3fcfb528d 100644 --- a/src/server/game/Maps/Map.h +++ b/src/server/game/Maps/Map.h @@ -21,16 +21,18 @@ #include "Define.h" -#include "GridDefines.h" #include "Cell.h" -#include "Timer.h" -#include "SharedDefines.h" +#include "DynamicTree.h" +#include "GridDefines.h" #include "GridRefManager.h" #include "MapRefManager.h" -#include "DynamicTree.h" #include "ObjectGuid.h" #include "Optional.h" - +#include "SharedDefines.h" +#include "SpawnData.h" +#include "Timer.h" +#include "Transaction.h" +#include <boost/heap/fibonacci_heap.hpp> #include <bitset> #include <list> #include <memory> @@ -268,10 +270,41 @@ struct ZoneDynamicInfo #define MAX_FALL_DISTANCE 250000.0f // "unlimited fall" to find VMap ground if it is available, just larger than MAX_HEIGHT - INVALID_HEIGHT #define DEFAULT_HEIGHT_SEARCH 50.0f // default search distance to find height at nearby locations #define MIN_UNLOAD_DELAY 1 // immediate unload +#define MAP_INVALID_ZONE 0xFFFFFFFF typedef std::map<uint32/*leaderDBGUID*/, CreatureGroup*> CreatureGroupHolderType; +struct RespawnInfo; // forward declaration +struct CompareRespawnInfo +{ + bool operator()(RespawnInfo const* a, RespawnInfo const* b) const; +}; typedef std::unordered_map<uint32 /*zoneId*/, ZoneDynamicInfo> ZoneDynamicInfoMap; +typedef boost::heap::fibonacci_heap<RespawnInfo*, boost::heap::compare<CompareRespawnInfo>> RespawnListContainer; +typedef RespawnListContainer::handle_type RespawnListHandle; +typedef std::unordered_map<uint32, RespawnInfo*> RespawnInfoMap; +typedef std::vector<RespawnInfo*> RespawnVector; +struct RespawnInfo +{ + SpawnObjectType type; + ObjectGuid::LowType spawnId; + uint32 entry; + time_t respawnTime; + uint32 gridId; + uint32 zoneId; + RespawnListHandle handle; +}; +inline bool CompareRespawnInfo::operator()(RespawnInfo const* a, RespawnInfo const* b) const +{ + if (a == b) + return false; + if (a->respawnTime != b->respawnTime) + return (a->respawnTime > b->respawnTime); + if (a->spawnId != b->spawnId) + return a->spawnId < b->spawnId; + ASSERT(a->type != b->type, "Duplicate respawn entry for spawnId (%u,%u) found!", a->type, a->spawnId); + return a->type < b->type; +} class TC_GAME_API Map : public GridRefManager<NGridType> { @@ -321,17 +354,19 @@ class TC_GAME_API Map : public GridRefManager<NGridType> GridCoord p = Trinity::ComputeGridCoord(x, y); return !getNGrid(p.x_coord, p.y_coord) || getNGrid(p.x_coord, p.y_coord)->GetGridState() == GRID_STATE_REMOVAL; } + bool IsRemovalGrid(Position const& pos) const { return IsRemovalGrid(pos.GetPositionX(), pos.GetPositionY()); } - bool IsGridLoaded(float x, float y) const - { - return IsGridLoaded(Trinity::ComputeGridCoord(x, y)); - } + bool IsGridLoaded(uint32 gridId) const { return IsGridLoaded(GridCoord(gridId % MAX_NUMBER_OF_GRIDS, gridId / MAX_NUMBER_OF_GRIDS)); } + bool IsGridLoaded(float x, float y) const { return IsGridLoaded(Trinity::ComputeGridCoord(x, y)); } + bool IsGridLoaded(Position const& pos) const { return IsGridLoaded(pos.GetPositionX(), pos.GetPositionY()); } bool GetUnloadLock(GridCoord const& p) const { return getNGrid(p.x_coord, p.y_coord)->getUnloadLock(); } void SetUnloadLock(GridCoord const& p, bool on) { getNGrid(p.x_coord, p.y_coord)->setUnloadExplicitLock(on); } void LoadGrid(float x, float y); void LoadAllCells(); bool UnloadGrid(NGridType& ngrid, bool pForce); + void GridMarkNoUnload(uint32 x, uint32 y); + void GridUnmarkNoUnload(uint32 x, uint32 y); virtual void UnloadAll(); void ResetGridExpiry(NGridType &grid, float factor = 1) const @@ -350,18 +385,16 @@ class TC_GAME_API Map : public GridRefManager<NGridType> Map const* GetParent() const { return m_parentMap; } - // some calls like isInWater should not use vmaps due to processor power - // can return INVALID_HEIGHT if under z+2 z coord not found height - float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; - float GetMinHeight(float x, float y) const; - void GetFullTerrainStatusForPosition(float x, float y, float z, PositionFullTerrainStatus& data, uint8 reqLiquidType = MAP_ALL_LIQUIDS) const; ZLiquidStatus GetLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, LiquidData* data = nullptr) const; bool GetAreaInfo(float x, float y, float z, uint32& mogpflags, int32& adtId, int32& rootId, int32& groupId) const; uint32 GetAreaId(float x, float y, float z, bool *isOutdoors = nullptr) const; + uint32 GetAreaId(Position const& pos) const { return GetAreaId(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } uint32 GetZoneId(float x, float y, float z) const; + uint32 GetZoneId(Position const& pos) const { return GetZoneId(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, float x, float y, float z) const; + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, Position const& pos) const { GetZoneAndAreaId(zoneid, areaid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } bool IsOutdoors(float x, float y, float z) const; @@ -464,6 +497,9 @@ class TC_GAME_API Map : public GridRefManager<NGridType> Corpse* GetCorpse(ObjectGuid const& guid); Creature* GetCreature(ObjectGuid const& guid); GameObject* GetGameObject(ObjectGuid const& guid); + Creature* GetCreatureBySpawnId(ObjectGuid::LowType spawnId) const; + GameObject* GetGameObjectBySpawnId(ObjectGuid::LowType spawnId) const; + WorldObject* GetWorldObjectBySpawnId(SpawnObjectType type, ObjectGuid::LowType spawnId) const { return (type == SPAWN_TYPE_GAMEOBJECT) ? reinterpret_cast<WorldObject*>(GetGameObjectBySpawnId(spawnId)) : reinterpret_cast<WorldObject*>(GetCreatureBySpawnId(spawnId)); } Transport* GetTransport(ObjectGuid const& guid); DynamicObject* GetDynamicObject(ObjectGuid const& guid); Pet* GetPet(ObjectGuid const& guid); @@ -472,9 +508,11 @@ class TC_GAME_API Map : public GridRefManager<NGridType> typedef std::unordered_multimap<ObjectGuid::LowType, Creature*> CreatureBySpawnIdContainer; CreatureBySpawnIdContainer& GetCreatureBySpawnIdStore() { return _creatureBySpawnIdStore; } + CreatureBySpawnIdContainer const& GetCreatureBySpawnIdStore() const { return _creatureBySpawnIdStore; } typedef std::unordered_multimap<ObjectGuid::LowType, GameObject*> GameObjectBySpawnIdContainer; GameObjectBySpawnIdContainer& GetGameObjectBySpawnIdStore() { return _gameobjectBySpawnIdStore; } + GameObjectBySpawnIdContainer const& GetGameObjectBySpawnIdStore() const { return _gameobjectBySpawnIdStore; } std::unordered_set<Corpse*> const* GetCorpsesInCell(uint32 cellId) const { @@ -504,7 +542,11 @@ class TC_GAME_API Map : public GridRefManager<NGridType> BattlegroundMap const* ToBattlegroundMap() const { if (IsBattlegroundOrArena()) return reinterpret_cast<BattlegroundMap const*>(this); return nullptr; } float GetWaterOrGroundLevel(uint32 phasemask, float x, float y, float z, float* ground = nullptr, bool swim = false) const; - float GetHeight(uint32 phasemask, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; + float GetMinHeight(float x, float y) const; + float GetHeight(float x, float y, float z, bool checkVMap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const; + float GetHeight(Position const& pos, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return GetHeight(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist); } + float GetHeight(uint32 phasemask, float x, float y, float z, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return std::max<float>(GetHeight(x, y, z, vmap, maxSearchDist), GetGameObjectFloor(phasemask, x, y, z, maxSearchDist)); } + float GetHeight(uint32 phasemask, Position const& pos, bool vmap = true, float maxSearchDist = DEFAULT_HEIGHT_SEARCH) const { return GetHeight(phasemask, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), vmap, maxSearchDist); } bool isInLineOfSight(float x1, float y1, float z1, float x2, float y2, float z2, uint32 phasemask, LineOfSightChecks checks, VMAP::ModelIgnoreFlags ignoreFlags) const; void Balance() { _dynamicTree.balance(); } void RemoveGameObjectModel(GameObjectModel const& model) { _dynamicTree.remove(model); } @@ -522,28 +564,24 @@ class TC_GAME_API Map : public GridRefManager<NGridType> time_t GetLinkedRespawnTime(ObjectGuid guid) const; time_t GetCreatureRespawnTime(ObjectGuid::LowType dbGuid) const { - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t>::const_iterator itr = _creatureRespawnTimes.find(dbGuid); - if (itr != _creatureRespawnTimes.end()) - return itr->second; - - return time_t(0); + RespawnInfoMap::const_iterator itr = _creatureRespawnTimesBySpawnId.find(dbGuid); + return itr != _creatureRespawnTimesBySpawnId.end() ? itr->second->respawnTime : 0; } time_t GetGORespawnTime(ObjectGuid::LowType dbGuid) const { - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t>::const_iterator itr = _goRespawnTimes.find(dbGuid); - if (itr != _goRespawnTimes.end()) - return itr->second; - - return time_t(0); + RespawnInfoMap::const_iterator itr = _gameObjectRespawnTimesBySpawnId.find(dbGuid); + return itr != _gameObjectRespawnTimesBySpawnId.end() ? itr->second->respawnTime : 0; } - void SaveCreatureRespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime); - void RemoveCreatureRespawnTime(ObjectGuid::LowType dbGuid); - void SaveGORespawnTime(ObjectGuid::LowType dbGuid, time_t respawnTime); - void RemoveGORespawnTime(ObjectGuid::LowType dbGuid); + time_t GetRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId) const { return (type == SPAWN_TYPE_GAMEOBJECT) ? GetGORespawnTime(spawnId) : GetCreatureRespawnTime(spawnId); } + + void UpdatePlayerZoneStats(uint32 oldZone, uint32 newZone); + + void SaveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 entry, time_t respawnTime, uint32 zoneId, uint32 gridId = 0, bool writeDB = true, bool replace = false, SQLTransaction dbTrans = nullptr); + void SaveRespawnTimeDB(SpawnObjectType type, ObjectGuid::LowType spawnId, time_t respawnTime, SQLTransaction dbTrans = nullptr); void LoadRespawnTimes(); - void DeleteRespawnTimes(); + void DeleteRespawnTimes() { DeleteRespawnInfo(); DeleteRespawnTimesInDB(GetId(), GetInstanceId()); } void LoadCorpseData(); void DeleteCorpseData(); @@ -702,6 +740,65 @@ class TC_GAME_API Map : public GridRefManager<NGridType> typedef std::multimap<time_t, ScriptAction> ScriptScheduleMap; ScriptScheduleMap m_scriptSchedule; + public: + void ProcessRespawns(); + void ApplyDynamicModeRespawnScaling(WorldObject const* obj, ObjectGuid::LowType spawnId, uint32& respawnDelay, uint32 mode) const; + + private: + // if return value is true, we can respawn + // if return value is false, reschedule the respawn to new value of info->respawnTime iff nonzero, delete otherwise + // if return value is false and info->respawnTime is nonzero, it is guaranteed to be greater than time(NULL) + bool CheckRespawn(RespawnInfo* info); + void DoRespawn(SpawnObjectType type, ObjectGuid::LowType spawnId, uint32 gridId); + void Respawn(RespawnInfo* info, bool force = false, SQLTransaction dbTrans = nullptr); + void Respawn(RespawnVector& respawnData, bool force = false, SQLTransaction dbTrans = nullptr); + void AddRespawnInfo(RespawnInfo& info, bool replace = false); + void DeleteRespawnInfo(); + void DeleteRespawnInfo(RespawnInfo* info); + void DeleteRespawnInfo(RespawnVector& toDelete) + { + for (RespawnInfo* info : toDelete) + DeleteRespawnInfo(info); + toDelete.clear(); + } + void DeleteRespawnInfo(SpawnObjectTypeMask types, uint32 zoneId = 0) + { + RespawnVector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + DeleteRespawnInfo(v); + } + void DeleteRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + DeleteRespawnInfo(info); + } + + public: + void GetRespawnInfo(RespawnVector& respawnData, SpawnObjectTypeMask types, uint32 zoneId = 0) const; + RespawnInfo* GetRespawnInfo(SpawnObjectType type, ObjectGuid::LowType spawnId) const; + void RemoveRespawnTime(RespawnInfo* info, bool doRespawn = false, SQLTransaction dbTrans = nullptr); + void RemoveRespawnTime(RespawnVector& respawnData, bool doRespawn = false, SQLTransaction dbTrans = nullptr); + void RemoveRespawnTime(SpawnObjectTypeMask types = SPAWN_TYPEMASK_ALL, uint32 zoneId = 0, bool doRespawn = false, SQLTransaction dbTrans = nullptr) + { + RespawnVector v; + GetRespawnInfo(v, types, zoneId); + if (!v.empty()) + RemoveRespawnTime(v, doRespawn, dbTrans); + } + void RemoveRespawnTime(SpawnObjectType type, ObjectGuid::LowType spawnId, bool doRespawn = false, SQLTransaction dbTrans = nullptr) + { + if (RespawnInfo* info = GetRespawnInfo(type, spawnId)) + RemoveRespawnTime(info, doRespawn, dbTrans); + } + + SpawnGroupTemplateData const* GetSpawnGroupData(uint32 groupId) const; + bool SpawnGroupSpawn(uint32 groupId, bool ignoreRespawn = false, bool force = false, std::vector<WorldObject*>* spawnedObjects = nullptr); + bool SpawnGroupDespawn(uint32 groupId, bool deleteRespawnTimes = false); + void SetSpawnGroupActive(uint32 groupId, bool state); + bool IsSpawnGroupActive(uint32 groupId) const; + + private: // Type specific code for add/remove to/from grid template<class T> void AddToGrid(T* object, Cell const& cell); @@ -730,8 +827,15 @@ class TC_GAME_API Map : public GridRefManager<NGridType> m_activeNonPlayers.erase(obj); } - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _creatureRespawnTimes; - std::unordered_map<ObjectGuid::LowType /*dbGUID*/, time_t> _goRespawnTimes; + RespawnListContainer _respawnTimes; + RespawnInfoMap _creatureRespawnTimesBySpawnId; + RespawnInfoMap _gameObjectRespawnTimesBySpawnId; + RespawnInfoMap& GetRespawnMapForType(SpawnObjectType type) { return (type == SPAWN_TYPE_GAMEOBJECT) ? _gameObjectRespawnTimesBySpawnId : _creatureRespawnTimesBySpawnId; } + RespawnInfoMap const& GetRespawnMapForType(SpawnObjectType type) const { return (type == SPAWN_TYPE_GAMEOBJECT) ? _gameObjectRespawnTimesBySpawnId : _creatureRespawnTimesBySpawnId; } + std::unordered_set<uint32> _toggledSpawnGroupIds; + + uint32 _respawnCheckTimer; + std::unordered_map<uint32, uint32> _zonePlayerCountMap; ZoneDynamicInfoMap _zoneDynamicInfo; uint32 _defaultLight; diff --git a/src/server/game/Maps/MapManager.h b/src/server/game/Maps/MapManager.h index e628a7030ae..96147c7ded7 100644 --- a/src/server/game/Maps/MapManager.h +++ b/src/server/game/Maps/MapManager.h @@ -44,16 +44,22 @@ class TC_GAME_API MapManager Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid); return m->GetAreaId(x, y, z); } + uint32 GetAreaId(uint32 mapid, Position const& pos) const { return GetAreaId(mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetAreaId(WorldLocation const& loc) const { return GetAreaId(loc.GetMapId(), loc); } uint32 GetZoneId(uint32 mapid, float x, float y, float z) const { Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid); return m->GetZoneId(x, y, z); } - void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) + uint32 GetZoneId(uint32 mapid, Position const& pos) const { return GetZoneId(mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + uint32 GetZoneId(WorldLocation const& loc) const { return GetZoneId(loc.GetMapId(), loc); } + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, float x, float y, float z) const { Map const* m = const_cast<MapManager*>(this)->CreateBaseMap(mapid); m->GetZoneAndAreaId(zoneid, areaid, x, y, z); } + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, uint32 mapid, Position const& pos) const { GetZoneAndAreaId(zoneid, areaid, mapid, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ()); } + void GetZoneAndAreaId(uint32& zoneid, uint32& areaid, WorldLocation const& loc) const { GetZoneAndAreaId(zoneid, areaid, loc.GetMapId(), loc); } void Initialize(void); void Update(uint32); diff --git a/src/server/game/Maps/MapScripts.cpp b/src/server/game/Maps/MapScripts.cpp index e365a54c594..20ebc1db460 100644 --- a/src/server/game/Maps/MapScripts.cpp +++ b/src/server/game/Maps/MapScripts.cpp @@ -671,7 +671,7 @@ void Map::ScriptsProcess() Unit* uSource = nullptr; Unit* uTarget = nullptr; - // source/target cast spell at target/source (script->datalong2: 0: s->t 1: s->s 2: t->t 3: t->s + // source/target cast spell at target/source (script->datalong2: 0: s->t 1: s->s 2: t->t 3: t->s) switch (step.script->CastSpell.Flags) { case SF_CASTSPELL_SOURCE_TO_TARGET: // source -> target diff --git a/src/server/game/Maps/SpawnData.h b/src/server/game/Maps/SpawnData.h new file mode 100644 index 00000000000..768d1fd26bd --- /dev/null +++ b/src/server/game/Maps/SpawnData.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_SPAWNDATA_H +#define TRINITY_SPAWNDATA_H + +#include "Position.h" + +enum SpawnObjectType +{ + SPAWN_TYPE_CREATURE = 0, + SPAWN_TYPE_GAMEOBJECT = 1, + + SPAWN_TYPE_MAX +}; + +enum SpawnObjectTypeMask +{ + SPAWN_TYPEMASK_CREATURE = (1 << SPAWN_TYPE_CREATURE), + SPAWN_TYPEMASK_GAMEOBJECT = (1 << SPAWN_TYPE_GAMEOBJECT), + + SPAWN_TYPEMASK_ALL = (1 << SPAWN_TYPE_MAX)-1 +}; + +enum SpawnGroupFlags +{ + SPAWNGROUP_FLAG_NONE = 0x00, + SPAWNGROUP_FLAG_SYSTEM = 0x01, + SPAWNGROUP_FLAG_COMPATIBILITY_MODE = 0x02, + SPAWNGROUP_FLAG_MANUAL_SPAWN = 0x04, + SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE = 0x08, + SPAWNGROUP_FLAG_ESCORTQUESTNPC = 0x10, + + SPAWNGROUP_FLAGS_ALL = (SPAWNGROUP_FLAG_SYSTEM | SPAWNGROUP_FLAG_COMPATIBILITY_MODE | SPAWNGROUP_FLAG_MANUAL_SPAWN | SPAWNGROUP_FLAG_DYNAMIC_SPAWN_RATE | SPAWNGROUP_FLAG_ESCORTQUESTNPC) +}; + +struct SpawnGroupTemplateData +{ + uint32 groupId; + std::string name; + uint32 mapId; + SpawnGroupFlags flags; +}; + +struct SpawnData +{ + SpawnObjectType const type; + uint32 spawnId = 0; + uint32 id = 0; // entry in respective _template table + WorldLocation spawnPoint; + uint32 phaseMask = 0; + int32 spawntimesecs = 0; + uint8 spawnMask = 0; + SpawnGroupTemplateData const* spawnGroupData = nullptr; + uint32 scriptId = 0; + bool dbData = true; + + protected: + SpawnData(SpawnObjectType t) : type(t) {} +}; + +#endif diff --git a/src/server/game/Miscellaneous/Formulas.h b/src/server/game/Miscellaneous/Formulas.h index 81e906ed47d..7931860868e 100644 --- a/src/server/game/Miscellaneous/Formulas.h +++ b/src/server/game/Miscellaneous/Formulas.h @@ -158,6 +158,13 @@ namespace Trinity baseGain = 0; } + if (sWorld->getIntConfig(CONFIG_MIN_CREATURE_SCALED_XP_RATIO)) + { + // Use mob level instead of player level to avoid overscaling on gain in a min is enforced + uint32 baseGainMin = (mob_level * 5 + nBaseExp) * sWorld->getIntConfig(CONFIG_MIN_CREATURE_SCALED_XP_RATIO) / 100; + baseGain = std::max(baseGainMin, baseGain); + } + sScriptMgr->OnBaseGainCalculation(baseGain, pl_level, mob_level, content); return baseGain; } diff --git a/src/server/game/Miscellaneous/Language.h b/src/server/game/Miscellaneous/Language.h index d96b33fd968..400685c1c1b 100644 --- a/src/server/game/Miscellaneous/Language.h +++ b/src/server/game/Miscellaneous/Language.h @@ -1147,7 +1147,32 @@ enum TrinityStrings LANG_COMMAND_MUTEHISTORY_EMPTY = 5060, LANG_COMMAND_MUTEHISTORY_OUTPUT = 5061, - // Room for more Trinity strings 5062-9999 + // Scene debugs commands [Master only, not used in 3.3.5] + /*LANG_COMMAND_SCENE_DEBUG_ON = 5062, + LANG_COMMAND_SCENE_DEBUG_OFF = 5063, + LANG_COMMAND_SCENE_DEBUG_PLAY = 5064, + LANG_COMMAND_SCENE_DEBUG_TRIGGER = 5065, + LANG_COMMAND_SCENE_DEBUG_CANCEL = 5066, + LANG_COMMAND_SCENE_DEBUG_COMPLETE = 5067, + LANG_DEBUG_SCENE_OBJECT_LIST = 5068, + LANG_DEBUG_SCENE_OBJECT_DETAIL = 5069, */ + + // Strings added for dynamic_spawning + LANG_SPAWNINFO_GROUP_ID = 5070, + LANG_SPAWNINFO_COMPATIBILITY_MODE = 5071, + LANG_SPAWNINFO_GUIDINFO = 5072, + LANG_SPAWNINFO_SPAWNID_LOCATION = 5073, + LANG_SPAWNINFO_DISTANCEFROMPLAYER = 5074, + LANG_SPAWNGROUP_BADGROUP = 5075, + LANG_SPAWNGROUP_SPAWNCOUNT = 5076, + LANG_LIST_RESPAWNS_RANGE = 5077, + LANG_LIST_RESPAWNS_ZONE = 5078, + LANG_LIST_RESPAWNS_LISTHEADER = 5079, + LANG_LIST_RESPAWNS_OVERDUE = 5080, + LANG_LIST_RESPAWNS_CREATURES = 5081, + LANG_LIST_RESPAWNS_GAMEOBJECTS = 5082, + + // Room for more Trinity strings 5084-6603 // Level requirement notifications LANG_SAY_REQ = 6604, diff --git a/src/server/game/Miscellaneous/SharedDefines.h b/src/server/game/Miscellaneous/SharedDefines.h index 7ea78f14ca4..d6bea21f2ad 100644 --- a/src/server/game/Miscellaneous/SharedDefines.h +++ b/src/server/game/Miscellaneous/SharedDefines.h @@ -1443,7 +1443,7 @@ enum Targets TARGET_DEST_CHANNEL_CASTER = 106, TARGET_UNK_DEST_AREA_UNK_107 = 107, // not enough info - only generic spells avalible TARGET_GAMEOBJECT_CONE = 108, - TARGET_DEST_UNK_110 = 110, // 1 spell + TARGET_UNIT_CONE_ENTRY_110 = 110, // 1 spell TOTAL_SPELL_TARGETS }; diff --git a/src/server/game/Movement/MotionMaster.cpp b/src/server/game/Movement/MotionMaster.cpp index 6e771a29979..3f77e472189 100644 --- a/src/server/game/Movement/MotionMaster.cpp +++ b/src/server/game/Movement/MotionMaster.cpp @@ -17,25 +17,28 @@ */ #include "MotionMaster.h" +#include "ConfusedMovementGenerator.h" #include "Creature.h" #include "CreatureAISelector.h" #include "DBCStores.h" -#include "Log.h" -#include "Map.h" -#include "PathGenerator.h" -#include "ScriptSystem.h" -#include "ConfusedMovementGenerator.h" #include "FleeingMovementGenerator.h" +#include "FormationMovementGenerator.h" #include "HomeMovementGenerator.h" #include "IdleMovementGenerator.h" +#include "Log.h" +#include "Map.h" +#include "MoveSpline.h" +#include "MoveSplineInit.h" +#include "PathGenerator.h" +#include "Player.h" #include "PointMovementGenerator.h" -#include "TargetedMovementGenerator.h" -#include "WaypointMovementGenerator.h" #include "RandomMovementGenerator.h" +#include "ScriptSystem.h" #include "SplineChainMovementGenerator.h" -#include "FormationMovementGenerator.h" -#include "MoveSpline.h" -#include "MoveSplineInit.h" +#include "TargetedMovementGenerator.h" +#include "Unit.h" +#include "WaypointDefines.h" +#include "WaypointMovementGenerator.h" inline MovementGenerator* GetIdleMovementGenerator() { @@ -70,7 +73,7 @@ void MotionMaster::Initialize() // clear ALL movement generators (including default) while (!empty()) { - MovementGenerator *curr = top(); + MovementGenerator* curr = top(); pop(); if (curr) DirectDelete(curr); @@ -119,21 +122,15 @@ void MotionMaster::Clear(bool reset /*= true*/) DirectClean(reset); } -void MotionMaster::ClearExpireList() +void MotionMaster::Clear(MovementSlot slot) { - for (auto itr : _expireList) - DirectDelete(itr); - - _expireList.clear(); - - if (empty()) - Initialize(); - else if (NeedInitTop()) - InitTop(); - else if (_cleanFlag & MMCF_RESET) - top()->Reset(_owner); + if (empty() || slot >= MAX_MOTION_SLOT) + return; - _cleanFlag &= ~MMCF_RESET; + if (_cleanFlag & MMCF_UPDATE) + DelayedClean(slot); + else + DirectClean(slot); } void MotionMaster::MovementExpired(bool reset /*= true*/) @@ -158,27 +155,32 @@ MovementGeneratorType MotionMaster::GetCurrentMovementGeneratorType() const return top()->GetMovementGeneratorType(); } -MovementGeneratorType MotionMaster::GetMotionSlotType(int slot) const +MovementGeneratorType MotionMaster::GetMotionSlotType(MovementSlot slot) const { - if (!_slot[slot]) + if (empty() || slot >= MAX_MOTION_SLOT || !_slot[slot]) return MAX_MOTION_TYPE; - else - return _slot[slot]->GetMovementGeneratorType(); + + return _slot[slot]->GetMovementGeneratorType(); } -MovementGenerator* MotionMaster::GetMotionSlot(int slot) const +MovementGenerator* MotionMaster::GetMotionSlot(MovementSlot slot) const { - ASSERT(slot >= 0); + if (empty() || slot >= MAX_MOTION_SLOT || !_slot[slot]) + return nullptr; + return _slot[slot]; } void MotionMaster::PropagateSpeedChange() { - for (int i = 0; i <= _top; ++i) - { - if (_slot[i]) - _slot[i]->UnitSpeedChanged(); - } + if (empty()) + return; + + MovementGenerator* movement = top(); + if (!movement) + return; + + movement->UnitSpeedChanged(); } bool MotionMaster::GetDestination(float &x, float &y, float &z) @@ -675,16 +677,21 @@ void MotionMaster::MoveDistract(uint32 timer) Mutate(mgen, MOTION_SLOT_CONTROLLED); } -void MotionMaster::MovePath(uint32 path_id, bool repeatable) +void MotionMaster::MovePath(uint32 pathId, bool repeatable) { - if (!path_id) + if (!pathId) return; - Mutate(new WaypointMovementGenerator<Creature>(path_id, repeatable), MOTION_SLOT_IDLE); + Mutate(new WaypointMovementGenerator<Creature>(pathId, repeatable), MOTION_SLOT_IDLE); + + TC_LOG_DEBUG("misc", "%s (GUID: %u) starts moving over path(Id:%u, repeatable: %s).", _owner->GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature", _owner->GetGUID().GetCounter(), pathId, repeatable ? "YES" : "NO"); +} + +void MotionMaster::MovePath(WaypointPath& path, bool repeatable) +{ + Mutate(new WaypointMovementGenerator<Creature>(path, repeatable), MOTION_SLOT_IDLE); - TC_LOG_DEBUG("misc", "%s (GUID: %u) starts moving over path(Id:%u, repeatable: %s).", - _owner->GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature", - _owner->GetGUID().GetCounter(), path_id, repeatable ? "YES" : "NO"); + TC_LOG_DEBUG("misc", "%s (GUID: %u) start moving over path(repeatable: %s)", _owner->GetTypeId() == TYPEID_PLAYER ? "Player" : "Creature", _owner->GetGUID().GetCounter(), repeatable ? "YES" : "NO"); } void MotionMaster::MoveRotate(uint32 time, RotateDirection direction) @@ -731,7 +738,7 @@ void MotionMaster::InitTop() void MotionMaster::Mutate(MovementGenerator *m, MovementSlot slot) { - if (MovementGenerator *curr = _slot[slot]) + if (MovementGenerator* curr = _slot[slot]) { _slot[slot] = nullptr; // in case a new one is generated in this slot during directdelete if (_top == slot && (_cleanFlag & MMCF_UPDATE)) @@ -758,9 +765,10 @@ void MotionMaster::DirectClean(bool reset) { while (size() > 1) { - MovementGenerator *curr = top(); + MovementGenerator* curr = top(); pop(); - if (curr) DirectDelete(curr); + if (curr) + DirectDelete(curr); } if (empty()) @@ -776,18 +784,47 @@ void MotionMaster::DelayedClean() { while (size() > 1) { - MovementGenerator *curr = top(); + MovementGenerator* curr = top(); pop(); if (curr) DelayedDelete(curr); } } +void MotionMaster::DirectClean(MovementSlot slot) +{ + if (MovementGenerator* motion = GetMotionSlot(slot)) + { + _slot[slot] = nullptr; + DirectDelete(motion); + } + + while (!empty() && !top()) + --_top; + + if (empty()) + Initialize(); + else if (NeedInitTop()) + InitTop(); +} + +void MotionMaster::DelayedClean(MovementSlot slot) +{ + if (MovementGenerator* motion = GetMotionSlot(slot)) + { + _slot[slot] = nullptr; + DelayedDelete(motion); + } + + while (!empty() && !top()) + --_top; +} + void MotionMaster::DirectExpire(bool reset) { if (size() > 1) { - MovementGenerator *curr = top(); + MovementGenerator* curr = top(); pop(); DirectDelete(curr); } @@ -807,7 +844,7 @@ void MotionMaster::DelayedExpire() { if (size() > 1) { - MovementGenerator *curr = top(); + MovementGenerator* curr = top(); pop(); DelayedDelete(curr); } @@ -826,9 +863,26 @@ void MotionMaster::DirectDelete(MovementGenerator* curr) void MotionMaster::DelayedDelete(MovementGenerator* curr) { - TC_LOG_FATAL("misc", "Unit (Entry %u) is trying to delete its updating Movement Generator (Type %u)!", _owner->GetEntry(), curr->GetMovementGeneratorType()); + TC_LOG_DEBUG("misc", "MotionMaster::DelayedDelete: unit (%u) delayed deleting movement generator (type %u)", _owner->GetEntry(), curr->GetMovementGeneratorType()); if (IsStatic(curr)) return; _expireList.push_back(curr); } + +void MotionMaster::ClearExpireList() +{ + for (auto itr : _expireList) + DirectDelete(itr); + + _expireList.clear(); + + if (empty()) + Initialize(); + else if (NeedInitTop()) + InitTop(); + else if (_cleanFlag & MMCF_RESET) + top()->Reset(_owner); + + _cleanFlag &= ~MMCF_RESET; +} diff --git a/src/server/game/Movement/MotionMaster.h b/src/server/game/Movement/MotionMaster.h index 4e20f9ab814..d132d8b30d0 100644 --- a/src/server/game/Movement/MotionMaster.h +++ b/src/server/game/Movement/MotionMaster.h @@ -31,6 +31,7 @@ class Unit; class PathGenerator; struct SplineChainLink; struct SplineChainResumeInfo; +struct WaypointPath; // Creature Entry ID used for waypoints show, visible only for GMs #define VISUAL_WAYPOINT 1 @@ -61,9 +62,9 @@ enum MovementGeneratorType : uint8 MAX_MOTION_TYPE // limit }; -enum MovementSlot +enum MovementSlot : uint8 { - MOTION_SLOT_IDLE, + MOTION_SLOT_IDLE = 0, MOTION_SLOT_ACTIVE, MOTION_SLOT_CONTROLLED, MAX_MOTION_SLOT @@ -105,11 +106,12 @@ class TC_GAME_API MotionMaster void UpdateMotion(uint32 diff); void Clear(bool reset = true); + void Clear(MovementSlot slot); void MovementExpired(bool reset = true); MovementGeneratorType GetCurrentMovementGeneratorType() const; - MovementGeneratorType GetMotionSlotType(int slot) const; - MovementGenerator* GetMotionSlot(int slot) const; + MovementGeneratorType GetMotionSlotType(MovementSlot slot) const; + MovementGenerator* GetMotionSlot(MovementSlot slot) const; void PropagateSpeedChange(); @@ -159,7 +161,8 @@ class TC_GAME_API MotionMaster void MoveSeekAssistanceDistract(uint32 timer); void MoveTaxiFlight(uint32 path, uint32 pathnode); void MoveDistract(uint32 time); - void MovePath(uint32 path_id, bool repeatable); + void MovePath(uint32 pathId, bool repeatable); + void MovePath(WaypointPath& path, bool repeatable); void MoveRotate(uint32 time, RotateDirection direction); void MoveFormation(uint32 id, Position destination, uint32 moveType, bool forceRun = false, bool forceOrientation = false); @@ -172,10 +175,12 @@ class TC_GAME_API MotionMaster bool NeedInitTop() const; void InitTop(); - void Mutate(MovementGenerator *m, MovementSlot slot); + void Mutate(MovementGenerator* m, MovementSlot slot); void DirectClean(bool reset); void DelayedClean(); + void DirectClean(MovementSlot slot); + void DelayedClean(MovementSlot slot); void DirectExpire(bool reset); void DelayedExpire(); void DirectDelete(MovementGenerator* curr); diff --git a/src/server/game/Movement/MovementGenerator.h b/src/server/game/Movement/MovementGenerator.h index 8f75e3a2361..39b2a16cc8b 100755 --- a/src/server/game/Movement/MovementGenerator.h +++ b/src/server/game/Movement/MovementGenerator.h @@ -36,10 +36,11 @@ class TC_GAME_API MovementGenerator virtual void Finalize(Unit*) = 0; virtual void Reset(Unit*) = 0; virtual bool Update(Unit*, uint32 diff) = 0; - virtual MovementGeneratorType GetMovementGeneratorType() const = 0; virtual void UnitSpeedChanged() { } + virtual void Pause(uint32/* timer = 0*/) { } // timer in ms + virtual void Resume(uint32/* overrideTimer = 0*/) { } // timer in ms // used by Evade code for select point to evade with expected restart default movement virtual bool GetResetPosition(Unit*, float& /*x*/, float& /*y*/, float& /*z*/) { return false; } diff --git a/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp index 498cdf04876..133de699e84 100644 --- a/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/HomeMovementGenerator.cpp @@ -83,11 +83,7 @@ void HomeMovementGenerator<Creature>::DoFinalize(Creature* owner) owner->SetWalk(true); owner->LoadCreaturesAddon(); owner->AI()->JustReachedHome(); - if (owner->isRegeneratingHealth()) - { - owner->SetFullHealth(); - owner->SetPower(POWER_MANA, owner->GetMaxPower(POWER_MANA)); - } + owner->SetSpawnHealth(); } } diff --git a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp index a952421833d..7da90e2d515 100755 --- a/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/PointMovementGenerator.cpp @@ -18,7 +18,6 @@ #include "CreatureAI.h" #include "Creature.h" -#include "CreatureGroups.h" #include "Player.h" #include "MoveSplineInit.h" #include "MoveSpline.h" @@ -55,8 +54,7 @@ void PointMovementGenerator<T>::DoInitialize(T* owner) // Call for creature group update if (Creature* creature = owner->ToCreature()) - if (creature->GetFormation() && creature->GetFormation()->getLeader() == creature) - creature->GetFormation()->LeaderMoveTo(Position(_x, _y, _z), _movementId); + creature->SignalFormationMovement(Position(_x, _y, _z), _movementId); } template<class T> @@ -90,8 +88,7 @@ bool PointMovementGenerator<T>::DoUpdate(T* owner, uint32 /*diff*/) // Call for creature group update if (Creature* creature = owner->ToCreature()) - if (creature->GetFormation() && creature->GetFormation()->getLeader() == creature) - creature->GetFormation()->LeaderMoveTo(Position(_x, _y, _z), _movementId); + creature->SignalFormationMovement(Position(_x, _y, _z), _movementId); } return !owner->movespline->Finalized(); diff --git a/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp index 0a1972c0972..b2a9e5b83b9 100644 --- a/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/RandomMovementGenerator.cpp @@ -18,7 +18,6 @@ #include "RandomMovementGenerator.h" #include "Creature.h" -#include "CreatureGroups.h" #include "Map.h" #include "MoveSplineInit.h" #include "MoveSpline.h" @@ -116,8 +115,7 @@ void RandomMovementGenerator<Creature>::SetRandomLocation(Creature* owner) _timer.Reset(traveltime + resetTimer); // Call for creature group update - if (owner->GetFormation() && owner->GetFormation()->getLeader() == owner) - owner->GetFormation()->LeaderMoveTo(position); + owner->SignalFormationMovement(position); } template<class T> diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp index f292c0a8091..74a8588163c 100755 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.cpp @@ -17,99 +17,135 @@ */ #include "WaypointMovementGenerator.h" +#include "Creature.h" #include "CreatureAI.h" -#include "CreatureGroups.h" #include "Log.h" #include "MapManager.h" #include "MoveSpline.h" #include "MoveSplineInit.h" #include "ObjectMgr.h" +#include "Player.h" #include "Transport.h" +#include "WaypointDefines.h" +#include "WaypointManager.h" #include "World.h" +WaypointMovementGenerator<Creature>::WaypointMovementGenerator(uint32 pathId, bool repeating) : _nextMoveTime(0), _recalculateSpeed(false), _isArrivalDone(false), _pathId(pathId), + _repeating(repeating), _loadedFromDB(true), _stalled(false), _done(false) +{ +} + +WaypointMovementGenerator<Creature>::WaypointMovementGenerator(WaypointPath& path, bool repeating) : _nextMoveTime(0), _recalculateSpeed(false), _isArrivalDone(false), _pathId(0), + _repeating(repeating), _loadedFromDB(false), _stalled(false), _done(false) +{ + _path = &path; +} + void WaypointMovementGenerator<Creature>::LoadPath(Creature* creature) { - if (!path_id) - path_id = creature->GetWaypointPath(); + if (_loadedFromDB) + { + if (!_pathId) + _pathId = creature->GetWaypointPath(); - i_path = sWaypointMgr->GetPath(path_id); + _path = sWaypointMgr->GetPath(_pathId); + } - if (!i_path) + if (!_path) { // No path id found for entry - TC_LOG_ERROR("sql.sql", "WaypointMovementGenerator::LoadPath: creature %s (Entry: %u GUID: %u DB GUID: %u) doesn't have waypoint path id: %u", creature->GetName().c_str(), creature->GetEntry(), creature->GetGUID().GetCounter(), creature->GetSpawnId(), path_id); + TC_LOG_ERROR("sql.sql", "WaypointMovementGenerator::LoadPath: creature %s (Entry: %u GUID: %u DB GUID: %u) doesn't have waypoint path id: %u", creature->GetName().c_str(), creature->GetEntry(), creature->GetGUID().GetCounter(), creature->GetSpawnId(), _pathId); return; } - StartMoveNow(creature); + _nextMoveTime.Reset(1000); + + // inform AI + if (creature->AI()) + creature->AI()->WaypointPathStarted(1, _path->id); } void WaypointMovementGenerator<Creature>::DoInitialize(Creature* creature) { + _done = false; LoadPath(creature); - creature->AddUnitState(UNIT_STATE_ROAMING|UNIT_STATE_ROAMING_MOVE); } void WaypointMovementGenerator<Creature>::DoFinalize(Creature* creature) { - creature->ClearUnitState(UNIT_STATE_ROAMING|UNIT_STATE_ROAMING_MOVE); + creature->ClearUnitState(UNIT_STATE_ROAMING | UNIT_STATE_ROAMING_MOVE); creature->SetWalk(false); } void WaypointMovementGenerator<Creature>::DoReset(Creature* creature) { - creature->AddUnitState(UNIT_STATE_ROAMING|UNIT_STATE_ROAMING_MOVE); - StartMoveNow(creature); + if (!_done && CanMove(creature)) + StartMoveNow(creature); + else if (_done) + { + // mimic IdleMovementGenerator + if (!creature->IsStopped()) + creature->StopMoving(); + } } void WaypointMovementGenerator<Creature>::OnArrived(Creature* creature) { - if (!i_path || i_path->empty()) + if (!_path || _path->nodes.empty()) return; - if (m_isArrivalDone) - return; - - m_isArrivalDone = true; - if (i_path->at(i_currentNode)->event_id && urand(0, 99) < i_path->at(i_currentNode)->event_chance) + ASSERT(_currentNode < _path->nodes.size(), "WaypointMovementGenerator::OnArrived: tried to reference a node id (%u) which is not included in path (%u)", _currentNode, _path->id); + WaypointNode const &waypoint = _path->nodes.at(_currentNode); + if (waypoint.delay) { - TC_LOG_DEBUG("maps.script", "Creature movement start script %u at point %u for %s.", i_path->at(i_currentNode)->event_id, i_currentNode, creature->GetGUID().ToString().c_str()); creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); - creature->GetMap()->ScriptsStart(sWaypointScripts, i_path->at(i_currentNode)->event_id, creature, nullptr); + _nextMoveTime.Reset(waypoint.delay); } - // Inform script - MovementInform(creature); - creature->UpdateWaypointID(i_currentNode); - - if (i_path->at(i_currentNode)->delay) + if (waypoint.eventId && urand(0, 99) < waypoint.eventChance) { + TC_LOG_DEBUG("maps.script", "Creature movement start script %u at point %u for %s.", waypoint.eventId, _currentNode, creature->GetGUID().ToString().c_str()); creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); - Stop(i_path->at(i_currentNode)->delay); + creature->GetMap()->ScriptsStart(sWaypointScripts, waypoint.eventId, creature, nullptr); + } + + // inform AI + if (creature->AI()) + { + creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, _currentNode); + creature->AI()->WaypointReached(waypoint.id, _path->id); } + + creature->UpdateCurrentWaypointInfo(waypoint.id, _path->id); } bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature) { - if (!i_path || i_path->empty()) - return false; + if (!creature || !creature->IsAlive()) + return true; - // Dont allow dead creatures to move - if (!creature->IsAlive()) - return false; + if (_done || !_path || _path->nodes.empty()) + return true; - if (Stopped()) + // if the owner is the leader of its formation, check members status + if (creature->IsFormationLeader() && !creature->IsFormationLeaderMoveAllowed()) + { + _nextMoveTime.Reset(1000); return true; + } - bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && creature->GetTransGUID(); + bool transportPath = creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) && !creature->GetTransGUID().IsEmpty(); - if (m_isArrivalDone) + if (_isArrivalDone) { - if ((i_currentNode == i_path->size() - 1) && !repeating) // If that's our last waypoint + ASSERT(_currentNode < _path->nodes.size(), "WaypointMovementGenerator::StartMove: tried to reference a node id (%u) which is not included in path (%u)", _currentNode, _path->id); + WaypointNode const &waypoint = _path->nodes.at(_currentNode); + + if ((_currentNode == _path->nodes.size() - 1) && !_repeating) // If that's our last waypoint { - float x = i_path->at(i_currentNode)->x; - float y = i_path->at(i_currentNode)->y; - float z = i_path->at(i_currentNode)->z; + float x = waypoint.x; + float y = waypoint.y; + float z = waypoint.z; float o = creature->GetOrientation(); if (!transportPath) @@ -127,21 +163,31 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature) transportPath = false; // else if (vehicle) - this should never happen, vehicle offsets are const } + _done = true; + creature->UpdateCurrentWaypointInfo(0, 0); - creature->GetMotionMaster()->Initialize(); - return false; + // inform AI + if (creature->AI()) + creature->AI()->WaypointPathEnded(waypoint.id, _path->id); + return true; } - i_currentNode = (i_currentNode+1) % i_path->size(); + _currentNode = (_currentNode + 1) % _path->nodes.size(); + + // inform AI + if (creature->AI()) + creature->AI()->WaypointStarted(waypoint.id, _path->id); } - WaypointData const* node = i_path->at(i_currentNode); + ASSERT(_currentNode < _path->nodes.size(), "WaypointMovementGenerator::StartMove: tried to reference a node id (%u) which is not included in path (%u)", _currentNode, _path->id); + WaypointNode const &waypoint = _path->nodes.at(_currentNode); + Position formationDest(waypoint.x, waypoint.y, waypoint.z, (waypoint.orientation && waypoint.delay) ? waypoint.orientation : 0.0f); - m_isArrivalDone = false; + _isArrivalDone = false; + _recalculateSpeed = false; creature->AddUnitState(UNIT_STATE_ROAMING_MOVE); - Position formationDest(node->x, node->y, node->z, (node->orientation && node->delay) ? node->orientation : 0.0f); Movement::MoveSplineInit init(creature); //! If creature is on transport, we assume waypoints set in DB are already transport offsets @@ -158,13 +204,13 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature) //! Do not use formationDest here, MoveTo requires transport offsets due to DisableTransportPathTransformations() call //! but formationDest contains global coordinates - init.MoveTo(node->x, node->y, node->z); + init.MoveTo(waypoint.x, waypoint.y, waypoint.z); //! Accepts angles such as 0.00001 and -0.00001, 0 must be ignored, default value in waypoint table - if (node->orientation && node->delay) - init.SetFacing(node->orientation); + if (waypoint.orientation && waypoint.delay) + init.SetFacing(waypoint.orientation); - switch (node->move_type) + switch (waypoint.moveType) { case WAYPOINT_MOVE_TYPE_LAND: init.SetAnimation(Movement::ToGround); @@ -178,87 +224,135 @@ bool WaypointMovementGenerator<Creature>::StartMove(Creature* creature) case WAYPOINT_MOVE_TYPE_WALK: init.SetWalk(true); break; + default: + break; } init.Launch(); - // Call for creature group update - if (creature->GetFormation() && creature->GetFormation()->getLeader() == creature) - creature->GetFormation()->LeaderMoveTo(formationDest, node->id, node->move_type, (node->orientation && node->delay) ? true : false); + // inform formation + creature->SignalFormationMovement(formationDest, waypoint.id, waypoint.moveType, (waypoint.orientation && waypoint.delay) ? true : false); return true; } bool WaypointMovementGenerator<Creature>::DoUpdate(Creature* creature, uint32 diff) { - // Waypoint movement can be switched on/off - // This is quite handy for escort quests and other stuff - if (creature->HasUnitState(UNIT_STATE_NOT_MOVE)) + if (!creature || !creature->IsAlive()) + return true; + + if (_done || !_path || _path->nodes.empty()) + return true; + + if (_stalled || creature->HasUnitState(UNIT_STATE_NOT_MOVE) || creature->IsMovementPreventedByCasting()) { - creature->ClearUnitState(UNIT_STATE_ROAMING_MOVE); + creature->StopMoving(); return true; } - // prevent a crash at empty waypoint path. - if (!i_path || i_path->empty()) - return false; - if (Stopped()) + if (!_nextMoveTime.Passed()) { - if (CanMove(diff)) - return StartMove(creature); + if (creature->movespline->Finalized()) + { + _nextMoveTime.Update(diff); + if (_nextMoveTime.Passed()) + return StartMoveNow(creature); + } } else { - // Set home position at place on waypoint movement. - if (!creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) || !creature->GetTransGUID()) - creature->SetHomePosition(creature->GetPosition()); - - if (creature->IsStopped()) - Stop(sWorld->getIntConfig(CONFIG_CREATURE_STOP_FOR_PLAYER)); - else if (creature->movespline->Finalized()) + if (creature->movespline->Finalized()) { OnArrived(creature); - return StartMove(creature); + _isArrivalDone = true; + + if (_nextMoveTime.Passed()) + return StartMove(creature); + } + else + { + // Set home position at place on waypoint movement. + if (!creature->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT) || creature->GetTransGUID().IsEmpty()) + creature->SetHomePosition(creature->GetPosition()); + + if (_recalculateSpeed) + { + if (_nextMoveTime.Passed()) + StartMove(creature); + } } } - return true; - } + return true; +} void WaypointMovementGenerator<Creature>::MovementInform(Creature* creature) { if (creature->AI()) - creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, i_currentNode); + creature->AI()->MovementInform(WAYPOINT_MOTION_TYPE, _currentNode); } bool WaypointMovementGenerator<Creature>::GetResetPos(Creature*, float& x, float& y, float& z) { // prevent a crash at empty waypoint path. - if (!i_path || i_path->empty()) + if (!_path || _path->nodes.empty()) return false; - WaypointData const* node = i_path->at(i_currentNode); - x = node->x; y = node->y; z = node->z; + ASSERT(_currentNode < _path->nodes.size(), "WaypointMovementGenerator::GetResetPos: tried to reference a node id (%u) which is not included in path (%u)", _currentNode, _path->id); + WaypointNode const &waypoint = _path->nodes.at(_currentNode); + + x = waypoint.x; + y = waypoint.y; + z = waypoint.z; return true; } +void WaypointMovementGenerator<Creature>::Pause(uint32 timer/* = 0*/) +{ + _stalled = timer ? false : true; + _nextMoveTime.Reset(timer ? timer : 1); +} + +void WaypointMovementGenerator<Creature>::Resume(uint32 overrideTimer/* = 0*/) +{ + _stalled = false; + if (overrideTimer) + _nextMoveTime.Reset(overrideTimer); +} + +bool WaypointMovementGenerator<Creature>::CanMove(Creature* creature) +{ + return _nextMoveTime.Passed() && !creature->HasUnitState(UNIT_STATE_NOT_MOVE) && !creature->IsMovementPreventedByCasting(); +} //----------------------------------------------------// +#define FLIGHT_TRAVEL_UPDATE 100 +#define TIMEDIFF_NEXT_WP 250 +#define SKIP_SPLINE_POINT_DISTANCE_SQ (40.f * 40.f) +#define PLAYER_FLIGHT_SPEED 32.0f + +FlightPathMovementGenerator::FlightPathMovementGenerator(uint32 startNode) +{ + _currentNode = startNode; + _endGridX = 0.0f; + _endGridY = 0.0f; + _endMapId = 0; + _preloadTargetNode = 0; +} + uint32 FlightPathMovementGenerator::GetPathAtMapEnd() const { - if (i_currentNode >= i_path.size()) - return i_path.size(); + if (_currentNode >= _path.size()) + return _path.size(); - uint32 curMapId = i_path[i_currentNode]->MapID; - for (uint32 i = i_currentNode; i < i_path.size(); ++i) - if (i_path[i]->MapID != curMapId) - return i; + uint32 curMapId = _path[_currentNode]->MapID; + for (uint32 itr = _currentNode; itr < _path.size(); ++itr) + if (_path[itr]->MapID != curMapId) + return itr; - return i_path.size(); + return _path.size(); } -#define SKIP_SPLINE_POINT_DISTANCE_SQ (40.0f * 40.0f) - bool IsNodeIncludedInShortenedPath(TaxiPathNodeEntry const* p1, TaxiPathNodeEntry const* p2) { return p1->MapID != p2->MapID || std::pow(p1->LocX - p2->LocX, 2) + std::pow(p1->LocY - p2->LocY, 2) > SKIP_SPLINE_POINT_DISTANCE_SQ; @@ -268,6 +362,7 @@ void FlightPathMovementGenerator::LoadPath(Player* player) { _pointsForPathSwitch.clear(); std::deque<uint32> const& taxi = player->m_taxi.GetPath(); + float discount = player->GetReputationPriceDiscount(player->m_taxi.GetFlightMasterFactionTemplate()); for (uint32 src = 0, dst = 1; dst < taxi.size(); src = dst++) { uint32 path, cost; @@ -283,24 +378,24 @@ void FlightPathMovementGenerator::LoadPath(Player* player) bool passedPreviousSegmentProximityCheck = false; for (uint32 i = 0; i < nodes.size(); ++i) { - if (passedPreviousSegmentProximityCheck || !src || i_path.empty() || IsNodeIncludedInShortenedPath(i_path[i_path.size() - 1], nodes[i])) + if (passedPreviousSegmentProximityCheck || !src || _path.empty() || IsNodeIncludedInShortenedPath(_path[_path.size() - 1], nodes[i])) { if ((!src || (IsNodeIncludedInShortenedPath(start, nodes[i]) && i >= 2)) && (dst == taxi.size() - 1 || (IsNodeIncludedInShortenedPath(end, nodes[i]) && i < nodes.size() - 1))) { passedPreviousSegmentProximityCheck = true; - i_path.push_back(nodes[i]); + _path.push_back(nodes[i]); } } else { - i_path.pop_back(); + _path.pop_back(); --_pointsForPathSwitch.back().PathIndex; } } } - _pointsForPathSwitch.push_back({ uint32(i_path.size() - 1), int32(cost) }); + _pointsForPathSwitch.push_back({ uint32(_path.size() - 1), int32(ceil(cost * discount)) }); } } @@ -331,8 +426,6 @@ void FlightPathMovementGenerator::DoFinalize(Player* player) player->RemoveFlag(PLAYER_FLAGS, PLAYER_FLAGS_TAXI_BENCHMARK); } -#define PLAYER_FLIGHT_SPEED 32.0f - void FlightPathMovementGenerator::DoReset(Player* player) { player->getHostileRefManager().setOnlineOfflineState(false); @@ -343,7 +436,7 @@ void FlightPathMovementGenerator::DoReset(Player* player) uint32 end = GetPathAtMapEnd(); for (uint32 i = GetCurrentNode(); i != end; ++i) { - G3D::Vector3 vertice(i_path[i]->LocX, i_path[i]->LocY, i_path[i]->LocZ); + G3D::Vector3 vertice(_path[i]->LocX, _path[i]->LocY, _path[i]->LocZ); init.Path().push_back(vertice); } init.SetFirstPointId(GetCurrentNode()); @@ -355,13 +448,13 @@ void FlightPathMovementGenerator::DoReset(Player* player) bool FlightPathMovementGenerator::DoUpdate(Player* player, uint32 /*diff*/) { uint32 pointId = (uint32)player->movespline->currentPathIdx(); - if (pointId > i_currentNode) + if (pointId > _currentNode) { bool departureEvent = true; do { - DoEventIfAny(player, i_path[i_currentNode], departureEvent); - while (!_pointsForPathSwitch.empty() && _pointsForPathSwitch.front().PathIndex <= i_currentNode) + DoEventIfAny(player, _path[_currentNode], departureEvent); + while (!_pointsForPathSwitch.empty() && _pointsForPathSwitch.front().PathIndex <= _currentNode) { _pointsForPathSwitch.pop_front(); player->m_taxi.NextTaxiDestination(); @@ -372,31 +465,31 @@ bool FlightPathMovementGenerator::DoUpdate(Player* player, uint32 /*diff*/) } } - if (pointId == i_currentNode) + if (pointId == _currentNode) break; - if (i_currentNode == _preloadTargetNode) + if (_currentNode == _preloadTargetNode) PreloadEndGrid(); - i_currentNode += (uint32)departureEvent; + _currentNode += departureEvent ? 1 : 0; departureEvent = !departureEvent; } while (true); } - return i_currentNode < (i_path.size() - 1); + return _currentNode < (_path.size() - 1); } void FlightPathMovementGenerator::SetCurrentNodeAfterTeleport() { - if (i_path.empty() || i_currentNode >= i_path.size()) + if (_path.empty() || _currentNode >= _path.size()) return; - uint32 map0 = i_path[i_currentNode]->MapID; - for (size_t i = i_currentNode + 1; i < i_path.size(); ++i) + uint32 map0 = _path[_currentNode]->MapID; + for (size_t i = _currentNode + 1; i < _path.size(); ++i) { - if (i_path[i]->MapID != map0) + if (_path[i]->MapID != map0) { - i_currentNode = i; + _currentNode = i; return; } } @@ -413,7 +506,7 @@ void FlightPathMovementGenerator::DoEventIfAny(Player* player, TaxiPathNodeEntry bool FlightPathMovementGenerator::GetResetPos(Player*, float& x, float& y, float& z) { - TaxiPathNodeEntry const* node = i_path[i_currentNode]; + TaxiPathNodeEntry const* node = _path[_currentNode]; x = node->LocX; y = node->LocY; z = node->LocZ; @@ -424,11 +517,11 @@ void FlightPathMovementGenerator::InitEndGridInfo() { /*! Storage to preload flightmaster grid at end of flight. For multi-stop flights, this will be reinitialized for each flightmaster at the end of each spline (or stop) in the flight. */ - uint32 nodeCount = i_path.size(); //! Number of nodes in path. - _endMapId = i_path[nodeCount - 1]->MapID; //! MapId of last node + uint32 nodeCount = _path.size(); //! Number of nodes in path. + _endMapId = _path[nodeCount - 1]->MapID; //! MapId of last node _preloadTargetNode = nodeCount - 3; - _endGridX = i_path[nodeCount - 1]->LocX; - _endGridY = i_path[nodeCount - 1]->LocY; + _endGridX = _path[nodeCount - 1]->LocX; + _endGridY = _path[nodeCount - 1]->LocY; } void FlightPathMovementGenerator::PreloadEndGrid() @@ -439,7 +532,7 @@ void FlightPathMovementGenerator::PreloadEndGrid() // Load the grid if (endMap) { - TC_LOG_DEBUG("misc", "Preloading rid (%f, %f) for map %u at node index %u/%u", _endGridX, _endGridY, _endMapId, _preloadTargetNode, (uint32)(i_path.size() - 1)); + TC_LOG_DEBUG("misc", "Preloading rid (%f, %f) for map %u at node index %u/%u", _endGridX, _endGridY, _endMapId, _preloadTargetNode, (uint32)(_path.size() - 1)); endMap->LoadGrid(_endGridX, _endGridY); } else diff --git a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h index 7426a166ea1..e447bad7bb8 100755 --- a/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h +++ b/src/server/game/Movement/MovementGenerators/WaypointMovementGenerator.h @@ -19,103 +19,91 @@ #ifndef TRINITY_WAYPOINTMOVEMENTGENERATOR_H #define TRINITY_WAYPOINTMOVEMENTGENERATOR_H -/** @page PathMovementGenerator is used to generate movements +/** + * @page PathMovementGenerator is used to generate movements * of waypoints and flight paths. Each serves the purpose * of generate activities so that it generates updated * packets for the players. */ -#include "MovementGenerator.h" -#include "Creature.h" #include "DBCStructure.h" -#include "Player.h" +#include "MovementGenerator.h" #include "Timer.h" -#include "WaypointManager.h" -#define FLIGHT_TRAVEL_UPDATE 100 -#define TIMEDIFF_NEXT_WP 250 +class Creature; +class Player; +struct WaypointPath; -template<class T, class P> +template<class Entity, class BasePath> class PathMovementBase { public: - PathMovementBase() : i_path(), i_currentNode(0) { } + PathMovementBase() : _path(), _currentNode(0) { } virtual ~PathMovementBase() { }; - uint32 GetCurrentNode() const { return i_currentNode; } + uint32 GetCurrentNode() const { return _currentNode; } protected: - P i_path; - uint32 i_currentNode; + BasePath _path; + uint32 _currentNode; }; template<class T> class WaypointMovementGenerator; template<> -class WaypointMovementGenerator<Creature> : public MovementGeneratorMedium< Creature, WaypointMovementGenerator<Creature> >, - public PathMovementBase<Creature, WaypointPath const*> +class WaypointMovementGenerator<Creature> : public MovementGeneratorMedium<Creature, WaypointMovementGenerator<Creature>>, public PathMovementBase<Creature, WaypointPath const*> { public: - WaypointMovementGenerator(uint32 _path_id = 0, bool _repeating = true) - : i_nextMoveTime(0), m_isArrivalDone(false), path_id(_path_id), repeating(_repeating) { } - ~WaypointMovementGenerator() { i_path = nullptr; } + explicit WaypointMovementGenerator(uint32 pathId = 0, bool repeating = true); + explicit WaypointMovementGenerator(WaypointPath& path, bool repeating = true); + + ~WaypointMovementGenerator() { _path = nullptr; } + void DoInitialize(Creature*); void DoFinalize(Creature*); void DoReset(Creature*); bool DoUpdate(Creature*, uint32 diff); - void MovementInform(Creature*); - MovementGeneratorType GetMovementGeneratorType() const override { return WAYPOINT_MOTION_TYPE; } + void UnitSpeedChanged() override { _recalculateSpeed = true; } + void Pause(uint32 timer = 0) override; + void Resume(uint32 overrideTimer = 0) override; - // now path movement implmementation - void LoadPath(Creature*); + void MovementInform(Creature*); bool GetResetPos(Creature*, float& x, float& y, float& z); private: - - void Stop(int32 time) { i_nextMoveTime.Reset(time);} - - bool Stopped() { return !i_nextMoveTime.Passed();} - - bool CanMove(int32 diff) - { - i_nextMoveTime.Update(diff); - return i_nextMoveTime.Passed(); - } - + void LoadPath(Creature*); void OnArrived(Creature*); bool StartMove(Creature*); - - void StartMoveNow(Creature* creature) + bool CanMove(Creature*); + bool StartMoveNow(Creature* creature) { - i_nextMoveTime.Reset(0); - StartMove(creature); + _nextMoveTime.Reset(0); + return StartMove(creature); } - TimeTrackerSmall i_nextMoveTime; - bool m_isArrivalDone; - uint32 path_id; - bool repeating; + TimeTrackerSmall _nextMoveTime; + bool _recalculateSpeed; + bool _isArrivalDone; + uint32 _pathId; + bool _repeating; + bool _loadedFromDB; + bool _stalled; + bool _done; }; -/** FlightPathMovementGenerator generates movement of the player for the paths +/** + * FlightPathMovementGenerator generates movement of the player for the paths * and hence generates ground and activities for the player. */ -class FlightPathMovementGenerator : public MovementGeneratorMedium< Player, FlightPathMovementGenerator >, - public PathMovementBase<Player, TaxiPathNodeList> +class FlightPathMovementGenerator : public MovementGeneratorMedium<Player, FlightPathMovementGenerator>, public PathMovementBase<Player, TaxiPathNodeList> { public: - explicit FlightPathMovementGenerator(uint32 startNode = 0) - { - i_currentNode = startNode; - _endGridX = 0.0f; - _endGridY = 0.0f; - _endMapId = 0; - _preloadTargetNode = 0; - } + explicit FlightPathMovementGenerator(uint32 startNode = 0); + void LoadPath(Player* player); void DoInitialize(Player*); void DoReset(Player*); @@ -123,15 +111,14 @@ class FlightPathMovementGenerator : public MovementGeneratorMedium< Player, Flig bool DoUpdate(Player*, uint32); MovementGeneratorType GetMovementGeneratorType() const override { return FLIGHT_MOTION_TYPE; } - TaxiPathNodeList const& GetPath() { return i_path; } + TaxiPathNodeList const& GetPath() { return _path; } uint32 GetPathAtMapEnd() const; - bool HasArrived() const { return (i_currentNode >= i_path.size()); } + bool HasArrived() const { return (_currentNode >= _path.size()); } void SetCurrentNodeAfterTeleport(); - void SkipCurrentNode() { ++i_currentNode; } + void SkipCurrentNode() { ++_currentNode; } void DoEventIfAny(Player* player, TaxiPathNodeEntry const* node, bool departure); bool GetResetPos(Player*, float& x, float& y, float& z); - void InitEndGridInfo(); void PreloadEndGrid(); diff --git a/src/server/game/Movement/Waypoints/WaypointDefines.h b/src/server/game/Movement/Waypoints/WaypointDefines.h new file mode 100644 index 00000000000..dbb7a15fa5c --- /dev/null +++ b/src/server/game/Movement/Waypoints/WaypointDefines.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2008-2017 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITY_WAYPOINTDEFINES_H +#define TRINITY_WAYPOINTDEFINES_H + +#include "Define.h" +#include <vector> + +enum WaypointMoveType +{ + WAYPOINT_MOVE_TYPE_WALK, + WAYPOINT_MOVE_TYPE_RUN, + WAYPOINT_MOVE_TYPE_LAND, + WAYPOINT_MOVE_TYPE_TAKEOFF, + + WAYPOINT_MOVE_TYPE_MAX +}; + +struct WaypointNode +{ + WaypointNode() : id(0), x(0.f), y(0.f), z(0.f), orientation(0.f), delay(0), eventId(0), moveType(WAYPOINT_MOVE_TYPE_RUN), eventChance(0) { } + WaypointNode(uint32 _id, float _x, float _y, float _z, float _orientation = 0.f, uint32 _delay = 0) + { + id = _id; + x = _x; + y = _y; + z = _z; + orientation = _orientation; + delay = _delay; + eventId = 0; + moveType = WAYPOINT_MOVE_TYPE_WALK; + eventChance = 100; + } + + uint32 id; + float x, y, z, orientation; + uint32 delay; + uint32 eventId; + uint32 moveType; + uint8 eventChance; +}; + +struct WaypointPath +{ + WaypointPath() : id(0) { } + WaypointPath(uint32 _id, std::vector<WaypointNode>&& _nodes) + { + id = _id; + nodes = _nodes; + } + + std::vector<WaypointNode> nodes; + uint32 id; +}; + +#endif diff --git a/src/server/game/Movement/Waypoints/WaypointManager.cpp b/src/server/game/Movement/Waypoints/WaypointManager.cpp index 76c6228d302..71b2c29621b 100644 --- a/src/server/game/Movement/Waypoints/WaypointManager.cpp +++ b/src/server/game/Movement/Waypoints/WaypointManager.cpp @@ -16,27 +16,12 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include "WaypointManager.h" #include "DatabaseEnv.h" #include "GridDefines.h" -#include "WaypointManager.h" #include "MapManager.h" #include "Log.h" -WaypointMgr::WaypointMgr() { } - -WaypointMgr::~WaypointMgr() -{ - for (WaypointPathContainer::iterator itr = _waypointStore.begin(); itr != _waypointStore.end(); ++itr) - { - for (WaypointPath::const_iterator it = itr->second.begin(); it != itr->second.end(); ++it) - delete *it; - - itr->second.clear(); - } - - _waypointStore.clear(); -} - void WaypointMgr::Load() { uint32 oldMSTime = getMSTime(); @@ -46,7 +31,7 @@ void WaypointMgr::Load() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 waypoints. DB table `waypoint_data` is empty!"); + TC_LOG_INFO("server.loading", ">> Loaded 0 waypoints. DB table `waypoint_data` is empty!"); return; } @@ -55,11 +40,7 @@ void WaypointMgr::Load() do { Field* fields = result->Fetch(); - WaypointData* wp = new WaypointData(); - uint32 pathId = fields[0].GetUInt32(); - WaypointPath& path = _waypointStore[pathId]; - float x = fields[2].GetFloat(); float y = fields[3].GetFloat(); float z = fields[4].GetFloat(); @@ -68,25 +49,27 @@ void WaypointMgr::Load() Trinity::NormalizeMapCoord(x); Trinity::NormalizeMapCoord(y); - wp->id = fields[1].GetUInt32(); - wp->x = x; - wp->y = y; - wp->z = z; - wp->orientation = o; - wp->move_type = fields[6].GetUInt32(); + WaypointNode waypoint; + waypoint.id = fields[1].GetUInt32(); + waypoint.x = x; + waypoint.y = y; + waypoint.z = z; + waypoint.orientation = o; + waypoint.moveType = fields[6].GetUInt32(); - if (wp->move_type >= WAYPOINT_MOVE_TYPE_MAX) + if (waypoint.moveType >= WAYPOINT_MOVE_TYPE_MAX) { - TC_LOG_ERROR("sql.sql", "Waypoint %u in waypoint_data has invalid move_type, ignoring", wp->id); - delete wp; + TC_LOG_ERROR("sql.sql", "Waypoint %u in waypoint_data has invalid move_type, ignoring", waypoint.id); continue; } - wp->delay = fields[7].GetUInt32(); - wp->event_id = fields[8].GetUInt32(); - wp->event_chance = fields[9].GetInt16(); + waypoint.delay = fields[7].GetUInt32(); + waypoint.eventId = fields[8].GetUInt32(); + waypoint.eventChance = fields[9].GetInt16(); - path.push_back(wp); + WaypointPath& path = _waypointStore[pathId]; + path.id = pathId; + path.nodes.push_back(std::move(waypoint)); ++count; } while (result->NextRow()); @@ -102,14 +85,9 @@ WaypointMgr* WaypointMgr::instance() void WaypointMgr::ReloadPath(uint32 id) { - WaypointPathContainer::iterator itr = _waypointStore.find(id); + auto itr = _waypointStore.find(id); if (itr != _waypointStore.end()) - { - for (WaypointPath::const_iterator it = itr->second.begin(); it != itr->second.end(); ++it) - delete *it; - _waypointStore.erase(itr); - } PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_SEL_WAYPOINT_DATA_BY_ID); @@ -120,13 +98,10 @@ void WaypointMgr::ReloadPath(uint32 id) if (!result) return; - WaypointPath& path = _waypointStore[id]; - + std::vector<WaypointNode> values; do { Field* fields = result->Fetch(); - WaypointData* wp = new WaypointData(); - float x = fields[1].GetFloat(); float y = fields[2].GetFloat(); float z = fields[3].GetFloat(); @@ -135,26 +110,36 @@ void WaypointMgr::ReloadPath(uint32 id) Trinity::NormalizeMapCoord(x); Trinity::NormalizeMapCoord(y); - wp->id = fields[0].GetUInt32(); - wp->x = x; - wp->y = y; - wp->z = z; - wp->orientation = o; - wp->move_type = fields[5].GetUInt32(); + WaypointNode waypoint; + waypoint.id = fields[0].GetUInt32(); + waypoint.x = x; + waypoint.y = y; + waypoint.z = z; + waypoint.orientation = o; + waypoint.moveType = fields[5].GetUInt32(); - if (wp->move_type >= WAYPOINT_MOVE_TYPE_MAX) + if (waypoint.moveType >= WAYPOINT_MOVE_TYPE_MAX) { - TC_LOG_ERROR("sql.sql", "Waypoint %u in waypoint_data has invalid move_type, ignoring", wp->id); - delete wp; + TC_LOG_ERROR("sql.sql", "Waypoint %u in waypoint_data has invalid move_type, ignoring", waypoint.id); continue; } - wp->delay = fields[6].GetUInt32(); - wp->event_id = fields[7].GetUInt32(); - wp->event_chance = fields[8].GetUInt8(); - - path.push_back(wp); + waypoint.delay = fields[6].GetUInt32(); + waypoint.eventId = fields[7].GetUInt32(); + waypoint.eventChance = fields[8].GetUInt8(); + values.push_back(std::move(waypoint)); } while (result->NextRow()); + + _waypointStore[id] = WaypointPath(id, std::move(values)); +} + +WaypointPath const* WaypointMgr::GetPath(uint32 id) const +{ + auto itr = _waypointStore.find(id); + if (itr != _waypointStore.end()) + return &itr->second; + + return nullptr; } diff --git a/src/server/game/Movement/Waypoints/WaypointManager.h b/src/server/game/Movement/Waypoints/WaypointManager.h index f62805594ef..769e45432e7 100644 --- a/src/server/game/Movement/Waypoints/WaypointManager.h +++ b/src/server/game/Movement/Waypoints/WaypointManager.h @@ -20,32 +20,10 @@ #define TRINITY_WAYPOINTMANAGER_H #include "Define.h" +#include "WaypointDefines.h" #include <vector> #include <unordered_map> -enum WaypointMoveType -{ - WAYPOINT_MOVE_TYPE_WALK, - WAYPOINT_MOVE_TYPE_RUN, - WAYPOINT_MOVE_TYPE_LAND, - WAYPOINT_MOVE_TYPE_TAKEOFF, - - WAYPOINT_MOVE_TYPE_MAX -}; - -struct WaypointData -{ - uint32 id; - float x, y, z, orientation; - uint32 delay; - uint32 event_id; - uint32 move_type; - uint8 event_chance; -}; - -typedef std::vector<WaypointData*> WaypointPath; -typedef std::unordered_map<uint32, WaypointPath> WaypointPathContainer; - class TC_GAME_API WaypointMgr { public: @@ -58,20 +36,12 @@ class TC_GAME_API WaypointMgr void Load(); // Returns the path from a given id - WaypointPath const* GetPath(uint32 id) const - { - WaypointPathContainer::const_iterator itr = _waypointStore.find(id); - if (itr != _waypointStore.end()) - return &itr->second; - - return nullptr; - } + WaypointPath const* GetPath(uint32 id) const; private: - WaypointMgr(); - ~WaypointMgr(); + WaypointMgr() { } - WaypointPathContainer _waypointStore; + std::unordered_map<uint32, WaypointPath> _waypointStore; }; #define sWaypointMgr WaypointMgr::instance() diff --git a/src/server/game/OutdoorPvP/OutdoorPvP.cpp b/src/server/game/OutdoorPvP/OutdoorPvP.cpp index a2d195bea20..b2e962abc0f 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvP.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvP.cpp @@ -91,7 +91,7 @@ void OPvPCapturePoint::AddGO(uint32 type, ObjectGuid::LowType guid, uint32 entry { if (!entry) { - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) return; entry = data->id; @@ -117,7 +117,7 @@ void OPvPCapturePoint::AddCre(uint32 type, ObjectGuid::LowType guid, uint32 entr bool OPvPCapturePoint::AddObject(uint32 type, uint32 entry, uint32 map, Position const& pos, QuaternionData const& rot) { - if (ObjectGuid::LowType guid = sObjectMgr->AddGOData(entry, map, pos, rot, 0)) + if (ObjectGuid::LowType guid = sObjectMgr->AddGameObjectData(entry, map, pos, rot, 0)) { AddGO(type, guid, entry); return true; @@ -149,7 +149,7 @@ bool OPvPCapturePoint::SetCapturePointData(uint32 entry, uint32 map, Position co return false; } - m_capturePointSpawnId = sObjectMgr->AddGOData(entry, map, pos, rot, 0); + m_capturePointSpawnId = sObjectMgr->AddGameObjectData(entry, map, pos, rot, 0); if (m_capturePointSpawnId == 0) return false; @@ -217,7 +217,7 @@ bool OPvPCapturePoint::DelObject(uint32 type) go->SetRespawnTime(0); go->Delete(); } - sObjectMgr->DeleteGOData(spawnId); + sObjectMgr->DeleteGameObjectData(spawnId); m_ObjectTypes[m_Objects[type]] = 0; m_Objects[type] = 0; return true; @@ -225,7 +225,7 @@ bool OPvPCapturePoint::DelObject(uint32 type) bool OPvPCapturePoint::DelCapturePoint() { - sObjectMgr->DeleteGOData(m_capturePointSpawnId); + sObjectMgr->DeleteGameObjectData(m_capturePointSpawnId); m_capturePointSpawnId = 0; if (m_capturePoint) diff --git a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp index ebe3d568beb..48456efb424 100644 --- a/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp +++ b/src/server/game/OutdoorPvP/OutdoorPvPMgr.cpp @@ -54,7 +54,7 @@ void OutdoorPvPMgr::InitOutdoorPvP() QueryResult result = WorldDatabase.Query("SELECT TypeId, ScriptName FROM outdoorpvp_template"); if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 outdoor PvP definitions. DB table `outdoorpvp_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 outdoor PvP definitions. DB table `outdoorpvp_template` is empty."); return; } diff --git a/src/server/game/Pools/PoolMgr.cpp b/src/server/game/Pools/PoolMgr.cpp index 9305e226421..25b9bd5f363 100644 --- a/src/server/game/Pools/PoolMgr.cpp +++ b/src/server/game/Pools/PoolMgr.cpp @@ -153,34 +153,6 @@ bool PoolGroup<T>::CheckPool() const return true; } -template <class T> -PoolObject* PoolGroup<T>::RollOne(ActivePoolData& spawns, uint32 triggerFrom) -{ - if (!ExplicitlyChanced.empty()) - { - float roll = (float)rand_chance(); - - for (uint32 i = 0; i < ExplicitlyChanced.size(); ++i) - { - roll -= ExplicitlyChanced[i].chance; - // Triggering object is marked as spawned at this time and can be also rolled (respawn case) - // so this need explicit check for this case - if (roll < 0 && (ExplicitlyChanced[i].guid == triggerFrom || !spawns.IsActiveObject<T>(ExplicitlyChanced[i].guid))) - return &ExplicitlyChanced[i]; - } - } - if (!EqualChanced.empty()) - { - uint32 index = urand(0, EqualChanced.size()-1); - // Triggering object is marked as spawned at this time and can be also rolled (respawn case) - // so this need explicit check for this case - if (EqualChanced[index].guid == triggerFrom || !spawns.IsActiveObject<T>(EqualChanced[index].guid)) - return &EqualChanced[index]; - } - - return nullptr; -} - // Main method to despawn a creature or gameobject in a pool // If no guid is passed, the pool is just removed (event end case) // If guid is filled, cache will be used and no removal will occur, it just fill the cache @@ -222,7 +194,7 @@ void PoolGroup<Creature>::Despawn1Object(ObjectGuid::LowType guid) { sObjectMgr->RemoveCreatureFromGrid(guid, data); - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); if (!map->Instanceable()) { auto creatureBounds = map->GetCreatureBySpawnIdStore().equal_range(guid); @@ -230,6 +202,9 @@ void PoolGroup<Creature>::Despawn1Object(ObjectGuid::LowType guid) { Creature* creature = itr->second; ++itr; + // For dynamic spawns, save respawn time here + if (!creature->GetRespawnCompatibilityMode()) + creature->SaveRespawnTime(0, false); creature->AddObjectToRemoveList(); } } @@ -240,11 +215,11 @@ void PoolGroup<Creature>::Despawn1Object(ObjectGuid::LowType guid) template<> void PoolGroup<GameObject>::Despawn1Object(ObjectGuid::LowType guid) { - if (GameObjectData const* data = sObjectMgr->GetGOData(guid)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(guid)) { sObjectMgr->RemoveGameobjectFromGrid(guid, data); - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); if (!map->Instanceable()) { auto gameobjectBounds = map->GetGameObjectBySpawnIdStore().equal_range(guid); @@ -252,6 +227,10 @@ void PoolGroup<GameObject>::Despawn1Object(ObjectGuid::LowType guid) { GameObject* go = itr->second; ++itr; + + // For dynamic spawns, save respawn time here + if (!go->GetRespawnCompatibilityMode()) + go->SaveRespawnTime(0, false); go->AddObjectToRemoveList(); } } @@ -333,7 +312,6 @@ void PoolGroup<Pool>::RemoveOneRelation(uint32 child_pool_id) template <class T> void PoolGroup<T>::SpawnObject(ActivePoolData& spawns, uint32 limit, uint32 triggerFrom) { - uint32 lastDespawned = 0; int count = limit - spawns.GetActiveObjectCount(poolId); // If triggered from some object respawn this object is still marked as spawned @@ -342,32 +320,70 @@ void PoolGroup<T>::SpawnObject(ActivePoolData& spawns, uint32 limit, uint32 trig if (triggerFrom) ++count; - // This will try to spawn the rest of pool, not guaranteed - for (int i = 0; i < count; ++i) + if (count > 0) { - PoolObject* obj = RollOne(spawns, triggerFrom); - if (!obj) - continue; - if (obj->guid == lastDespawned) - continue; + PoolObjectList rolledObjects; + rolledObjects.reserve(count); - if (obj->guid == triggerFrom) + // roll objects to be spawned + if (!ExplicitlyChanced.empty()) { - ReSpawn1Object(obj); - triggerFrom = 0; - continue; + while (count && ExplicitlyChanced.size() > rolledObjects.size()) + { + --count; + float roll = (float)rand_chance(); + + for (PoolObject& obj : ExplicitlyChanced) + { + roll -= obj.chance; + // Triggering object is marked as spawned at this time and can be also rolled (respawn case) + // so this need explicit check for this case + if (roll < 0 && (obj.guid == triggerFrom || !spawns.IsActiveObject<T>(obj.guid))) + { + rolledObjects.push_back(obj); + break; + } + } + } } - spawns.ActivateObject<T>(obj->guid, poolId); - Spawn1Object(obj); + else if (!EqualChanced.empty()) + { + rolledObjects = EqualChanced; + + for (auto itr = rolledObjects.begin(); itr != rolledObjects.end();) + { + // remove most of the active objects so there is higher chance inactive ones are spawned + if (spawns.IsActiveObject<T>(itr->guid) && urand(1, 4) != 1) + itr = rolledObjects.erase(itr); + else + ++itr; + } - if (triggerFrom) + Trinity::Containers::RandomResize(rolledObjects, count); + } + + // try to spawn rolled objects + for (PoolObject& obj : rolledObjects) { - // One spawn one despawn no count increase - DespawnObject(spawns, triggerFrom); - lastDespawned = triggerFrom; - triggerFrom = 0; + if (spawns.IsActiveObject<T>(obj.guid)) + continue; + + if (obj.guid == triggerFrom) + { + ReSpawn1Object(&obj); + triggerFrom = 0; + } + else + { + spawns.ActivateObject<T>(obj.guid, poolId); + Spawn1Object(&obj); + } } } + + // One spawn one despawn no count increase + if (triggerFrom) + DespawnObject(spawns, triggerFrom); } // Method that is actualy doing the spawn job on 1 creature @@ -379,13 +395,13 @@ void PoolGroup<Creature>::Spawn1Object(PoolObject* obj) sObjectMgr->AddCreatureToGrid(obj->guid, data); // Spawn if necessary (loaded grids only) - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use spawn coords to spawn - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { Creature* creature = new Creature(); //TC_LOG_DEBUG("pool", "Spawning creature %u", guid); - if (!creature->LoadCreatureFromDB(obj->guid, map)) + if (!creature->LoadFromDB(obj->guid, map, true, false)) { delete creature; return; @@ -398,18 +414,18 @@ void PoolGroup<Creature>::Spawn1Object(PoolObject* obj) template <> void PoolGroup<GameObject>::Spawn1Object(PoolObject* obj) { - if (GameObjectData const* data = sObjectMgr->GetGOData(obj->guid)) + if (GameObjectData const* data = sObjectMgr->GetGameObjectData(obj->guid)) { sObjectMgr->AddGameobjectToGrid(obj->guid, data); // Spawn if necessary (loaded grids only) // this base map checked as non-instanced and then only existed - Map* map = sMapMgr->CreateBaseMap(data->mapid); + Map* map = sMapMgr->CreateBaseMap(data->spawnPoint.GetMapId()); // We use current coords to unspawn, not spawn coords since creature can have changed grid - if (!map->Instanceable() && map->IsGridLoaded(data->posX, data->posY)) + if (!map->Instanceable() && map->IsGridLoaded(data->spawnPoint)) { GameObject* pGameobject = new GameObject; //TC_LOG_DEBUG("pool", "Spawning gameobject %u", guid); - if (!pGameobject->LoadGameObjectFromDB(obj->guid, map, false)) + if (!pGameobject->LoadFromDB(obj->guid, map, false)) { delete pGameobject; return; @@ -688,7 +704,7 @@ void PoolMgr::LoadFromDB() uint32 pool_id = fields[1].GetUInt32(); float chance = fields[2].GetFloat(); - GameObjectData const* data = sObjectMgr->GetGOData(guid); + GameObjectData const* data = sObjectMgr->GetGameObjectData(guid); if (!data) { TC_LOG_ERROR("sql.sql", "`pool_gameobject` has a non existing gameobject spawn (GUID: %u) defined for pool id (%u), skipped.", guid, pool_id); @@ -1074,6 +1090,21 @@ void PoolMgr::DespawnPool(uint32 pool_id) mPoolQuestGroups[pool_id].DespawnObject(mSpawnedData); } +// Selects proper template overload to call based on passed type +uint32 PoolMgr::IsPartOfAPool(SpawnObjectType type, ObjectGuid::LowType spawnId) const +{ + switch (type) + { + case SPAWN_TYPE_CREATURE: + return IsPartOfAPool<Creature>(spawnId); + case SPAWN_TYPE_GAMEOBJECT: + return IsPartOfAPool<GameObject>(spawnId); + default: + ASSERT(false, "Invalid spawn type %u passed to PoolMgr::IsPartOfPool (with spawnId %u)", uint32(type), spawnId); + return 0; + } +} + // Method that check chance integrity of the creatures and gameobjects in this pool bool PoolMgr::CheckPool(uint32 pool_id) const { diff --git a/src/server/game/Pools/PoolMgr.h b/src/server/game/Pools/PoolMgr.h index 6c270e28851..6de34c05fb4 100644 --- a/src/server/game/Pools/PoolMgr.h +++ b/src/server/game/Pools/PoolMgr.h @@ -22,6 +22,7 @@ #include "Define.h" #include "Creature.h" #include "GameObject.h" +#include "SpawnData.h" #include "QuestDef.h" struct PoolTemplateData @@ -76,7 +77,6 @@ class TC_GAME_API PoolGroup bool isEmpty() const { return ExplicitlyChanced.empty() && EqualChanced.empty(); } void AddEntry(PoolObject& poolitem, uint32 maxentries); bool CheckPool() const; - PoolObject* RollOne(ActivePoolData& spawns, uint32 triggerFrom); void DespawnObject(ActivePoolData& spawns, ObjectGuid::LowType guid=0); void Despawn1Object(ObjectGuid::LowType guid); void SpawnObject(ActivePoolData& spawns, uint32 limit, uint32 triggerFrom); @@ -118,6 +118,7 @@ class TC_GAME_API PoolMgr template<typename T> uint32 IsPartOfAPool(uint32 db_guid_or_pool_id) const; + uint32 IsPartOfAPool(SpawnObjectType type, ObjectGuid::LowType spawnId) const; template<typename T> bool IsSpawnedObject(uint32 db_guid_or_pool_id) const { return mSpawnedData.IsActiveObject<T>(db_guid_or_pool_id); } diff --git a/src/server/game/Quests/QuestDef.cpp b/src/server/game/Quests/QuestDef.cpp index 55100ca7396..0c106f69b4a 100644 --- a/src/server/game/Quests/QuestDef.cpp +++ b/src/server/game/Quests/QuestDef.cpp @@ -213,15 +213,12 @@ uint32 Quest::XPValue(Player* player) const else if (diffFactor > 10) diffFactor = 10; - uint32 xp = diffFactor * xpentry->Exp[_rewardXPDifficulty] / 10; - if (xp <= 100) - xp = 5 * ((xp + 2) / 5); - else if (xp <= 500) - xp = 10 * ((xp + 5) / 10); - else if (xp <= 1000) - xp = 25 * ((xp + 12) / 25); - else - xp = 50 * ((xp + 25) / 50); + uint32 xp = RoundXPValue(diffFactor * xpentry->Exp[_rewardXPDifficulty] / 10); + if (sWorld->getIntConfig(CONFIG_MIN_QUEST_SCALED_XP_RATIO)) + { + uint32 minScaledXP = RoundXPValue(xpentry->Exp[_rewardXPDifficulty]) * sWorld->getIntConfig(CONFIG_MIN_QUEST_SCALED_XP_RATIO) / 100; + xp = std::max(minScaledXP, xp); + } return xp; } @@ -447,3 +444,15 @@ void Quest::AddQuestLevelToTitle(std::string &title, int32 level) questTitlePretty << "[" << level << "] " << title; title = questTitlePretty.str(); } + +uint32 Quest::RoundXPValue(uint32 xp) +{ + if (xp <= 100) + return 5 * ((xp + 2) / 5); + else if (xp <= 500) + return 10 * ((xp + 5) / 10); + else if (xp <= 1000) + return 25 * ((xp + 12) / 25); + else + return 50 * ((xp + 25) / 50); +} diff --git a/src/server/game/Quests/QuestDef.h b/src/server/game/Quests/QuestDef.h index d2a5e0d5d84..75c5abd7911 100644 --- a/src/server/game/Quests/QuestDef.h +++ b/src/server/game/Quests/QuestDef.h @@ -384,6 +384,9 @@ class TC_GAME_API Quest uint32 _startItemCount = 0; uint32 _rewardMailSenderEntry = 0; uint32 _specialFlags = 0; // custom flags, not sniffed/WDB + + // Helpers + static uint32 RoundXPValue(uint32 xp); }; struct QuestStatusData diff --git a/src/server/game/Scripting/ScriptMgr.cpp b/src/server/game/Scripting/ScriptMgr.cpp index 9f42ded8cc6..2a26261d9f1 100644 --- a/src/server/game/Scripting/ScriptMgr.cpp +++ b/src/server/game/Scripting/ScriptMgr.cpp @@ -955,11 +955,9 @@ private: // Utility macros for looping over scripts. #define FOR_SCRIPTS(T, C, E) \ - if (SCR_REG_LST(T).empty()) \ - return; \ - \ - for (SCR_REG_ITR(T) C = SCR_REG_LST(T).begin(); \ - C != SCR_REG_LST(T).end(); ++C) + if (!SCR_REG_LST(T).empty()) \ + for (SCR_REG_ITR(T) C = SCR_REG_LST(T).begin(); \ + C != SCR_REG_LST(T).end(); ++C) #define FOR_SCRIPTS_RET(T, C, E, R) \ if (SCR_REG_LST(T).empty()) \ @@ -1566,16 +1564,32 @@ bool ScriptMgr::OnCastItemCombatSpell(Player* player, Unit* victim, SpellInfo co return tmpscript->OnCastItemCombatSpell(player, victim, spellInfo, item); } -bool ScriptMgr::CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, CreatureData const* cData, Map const* map) +bool ScriptMgr::CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureData const* cData, Map const* map) { - ASSERT(actTemplate); - + ASSERT(map); CreatureTemplate const* baseTemplate = sObjectMgr->GetCreatureTemplate(entry); - if (!baseTemplate) - baseTemplate = actTemplate; + ASSERT(baseTemplate); + + // find out which template we'd be using + CreatureTemplate const* actTemplate = baseTemplate; + for (uint8 diff = uint8(map->GetSpawnMode()); diff > 0;) + { + if (uint32 diffEntry = baseTemplate->DifficultyEntry[diff - 1]) + if (CreatureTemplate const* diffTemplate = sObjectMgr->GetCreatureTemplate(diffEntry)) + { + actTemplate = diffTemplate; + break; + } + if (diff >= RAID_DIFFICULTY_10MAN_HEROIC && map->IsRaid()) + diff -= 2; + else + diff -= 1; + } + uint32 scriptId = baseTemplate->ScriptID; - if (cData && cData->ScriptId) - scriptId = cData->ScriptId; + if (cData && cData->scriptId) + scriptId = cData->scriptId; + GET_SCRIPT_RET(CreatureScript, scriptId, tmpscript, true); return tmpscript->CanSpawn(spawnId, entry, baseTemplate, actTemplate, cData, map); } @@ -2215,6 +2229,21 @@ AreaTriggerScript::AreaTriggerScript(char const* name) ScriptRegistry<AreaTriggerScript>::Instance()->AddScript(this); } +bool OnlyOnceAreaTriggerScript::OnTrigger(Player* player, AreaTriggerEntry const* trigger) +{ + uint32 const triggerId = trigger->id; + if (InstanceScript* instance = player->GetInstanceScript()) + { + if (instance->IsAreaTriggerDone(triggerId)) + return true; + else + instance->MarkAreaTriggerDone(triggerId); + } + return _OnTrigger(player, trigger); +} +void OnlyOnceAreaTriggerScript::ResetAreaTriggerDone(InstanceScript* script, uint32 triggerId) { script->ResetAreaTriggerDone(triggerId); } +void OnlyOnceAreaTriggerScript::ResetAreaTriggerDone(Player const* player, AreaTriggerEntry const* trigger) { if (InstanceScript* instance = player->GetInstanceScript()) ResetAreaTriggerDone(instance, trigger->id); } + BattlegroundScript::BattlegroundScript(char const* name) : ScriptObject(name) { diff --git a/src/server/game/Scripting/ScriptMgr.h b/src/server/game/Scripting/ScriptMgr.h index 311a7faf911..d801aba496d 100644 --- a/src/server/game/Scripting/ScriptMgr.h +++ b/src/server/game/Scripting/ScriptMgr.h @@ -448,6 +448,19 @@ class TC_GAME_API AreaTriggerScript : public ScriptObject virtual bool OnTrigger(Player* /*player*/, AreaTriggerEntry const* /*trigger*/) { return false; } }; +class TC_GAME_API OnlyOnceAreaTriggerScript : public AreaTriggerScript +{ + using AreaTriggerScript::AreaTriggerScript; + + public: + bool OnTrigger(Player* /*player*/, AreaTriggerEntry const* /*trigger*/) override; + + protected: + virtual bool _OnTrigger(Player* /*player*/, AreaTriggerEntry const* /*trigger*/) = 0; + void ResetAreaTriggerDone(InstanceScript* /*instance*/, uint32 /*triggerId*/); + void ResetAreaTriggerDone(Player const* /*player*/, AreaTriggerEntry const* /*trigger*/); +}; + class TC_GAME_API BattlegroundScript : public ScriptObject { protected: @@ -907,7 +920,7 @@ class TC_GAME_API ScriptMgr public: /* CreatureScript */ - bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureTemplate const* actTemplate, CreatureData const* cData, Map const* map); + bool CanSpawn(ObjectGuid::LowType spawnId, uint32 entry, CreatureData const* cData, Map const* map); CreatureAI* GetCreatureAI(Creature* creature); public: /* GameObjectScript */ @@ -1053,6 +1066,61 @@ class TC_GAME_API ScriptMgr std::string _currentContext; }; +template <class S> +class GenericSpellScriptLoader : public SpellScriptLoader +{ + public: + GenericSpellScriptLoader(char const* name) : SpellScriptLoader(name) { } + SpellScript* GetSpellScript() const override { return new S(); } +}; +#define RegisterSpellScript(spell_script) new GenericSpellScriptLoader<spell_script>(#spell_script) + +template <class A> +class GenericAuraScriptLoader : public SpellScriptLoader +{ + public: + GenericAuraScriptLoader(char const* name) : SpellScriptLoader(name) { } + AuraScript* GetAuraScript() const override { return new A(); } +}; +#define RegisterAuraScript(aura_script) new GenericAuraScriptLoader<aura_script>(#aura_script) + +template <class S, class A> +class GenericSpellAndAuraScriptLoader : public SpellScriptLoader +{ + public: + GenericSpellAndAuraScriptLoader(char const* name) : SpellScriptLoader(name) { } + SpellScript* GetSpellScript() const override { return new S(); } + AuraScript* GetAuraScript() const override { return new A(); } +}; +#define RegisterSpellAndAuraScriptPair(spell_script, aura_script) new GenericSpellAndAuraScriptLoader<spell_script, aura_script>(#spell_script) + +template <class AI> +class GenericCreatureScript : public CreatureScript +{ + public: + GenericCreatureScript(char const* name) : CreatureScript(name) { } + CreatureAI* GetAI(Creature* me) const override { return new AI(me); } +}; +#define RegisterCreatureAI(ai_name) new GenericCreatureScript<ai_name>(#ai_name) + +template <class AI, AI*(*AIFactory)(Creature*)> +class FactoryCreatureScript : public CreatureScript +{ + public: + FactoryCreatureScript(char const* name) : CreatureScript(name) { } + CreatureAI* GetAI(Creature* me) const override { return AIFactory(me); } +}; +#define RegisterCreatureAIWithFactory(ai_name, factory_fn) new FactoryCreatureScript<ai_name, &factory_fn>(#ai_name) + +template <class AI> +class GenericGameObjectScript : public GameObjectScript +{ + public: + GenericGameObjectScript(char const* name) : GameObjectScript(name) { } + GameObjectAI* GetAI(GameObject* go) const override { return new AI(go); } +}; +#define RegisterGameObjectAI(ai_name) new GenericGameObjectScript<ai_name>(#ai_name) + #define sScriptMgr ScriptMgr::instance() #endif diff --git a/src/server/game/Scripting/ScriptSystem.cpp b/src/server/game/Scripting/ScriptSystem.cpp index 1b4ad6b98d5..b814229151a 100644 --- a/src/server/game/Scripting/ScriptSystem.cpp +++ b/src/server/game/Scripting/ScriptSystem.cpp @@ -37,17 +37,17 @@ void SystemMgr::LoadScriptWaypoints() { uint32 oldMSTime = getMSTime(); - // Drop Existing Waypoint list - m_mPointMoveMap.clear(); + // drop Existing Waypoint list + _waypointStore.clear(); - uint64 uiCreatureCount = 0; + uint64 entryCount = 0; - // Load Waypoints + // load Waypoints QueryResult result = WorldDatabase.Query("SELECT COUNT(entry) FROM script_waypoint GROUP BY entry"); if (result) - uiCreatureCount = result->GetRowCount(); + entryCount = result->GetRowCount(); - TC_LOG_INFO("server.loading", "Loading Script Waypoints for " UI64FMTD " creature(s)...", uiCreatureCount); + TC_LOG_INFO("server.loading", "Loading Script Waypoints for " UI64FMTD " creature(s)...", entryCount); // 0 1 2 3 4 5 result = WorldDatabase.Query("SELECT entry, pointid, location_x, location_y, location_z, waittime FROM script_waypoint ORDER BY pointid"); @@ -61,29 +61,28 @@ void SystemMgr::LoadScriptWaypoints() do { - Field* pFields = result->Fetch(); - ScriptPointMove temp; - - temp.uiCreatureEntry = pFields[0].GetUInt32(); - uint32 uiEntry = temp.uiCreatureEntry; - temp.uiPointId = pFields[1].GetUInt32(); - temp.fX = pFields[2].GetFloat(); - temp.fY = pFields[3].GetFloat(); - temp.fZ = pFields[4].GetFloat(); - temp.uiWaitTime = pFields[5].GetUInt32(); - - CreatureTemplate const* pCInfo = sObjectMgr->GetCreatureTemplate(temp.uiCreatureEntry); - - if (!pCInfo) + Field* fields = result->Fetch(); + uint32 entry = fields[0].GetUInt32(); + uint32 id = fields[1].GetUInt32(); + float x = fields[2].GetFloat(); + float y = fields[3].GetFloat(); + float z = fields[4].GetFloat(); + uint32 waitTime = fields[5].GetUInt32(); + + CreatureTemplate const* info = sObjectMgr->GetCreatureTemplate(entry); + if (!info) { - TC_LOG_ERROR("sql.sql", "TSCR: DB table script_waypoint has waypoint for non-existant creature entry %u", temp.uiCreatureEntry); + TC_LOG_ERROR("sql.sql", "SystemMgr: DB table script_waypoint has waypoint for non-existant creature entry %u", entry); continue; } - if (!pCInfo->ScriptID) - TC_LOG_ERROR("sql.sql", "TSCR: DB table script_waypoint has waypoint for creature entry %u, but creature does not have ScriptName defined and then useless.", temp.uiCreatureEntry); + if (!info->ScriptID) + TC_LOG_ERROR("sql.sql", "SystemMgr: DB table script_waypoint has waypoint for creature entry %u, but creature does not have ScriptName defined and then useless.", entry); + + WaypointPath& path = _waypointStore[entry]; + path.id = entry; + path.nodes.emplace_back(id, x, y, z, 0.f, waitTime); - m_mPointMoveMap[uiEntry].push_back(temp); ++count; } while (result->NextRow()); @@ -163,6 +162,15 @@ void SystemMgr::LoadScriptSplineChains() } } +WaypointPath const* SystemMgr::GetPath(uint32 creatureEntry) const +{ + auto itr = _waypointStore.find(creatureEntry); + if (itr == _waypointStore.end()) + return nullptr; + + return &itr->second; +} + std::vector<SplineChainLink> const* SystemMgr::GetSplineChain(uint32 entry, uint16 chainId) const { auto it = m_mSplineChainsMap.find({ entry, chainId }); diff --git a/src/server/game/Scripting/ScriptSystem.h b/src/server/game/Scripting/ScriptSystem.h index 7828c24d680..5b42c5b5a25 100644 --- a/src/server/game/Scripting/ScriptSystem.h +++ b/src/server/game/Scripting/ScriptSystem.h @@ -21,59 +21,37 @@ #include "Define.h" #include "Hash.h" +#include "WaypointDefines.h" #include <unordered_map> #include <vector> class Creature; struct SplineChainLink; -#define TEXT_SOURCE_RANGE -1000000 //the amount of entries each text source has available - -struct ScriptPointMove -{ - uint32 uiCreatureEntry; - uint32 uiPointId; - float fX; - float fY; - float fZ; - uint32 uiWaitTime; -}; - -typedef std::vector<ScriptPointMove> ScriptPointVector; - class TC_GAME_API SystemMgr { - private: - SystemMgr(); - ~SystemMgr(); - SystemMgr(SystemMgr const&) = delete; - SystemMgr& operator=(SystemMgr const&) = delete; - public: static SystemMgr* instance(); - typedef std::unordered_map<uint32, ScriptPointVector> PointMoveMap; - - //Database + // database void LoadScriptWaypoints(); void LoadScriptSplineChains(); - ScriptPointVector const* GetPointMoveList(uint32 creatureEntry) const - { - PointMoveMap::const_iterator itr = m_mPointMoveMap.find(creatureEntry); - - if (itr == m_mPointMoveMap.end()) - return nullptr; - - return &itr->second; - } + WaypointPath const* GetPath(uint32 creatureEntry) const; std::vector<SplineChainLink> const* GetSplineChain(uint32 entry, uint16 chainId) const; std::vector<SplineChainLink> const* GetSplineChain(Creature const* who, uint16 id) const; - protected: - PointMoveMap m_mPointMoveMap; //coordinates for waypoints + private: typedef std::pair<uint32, uint16> ChainKeyType; // creature entry + chain ID + + SystemMgr(); + ~SystemMgr(); + + SystemMgr(SystemMgr const&) = delete; + SystemMgr& operator=(SystemMgr const&) = delete; + + std::unordered_map<uint32, WaypointPath> _waypointStore; std::unordered_map<ChainKeyType, std::vector<SplineChainLink>> m_mSplineChainsMap; // spline chains }; diff --git a/src/server/game/Server/WorldSession.cpp b/src/server/game/Server/WorldSession.cpp index 05f69568507..a5a4f9139b2 100644 --- a/src/server/game/Server/WorldSession.cpp +++ b/src/server/game/Server/WorldSession.cpp @@ -273,7 +273,7 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater) ///- Before we process anything: /// If necessary, kick the player because the client didn't send anything for too long /// (or they've been idling in character select) - if (IsConnectionIdle()) + if (IsConnectionIdle() && !HasPermission(rbac::RBAC_PERM_IGNORE_IDLE_CONNECTION)) m_Socket->CloseSocket(); ///- Retrieve packets from the receive queue and call the appropriate handlers diff --git a/src/server/game/Skills/SkillDiscovery.cpp b/src/server/game/Skills/SkillDiscovery.cpp index 44d9a6a153f..722b5427c72 100644 --- a/src/server/game/Skills/SkillDiscovery.cpp +++ b/src/server/game/Skills/SkillDiscovery.cpp @@ -56,7 +56,7 @@ void LoadSkillDiscoveryTable() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 skill discovery definitions. DB table `skill_discovery_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 skill discovery definitions. DB table `skill_discovery_template` is empty."); return; } diff --git a/src/server/game/Skills/SkillExtraItems.cpp b/src/server/game/Skills/SkillExtraItems.cpp index 3b066e71b8b..91e4ea0d244 100644 --- a/src/server/game/Skills/SkillExtraItems.cpp +++ b/src/server/game/Skills/SkillExtraItems.cpp @@ -61,7 +61,7 @@ void LoadSkillPerfectItemTable() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 spell perfection definitions. DB table `skill_perfect_item_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 spell perfection definitions. DB table `skill_perfect_item_template` is empty."); return; } @@ -148,7 +148,7 @@ void LoadSkillExtraItemTable() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 spell specialization definitions. DB table `skill_extra_item_template` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 spell specialization definitions. DB table `skill_extra_item_template` is empty."); return; } @@ -208,9 +208,6 @@ bool CanCreatePerfectItem(Player* player, uint32 spellId, float &perfectCreateCh return false; SkillPerfectItemEntry const* thisEntry = &ret->second; - // lack of entry means no perfection proc possible - if (!thisEntry) - return false; // if you don't have the spell needed, then no procs for you if (!player->HasSpell(thisEntry->requiredSpecialization)) @@ -233,10 +230,6 @@ bool CanCreateExtraItems(Player* player, uint32 spellId, float &additionalChance SkillExtraItemEntry const* specEntry = &ret->second; - // if no entry, then no extra items can be created - if (!specEntry) - return false; - // the player doesn't have the required specialization, return false if (!player->HasSpell(specEntry->requiredSpecialization)) return false; diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index 2d57d1472b4..80565e1b464 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -4563,7 +4563,7 @@ void AuraEffect::HandleAuraDummy(AuraApplication const* aurApp, uint8 mode, bool break; case 52172: // Coyote Spirit Despawn Aura case 60244: // Blood Parrot Despawn Aura - target->CastSpell((Unit*)nullptr, GetAmount(), true, nullptr, this); + target->CastSpell(nullptr, GetAmount(), true, nullptr, this); break; case 58600: // Restricted Flight Area case 58730: // Restricted Flight Area @@ -5121,7 +5121,7 @@ void AuraEffect::HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const } case 62292: // Blaze (Pool of Tar) // should we use custom damage? - target->CastSpell((Unit*)nullptr, m_spellInfo->Effects[m_effIndex].TriggerSpell, true); + target->CastSpell(nullptr, m_spellInfo->Effects[m_effIndex].TriggerSpell, true); break; case 62399: // Overload Circuit if (target->GetMap()->IsDungeon() && int(target->GetAppliedAuras().count(62399)) >= (target->GetMap()->IsHeroic() ? 4 : 2)) @@ -5145,7 +5145,7 @@ void AuraEffect::HandlePeriodicDummyAuraTick(Unit* target, Unit* caster) const // Mirror Image if (GetId() == 55342) // Set name of summons to name of caster - target->CastSpell((Unit*)nullptr, m_spellInfo->Effects[m_effIndex].TriggerSpell, true); + target->CastSpell(nullptr, m_spellInfo->Effects[m_effIndex].TriggerSpell, true); break; } case SPELLFAMILY_DRUID: diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index cff25bc8e2c..7c1cdc56de0 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -947,6 +947,9 @@ bool Aura::CanBeSaved() const if (IsPassive()) return false; + if (GetSpellInfo()->IsChanneled()) + return false; + // Check if aura is single target, not only spell info if (GetCasterGUID() != GetOwner()->GetGUID()) if (GetSpellInfo()->IsSingleTarget() || IsSingleTarget()) diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index e56c573a43c..334e51eeb56 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -549,6 +549,7 @@ m_caster((info->HasAttribute(SPELL_ATTR6_CAST_BY_CHARMER) && caster->GetCharmerO m_referencedFromCurrentSpell = false; m_executedCurrently = false; m_needComboPoints = m_spellInfo->NeedsComboPoints(); + m_comboTarget = nullptr; m_comboPointGain = 0; m_delayStart = 0; m_delayAtDamageCount = 0; @@ -1399,12 +1400,9 @@ void Spell::SelectImplicitTargetDestTargets(SpellEffIndex effIndex, SpellImplici default: { float angle = targetType.CalcDirectionAngle(); - float objSize = target->GetCombatReach(); - float dist = m_spellInfo->Effects[effIndex].CalcRadius(m_caster); - if (dist < objSize) - dist = objSize; - else if (targetType.GetTarget() == TARGET_DEST_TARGET_RANDOM) - dist = objSize + (dist - objSize) * float(rand_norm()); + float dist = m_spellInfo->Effects[effIndex].CalcRadius(nullptr); + if (targetType.GetTarget() == TARGET_DEST_TARGET_RANDOM) + dist *= float(rand_norm()); Position pos = dest._position; target->MovePositionToFirstCollision(pos, dist, angle); @@ -2551,7 +2549,8 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA // for delayed spells ignore negative spells (after duel end) for friendly targets /// @todo this cause soul transfer bugged // 63881 - Malady of the Mind jump spell (Yogg-Saron) - if (m_spellInfo->Speed > 0.0f && unit->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->IsPositive() && m_spellInfo->Id != 63881) + // 45034 - Curse of Boundless Agony jump spell (Kalecgos) + if (m_spellInfo->Speed > 0.0f && unit->GetTypeId() == TYPEID_PLAYER && !m_spellInfo->IsPositive() && m_spellInfo->Id != 63881 && m_spellInfo->Id != 45034) return SPELL_MISS_EVADE; // assisting case, healing and resurrection @@ -7881,7 +7880,7 @@ bool WorldObjectSpellAreaTargetCheck::operator()(WorldObject* target) if (!isInsideCylinder) return false; } - + return WorldObjectSpellTargetCheck::operator ()(target); } diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 852e34a059f..75c97b005af 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -821,27 +821,6 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex) m_caster->CastSpell(unitTarget, spell->Id, true); return; } - // Cloak of Shadows - case 35729: - { - uint32 dispelMask = SpellInfo::GetDispelMask(DISPEL_ALL); - Unit::AuraApplicationMap& Auras = unitTarget->GetAppliedAuras(); - for (Unit::AuraApplicationMap::iterator iter = Auras.begin(); iter != Auras.end();) - { - // remove all harmful spells on you... - SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); - if (((spell->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && spell->GetSchoolMask() != SPELL_SCHOOL_MASK_NORMAL) // only affect magic spells - || (spell->GetDispelMask() & dispelMask)) && - // ignore positive and passive auras - !iter->second->IsPositive() && !iter->second->GetBase()->IsPassive()) - { - m_caster->RemoveAura(iter); - } - else - ++iter; - } - return; - } } } @@ -1008,7 +987,7 @@ void Spell::EffectTriggerRitualOfSummoning(SpellEffIndex effIndex) finish(); - m_caster->CastSpell((Unit*)nullptr, spellInfo, false); + m_caster->CastSpell(nullptr, spellInfo, false); } void Spell::EffectJump(SpellEffIndex effIndex) @@ -2818,8 +2797,6 @@ void Spell::EffectEnchantItemTmp(SpellEffIndex effIndex) } return; } - if (!itemTarget) - return; uint32 enchant_id = m_spellInfo->Effects[effIndex].MiscValue; @@ -3209,7 +3186,7 @@ void Spell::EffectWeaponDmg(SpellEffIndex effIndex) // Mangle (Cat): CP if (m_spellInfo->SpellFamilyFlags[1] & 0x400) AddComboPointGain(unitTarget, 1); - + // Shred, Maul - Rend and Tear else if (m_spellInfo->SpellFamilyFlags[0] & 0x00008800 && unitTarget->HasAuraState(AURA_STATE_BLEEDING)) { @@ -3946,6 +3923,9 @@ void Spell::EffectSanctuary(SpellEffIndex /*effIndex*/) if (!unitTarget) return; + if (unitTarget->GetTypeId() == TYPEID_PLAYER) + unitTarget->ToPlayer()->SendAttackSwingCancelAttack(); // melee and ranged forced attack cancel + unitTarget->getHostileRefManager().UpdateVisibility(); Unit::AttackerSet const& attackers = unitTarget->getAttackers(); @@ -4655,9 +4635,13 @@ void Spell::EffectChargeDest(SpellEffIndex /*effIndex*/) if (m_targets.HasDst()) { Position pos = destTarget->GetPosition(); - float angle = m_caster->GetRelativeAngle(pos.GetPositionX(), pos.GetPositionY()); - float dist = m_caster->GetDistance(pos); - pos = m_caster->GetFirstCollisionPosition(dist, angle); + + if (!m_caster->IsWithinLOS(pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ())) + { + float angle = m_caster->GetRelativeAngle(pos.GetPositionX(), pos.GetPositionY()); + float dist = m_caster->GetDistance(pos); + pos = m_caster->GetFirstCollisionPosition(dist, angle); + } m_caster->GetMotionMaster()->MoveCharge(pos.m_positionX, pos.m_positionY, pos.m_positionZ); } diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 9e393f2fcad..e2946dbb45e 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -323,7 +323,7 @@ SpellImplicitTargetInfo::StaticData SpellImplicitTargetInfo::_data[TOTAL_SPELL_ {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_DEST, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 107 TARGET_UNK_DEST_AREA_UNK_107 {TARGET_OBJECT_TYPE_GOBJ, TARGET_REFERENCE_TYPE_CASTER, TARGET_SELECT_CATEGORY_CONE, TARGET_CHECK_DEFAULT, TARGET_DIR_FRONT}, // 108 TARGET_GAMEOBJECT_CONE {TARGET_OBJECT_TYPE_NONE, TARGET_REFERENCE_TYPE_NONE, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 109 - {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_NONE, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_DEFAULT, TARGET_DIR_NONE}, // 110 TARGET_DEST_UNK_110 + {TARGET_OBJECT_TYPE_DEST, TARGET_REFERENCE_TYPE_NONE, TARGET_SELECT_CATEGORY_NYI, TARGET_CHECK_ENTRY, TARGET_DIR_NONE}, // 110 TARGET_UNIT_CONE_ENTRY_110 }; SpellEffectInfo::SpellEffectInfo(SpellEntry const* spellEntry, SpellInfo const* spellInfo, uint8 effIndex) @@ -1345,10 +1345,9 @@ bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const if (HasAttribute(SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE) || HasAttribute(SPELL_ATTR2_UNAFFECTED_BY_AURA_SCHOOL_IMMUNE)) { // ...but not these (Divine shield, Ice block, Cyclone and Banish for example) - if (!auraSpellInfo || - (auraSpellInfo->Mechanic != MECHANIC_IMMUNE_SHIELD && - auraSpellInfo->Mechanic != MECHANIC_INVULNERABILITY && - (auraSpellInfo->Mechanic != MECHANIC_BANISH || (IsRankOf(auraSpellInfo) && auraSpellInfo->Dispel != DISPEL_NONE)))) // Banish shouldn't be immune to itself, but Cyclone should + if (auraSpellInfo->Mechanic != MECHANIC_IMMUNE_SHIELD && + auraSpellInfo->Mechanic != MECHANIC_INVULNERABILITY && + (auraSpellInfo->Mechanic != MECHANIC_BANISH || (IsRankOf(auraSpellInfo) && auraSpellInfo->Dispel != DISPEL_NONE))) // Banish shouldn't be immune to itself, but Cyclone should return true; } @@ -1370,7 +1369,8 @@ bool SpellInfo::CanDispelAura(SpellInfo const* auraSpellInfo) const return true; // These auras (Cyclone for example) are not dispelable - if (auraSpellInfo->HasAttribute(SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE) || auraSpellInfo->HasAttribute(SPELL_ATTR2_UNAFFECTED_BY_AURA_SCHOOL_IMMUNE)) + if ((auraSpellInfo->HasAttribute(SPELL_ATTR1_UNAFFECTED_BY_SCHOOL_IMMUNE) && auraSpellInfo->Mechanic != MECHANIC_NONE) + || auraSpellInfo->HasAttribute(SPELL_ATTR2_UNAFFECTED_BY_AURA_SCHOOL_IMMUNE)) return false; return true; @@ -2877,13 +2877,7 @@ void SpellInfo::ApplyAllSpellImmunitiesTo(Unit* target, uint8 effIndex, bool app target->ApplySpellImmune(Id, IMMUNITY_MECHANIC, i, apply); if (apply && HasAttribute(SPELL_ATTR1_DISPEL_AURAS_ON_IMMUNITY)) - { - // exception for purely snare mechanic (eg. hands of freedom)! - if (mechanicImmunity == (1 << MECHANIC_SNARE)) - target->RemoveMovementImpairingAuras(false); - else - target->RemoveAurasWithMechanic(mechanicImmunity, AURA_REMOVE_BY_DEFAULT, Id); - } + target->RemoveAurasWithMechanic(mechanicImmunity, AURA_REMOVE_BY_DEFAULT, Id); } if (uint32 dispelImmunity = immuneInfo->DispelImmune) @@ -3372,6 +3366,8 @@ bool SpellInfo::_IsPositiveEffect(uint8 effIndex, bool deep) const { case 29214: // Wrath of the Plaguebringer case 34700: // Allergic Reaction + case 41914: // Parasitic Shadowfiend (Illidan) + case 41917: // Parasitic Shadowfiend (Illidan) case 54836: // Wrath of the Plaguebringer case 61987: // Avenging Wrath Marker case 61988: // Divine Shield exclude aura diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index fc7b219d54d..22b16a29dde 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -3898,6 +3898,12 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS); // 100yd }); + // Coldflame (Lord Marrowgar) + ApplySpellFix({ 69146, 70823, 70824, 70825 }, [](SpellInfo* spellInfo) + { + spellInfo->AttributesEx4 &= ~SPELL_ATTR4_IGNORE_RESISTANCES; + }); + // Shadow's Fate ApplySpellFix({ 71169 }, [](SpellInfo* spellInfo) { @@ -4127,13 +4133,13 @@ void SpellMgr::LoadSpellInfoCorrections() // Summon Shadow Trap ApplySpellFix({ 73540 }, [](SpellInfo* spellInfo) { - spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(23); // 90 seconds + spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(3); // 60 seconds }); // Shadow Trap (visual) ApplySpellFix({ 73530 }, [](SpellInfo* spellInfo) { - spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(28); // 5 seconds + spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(27); // 3 seconds }); // Shadow Trap @@ -4459,6 +4465,18 @@ void SpellMgr::LoadSpellInfoCorrections() spellInfo->ProcFlags = 0; }); + // Shadowstep + ApplySpellFix({ 36563 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS); // 4yd + }); + + // Feral Charge - Cat + ApplySpellFix({ 49376 }, [](SpellInfo* spellInfo) + { + spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS); // 3yd + }); + for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo* spellInfo = mSpellInfoMap[i]; diff --git a/src/server/game/Spells/SpellScript.h b/src/server/game/Spells/SpellScript.h index 03f06961a4b..7e63770c3e1 100644 --- a/src/server/game/Spells/SpellScript.h +++ b/src/server/game/Spells/SpellScript.h @@ -786,13 +786,13 @@ class TC_GAME_API AuraScript : public _SpellScript HookList<EffectAbsorbHandler> AfterEffectAbsorb; // executed when mana shield aura effect is going to reduce damage - // example: OnEffectManaShield += AuraEffectAbsorbFn(class::function, EffectIndexSpecifier); + // example: OnEffectManaShield += AuraEffectManaShieldFn(class::function, EffectIndexSpecifier); // where function is: void function (AuraEffect* aurEff, DamageInfo& dmgInfo, uint32& absorbAmount); HookList<EffectManaShieldHandler> OnEffectManaShield; #define AuraEffectManaShieldFn(F, I) EffectManaShieldFunction(&F, I) // executed after mana shield aura effect reduced damage to target - absorbAmount is real amount absorbed by aura - // example: AfterEffectManaShield += AuraEffectAbsorbFn(class::function, EffectIndexSpecifier); + // example: AfterEffectManaShield += AuraEffectManaShieldFn(class::function, EffectIndexSpecifier); // where function is: void function (AuraEffect* aurEff, DamageInfo& dmgInfo, uint32& absorbAmount); HookList<EffectManaShieldHandler> AfterEffectManaShield; diff --git a/src/server/game/Tools/PlayerDump.cpp b/src/server/game/Tools/PlayerDump.cpp index 0357fcc4e15..6bcf3128427 100644 --- a/src/server/game/Tools/PlayerDump.cpp +++ b/src/server/game/Tools/PlayerDump.cpp @@ -936,7 +936,7 @@ DumpReturn PlayerDumpReader::LoadDump(std::string const& file, uint32 account, s if (!ChangeColumn(ts, line, "at_login", "1")) return DUMP_FILE_BROKEN; } - else if (!ChangeColumn(ts, line, "name", name.c_str())) // characters.name + else if (!ChangeColumn(ts, line, "name", name)) // characters.name return DUMP_FILE_BROKEN; break; } diff --git a/src/server/game/Warden/WardenCheckMgr.cpp b/src/server/game/Warden/WardenCheckMgr.cpp index 3b8387e3381..5a7a6bf49dd 100644 --- a/src/server/game/Warden/WardenCheckMgr.cpp +++ b/src/server/game/Warden/WardenCheckMgr.cpp @@ -38,6 +38,8 @@ WardenCheckMgr::~WardenCheckMgr() void WardenCheckMgr::LoadWardenChecks() { + uint32 oldMSTime = getMSTime(); + // Check if Warden is enabled by config before loading anything if (!sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED)) { @@ -141,11 +143,13 @@ void WardenCheckMgr::LoadWardenChecks() } while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %u warden checks.", count); + TC_LOG_INFO("server.loading", ">> Loaded %u warden checks in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } void WardenCheckMgr::LoadWardenOverrides() { + uint32 oldMSTime = getMSTime(); + // Check if Warden is enabled by config before loading anything if (!sWorld->getBoolConfig(CONFIG_WARDEN_ENABLED)) { @@ -187,7 +191,7 @@ void WardenCheckMgr::LoadWardenOverrides() } while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %u warden action overrides.", count); + TC_LOG_INFO("server.loading", ">> Loaded %u warden action overrides in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } WardenCheckMgr* WardenCheckMgr::instance() diff --git a/src/server/game/Weather/WeatherMgr.cpp b/src/server/game/Weather/WeatherMgr.cpp index 95728b7e8ae..98fbf897e27 100644 --- a/src/server/game/Weather/WeatherMgr.cpp +++ b/src/server/game/Weather/WeatherMgr.cpp @@ -95,7 +95,7 @@ void LoadWeatherData() if (!result) { - TC_LOG_ERROR("server.loading", ">> Loaded 0 weather definitions. DB table `game_weather` is empty."); + TC_LOG_INFO("server.loading", ">> Loaded 0 weather definitions. DB table `game_weather` is empty."); return; } diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index ae5cc87d097..03081a113de 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -81,7 +81,7 @@ #include "VMapFactory.h" #include "VMapManager2.h" #include "WardenCheckMgr.h" -#include "WaypointMovementGenerator.h" +#include "WaypointManager.h" #include "WeatherMgr.h" #include "WhoListStorage.h" #include "WorldSession.h" @@ -134,6 +134,11 @@ World::World() memset(m_int_configs, 0, sizeof(m_int_configs)); memset(m_bool_configs, 0, sizeof(m_bool_configs)); memset(m_float_configs, 0, sizeof(m_float_configs)); + + _guidWarn = false; + _guidAlert = false; + _warnDiff = 0; + _warnShutdownTime = time(NULL); } /// World destructor @@ -196,6 +201,59 @@ void World::SetClosed(bool val) sScriptMgr->OnOpenStateChange(!val); } +void World::TriggerGuidWarning() +{ + // Lock this only to prevent multiple maps triggering at the same time + std::lock_guard<std::mutex> lock(_guidAlertLock); + + time_t gameTime = GameTime::GetGameTime(); + time_t today = (gameTime / DAY) * DAY; + + // Check if our window to restart today has passed. 5 mins until quiet time + while (gameTime >= (today + (getIntConfig(CONFIG_RESPAWN_RESTARTQUIETTIME) * HOUR) - 1810)) + today += DAY; + + // Schedule restart for 30 minutes before quiet time, or as long as we have + _warnShutdownTime = today + (getIntConfig(CONFIG_RESPAWN_RESTARTQUIETTIME) * HOUR) - 1800; + + _guidWarn = true; + SendGuidWarning(); +} + +void World::TriggerGuidAlert() +{ + // Lock this only to prevent multiple maps triggering at the same time + std::lock_guard<std::mutex> lock(_guidAlertLock); + + DoGuidAlertRestart(); + _guidAlert = true; + _guidWarn = false; +} + +void World::DoGuidWarningRestart() +{ + if (m_ShutdownTimer) + return; + + ShutdownServ(1800, SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE); + _warnShutdownTime += HOUR; +} + +void World::DoGuidAlertRestart() +{ + if (m_ShutdownTimer) + return; + + ShutdownServ(300, SHUTDOWN_MASK_RESTART, RESTART_EXIT_CODE, _alertRestartReason); +} + +void World::SendGuidWarning() +{ + if (!m_ShutdownTimer && _guidWarn && getIntConfig(CONFIG_RESPAWN_GUIDWARNING_FREQUENCY) > 0) + SendServerMessage(SERVER_MSG_STRING, _guidWarningMsg.c_str()); + _warnDiff = 0; +} + /// Find a session by its id WorldSession* World::FindSession(uint32 id) const { @@ -670,6 +728,27 @@ void World::LoadConfigSettings(bool reload) m_float_configs[CONFIG_GROUP_XP_DISTANCE] = sConfigMgr->GetFloatDefault("MaxGroupXPDistance", 74.0f); m_float_configs[CONFIG_MAX_RECRUIT_A_FRIEND_DISTANCE] = sConfigMgr->GetFloatDefault("MaxRecruitAFriendBonusDistance", 100.0f); + m_int_configs[CONFIG_MIN_QUEST_SCALED_XP_RATIO] = sConfigMgr->GetIntDefault("MinQuestScaledXPRatio", 0); + if (m_int_configs[CONFIG_MIN_QUEST_SCALED_XP_RATIO] > 100) + { + TC_LOG_ERROR("server.loading", "MinQuestScaledXPRatio (%i) must be in range 0..100. Set to 0.", m_int_configs[CONFIG_MIN_QUEST_SCALED_XP_RATIO]); + m_int_configs[CONFIG_MIN_QUEST_SCALED_XP_RATIO] = 0; + } + + m_int_configs[CONFIG_MIN_CREATURE_SCALED_XP_RATIO] = sConfigMgr->GetIntDefault("MinCreatureScaledXPRatio", 0); + if (m_int_configs[CONFIG_MIN_CREATURE_SCALED_XP_RATIO] > 100) + { + TC_LOG_ERROR("server.loading", "MinCreatureScaledXPRatio (%i) must be in range 0..100. Set to 0.", m_int_configs[CONFIG_MIN_CREATURE_SCALED_XP_RATIO]); + m_int_configs[CONFIG_MIN_CREATURE_SCALED_XP_RATIO] = 0; + } + + m_int_configs[CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO] = sConfigMgr->GetIntDefault("MinDiscoveredScaledXPRatio", 0); + if (m_int_configs[CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO] > 100) + { + TC_LOG_ERROR("server.loading", "MinDiscoveredScaledXPRatio (%i) must be in range 0..100. Set to 0.", m_int_configs[CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO]); + m_int_configs[CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO] = 0; + } + /// @todo Add MonsterSight (with meaning) in worldserver.conf or put them as define m_float_configs[CONFIG_SIGHT_MONSTER] = sConfigMgr->GetFloatDefault("MonsterSight", 50.0f); @@ -916,6 +995,12 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_GROUP_VISIBILITY] = sConfigMgr->GetIntDefault("Visibility.GroupMode", 1); m_int_configs[CONFIG_MAIL_DELIVERY_DELAY] = sConfigMgr->GetIntDefault("MailDeliveryDelay", HOUR); + m_int_configs[CONFIG_CLEAN_OLD_MAIL_TIME] = sConfigMgr->GetIntDefault("CleanOldMailTime", 4); + if (m_int_configs[CONFIG_CLEAN_OLD_MAIL_TIME] > 23) + { + TC_LOG_ERROR("server.loading", "CleanOldMailTime (%u) must be an hour, between 0 and 23. Set to 4.", m_int_configs[CONFIG_CLEAN_OLD_MAIL_TIME]); + m_int_configs[CONFIG_CLEAN_OLD_MAIL_TIME] = 4; + } m_int_configs[CONFIG_UPTIME_UPDATE] = sConfigMgr->GetIntDefault("UpdateUptimeInterval", 10); if (int32(m_int_configs[CONFIG_UPTIME_UPDATE]) <= 0) @@ -1192,6 +1277,50 @@ void World::LoadConfigSettings(bool reload) m_int_configs[CONFIG_NO_GRAY_AGGRO_BELOW] = m_int_configs[CONFIG_NO_GRAY_AGGRO_ABOVE]; } + // Respawn Settings + m_int_configs[CONFIG_RESPAWN_MINCHECKINTERVALMS] = sConfigMgr->GetIntDefault("Respawn.MinCheckIntervalMS", 5000); + m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] = sConfigMgr->GetIntDefault("Respawn.DynamicMode", 0); + if (m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] > 1) + { + TC_LOG_ERROR("server.loading", "Invalid value for Respawn.DynamicMode (%u). Set to 0.", m_int_configs[CONFIG_RESPAWN_DYNAMICMODE]); + m_int_configs[CONFIG_RESPAWN_DYNAMICMODE] = 0; + } + m_bool_configs[CONFIG_RESPAWN_DYNAMIC_ESCORTNPC] = sConfigMgr->GetBoolDefault("Respawn.DynamicEscortNPC", false); + m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] = sConfigMgr->GetIntDefault("Respawn.GuidWarnLevel", 12000000); + if (m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] > 16777215) + { + TC_LOG_ERROR("server.loading", "Respawn.GuidWarnLevel (%u) cannot be greater than maximum GUID (16777215). Set to 12000000.", m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL]); + m_int_configs[CONFIG_RESPAWN_GUIDWARNLEVEL] = 12000000; + } + m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] = sConfigMgr->GetIntDefault("Respawn.GuidAlertLevel", 16000000); + if (m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] > 16777215) + { + TC_LOG_ERROR("server.loading", "Respawn.GuidWarnLevel (%u) cannot be greater than maximum GUID (16777215). Set to 16000000.", m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL]); + m_int_configs[CONFIG_RESPAWN_GUIDALERTLEVEL] = 16000000; + } + m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] = sConfigMgr->GetIntDefault("Respawn.RestartQuietTime", 3); + if (m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] > 23) + { + TC_LOG_ERROR("server.loading", "Respawn.RestartQuietTime (%u) must be an hour, between 0 and 23. Set to 3.", m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME]); + m_int_configs[CONFIG_RESPAWN_RESTARTQUIETTIME] = 3; + } + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = sConfigMgr->GetFloatDefault("Respawn.DynamicRateCreature", 10.0f); + if (m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] < 0.0f) + { + TC_LOG_ERROR("server.loading", "Respawn.DynamicRateCreature (%f) must be positive. Set to 10.", m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE]); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_CREATURE] = 10.0f; + } + m_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE] = sConfigMgr->GetIntDefault("Respawn.DynamicMinimumCreature", 10); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = sConfigMgr->GetFloatDefault("Respawn.DynamicRateGameObject", 10.0f); + if (m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] < 0.0f) + { + TC_LOG_ERROR("server.loading", "Respawn.DynamicRateGameObject (%f) must be positive. Set to 10.", m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT]); + m_float_configs[CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT] = 10.0f; + } + m_int_configs[CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT] = sConfigMgr->GetIntDefault("Respawn.DynamicMinimumGameObject", 10); + _guidWarningMsg = sConfigMgr->GetStringDefault("Respawn.WarningMessage", "There will be an unscheduled server restart at 03:00. The server will be available again shortly after."); + _alertRestartReason = sConfigMgr->GetStringDefault("Respawn.AlertRestartReason", "Urgent Maintenance"); + m_int_configs[CONFIG_RESPAWN_GUIDWARNING_FREQUENCY] = sConfigMgr->GetIntDefault("Respawn.WarningFrequency", 1800); ///- Read the "Data" directory from the config file std::string dataPath = sConfigMgr->GetStringDefault("DataDir", "./"); if (dataPath.empty() || (dataPath.at(dataPath.length()-1) != '/' && dataPath.at(dataPath.length()-1) != '\\')) @@ -1504,6 +1633,7 @@ void World::SetInitialWorldSettings() sObjectMgr->LoadPageTextLocales(); sObjectMgr->LoadGossipMenuItemsLocales(); sObjectMgr->LoadPointOfInterestLocales(); + sObjectMgr->LoadQuestGreetingsLocales(); sObjectMgr->SetDBCLocaleIndex(GetDefaultDbcLocale()); // Get once for all the locale index of DBC language (console/broadcasts) TC_LOG_INFO("server.loading", ">> Localization strings loaded in %u ms", GetMSTimeDiffToNow(oldMSTime)); @@ -1601,6 +1731,12 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Creature Base Stats..."); sObjectMgr->LoadCreatureClassLevelStats(); + TC_LOG_INFO("server.loading", "Loading Spawn Group Templates..."); + sObjectMgr->LoadSpawnGroupTemplates(); + + TC_LOG_INFO("server.loading", "Loading instance spawn groups..."); + sObjectMgr->LoadInstanceSpawnGroups(); + TC_LOG_INFO("server.loading", "Loading Creature Data..."); sObjectMgr->LoadCreatures(); @@ -1617,10 +1753,13 @@ void World::SetInitialWorldSettings() sObjectMgr->LoadCreatureAddons(); // must be after LoadCreatureTemplates() and LoadCreatures() TC_LOG_INFO("server.loading", "Loading Gameobject Data..."); - sObjectMgr->LoadGameobjects(); + sObjectMgr->LoadGameObjects(); + + TC_LOG_INFO("server.loading", "Loading Spawn Group Data..."); + sObjectMgr->LoadSpawnGroups(); TC_LOG_INFO("server.loading", "Loading GameObject Addon Data..."); - sObjectMgr->LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameobjects() + sObjectMgr->LoadGameObjectAddons(); // must be after LoadGameObjectTemplate() and LoadGameObjects() TC_LOG_INFO("server.loading", "Loading GameObject Quest Items..."); sObjectMgr->LoadGameObjectQuestItems(); @@ -1646,11 +1785,15 @@ void World::SetInitialWorldSettings() TC_LOG_INFO("server.loading", "Loading Quests Starters and Enders..."); sObjectMgr->LoadQuestStartersAndEnders(); // must be after quest load + TC_LOG_INFO("server.loading", "Loading Quests Greetings..."); + sObjectMgr->LoadQuestGreetings(); // must be loaded after creature_template, gameobject_template tables + TC_LOG_INFO("server.loading", "Loading Objects Pooling Data..."); sPoolMgr->LoadFromDB(); TC_LOG_INFO("server.loading", "Loading Game Event Data..."); // must be after loading pools fully - sGameEventMgr->LoadFromDB(); + sGameEventMgr->LoadHolidayDates(); // Must be after loading DBC + sGameEventMgr->LoadFromDB(); // Must be after loading holiday dates TC_LOG_INFO("server.loading", "Loading UNIT_NPC_FLAG_SPELLCLICK Data..."); // must be after LoadQuests sObjectMgr->LoadNPCSpellClickSpells(); @@ -1914,7 +2057,8 @@ void World::SetInitialWorldSettings() tm localTm; time_t gameTime = GameTime::GetGameTime(); localtime_r(&gameTime, &localTm); - mail_timer = ((((localTm.tm_hour + 20) % 24)* HOUR * IN_MILLISECONDS) / m_timers[WUPDATE_AUCTIONS].GetInterval()); + uint8 CleanOldMailsTime = getIntConfig(CONFIG_CLEAN_OLD_MAIL_TIME); + mail_timer = ((((localTm.tm_hour + (24 - CleanOldMailsTime)) % 24)* HOUR * IN_MILLISECONDS) / m_timers[WUPDATE_AUCTIONS].GetInterval()); //1440 mail_timer_expires = ((DAY * IN_MILLISECONDS) / (m_timers[WUPDATE_AUCTIONS].GetInterval())); TC_LOG_INFO("server.loading", "Mail timer set to: " UI64FMTD ", mail return is called every " UI64FMTD " minutes", uint64(mail_timer), uint64(mail_timer_expires)); @@ -2289,6 +2433,16 @@ void World::Update(uint32 diff) // update the instance reset times sInstanceSaveMgr->Update(); + // Check for shutdown warning + if (_guidWarn && !_guidAlert) + { + _warnDiff += diff; + if (GameTime::GetGameTime() >= _warnShutdownTime) + DoGuidWarningRestart(); + else if (_warnDiff > getIntConfig(CONFIG_RESPAWN_GUIDWARNING_FREQUENCY) * IN_MILLISECONDS) + SendGuidWarning(); + } + // And last, but not least handle the issued cli commands ProcessCliCommands(); diff --git a/src/server/game/World/World.h b/src/server/game/World/World.h index 5254b32bbdf..8d06620641b 100644 --- a/src/server/game/World/World.h +++ b/src/server/game/World/World.h @@ -202,7 +202,6 @@ enum WorldFloatConfigs CONFIG_ARENA_WIN_RATING_MODIFIER_2, CONFIG_ARENA_LOSE_RATING_MODIFIER, CONFIG_ARENA_MATCHMAKER_RATING_MODIFIER, - CONFIG_RESPAWN_DYNAMICRADIUS, CONFIG_RESPAWN_DYNAMICRATE_CREATURE, CONFIG_RESPAWN_DYNAMICRATE_GAMEOBJECT, FLOAT_CONFIG_VALUE_COUNT @@ -251,6 +250,9 @@ enum WorldIntConfigs CONFIG_DAILY_QUEST_RESET_TIME_HOUR, CONFIG_MAX_PRIMARY_TRADE_SKILL, CONFIG_MIN_PETITION_SIGNS, + CONFIG_MIN_QUEST_SCALED_XP_RATIO, + CONFIG_MIN_CREATURE_SCALED_XP_RATIO, + CONFIG_MIN_DISCOVERED_SCALED_XP_RATIO, CONFIG_GM_LOGIN_STATE, CONFIG_GM_VISIBLE_STATE, CONFIG_GM_ACCEPT_TICKETS, @@ -263,6 +265,7 @@ enum WorldIntConfigs CONFIG_FORCE_SHUTDOWN_THRESHOLD, CONFIG_GROUP_VISIBILITY, CONFIG_MAIL_DELIVERY_DELAY, + CONFIG_CLEAN_OLD_MAIL_TIME, CONFIG_UPTIME_UPDATE, CONFIG_SKILL_CHANCE_ORANGE, CONFIG_SKILL_CHANCE_YELLOW, @@ -379,13 +382,11 @@ enum WorldIntConfigs CONFIG_AUCTION_GETALL_DELAY, CONFIG_AUCTION_SEARCH_DELAY, CONFIG_TALENTS_INSPECTING, - CONFIG_RESPAWN_MINCELLCHECKMS, + CONFIG_RESPAWN_MINCHECKINTERVALMS, CONFIG_RESPAWN_DYNAMICMODE, CONFIG_RESPAWN_GUIDWARNLEVEL, CONFIG_RESPAWN_GUIDALERTLEVEL, CONFIG_RESPAWN_RESTARTQUIETTIME, - CONFIG_RESPAWN_ACTIVITYSCOPECREATURE, - CONFIG_RESPAWN_ACTIVITYSCOPEGAMEOBJECT, CONFIG_RESPAWN_DYNAMICMINIMUM_CREATURE, CONFIG_RESPAWN_DYNAMICMINIMUM_GAMEOBJECT, CONFIG_RESPAWN_GUIDWARNING_FREQUENCY, @@ -765,6 +766,10 @@ class TC_GAME_API World void ReloadRBAC(); void RemoveOldCorpses(); + void TriggerGuidWarning(); + void TriggerGuidAlert(); + bool IsGuidWarning() { return _guidWarn; } + bool IsGuidAlert() { return _guidAlert; } protected: void _UpdateGameTime(); @@ -859,7 +864,21 @@ class TC_GAME_API World AutobroadcastsWeightMap m_AutobroadcastsWeights; void ProcessQueryCallbacks(); + + void SendGuidWarning(); + void DoGuidWarningRestart(); + void DoGuidAlertRestart(); QueryCallbackProcessor _queryProcessor; + + std::string _guidWarningMsg; + std::string _alertRestartReason; + + std::mutex _guidAlertLock; + + bool _guidWarn; + bool _guidAlert; + uint32 _warnDiff; + time_t _warnShutdownTime; }; TC_GAME_API extern Realm realm; |
