TrinityCore
GameObject.cpp
Go to the documentation of this file.
1/*
2 * This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12 * more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "GameObject.h"
19#include "ArtifactPackets.h"
20#include "AzeriteItem.h"
21#include "AzeritePackets.h"
22#include "Battleground.h"
23#include "BattlegroundPackets.h"
24#include "CellImpl.h"
25#include "Containers.h"
26#include "CreatureAISelector.h"
27#include "DatabaseEnv.h"
28#include "DB2Stores.h"
29#include "G3DPosition.hpp"
30#include "GameEventSender.h"
31#include "GameObjectAI.h"
32#include "GameObjectModel.h"
33#include "GameObjectPackets.h"
34#include "GameTime.h"
35#include "GossipDef.h"
36#include "GridNotifiersImpl.h"
37#include "Group.h"
38#include "Item.h"
39#include "ItemBonusMgr.h"
40#include "Log.h"
41#include "Loot.h"
42#include "LootMgr.h"
43#include "Map.h"
44#include "MapManager.h"
45#include "MiscPackets.h"
46#include "ObjectAccessor.h"
47#include "ObjectMgr.h"
48#include "OutdoorPvPMgr.h"
49#include "PhasingHandler.h"
50#include "PoolMgr.h"
51#include "QueryPackets.h"
52#include "Util.h"
53#include "SpellAuras.h"
54#include "SpellMgr.h"
55#include "Transport.h"
56#include "Vignette.h"
57#include "World.h"
58#include <G3D/Box.h>
59#include <G3D/CoordinateFrame.h>
60#include <G3D/Quat.h>
61#include <sstream>
62
64{
65 for (uint8 loc = LOCALE_enUS; loc < TOTAL_LOCALES; ++loc)
66 QueryData[loc] = BuildQueryData(static_cast<LocaleConstant>(loc));
67}
68
70{
72
73 queryTemp.GameObjectID = entry;
74
75 queryTemp.Allow = true;
77
78 stats.Type = type;
79 stats.DisplayID = displayId;
80
81 stats.Name[0] = name;
82 stats.IconName = IconName;
84 stats.UnkString = unk1;
85
86 if (loc != LOCALE_enUS)
87 if (GameObjectLocale const* gameObjectLocale = sObjectMgr->GetGameObjectLocale(entry))
88 {
89 ObjectMgr::GetLocaleString(gameObjectLocale->Name, loc, stats.Name[0]);
90 ObjectMgr::GetLocaleString(gameObjectLocale->CastBarCaption, loc, stats.CastBarCaption);
91 ObjectMgr::GetLocaleString(gameObjectLocale->Unk1, loc, stats.UnkString);
92 }
93
94 stats.Size = size;
95
96 if (std::vector<uint32> const* items = sObjectMgr->GetGameObjectQuestItemList(entry))
97 for (int32 item : *items)
98 stats.QuestItems.push_back(item);
99
100 memcpy(stats.Data.data(), raw.data, MAX_GAMEOBJECT_DATA * sizeof(int32));
102
103 queryTemp.Write();
104 queryTemp.ShrinkToFit();
105 return queryTemp.Move();
106}
107
109{
110 return fabs(x * x + y * y + z * z + w * w - 1.0f) < 1e-5f;
111}
112
113void QuaternionData::toEulerAnglesZYX(float& Z, float& Y, float& X) const
114{
115 G3D::Matrix3(G3D::Quat(x, y, z, w)).toEulerAnglesZYX(Z, Y, X);
116}
117
119{
120 G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(Z, Y, X));
121 return QuaternionData(quat.x, quat.y, quat.z, quat.w);
122}
123
125
127{
128//11 GAMEOBJECT_TYPE_TRANSPORT
130{
131public:
132 static constexpr Milliseconds PositionUpdateInterval = 50ms;
133
134 explicit Transport(GameObject& owner) : GameObjectTypeBase(owner), _animationInfo(sTransportMgr->GetTransportAnimInfo(owner.GetGOInfo()->entry)),
137 {
138 GameObjectTemplate const* goInfo = _owner.GetGOInfo();
139 if (goInfo->transport.Timeto2ndfloor > 0)
140 {
141 _stopFrames.push_back(goInfo->transport.Timeto2ndfloor);
142 if (goInfo->transport.Timeto3rdfloor > 0)
143 {
144 _stopFrames.push_back(goInfo->transport.Timeto3rdfloor);
145 if (goInfo->transport.Timeto4thfloor > 0)
146 {
147 _stopFrames.push_back(goInfo->transport.Timeto4thfloor);
148 if (goInfo->transport.Timeto5thfloor > 0)
149 {
150 _stopFrames.push_back(goInfo->transport.Timeto5thfloor);
151 if (goInfo->transport.Timeto6thfloor > 0)
152 {
153 _stopFrames.push_back(goInfo->transport.Timeto6thfloor);
154 if (goInfo->transport.Timeto7thfloor > 0)
155 {
156 _stopFrames.push_back(goInfo->transport.Timeto7thfloor);
157 if (goInfo->transport.Timeto8thfloor > 0)
158 {
159 _stopFrames.push_back(goInfo->transport.Timeto8thfloor);
160 if (goInfo->transport.Timeto9thfloor > 0)
161 {
162 _stopFrames.push_back(goInfo->transport.Timeto9thfloor);
163 if (goInfo->transport.Timeto10thfloor > 0)
164 _stopFrames.push_back(goInfo->transport.Timeto10thfloor);
165 }
166 }
167 }
168 }
169 }
170 }
171 }
172 }
173
174 if (!_stopFrames.empty())
175 {
176 _pathProgress = 0;
178 }
179
181 }
182
183 void Update(uint32 diff) override
184 {
185 if (!_animationInfo)
186 return;
187
190 return;
191
193
195 uint32 period = GetTransportPeriod();
196 uint32 newProgress = 0;
197 if (_stopFrames.empty())
198 newProgress = now % period;
199 else
200 {
201 int32 stopTargetTime = 0;
203 stopTargetTime = 0;
204 else
206
207 if (now < uint32(*_owner.m_gameObjectData->Level))
208 {
209 int32 timeToStop = _owner.m_gameObjectData->Level - _stateChangeTime;
210 float stopSourcePathPct = float(_stateChangeProgress) / float(period);
211 float stopTargetPathPct = float(stopTargetTime) / float(period);
212 float timeSinceStopProgressPct = float(now - _stateChangeTime) / float(timeToStop);
213
214 float progressPct;
216 {
218 stopTargetPathPct = 1.0f;
219
220 float pathPctBetweenStops = stopTargetPathPct - stopSourcePathPct;
221 if (pathPctBetweenStops < 0.0f)
222 pathPctBetweenStops += 1.0f;
223
224 progressPct = pathPctBetweenStops * timeSinceStopProgressPct + stopSourcePathPct;
225 if (progressPct > 1.0f)
226 progressPct = progressPct - 1.0f;
227 }
228 else
229 {
230 float pathPctBetweenStops = stopSourcePathPct - stopTargetPathPct;
231 if (pathPctBetweenStops < 0.0f)
232 pathPctBetweenStops += 1.0f;
233
234 progressPct = stopSourcePathPct - pathPctBetweenStops * timeSinceStopProgressPct;
235 if (progressPct < 0.0f)
236 progressPct += 1.0f;
237 }
238
239 newProgress = uint32(float(period) * progressPct) % period;
240 }
241 else
242 newProgress = stopTargetTime;
243
244 if (int32(newProgress) == stopTargetTime && newProgress != _pathProgress)
245 {
246 uint32 eventId = [&]()
247 {
249 {
250 case 0:
252 case 1:
254 case 2:
256 case 3:
258 case 4:
260 case 5:
262 case 6:
264 case 7:
266 case 8:
268 case 9:
270 default:
271 return 0u;
272 }
273 }();
274 if (eventId)
275 GameEvents::Trigger(eventId, &_owner, &_owner);
276
278 {
279 GOState currentState = _owner.GetGoState();
280 GOState newState;
281 if (currentState == GO_STATE_TRANSPORT_ACTIVE)
283 else if (currentState - GO_STATE_TRANSPORT_ACTIVE == int32(_stopFrames.size()))
284 newState = GOState(currentState - 1);
286 newState = GOState(currentState - 1);
287 else
288 newState = GOState(currentState + 1);
289
290 _owner.SetGoState(newState);
291 }
292 }
293 }
294
295 if (_pathProgress == newProgress)
296 return;
297
298 _pathProgress = newProgress;
299
300 TransportAnimationEntry const* oldAnimation = _animationInfo->GetPrevAnimNode(newProgress);
301 TransportAnimationEntry const* newAnimation = _animationInfo->GetNextAnimNode(newProgress);
302 if (oldAnimation && newAnimation)
303 {
304 G3D::Matrix3 pathRotation = G3D::Quat(_owner.m_gameObjectData->ParentRotation->x, _owner.m_gameObjectData->ParentRotation->y,
305 _owner.m_gameObjectData->ParentRotation->z, _owner.m_gameObjectData->ParentRotation->w).toRotationMatrix();
306
307 G3D::Vector3 prev(oldAnimation->Pos.X, oldAnimation->Pos.Y, oldAnimation->Pos.Z);
308 G3D::Vector3 next(newAnimation->Pos.X, newAnimation->Pos.Y, newAnimation->Pos.Z);
309
310 G3D::Vector3 dst = next;
311 if (prev != next)
312 {
313 float animProgress = float(newProgress - oldAnimation->TimeIndex) / float(newAnimation->TimeIndex - oldAnimation->TimeIndex);
314
315 dst = prev.lerp(next, animProgress);
316 }
317
318 dst = dst * pathRotation;
319 dst += PositionToVector3(&_owner.GetStationaryPosition());
320
321 _owner.GetMap()->GameObjectRelocation(&_owner, dst.x, dst.y, dst.z, _owner.GetOrientation());
322 }
323
324 TransportRotationEntry const* oldRotation = _animationInfo->GetPrevAnimRotation(newProgress);
325 TransportRotationEntry const* newRotation = _animationInfo->GetNextAnimRotation(newProgress);
326 if (oldRotation && newRotation)
327 {
328 G3D::Quat prev(oldRotation->Rot[0], oldRotation->Rot[1], oldRotation->Rot[2], oldRotation->Rot[3]);
329 G3D::Quat next(newRotation->Rot[0], newRotation->Rot[1], newRotation->Rot[2], newRotation->Rot[3]);
330
331 G3D::Quat rotation = next;
332
333 if (prev != next)
334 {
335 float animProgress = float(newProgress - oldRotation->TimeIndex) / float(newRotation->TimeIndex - oldRotation->TimeIndex);
336
337 rotation = prev.slerp(next, animProgress);
338 }
339
340 _owner.SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w);
342 }
343
344 // update progress marker for client
345 _owner.SetPathProgressForClient(float(_pathProgress) / float(period));
346 }
347
348 void OnStateChanged(GOState oldState, GOState newState) override
349 {
351
352 // transports without stop frames just keep animating in state 24
353 if (_stopFrames.empty())
354 {
355 if (newState != GO_STATE_TRANSPORT_ACTIVE)
357 return;
358 }
359
360 int32 stopPathProgress = 0;
361
362 if (newState != GO_STATE_TRANSPORT_ACTIVE)
363 {
365 uint32 stopFrame = newState - GO_STATE_TRANSPORT_STOPPED;
366 ASSERT(stopFrame < _stopFrames.size());
367 stopPathProgress = _stopFrames[stopFrame];
368 }
369
372 uint32 timeToStop = std::abs(int32(_pathProgress) - stopPathProgress);
375
376 if (oldState == GO_STATE_ACTIVE || oldState == newState)
377 {
378 // initialization
379 if (int32(_pathProgress) > stopPathProgress)
381 else
383
384 return;
385 }
386
387 int32 pauseTimesCount = _stopFrames.size();
388 int32 newToOldStateDelta = newState - oldState;
389 if (newToOldStateDelta < 0)
390 newToOldStateDelta += pauseTimesCount + 1;
391
392 int32 oldToNewStateDelta = oldState - newState;
393 if (oldToNewStateDelta < 0)
394 oldToNewStateDelta += pauseTimesCount + 1;
395
396 // this additional check is neccessary because client doesn't check dynamic flags on progress update
397 // instead it multiplies progress from dynamicflags field by -1 and then compares that against 0
398 // when calculating path progress while we simply check the flag if (!_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT))
399 bool isAtStartOfPath = _stateChangeProgress == 0;
400
401 if (oldToNewStateDelta < newToOldStateDelta && !isAtStartOfPath)
403 else
405 }
406
407 void OnRelocated() override
408 {
410 }
411
413 {
414 for (WorldObject* passenger : _passengers)
415 {
416 float x, y, z, o;
417 passenger->m_movementInfo.transport.pos.GetPosition(x, y, z, o);
418 CalculatePassengerPosition(x, y, z, &o);
419 UpdatePassengerPosition(_owner.GetMap(), passenger, x, y, z, o, true);
420 }
421 }
422
424 {
425 if (_animationInfo)
427
428 return 1;
429 }
430
431 std::vector<uint32> const* GetPauseTimes() const
432 {
433 return &_stopFrames;
434 }
435
436 ObjectGuid GetTransportGUID() const override { return _owner.GetGUID(); }
437
438 float GetTransportOrientation() const override { return _owner.GetOrientation(); }
439
440 void AddPassenger(WorldObject* passenger) override
441 {
442 if (!_owner.IsInWorld())
443 return;
444
445 if (_passengers.insert(passenger).second)
446 {
447 passenger->SetTransport(this);
449 TC_LOG_DEBUG("entities.transport", "Object {} boarded transport {}.", passenger->GetName(), _owner.GetName());
450 }
451 }
452
454 {
455 if (_passengers.erase(passenger) > 0)
456 {
457 passenger->SetTransport(nullptr);
458 passenger->m_movementInfo.transport.Reset();
459 TC_LOG_DEBUG("entities.transport", "Object {} removed from transport {}.", passenger->GetName(), _owner.GetName());
460
461 if (Player* plr = passenger->ToPlayer())
462 plr->SetFallInformation(0, plr->GetPositionZ());
463 }
464
465 return this;
466 }
467
468 void CalculatePassengerPosition(float& x, float& y, float& z, float* o) const override
469 {
471 }
472
473 void CalculatePassengerOffset(float& x, float& y, float& z, float* o) const override
474 {
476 }
477
478 int32 GetMapIdForSpawning() const override
479 {
481 }
482
484 {
486 }
487
488private:
493 std::vector<uint32> _stopFrames;
496 std::unordered_set<WorldObject*> _passengers;
497};
498
500{
501}
502
504{
505 if (Transport* transport = dynamic_cast<Transport*>(&type))
506 transport->SetAutoCycleBetweenStopFrames(_on);
507}
508
510{
511public:
513
514 void SetState(FlagState newState, Player* player)
515 {
516 if (_state == newState)
517 return;
518
519 FlagState oldState = _state;
520 _state = newState;
521
522 if (player && newState == FlagState::Taken)
523 _carrierGUID = player->GetGUID();
524 else
526
527 if (newState == FlagState::Taken && oldState == FlagState::InBase)
529 else if (newState == FlagState::InBase || newState == FlagState::Respawning)
531
533
534 if (newState == FlagState::Respawning)
536 else
537 _respawnTime = 0;
538
539 if (ZoneScript* zoneScript = _owner.GetZoneScript())
540 zoneScript->OnFlagStateChange(&_owner, oldState, _state, player);
541 }
542
543 void Update([[maybe_unused]] uint32 diff) override
544 {
546 SetState(FlagState::InBase, nullptr);
547 }
548
549 bool IsNeverVisibleFor([[maybe_unused]] WorldObject const* seer, [[maybe_unused]] bool allowServersideObjects) const override
550 {
551 return _state != FlagState::InBase;
552 }
553
554 FlagState GetState() const { return _state; }
555 ObjectGuid const& GetCarrierGUID() const { return _carrierGUID; }
556 time_t GetTakenFromBaseTime() const { return _takenFromBaseTime; }
557
558private:
563};
564
565SetNewFlagState::SetNewFlagState(FlagState state, Player* player) : _state(state), _player(player)
566{
567}
568
570{
571 if (NewFlag* newFlag = dynamic_cast<NewFlag*>(&type))
572 newFlag->SetState(_state, _player);
573}
574
576{
577public:
578 explicit ControlZone(GameObject& owner) : GameObjectTypeBase(owner), _value(static_cast<float>(owner.GetGOInfo()->controlZone.startingValue))
579 {
580 if (owner.GetMap()->Instanceable())
581 _heartbeatRate = 1s;
582 else if (owner.GetGOInfo()->controlZone.FrequentHeartbeat)
583 _heartbeatRate = 2500ms;
584 else
585 _heartbeatRate = 5s;
586
589 _contestedTriggered = false;
590 }
591
592 void Update(uint32 diff) override
593 {
595 return;
596
599 {
602 }
603 }
604
606 {
607 if (_value < GetMaxHordeValue())
608 return TEAM_HORDE;
609
611 return TEAM_ALLIANCE;
612
613 return TEAM_NEUTRAL;
614 }
615
617
618 void ActivateObject(GameObjectActions action, int32 /*param*/, WorldObject* /*spellCaster*/, uint32 /*spellId*/, int32 /*effectIndex*/) override
619 {
620 switch (action)
621 {
623 for (ObjectGuid const& guid : _insidePlayers)
624 if (Player* player = ObjectAccessor::GetPlayer(_owner, guid))
625 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0);
626
627 _insidePlayers.clear();
628 break;
629 default:
630 break;
631 }
632 }
633
634 void SetValue(float value)
635 {
636 _value = RoundToInterval<float>(value, 0.0f, 100.0f);
637 }
638
640 {
641 // update player list inside control zone
642 std::vector<Player*> targetList;
643 SearchTargets(targetList);
644
645 TeamId oldControllingTeam = GetControllingTeam();
646 float pointsGained = CalculatePointsPerSecond(targetList) * _heartbeatRate.count() / 1000.0f;
647 if (pointsGained == 0)
648 return;
649
650 int32 oldRoundedValue = static_cast<int32>(_value);
651 SetValue(_value + pointsGained);
652 int32 roundedValue = static_cast<int32>(_value);
653 if (oldRoundedValue == roundedValue)
654 return;
655
656 TeamId newControllingTeam = GetControllingTeam();
657 TeamId assaultingTeam = pointsGained > 0 ? TEAM_ALLIANCE : TEAM_HORDE;
658
659 if (oldControllingTeam != newControllingTeam)
660 _contestedTriggered = false;
661
662 if (oldControllingTeam != TEAM_ALLIANCE && newControllingTeam == TEAM_ALLIANCE)
664 else if (oldControllingTeam != TEAM_HORDE && newControllingTeam == TEAM_HORDE)
666 else if (oldControllingTeam == TEAM_HORDE && newControllingTeam == TEAM_NEUTRAL)
668 else if (oldControllingTeam == TEAM_ALLIANCE && newControllingTeam == TEAM_NEUTRAL)
670
671 if (roundedValue == 100 && newControllingTeam == TEAM_ALLIANCE && assaultingTeam == TEAM_ALLIANCE)
673 else if (roundedValue == 0 && newControllingTeam == TEAM_HORDE && assaultingTeam == TEAM_HORDE)
675
676 if (oldRoundedValue == 100 && assaultingTeam == TEAM_HORDE && !_contestedTriggered)
677 {
679 _contestedTriggered = true;
680 }
681 else if (oldRoundedValue == 0 && assaultingTeam == TEAM_ALLIANCE && !_contestedTriggered)
682 {
684 _contestedTriggered = true;
685 }
686
687 for (Player* player : targetList)
688 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, roundedValue);
689 }
690
691 void SearchTargets(std::vector<Player*>& targetList)
692 {
696 HandleUnitEnterExit(targetList);
697 }
698
699 float CalculatePointsPerSecond(std::vector<Player*> const& targetList)
700 {
701 int32 delta = 0;
702
703 for (Player* player : targetList)
704 {
705 if (!player->IsOutdoorPvPActive())
706 continue;
707
708 if (player->GetTeamId() == TEAM_HORDE)
709 delta--;
710 else
711 delta++;
712 }
713
718
719 if (static_cast<uint32>(std::abs(delta)) < minSuperiority)
720 return 0;
721
722 float slope = (static_cast<float>(minTime) - maxTime) / (maxSuperiority - minSuperiority);
723 float intercept = maxTime - slope * minSuperiority;
724 float timeNeeded = slope * std::abs(delta) + intercept;
725 float percentageIncrease = 100.0f / timeNeeded;
726
727 if (delta < 0)
728 percentageIncrease *= -1;
729
730 return percentageIncrease;
731 }
732
733 void HandleUnitEnterExit(std::vector<Player*> const& newTargetList)
734 {
735 GuidUnorderedSet exitPlayers(std::move(_insidePlayers));
736
737 std::vector<Player*> enteringPlayers;
738
739 for (Player* unit : newTargetList)
740 {
741 if (exitPlayers.erase(unit->GetGUID()) == 0) // erase(key_type) returns number of elements erased
742 enteringPlayers.push_back(unit);
743
744 _insidePlayers.insert(unit->GetGUID());
745 }
746
747 for (Player* player : enteringPlayers)
748 {
749 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 1);
750 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, static_cast<int32>(_value));
752 }
753
754 for (ObjectGuid const& exitPlayerGuid : exitPlayers)
755 {
756 if (Player* player = ObjectAccessor::GetPlayer(_owner, exitPlayerGuid))
757 {
758 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0);
759 }
760 }
761 }
762
763 float GetMaxHordeValue() const
764 {
765 // ex: if neutralPercent is 40; then 0 - 30 is Horde Controlled
766 return 50.0f - _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f;
767 }
768
770 {
771 // ex: if neutralPercent is 40; then 70 - 100 is Alliance Controlled
772 return 50.0f + _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f;
773 }
774
775 void TriggerEvent(uint32 eventId) const
776 {
777 if (eventId <= 0)
778 return;
779
781 GameEvents::Trigger(eventId, &_owner, nullptr);
782 }
783
785 {
787 }
788
789private:
794 float _value;
796};
797
799{
800}
801
803{
804 if (ControlZone* controlZone = dynamic_cast<ControlZone*>(&type))
805 {
806 uint32 value = controlZone->GetStartingValue();
807 if (_value.has_value())
808 value = *_value;
809
810 controlZone->SetValue(value);
811 }
812}
813}
814
816 m_model(nullptr), m_goValue(), m_AI(nullptr), m_respawnCompatibilityMode(false), _animKitId(0), _worldEffectID(0)
817{
820
822 m_updateFlag.Rotation = true;
823
824 m_respawnTime = 0;
825 m_respawnDelayTime = 300;
826 m_despawnDelay = 0;
828 m_restockTime = 0;
830 m_spawnedByDefault = true;
831 m_usetimes = 0;
832 m_spellId = 0;
833 m_cooldownTime = 0;
835 m_goInfo = nullptr;
836 m_goData = nullptr;
838 m_goTemplateAddon = nullptr;
839
840 m_spawnId = UI64LIT(0);
841
842 ResetLootMode(); // restore default loot mode
843 m_stationaryPosition.Relocate(0.0f, 0.0f, 0.0f, 0.0f);
844}
845
847{
848 delete m_AI;
849 delete m_model;
850}
851
853{
854 delete m_AI;
855 m_AI = nullptr;
856}
857
859{
860 AIM_Destroy();
861
863
864 if (!m_AI)
865 return false;
866
868 return true;
869}
870
871std::string const& GameObject::GetAIName() const
872{
873 return sObjectMgr->GetGameObjectTemplate(GetEntry())->AIName;
874}
875
876void GameObject::CleanupsBeforeDelete(bool finalCleanup)
877{
878 SetVignette(0);
879
881
883}
884
886{
887 ObjectGuid ownerGUID = GetOwnerGUID();
888 if (!ownerGUID)
889 return;
890
891 if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID))
892 {
893 owner->RemoveGameObject(this, false);
895 return;
896 }
897
898 // This happens when a mage portal is despawned after the caster changes map (for example using the portal)
899 TC_LOG_DEBUG("misc", "Removed GameObject ({} Entry: {} SpellId: {} LinkedGO: {}) that just lost any reference to the owner ({}) GO list",
900 GetGUID().ToString(), GetGOInfo()->entry, m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), ownerGUID.ToString());
902}
903
905{
907 if (!IsInWorld())
908 {
909 if (m_zoneScript)
911
913 if (m_spawnId)
914 GetMap()->GetGameObjectBySpawnIdStore().insert(std::make_pair(m_spawnId, this));
915
916 // The state can be changed after GameObject::Create but before GameObject::AddToWorld
917 bool toggledState = GetGoType() == GAMEOBJECT_TYPE_CHEST ? getLootState() == GO_READY : (GetGoState() == GO_STATE_READY || IsTransport());
918 if (m_model)
919 {
920 if (Transport* trans = ToTransport())
921 trans->SetDelayedAddModelToMap();
922 else
924 }
925
926 EnableCollision(toggledState);
928 }
929}
930
932{
934 if (IsInWorld())
935 {
936 if (m_zoneScript)
938
940 if (m_model)
941 if (GetMap()->ContainsGameObjectModel(*m_model))
943
944 // If linked trap exists, despawn it
945 if (GameObject* linkedTrap = GetLinkedTrap())
946 linkedTrap->DespawnOrUnsummon();
947
949
950 if (m_spawnId)
951 Trinity::Containers::MultimapErasePair(GetMap()->GetGameObjectBySpawnIdStore(), m_spawnId, this);
953 }
954}
955
956bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit, bool dynamic, ObjectGuid::LowType spawnid)
957{
958 ASSERT(map);
959 SetMap(map);
960
961 Relocate(pos);
963 if (!IsPositionValid())
964 {
965 TC_LOG_ERROR("misc", "Gameobject (Spawn id: {} Entry: {}) not created. Suggested coordinates isn't valid (X: {} Y: {})", GetSpawnId(), entry, pos.GetPositionX(), pos.GetPositionY());
966 return false;
967 }
968
969 // Set if this object can handle dynamic spawns
970 if (!dynamic)
972
974
976 if (m_zoneScript)
977 {
979 if (!entry)
980 return false;
981 }
982
983 GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry);
984 if (!goInfo)
985 {
986 TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: {} Entry: {}) not created: non-existing entry in `gameobject_template`. Map: {} (X: {} Y: {} Z: {})", GetSpawnId(), entry, map->GetId(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
987 return false;
988 }
989
991 {
992 TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: {} Entry: {}) not created: gameobject type GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT cannot be manually created.", GetSpawnId(), entry);
993 return false;
994 }
995
996 ObjectGuid guid;
997 if (goInfo->type != GAMEOBJECT_TYPE_TRANSPORT)
998 guid = ObjectGuid::Create<HighGuid::GameObject>(map->GetId(), goInfo->entry, map->GenerateLowGuid<HighGuid::GameObject>());
999 else
1000 {
1001 guid = ObjectGuid::Create<HighGuid::Transport>(map->GenerateLowGuid<HighGuid::Transport>());
1002 m_updateFlag.ServerTime = true;
1003 }
1004
1005 Object::_Create(guid);
1006
1007 m_goInfo = goInfo;
1008 m_goTemplateAddon = sObjectMgr->GetGameObjectTemplateAddon(entry);
1009
1010 if (goInfo->type >= MAX_GAMEOBJECT_TYPE)
1011 {
1012 TC_LOG_ERROR("sql.sql", "Gameobject ({} Spawn id: {} Entry: {}) not created: non-existing GO type '{}' in `gameobject_template`. It will crash client if created.", guid.ToString(), GetSpawnId(), entry, goInfo->type);
1013 return false;
1014 }
1015
1016 SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w);
1017 GameObjectAddon const* gameObjectAddon = sObjectMgr->GetGameObjectAddon(GetSpawnId());
1018
1019 // For most of gameobjects is (0, 0, 0, 1) quaternion, there are only some transports with not standard rotation
1020 QuaternionData parentRotation;
1021 if (gameObjectAddon)
1022 parentRotation = gameObjectAddon->ParentRotation;
1023
1024 SetParentRotation(parentRotation);
1025
1026 SetObjectScale(goInfo->size);
1027
1028 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1029 {
1030 SetFaction(goOverride->Faction);
1031 ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
1032 }
1033
1035 {
1037 {
1038 m_updateFlag.GameObject = true;
1040 }
1041
1044 }
1045
1046 SetEntry(goInfo->entry);
1047
1048 // set name for logs usage, doesn't affect anything ingame
1049 SetName(goInfo->name);
1050
1051 SetDisplayId(goInfo->displayId);
1052
1053 CreateModel();
1054 // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3
1055 SetGoType(GameobjectTypes(goInfo->type));
1056 m_prevGoState = goState;
1057 SetGoState(goState);
1058 SetGoArtKit(artKit);
1059
1061
1062 switch (goInfo->type)
1063 {
1065 SetGoAnimProgress(animProgress);
1066 m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock);
1067 break;
1069 {
1070 // TODO: Get the values somehow, no longer in gameobject_template
1071 m_goValue.Building.Health = 20000/*goinfo->destructibleBuilding.intactNumHits + goinfo->destructibleBuilding.damagedNumHits*/;
1073 SetGoAnimProgress(255);
1074 // yes, even after the updatefield rewrite this garbage hack is still in client
1075 QuaternionData reinterpretId;
1076 memcpy(&reinterpretId.x, &m_goInfo->destructibleBuilding.DestructibleModelRec, sizeof(float));
1078 break;
1079 }
1081 {
1082 m_goTypeImpl = std::make_unique<GameObjectType::Transport>(*this);
1083 if (goInfo->transport.startOpen)
1085 else
1087
1088 SetGoAnimProgress(animProgress);
1089 setActive(true);
1090 break;
1091 }
1093 SetLevel(0);
1094 SetGoAnimProgress(255);
1095 break;
1097 if (GetGOInfo()->trap.stealthed)
1098 {
1101 }
1102
1103 if (GetGOInfo()->trap.stealthAffected)
1104 {
1107 }
1108 break;
1110 m_goTypeImpl = std::make_unique<GameObjectType::ControlZone>(*this);
1111 setActive(true);
1112 break;
1114 m_goTypeImpl = std::make_unique<GameObjectType::NewFlag>(*this);
1115 if (map->Instanceable())
1116 setActive(true);
1117 break;
1119 if (map->Instanceable())
1120 setActive(true);
1121 break;
1125 break;
1132 if (map->Instanceable())
1133 setActive(true);
1134 break;
1135 default:
1136 SetGoAnimProgress(animProgress);
1137 break;
1138 }
1139
1140 if (gameObjectAddon)
1141 {
1142 if (gameObjectAddon->InvisibilityValue)
1143 {
1144 m_invisibility.AddFlag(gameObjectAddon->invisibilityType);
1145 m_invisibility.AddValue(gameObjectAddon->invisibilityType, gameObjectAddon->InvisibilityValue);
1146 }
1147
1148 if (gameObjectAddon->WorldEffectID)
1149 {
1150 m_updateFlag.GameObject = true;
1151 SetWorldEffectID(gameObjectAddon->WorldEffectID);
1152 }
1153
1154 if (gameObjectAddon->AIAnimKitID)
1155 _animKitId = gameObjectAddon->AIAnimKitID;
1156 }
1157
1158 if (uint32 vignetteId = GetGOInfo()->GetSpawnVignette())
1159 SetVignette(vignetteId);
1160
1162
1163 m_stringIds[0] = goInfo->StringId;
1164
1166
1167 if (spawnid)
1168 m_spawnId = spawnid;
1169
1170 if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry())
1171 {
1172 if (GameObject* linkedGo = GameObject::CreateGameObject(linkedEntry, map, pos, rotation, 255, GO_STATE_READY))
1173 {
1174 SetLinkedTrap(linkedGo);
1175 if (!map->AddToMap(linkedGo))
1176 delete linkedGo;
1177 }
1178 }
1179
1180 // Check if GameObject is Infinite
1181 if (goInfo->IsInfiniteGameObject())
1183
1184 // Check if GameObject is Gigantic
1185 if (goInfo->IsGiganticGameObject())
1187
1188 // Check if GameObject is Large
1189 if (goInfo->IsLargeGameObject())
1191
1192 return true;
1193}
1194
1195GameObject* GameObject::CreateGameObject(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit /*= 0*/)
1196{
1197 GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry);
1198 if (!goInfo)
1199 return nullptr;
1200
1201 GameObject* go = new GameObject();
1202 if (!go->Create(entry, map, pos, rotation, animProgress, goState, artKit, false, 0))
1203 {
1204 delete go;
1205 return nullptr;
1206 }
1207
1208 return go;
1209}
1210
1212{
1213 GameObject* go = new GameObject();
1214 if (!go->LoadFromDB(spawnId, map, addToMap))
1215 {
1216 delete go;
1217 return nullptr;
1218 }
1219
1220 return go;
1221}
1222
1224{
1225 WorldObject::Update(diff);
1226
1227 if (AI())
1228 AI()->UpdateAI(diff);
1229 else if (!AIM_Initialize())
1230 TC_LOG_ERROR("misc", "Could not initialize GameObjectAI");
1231
1232 if (m_despawnDelay)
1233 {
1234 if (m_despawnDelay > diff)
1235 m_despawnDelay -= diff;
1236 else
1237 {
1238 m_despawnDelay = 0;
1240 }
1241 }
1242
1243 if (m_goTypeImpl)
1244 m_goTypeImpl->Update(diff);
1245
1246 if (m_perPlayerState)
1247 {
1248 for (auto itr = m_perPlayerState->begin(); itr != m_perPlayerState->end(); )
1249 {
1250 if (itr->second.ValidUntil > GameTime::GetSystemTime())
1251 {
1252 ++itr;
1253 continue;
1254 }
1255
1256 Player* seer = ObjectAccessor::GetPlayer(*this, itr->first);
1257 bool needsStateUpdate = itr->second.State != GetGoState();
1258 bool despawned = itr->second.Despawned;
1259
1260 itr = m_perPlayerState->erase(itr);
1261
1262 if (seer)
1263 {
1264 if (despawned)
1265 {
1266 seer->UpdateVisibilityOf(this);
1267 }
1268 else if (needsStateUpdate)
1269 {
1270 UF::ObjectData::Base objMask;
1273
1274 UpdateData udata(GetMapId());
1275 BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), seer);
1276 WorldPacket packet;
1277 udata.BuildPacket(&packet);
1278 seer->SendDirectMessage(&packet);
1279 }
1280 }
1281 }
1282 }
1283
1284 switch (m_lootState)
1285 {
1286 case GO_NOT_READY:
1287 {
1288 switch (GetGoType())
1289 {
1291 {
1292 // Arming Time for GAMEOBJECT_TYPE_TRAP (6)
1293 GameObjectTemplate const* goInfo = GetGOInfo();
1294 // Bombs
1295 if (goInfo->trap.charges == 2)
1296 // Hardcoded tooltip value
1298 else if (Unit* owner = GetOwner())
1299 if (owner->IsInCombat())
1301
1303 break;
1304 }
1306 {
1307 // fishing code (bobber ready)
1309 {
1310 // splash bobber (bobber ready now)
1311 Unit* caster = GetOwner();
1312 if (caster && caster->GetTypeId() == TYPEID_PLAYER)
1313 SendCustomAnim(0);
1314
1315 m_lootState = GO_READY; // can be successfully open with some chance
1316 }
1317 return;
1318 }
1321 return;
1322 // If there is no restock timer, or if the restock timer passed, the chest becomes ready to loot
1323 m_restockTime = 0;
1325 ClearLoot();
1327 break;
1328 default:
1329 m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY
1330 break;
1331 }
1332 [[fallthrough]];
1333 }
1334 case GO_READY:
1335 {
1337 {
1338 if (m_respawnTime > 0) // timer on
1339 {
1340 time_t now = GameTime::GetGameTime();
1341 if (m_respawnTime <= now) // timer expired
1342 {
1343 ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::GameObject>(GetMapId(), GetEntry(), m_spawnId);
1344 time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);
1345 if (linkedRespawntime) // Can't respawn, the master is dead
1346 {
1347 ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid);
1348 if (targetGuid == dbtableHighGuid) // if linking self, never respawn
1350 else
1351 m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little
1353 return;
1354 }
1355
1356 m_respawnTime = 0;
1357 m_SkillupList.clear();
1358 m_usetimes = 0;
1359
1360 switch (GetGoType())
1361 {
1362 case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now
1363 {
1364 Unit* caster = GetOwner();
1365 if (caster && caster->GetTypeId() == TYPEID_PLAYER)
1366 {
1367 caster->ToPlayer()->RemoveGameObject(this, false);
1368
1370 }
1371 // can be delete
1373 return;
1374 }
1377 // 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)
1378 if (GetGoState() != GO_STATE_READY)
1380 break;
1382 // Initialize a new max fish count on respawn
1383 m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock);
1384 break;
1385 default:
1386 break;
1387 }
1388
1389 // Despawn timer
1390 if (!m_spawnedByDefault)
1391 {
1392 // Can be despawned or destroyed
1394 return;
1395 }
1396
1397 // Call AI Reset (required for example in SmartAI to clear one time events)
1398 if (AI())
1399 AI()->Reset();
1400
1401 // Respawn timer
1403 if (poolid)
1404 sPoolMgr->UpdatePool<GameObject>(GetMap()->GetPoolData(), poolid, GetSpawnId());
1405 else
1406 GetMap()->AddToMap(this);
1407 }
1408 }
1409 }
1410
1411 // Set respawn timer
1414
1415 if (isSpawned())
1416 {
1417 GameObjectTemplate const* goInfo = GetGOInfo();
1418 if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
1419 {
1421 break;
1422
1423 // Type 2 (bomb) does not need to be triggered by a unit and despawns after casting its spell.
1424 if (goInfo->trap.charges == 2)
1425 {
1427 break;
1428 }
1429
1430 // Type 0 despawns after being triggered, type 1 does not.
1432 float radius = goInfo->trap.radius / 2.f; // this division seems to date back to when the field was called diameter, don't think it is still relevant.
1433 if (!radius)
1434 break;
1435
1436 // Pointer to appropriate target if found any
1437 Unit* target = nullptr;
1438
1439 if (GetOwner() || goInfo->trap.Checkallunits)
1440 {
1441 // Hunter trap: Search units which are unfriendly to the trap's owner
1444 Cell::VisitAllObjects(this, searcher, radius);
1445 }
1446 else
1447 {
1448 // Environmental trap: Any player
1449 Player* player = nullptr;
1450 Trinity::AnyPlayerInObjectRangeCheck checker(this, radius);
1452 Cell::VisitWorldObjects(this, searcher, radius);
1453 target = player;
1454 }
1455
1456 if (target)
1457 SetLootState(GO_ACTIVATED, target);
1458
1459 }
1460 else if (goInfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT)
1461 {
1464 if (hordeCapturing || allianceCapturing)
1465 {
1467 {
1469 if (hordeCapturing)
1470 {
1472 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
1473 {
1474 if (Battleground* bg = map->GetBG())
1475 {
1476 if (goInfo->capturePoint.CaptureEventHorde)
1478 bg->SendBroadcastText(goInfo->capturePoint.CaptureBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE);
1479 }
1480 }
1481 }
1482 else
1483 {
1485 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
1486 {
1487 if (Battleground* bg = map->GetBG())
1488 {
1492 }
1493 }
1494 }
1495
1498 }
1499 else
1501 }
1502 }
1503 else if (uint32 max_charges = goInfo->GetCharges())
1504 {
1505 if (m_usetimes >= max_charges)
1506 {
1507 m_usetimes = 0;
1508 SetLootState(GO_JUST_DEACTIVATED); // can be despawned or destroyed
1509 }
1510 }
1511 }
1512
1513 break;
1514 }
1515 case GO_ACTIVATED:
1516 {
1517 switch (GetGoType())
1518 {
1523 break;
1526 {
1528
1530 m_cooldownTime = 0;
1531 }
1532 break;
1534 if (m_loot)
1535 {
1536 m_loot->Update();
1537
1538 // Non-consumable chest was partially looted and restock time passed, restock all loot now
1540 {
1541 m_restockTime = 0;
1543 ClearLoot();
1545 }
1546 }
1547
1548 for (auto&& [playerOwner, loot] : m_personalLoot)
1549 loot->Update();
1550 break;
1552 {
1553 GameObjectTemplate const* goInfo = GetGOInfo();
1554 if (goInfo->trap.charges == 2 && goInfo->trap.spell)
1555 {
1557 CastSpell(nullptr, goInfo->trap.spell);
1559 }
1560 else if (Unit* target = ObjectAccessor::GetUnit(*this, m_lootStateUnitGUID))
1561 {
1562 // Some traps do not have a spell but should be triggered
1563 CastSpellExtraArgs args;
1565 if (goInfo->trap.spell)
1566 CastSpell(target, goInfo->trap.spell, args);
1567
1568 // Template value or 4 seconds
1570
1571 if (goInfo->trap.charges == 1)
1573 else if (!goInfo->trap.charges)
1575 }
1576 break;
1577 }
1578 default:
1579 break;
1580 }
1581 break;
1582 }
1584 {
1585 // If nearby linked trap exists, despawn it
1586 if (GameObject* linkedTrap = GetLinkedTrap())
1587 linkedTrap->DespawnOrUnsummon();
1588
1589 //if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed
1591 {
1592 uint32 spellId = GetGOInfo()->goober.spell;
1593
1594 if (spellId)
1595 {
1596 for (GuidSet::const_iterator it = m_unique_users.begin(); it != m_unique_users.end(); ++it)
1597 // m_unique_users can contain only player GUIDs
1598 if (Player* owner = ObjectAccessor::GetPlayer(*this, *it))
1599 owner->CastSpell(owner, spellId, false);
1600
1601 m_unique_users.clear();
1602 m_usetimes = 0;
1603 }
1604
1605 // Only goobers with a lock id or a reset time may reset their go state
1606 if (GetGOInfo()->GetLockId() || GetGOInfo()->GetAutoCloseTime())
1608
1609 //any return here in case battleground traps
1610 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1611 if (goOverride->Flags & GO_FLAG_NODESPAWN)
1612 return;
1613 }
1614
1615 ClearLoot();
1616
1617 // Do not delete chests or goobers that are not consumed on loot, while still allowing them to despawn when they expire if summoned
1618 bool isSummonedAndExpired = (GetOwner() || GetSpellId()) && m_respawnTime == 0;
1619 if ((GetGoType() == GAMEOBJECT_TYPE_CHEST || GetGoType() == GAMEOBJECT_TYPE_GOOBER) && !GetGOInfo()->IsDespawnAtAction() && !isSummonedAndExpired)
1620 {
1622 {
1623 // Start restock timer when the chest is fully looted
1627 }
1628 else
1631 return;
1632 }
1633 else if (!GetOwnerGUID().IsEmpty() || GetSpellId())
1634 {
1635 SetRespawnTime(0);
1636
1638 {
1640 go->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::InBase, nullptr));
1641 }
1642
1643 Delete();
1644 return;
1645 }
1646
1648
1649 //burning flags in some battlegrounds, if you find better condition, just add it
1650 if (GetGOInfo()->IsDespawnAtAction() || GetGoAnimProgress() > 0)
1651 {
1653 //reset flags
1654 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1655 ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
1656 }
1657
1658 if (!m_respawnDelayTime)
1659 return;
1660
1661 if (!m_spawnedByDefault)
1662 {
1663 m_respawnTime = 0;
1664
1665 if (m_spawnId)
1667 else
1668 Delete();
1669
1670 return;
1671 }
1672
1673 uint32 respawnDelay = m_respawnDelayTime;
1674 if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE))
1675 GetMap()->ApplyDynamicModeRespawnScaling(this, this->m_spawnId, respawnDelay, scalingMode);
1676 m_respawnTime = GameTime::GetGameTime() + respawnDelay;
1677
1678 // if option not set then object will be saved at grid unload
1679 // Otherwise just save respawn time to map object memory
1681
1684 else
1686
1687 break;
1688 }
1689 }
1690}
1691
1693{
1694 if (m_spawnId)
1695 {
1696 if (GameObjectOverride const* goOverride = sObjectMgr->GetGameObjectOverride(m_spawnId))
1697 return goOverride;
1698 }
1699
1700 return m_goTemplateAddon;
1701}
1702
1704{
1705 // Do not refresh despawned GO from spellcast (GO's from spellcast are destroyed after despawn)
1707 return;
1708
1709 if (isSpawned())
1710 GetMap()->AddToMap(this);
1711}
1712
1714{
1715 AddUse();
1716 m_unique_users.insert(player->GetGUID());
1717}
1718
1720{
1721 if (delay > 0ms)
1722 {
1723 if (!m_despawnDelay || m_despawnDelay > delay.count())
1724 {
1725 m_despawnDelay = delay.count();
1726 m_despawnRespawnTime = forceRespawnTime;
1727 }
1728 }
1729 else
1730 {
1731 if (m_goData)
1732 {
1733 uint32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_goData->spawntimesecs;
1734 SaveRespawnTime(respawnDelay);
1735 }
1736 Delete();
1737 }
1738}
1739
1741{
1742 PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[seer->GetGUID()];
1743 perPlayerState.ValidUntil = GameTime::GetSystemTime() + respawnTime;
1744 perPlayerState.Despawned = true;
1745 seer->UpdateVisibilityOf(this);
1746}
1747
1749{
1752
1754 {
1756 SendMessageToSet(packet.Write(), true);
1757 }
1758
1760
1763
1764 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1765 ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
1766
1768 if (m_respawnCompatibilityMode && poolid)
1769 sPoolMgr->UpdatePool<GameObject>(GetMap()->GetPoolData(), poolid, GetSpawnId());
1770 else
1772}
1773
1775{
1777 packet.ObjectGUID = GetGUID();
1778 SendMessageToSet(packet.Write(), true);
1779}
1780
1782{
1783 uint32 defaultzone = 1;
1784
1785 Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING, nullptr);
1786
1787 uint32 areaId = GetAreaId();
1788 ItemContext itemContext = ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), lootOwner);
1789 while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId))
1790 {
1791 fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_DEFAULT, itemContext);
1792 if (!fishLoot->isLooted())
1793 break;
1794
1795 areaId = areaEntry->ParentAreaID;
1796 }
1797
1798 if (fishLoot->isLooted())
1799 fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_DEFAULT, itemContext);
1800
1801 return fishLoot;
1802}
1803
1805{
1806 uint32 defaultzone = 1;
1807
1808 Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING_JUNK, nullptr);
1809
1810 uint32 areaId = GetAreaId();
1811 ItemContext itemContext = ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), lootOwner);
1812 while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId))
1813 {
1814 fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH, itemContext);
1815 if (!fishLoot->isLooted())
1816 break;
1817
1818 areaId = areaEntry->ParentAreaID;
1819 }
1820
1821 if (fishLoot->isLooted())
1822 fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH, itemContext);
1823
1824 return fishLoot;
1825}
1826
1828{
1829 // this should only be used when the gameobject has already been loaded
1830 // preferably after adding to map, because mapid may not be valid otherwise
1831 GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId);
1832 if (!data)
1833 {
1834 TC_LOG_ERROR("misc", "GameObject::SaveToDB failed, cannot get gameobject data!");
1835 return;
1836 }
1837
1838 uint32 mapId = GetMapId();
1839 if (TransportBase* transport = GetTransport())
1840 if (transport->GetMapIdForSpawning() >= 0)
1841 mapId = transport->GetMapIdForSpawning();
1842
1843 SaveToDB(mapId, data->spawnDifficulties);
1844}
1845
1846void GameObject::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDifficulties)
1847{
1848 GameObjectTemplate const* goI = GetGOInfo();
1849 if (!goI)
1850 return;
1851
1852 if (!m_spawnId)
1853 m_spawnId = sObjectMgr->GenerateGameObjectSpawnId();
1854
1855 // update in loaded data (changing data only in this place)
1856 GameObjectData& data = sObjectMgr->NewOrExistGameObjectData(m_spawnId);
1857
1858 if (!data.spawnId)
1859 data.spawnId = m_spawnId;
1860 ASSERT(data.spawnId == m_spawnId);
1861 data.id = GetEntry();
1862 data.mapId = GetMapId();
1863 data.spawnPoint.Relocate(this);
1867 data.goState = GetGoState();
1868 data.spawnDifficulties = spawnDifficulties;
1869 data.artKit = GetGoArtKit();
1870 if (!data.spawnGroupData)
1871 data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup();
1872
1873 data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : data.phaseId;
1874 data.phaseGroup = GetDBPhase() < 0 ? -GetDBPhase() : data.phaseGroup;
1875
1876 // Update in DB
1877 WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
1878
1879 uint8 index = 0;
1880
1882 stmt->setUInt64(0, m_spawnId);
1883 trans->Append(stmt);
1884
1885 stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT);
1886 stmt->setUInt64(index++, m_spawnId);
1887 stmt->setUInt32(index++, GetEntry());
1888 stmt->setUInt16(index++, uint16(mapid));
1889 stmt->setString(index++, [&data]() -> std::string
1890 {
1891 if (data.spawnDifficulties.empty())
1892 return "";
1893
1894 std::ostringstream os;
1895 auto itr = data.spawnDifficulties.begin();
1896 os << int32(*itr++);
1897
1898 for (; itr != data.spawnDifficulties.end(); ++itr)
1899 os << ',' << int32(*itr);
1900
1901 return os.str();
1902 }());
1903 stmt->setUInt32(index++, data.phaseId);
1904 stmt->setUInt32(index++, data.phaseGroup);
1905 stmt->setFloat(index++, GetPositionX());
1906 stmt->setFloat(index++, GetPositionY());
1907 stmt->setFloat(index++, GetPositionZ());
1908 stmt->setFloat(index++, GetOrientation());
1909 stmt->setFloat(index++, m_localRotation.x);
1910 stmt->setFloat(index++, m_localRotation.y);
1911 stmt->setFloat(index++, m_localRotation.z);
1912 stmt->setFloat(index++, m_localRotation.w);
1913 stmt->setInt32(index++, int32(m_respawnDelayTime));
1914 stmt->setUInt8(index++, GetGoAnimProgress());
1915 stmt->setUInt8(index++, uint8(GetGoState()));
1916 trans->Append(stmt);
1917
1918 WorldDatabase.CommitTransaction(trans);
1919}
1920
1921bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool)
1922{
1923 GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
1924 if (!data)
1925 {
1926 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: {}) not found in table `gameobject`, can't load. ", spawnId);
1927 return false;
1928 }
1929
1930 uint32 entry = data->id;
1931 //uint32 map_id = data->mapid; // already used before call
1932
1933 uint32 animprogress = data->animprogress;
1934 GOState go_state = data->goState;
1935 uint32 artKit = data->artKit;
1936
1937 m_spawnId = spawnId;
1939 if (!Create(entry, map, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode, spawnId))
1940 return false;
1941
1944
1945 if (data->spawntimesecs >= 0)
1946 {
1947 m_spawnedByDefault = true;
1948
1949 if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction())
1950 {
1953 m_respawnTime = 0;
1954 }
1955 else
1956 {
1959
1960 // ready to respawn
1962 {
1963 m_respawnTime = 0;
1965 }
1966 }
1967 }
1968 else
1969 {
1971 {
1972 TC_LOG_WARN("sql.sql", "GameObject {} (SpawnID {}) is not spawned by default, but tries to use a non-hack spawn system. This will not work. Defaulting to compatibility mode.", entry, spawnId);
1974 }
1975
1976 m_spawnedByDefault = false;
1978 m_respawnTime = 0;
1979 }
1980
1981 m_goData = data;
1982
1983 m_stringIds[1] = data->StringId;
1984
1985 if (addToMap && !GetMap()->AddToMap(this))
1986 return false;
1987
1988 return true;
1989}
1990
1992{
1993 GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
1994 if (!data)
1995 return false;
1996
1997 CharacterDatabaseTransaction charTrans = CharacterDatabase.BeginTransaction();
1998
1999 sMapMgr->DoForAllMapsWithMapId(data->mapId,
2000 [spawnId, charTrans](Map* map) -> void
2001 {
2002 // despawn all active objects, and remove their respawns
2003 std::vector<GameObject*> toUnload;
2004 for (auto const& pair : Trinity::Containers::MapEqualRange(map->GetGameObjectBySpawnIdStore(), spawnId))
2005 toUnload.push_back(pair.second);
2006 for (GameObject* obj : toUnload)
2007 map->AddObjectToRemoveList(obj);
2008 map->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, spawnId, charTrans);
2009 }
2010 );
2011
2012 // delete data from memory
2013 sObjectMgr->DeleteGameObjectData(spawnId);
2014
2015 CharacterDatabase.CommitTransaction(charTrans);
2016
2017 WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
2018
2019 // ... and the database
2021 stmt->setUInt64(0, spawnId);
2022 trans->Append(stmt);
2023
2024 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER);
2026 stmt->setUInt64(1, spawnId);
2027 trans->Append(stmt);
2028
2029 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT);
2030 stmt->setUInt64(0, spawnId);
2031 trans->Append(stmt);
2032
2033 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN);
2034 stmt->setUInt64(0, spawnId);
2036 trans->Append(stmt);
2037
2038 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN);
2039 stmt->setUInt64(0, spawnId);
2041 trans->Append(stmt);
2042
2043 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN_MASTER);
2044 stmt->setUInt64(0, spawnId);
2046 trans->Append(stmt);
2047
2048 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN_MASTER);
2049 stmt->setUInt64(0, spawnId);
2051 trans->Append(stmt);
2052
2053 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT_ADDON);
2054 stmt->setUInt64(0, spawnId);
2055 trans->Append(stmt);
2056
2057 WorldDatabase.CommitTransaction(trans);
2058
2059 return true;
2060}
2061
2062/*********************************************************/
2063/*** QUEST SYSTEM ***/
2064/*********************************************************/
2065bool GameObject::hasQuest(uint32 quest_id) const
2066{
2067 return sObjectMgr->GetGOQuestRelations(GetEntry()).HasQuest(quest_id);
2068}
2069
2071{
2072 return sObjectMgr->GetGOQuestInvolvedRelations(GetEntry()).HasQuest(quest_id);
2073}
2074
2076{
2077 // If something is marked as a transport, don't transmit an out of range packet for it.
2078 GameObjectTemplate const* gInfo = GetGOInfo();
2079 if (!gInfo)
2080 return false;
2081
2083}
2084
2085// is Dynamic transport = non-stop Transport
2087{
2088 // If something is marked as a transport, don't transmit an out of range packet for it.
2089 GameObjectTemplate const* gInfo = GetGOInfo();
2090 if (!gInfo)
2091 return false;
2092
2094}
2095
2097{
2098 GameObjectTemplate const* gInfo = GetGOInfo();
2099 if (!gInfo)
2100 return false;
2101
2103}
2104
2106{
2107 if (m_goData && (forceDelay || m_respawnTime > GameTime::GetGameTime()) && m_spawnedByDefault)
2108 {
2110 {
2111 RespawnInfo ri;
2113 ri.spawnId = m_spawnId;
2116 return;
2117 }
2118
2119 uint32 thisRespawnTime = forceDelay ? GameTime::GetGameTime() + forceDelay : m_respawnTime;
2121 }
2122}
2123
2124bool GameObject::IsNeverVisibleFor(WorldObject const* seer, bool allowServersideObjects) const
2125{
2127 return true;
2128
2129 if (GetGOInfo()->GetServerOnly() && !allowServersideObjects)
2130 return true;
2131
2132 if (!GetDisplayId() && GetGOInfo()->IsDisplayMandatory())
2133 return true;
2134
2135 if (m_goTypeImpl)
2136 return m_goTypeImpl->IsNeverVisibleFor(seer, allowServersideObjects);
2137
2138 return false;
2139}
2140
2142{
2144 return true;
2145
2147 return true;
2148
2149 if (!seer)
2150 return false;
2151
2152 // Always seen by owner and friendly units
2153 if (!GetOwnerGUID().IsEmpty())
2154 {
2155 if (seer->GetGUID() == GetOwnerGUID())
2156 return true;
2157
2158 Unit* owner = GetOwner();
2159 if (Unit const* unitSeer = seer->ToUnit())
2160 if (owner && owner->IsFriendlyTo(unitSeer))
2161 return true;
2162 }
2163
2164 return false;
2165}
2166
2168{
2170 return true;
2171
2172 // Despawned
2173 if (!isSpawned())
2174 return true;
2175
2176 if (m_perPlayerState)
2178 if (state->Despawned)
2179 return true;
2180
2181 return false;
2182}
2183
2185{
2186 if (Unit* owner = GetOwner())
2187 return owner->GetLevelForTarget(target);
2188
2190 {
2191 if (Player const* player = target->ToPlayer())
2192 if (Optional<ContentTuningLevels> userLevels = sDB2Manager.GetContentTuningData(GetGOInfo()->ContentTuningId, player->m_playerData->CtrOptions->ContentTuningConditionMask))
2193 return uint8(std::clamp<int16>(player->GetLevel(), userLevels->MinLevel, userLevels->MaxLevel));
2194
2195 if (Unit const* targetUnit = target->ToUnit())
2196 return targetUnit->GetLevel();
2197 }
2198
2199 return 1;
2200}
2201
2203{
2204 time_t now = GameTime::GetGameTime();
2205 if (m_respawnTime > now)
2206 return m_respawnTime;
2207 else
2208 return now;
2209}
2210
2212{
2213 m_respawnTime = respawn > 0 ? GameTime::GetGameTime() + respawn : 0;
2214 m_respawnDelayTime = respawn > 0 ? respawn : 0;
2215 if (respawn && !m_spawnedByDefault)
2217}
2218
2220{
2222 {
2225 }
2226}
2227
2228bool GameObject::ActivateToQuest(Player const* target) const
2229{
2230 if (target->HasQuestForGO(GetEntry()))
2231 return true;
2232
2233 if (!sObjectMgr->IsGameObjectForQuests(GetEntry()))
2234 return false;
2235
2236 switch (GetGoType())
2237 {
2239 {
2240 GameObject* go = const_cast<GameObject*>(this);
2241 QuestGiverStatus questStatus = const_cast<Player*>(target)->GetQuestDialogStatus(go);
2242 if (questStatus != QuestGiverStatus::None && questStatus != QuestGiverStatus::Future)
2243 return true;
2244 break;
2245 }
2247 {
2248 // Chests become inactive while not ready to be looted
2249 if (getLootState() == GO_NOT_READY)
2250 return false;
2251
2252 // scan GO chest with loot including quest items
2257 {
2258 if (Battleground const* bg = target->GetBattleground())
2259 return bg->CanActivateGO(GetEntry(), bg->GetPlayerTeam(target->GetGUID()));
2260 return true;
2261 }
2262 break;
2263 }
2265 {
2267 return true;
2268 break;
2269 }
2271 {
2273 return true;
2274 break;
2275 }
2276 default:
2277 break;
2278 }
2279
2280 return false;
2281}
2282
2284{
2285 GameObjectTemplate const* trapInfo = sObjectMgr->GetGameObjectTemplate(trapEntry);
2286 if (!trapInfo || trapInfo->type != GAMEOBJECT_TYPE_TRAP)
2287 return;
2288
2289 SpellInfo const* trapSpell = sSpellMgr->GetSpellInfo(trapInfo->trap.spell, GetMap()->GetDifficultyID());
2290 if (!trapSpell) // checked at load already
2291 return;
2292
2293 if (GameObject* trapGO = GetLinkedTrap())
2294 trapGO->CastSpell(target, trapSpell->Id);
2295}
2296
2298{
2299 GameObject* ok = nullptr;
2300 Trinity::NearestGameObjectFishingHole u_check(*this, range);
2302 Cell::VisitGridObjects(this, checker, range);
2303 return ok;
2304}
2305
2307{
2309 return;
2310
2313
2315 m_cooldownTime = 0;
2316}
2317
2318void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = false */, Unit* user /*=nullptr*/)
2319{
2320 if (m_lootState != GO_READY)
2321 return;
2322
2323 if (!time_to_restore)
2324 time_to_restore = GetGOInfo()->GetAutoCloseTime();
2325
2326 SwitchDoorOrButton(true, alternative);
2328
2329 m_cooldownTime = time_to_restore ? (GameTime::GetGameTimeMS() + time_to_restore) : 0;
2330}
2331
2332void GameObject::ActivateObject(GameObjectActions action, int32 param, WorldObject* spellCaster /*= nullptr*/, uint32 spellId /*= 0*/, int32 effectIndex /*= -1*/)
2333{
2334 Unit* unitCaster = spellCaster ? spellCaster->ToUnit() : nullptr;
2335
2336 switch (action)
2337 {
2339 TC_LOG_FATAL("spell", "Spell {} has action type NONE in effect {}", spellId, effectIndex);
2340 break;
2346 break;
2347 case GameObjectActions::Disturb: // What's the difference with Open?
2348 if (unitCaster)
2349 Use(unitCaster);
2350 break;
2353 break;
2356 break;
2358 if (unitCaster)
2359 Use(unitCaster);
2360 break;
2362 if (unitCaster)
2363 UseDoorOrButton(0, false, unitCaster);
2365 break;
2368 break;
2370 // No use cases, implementation unknown
2371 break;
2373 if (unitCaster)
2374 UseDoorOrButton(0, true, unitCaster);
2375 break;
2378 break;
2380 // No use cases, implementation unknown
2381 break;
2384 break;
2387 break;
2390 break;
2394 break;
2400 {
2401 GameObjectTemplateAddon const* templateAddon = GetTemplateAddon();
2402
2403 uint32 artKitIndex = action != GameObjectActions::UseArtKit4 ? uint32(action) - uint32(GameObjectActions::UseArtKit0) : 4;
2404
2405 uint32 artKitValue = 0;
2406 if (templateAddon != nullptr)
2407 artKitValue = templateAddon->ArtKits[artKitIndex];
2408
2409 if (artKitValue == 0)
2410 TC_LOG_ERROR("sql.sql", "GameObject {} hit by spell {} needs `artkit{}` in `gameobject_template_addon`", GetEntry(), spellId, artKitIndex);
2411 else
2412 SetGoArtKit(artKitValue);
2413
2414 break;
2415 }
2428 SetGoState(GOState(action));
2429 else
2430 TC_LOG_ERROR("spell", "Spell {} targeted non-transport gameobject for transport only action \"Go to Floor\" {} in effect {}", spellId, int32(action), effectIndex);
2431 break;
2433 SetAnimKitId(param, false);
2434 break;
2436 if (unitCaster)
2437 UseDoorOrButton(0, false, unitCaster);
2438 SetAnimKitId(param, false);
2439 break;
2442 SetAnimKitId(param, false);
2443 break;
2445 SetAnimKitId(param, true);
2446 break;
2448 SetAnimKitId(0, false);
2449 break;
2451 if (unitCaster)
2452 UseDoorOrButton(0, false, unitCaster);
2453 SetAnimKitId(0, false);
2454 break;
2457 SetAnimKitId(0, false);
2458 break;
2460 SetSpellVisualId(param, Object::GetGUID(spellCaster));
2461 break;
2464 break;
2465 default:
2466 TC_LOG_ERROR("spell", "Spell {} has unhandled action {} in effect {}", spellId, int32(action), effectIndex);
2467 break;
2468 }
2469
2470 // Apply side effects of type
2471 if (m_goTypeImpl)
2472 m_goTypeImpl->ActivateObject(action, param, spellCaster, spellId, effectIndex);
2473}
2474
2476{
2478 GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGameObjectData(m_spawnId));
2479 if (data)
2480 data->artKit = kit;
2481}
2482
2484{
2485 GameObjectData const* data = nullptr;
2486 if (go)
2487 {
2488 go->SetGoArtKit(artkit);
2489 data = go->GetGameObjectData();
2490 }
2491 else if (lowguid)
2492 data = sObjectMgr->GetGameObjectData(lowguid);
2493
2494 if (data)
2495 const_cast<GameObjectData*>(data)->artKit = artkit;
2496}
2497
2498void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */)
2499{
2500 if (activate)
2502 else
2504
2505 if (GetGoState() == GO_STATE_READY) //if closed -> open
2507 else //if open -> close
2509}
2510
2512{
2513 // by default spell caster is user
2514 Unit* spellCaster = user;
2515 uint32 spellId = 0;
2516 bool triggered = false;
2517
2518 if (Player* playerUser = user->ToPlayer())
2519 {
2520 if (m_goInfo->GetNoDamageImmune() && playerUser->HasUnitFlag(UNIT_FLAG_IMMUNE))
2521 return;
2522
2523 if (!m_goInfo->IsUsableMounted())
2525
2526 playerUser->PlayerTalkClass->ClearMenus();
2527 if (AI()->OnGossipHello(playerUser))
2528 return;
2529 }
2530
2531 // If cooldown data present in template
2532 if (uint32 cooldown = GetGOInfo()->GetCooldown())
2533 {
2535 return;
2536
2538 }
2539
2540 switch (GetGoType())
2541 {
2542 case GAMEOBJECT_TYPE_DOOR: //0
2543 case GAMEOBJECT_TYPE_BUTTON: //1
2544 //doors/buttons never really despawn, only reset to default state/flags
2545 UseDoorOrButton(0, false, user);
2546 return;
2548 {
2549 if (user->GetTypeId() != TYPEID_PLAYER)
2550 return;
2551
2552 Player* player = user->ToPlayer();
2553
2554 player->PrepareGossipMenu(this, GetGOInfo()->questgiver.gossipID, true);
2555 player->SendPreparedGossip(this);
2556 return;
2557 }
2558 case GAMEOBJECT_TYPE_CHEST: //3
2559 {
2560 Player* player = user->ToPlayer();
2561 if (!player)
2562 return;
2563
2564 if (Battleground* bg = player->GetBattleground())
2565 if (!bg->CanActivateGO(GetEntry(), bg->GetPlayerTeam(user->GetGUID())))
2566 return;
2567
2568 GameObjectTemplate const* info = GetGOInfo();
2569 if (!m_loot && info->GetLootId())
2570 {
2571 if (info->GetLootId())
2572 {
2573 Group const* group = player->GetGroup();
2574 bool groupRules = group && info->chest.usegrouplootrules;
2575
2576 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, groupRules ? group : nullptr);
2577 m_loot.reset(loot);
2578
2580 loot->FillLoot(info->GetLootId(), LootTemplates_Gameobject, player, !groupRules, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
2581
2582 if (GetLootMode() > 0)
2583 if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
2584 loot->generateMoneyLoot(addon->Mingold, addon->Maxgold);
2585 }
2586
2587 if (info->chest.triggeredEvent)
2588 GameEvents::Trigger(info->chest.triggeredEvent, player, this);
2589
2590 // triggering linked GO
2591 if (uint32 trapEntry = info->chest.linkedTrap)
2592 TriggeringLinkedGameObject(trapEntry, player);
2593 }
2594 else if (!m_personalLoot.count(player->GetGUID()))
2595 {
2596 if (info->chest.chestPersonalLoot)
2597 {
2599 if (info->chest.DungeonEncounter)
2600 {
2601 std::vector<Player*> tappers;
2602 for (ObjectGuid tapperGuid : GetTapList())
2603 if (Player* tapper = ObjectAccessor::GetPlayer(*this, tapperGuid))
2604 tappers.push_back(tapper);
2605
2606 if (tappers.empty())
2607 tappers.push_back(player);
2608
2610 LootTemplates_Gameobject, LOOT_CHEST, this, addon ? addon->Mingold : 0, addon ? addon->Maxgold : 0,
2611 GetLootMode(), GetMap()->GetMapDifficulty(), tappers);
2612 }
2613 else
2614 {
2615 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr);
2616 m_personalLoot[player->GetGUID()].reset(loot);
2617
2619 loot->FillLoot(info->chest.chestPersonalLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
2620
2621 if (GetLootMode() > 0 && addon)
2622 loot->generateMoneyLoot(addon->Mingold, addon->Maxgold);
2623 }
2624 }
2625 }
2626
2627 if (!m_unique_users.count(player->GetGUID()) && !info->GetLootId())
2628 {
2629 if (info->chest.chestPushLoot)
2630 {
2631 Loot pushLoot(GetMap(), GetGUID(), LOOT_CHEST, nullptr);
2632 pushLoot.FillLoot(info->chest.chestPushLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
2633 pushLoot.AutoStore(player, NULL_BAG, NULL_SLOT);
2634 }
2635
2636 if (info->chest.triggeredEvent)
2637 GameEvents::Trigger(info->chest.triggeredEvent, player, this);
2638
2639 // triggering linked GO
2640 if (uint32 trapEntry = info->chest.linkedTrap)
2641 TriggeringLinkedGameObject(trapEntry, player);
2642
2643 AddUniqueUse(player);
2644 }
2645
2646 if (getLootState() != GO_ACTIVATED)
2647 SetLootState(GO_ACTIVATED, player);
2648
2649 // Send loot
2650 if (Loot* loot = GetLootForPlayer(player))
2651 player->SendLoot(*loot);
2652 break;
2653 }
2654 case GAMEOBJECT_TYPE_TRAP: //6
2655 {
2656 GameObjectTemplate const* goInfo = GetGOInfo();
2657 if (goInfo->trap.spell)
2658 CastSpell(user, goInfo->trap.spell);
2659
2660 m_cooldownTime = GameTime::GetGameTimeMS() + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)) * IN_MILLISECONDS; // template or 4 seconds
2661
2662 if (goInfo->trap.charges == 1) // Deactivate after trigger
2664
2665 return;
2666 }
2667 //Sitting: Wooden bench, chairs enzz
2668 case GAMEOBJECT_TYPE_CHAIR: //7
2669 {
2670 GameObjectTemplate const* info = GetGOInfo();
2671 if (ChairListSlots.empty()) // this is called once at first chair use to make list of available slots
2672 {
2673 if (info->chair.chairslots > 0) // sometimes chairs in DB have error in fields and we dont know number of slots
2674 for (uint32 i = 0; i < info->chair.chairslots; ++i)
2675 ChairListSlots[i].Clear(); // Last user of current slot set to 0 (none sit here yet)
2676 else
2677 ChairListSlots[0].Clear(); // error in DB, make one default slot
2678 }
2679
2680 // a chair may have n slots. we have to calculate their positions and teleport the player to the nearest one
2681
2682 float lowestDist = DEFAULT_VISIBILITY_DISTANCE;
2683
2684 uint32 nearest_slot = 0;
2685 float x_lowest = GetPositionX();
2686 float y_lowest = GetPositionY();
2687
2688 // the object orientation + 1/2 pi
2689 // every slot will be on that straight line
2690 float orthogonalOrientation = GetOrientation() + float(M_PI) * 0.5f;
2691 // find nearest slot
2692 bool found_free_slot = false;
2693 for (auto& [slot, sittingUnit] : ChairListSlots)
2694 {
2695 // the distance between this slot and the center of the go - imagine a 1D space
2696 float relativeDistance = (info->size * slot) - (info->size * (info->chair.chairslots - 1) / 2.0f);
2697
2698 float x_i = GetPositionX() + relativeDistance * std::cos(orthogonalOrientation);
2699 float y_i = GetPositionY() + relativeDistance * std::sin(orthogonalOrientation);
2700
2701 if (!sittingUnit.IsEmpty())
2702 {
2703 if (Unit* chairUser = ObjectAccessor::GetUnit(*this, sittingUnit))
2704 {
2705 if (chairUser->IsSitState() && chairUser->GetStandState() != UNIT_STAND_STATE_SIT && chairUser->GetExactDist2d(x_i, y_i) < 0.1f)
2706 continue; // This seat is already occupied by ChairUser. NOTE: Not sure if the ChairUser->GetStandState() != UNIT_STAND_STATE_SIT check is required.
2707
2708 sittingUnit.Clear(); // This seat is unoccupied.
2709 }
2710 else
2711 sittingUnit.Clear(); // The seat may of had an occupant, but they're offline.
2712 }
2713
2714 found_free_slot = true;
2715
2716 // calculate the distance between the player and this slot
2717 float thisDistance = user->GetDistance2d(x_i, y_i);
2718
2719 if (thisDistance <= lowestDist)
2720 {
2721 nearest_slot = slot;
2722 lowestDist = thisDistance;
2723 x_lowest = x_i;
2724 y_lowest = y_i;
2725 }
2726 }
2727
2728 if (found_free_slot)
2729 {
2730 auto itr = ChairListSlots.find(nearest_slot);
2731 if (itr != ChairListSlots.end())
2732 {
2733 itr->second = user->GetGUID(); //this slot in now used by player
2734 user->NearTeleportTo(x_lowest, y_lowest, GetPositionZ(), GetOrientation());
2736 if (info->chair.triggeredEvent)
2737 GameEvents::Trigger(info->chair.triggeredEvent, user, this);
2738 return;
2739 }
2740 }
2741
2742 return;
2743 }
2745 // triggering linked GO
2746 if (uint32 trapEntry = GetGOInfo()->spellFocus.linkedTrap)
2747 TriggeringLinkedGameObject(trapEntry, user);
2748 break;
2749 //big gun, its a spell/aura
2750 case GAMEOBJECT_TYPE_GOOBER: //10
2751 {
2752 GameObjectTemplate const* info = GetGOInfo();
2753 Player* player = user->ToPlayer();
2754
2755 if (player)
2756 {
2757 if (info->goober.pageID) // show page...
2758 {
2760 data.GameObjectGUID = GetGUID();
2761 player->SendDirectMessage(data.Write());
2762 }
2763 else if (info->goober.gossipID)
2764 {
2765 player->PrepareGossipMenu(this, info->goober.gossipID);
2766 player->SendPreparedGossip(this);
2767 }
2768
2769 if (info->goober.eventID)
2770 {
2771 TC_LOG_DEBUG("maps.script", "Goober ScriptStart id {} for GO entry {} (GUID {}).", info->goober.eventID, GetEntry(), GetSpawnId());
2772 GameEvents::Trigger(info->goober.eventID, player, this);
2773 }
2774
2775 // possible quest objective for active quests
2776 if (info->goober.questID && sObjectMgr->GetQuestTemplate(info->goober.questID))
2777 {
2778 //Quest require to be active for GO using
2780 break;
2781 }
2782
2783 if (Group* group = player->GetGroup())
2784 {
2785 for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
2786 if (Player* member = itr->GetSource())
2787 if (member->IsAtGroupRewardDistance(this))
2788 member->KillCreditGO(info->entry, GetGUID());
2789 }
2790 else
2791 player->KillCreditGO(info->entry, GetGUID());
2792 }
2793
2794 if (uint32 trapEntry = info->goober.linkedTrap)
2795 TriggeringLinkedGameObject(trapEntry, user);
2796
2797 if (info->goober.AllowMultiInteract && player)
2798 {
2799 if (info->IsDespawnAtAction())
2801 else
2803 }
2804 else
2805 {
2808
2809 // this appear to be ok, however others exist in addition to this that should have custom (ex: 190510, 188692, 187389)
2810 if (info->goober.customAnim)
2812 else
2814
2816 }
2817
2818 // cast this spell later if provided
2819 spellId = info->goober.spell;
2820 if (!info->goober.playerCast)
2821 spellCaster = nullptr;
2822
2823 break;
2824 }
2825 case GAMEOBJECT_TYPE_CAMERA: //13
2826 {
2827 GameObjectTemplate const* info = GetGOInfo();
2828 if (!info)
2829 return;
2830
2831 if (user->GetTypeId() != TYPEID_PLAYER)
2832 return;
2833
2834 Player* player = user->ToPlayer();
2835
2836 if (info->camera.camera)
2837 player->SendCinematicStart(info->camera.camera);
2838
2839 if (info->camera.eventID)
2840 GameEvents::Trigger(info->camera.eventID, player, this);
2841
2842 return;
2843 }
2844 //fishing bobber
2846 {
2847 Player* player = user->ToPlayer();
2848 if (!player)
2849 return;
2850
2851 if (player->GetGUID() != GetOwnerGUID())
2852 return;
2853
2854 switch (getLootState())
2855 {
2856 case GO_READY: // ready for loot
2857 {
2858 SetLootState(GO_ACTIVATED, player);
2859
2862
2863 SendUpdateToPlayer(player);
2864
2865 AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(GetAreaId());
2866 if (!areaEntry)
2867 {
2868 TC_LOG_ERROR("entities.gameobject", "Gameobject '{}' ({}) spawned in unknown area (x: {} y: {} z: {} map: {})",
2870 break;
2871 }
2872
2873 // Update the correct fishing skill according to the area's ContentTuning
2874 ContentTuningEntry const* areaContentTuning = DB2Manager::GetContentTuningForArea(areaEntry);
2875 if (!areaContentTuning)
2876 break;
2877
2878 player->UpdateFishingSkill(areaContentTuning->ExpansionID);
2879
2880 // Send loot
2881 int32 areaFishingLevel = sObjectMgr->GetFishingBaseSkillLevel(areaEntry);
2882
2883 uint32 playerFishingSkill = player->GetProfessionSkillForExp(SKILL_FISHING, areaContentTuning->ExpansionID);
2884 int32 playerFishingLevel = player->GetSkillValue(playerFishingSkill);
2885
2886 int32 roll = irand(1, 100);
2887 int32 chance = 100;
2888 if (playerFishingLevel < areaFishingLevel)
2889 {
2890 chance = int32(pow((double)playerFishingLevel / areaFishingLevel, 2) * 100);
2891 if (chance < 1)
2892 chance = 1;
2893 }
2894
2895 TC_LOG_DEBUG("misc", "Fishing check (skill {} level: {} area skill level: {} chance {} roll: {}", playerFishingSkill, playerFishingLevel, areaFishingLevel, chance, roll);
2896
2898 GameObject* fishingPool = LookupFishingHoleAround(20.0f + CONTACT_DISTANCE);
2899
2900 // If fishing skill is high enough, or if fishing on a pool, send correct loot.
2901 // Fishing pools have no skill requirement as of patch 3.3.0 (undocumented change).
2902 if (chance >= roll || fishingPool)
2903 {
2905 // prevent removing GO at spell cancel
2907 SetOwnerGUID(player->GetGUID());
2908 SetSpellId(0); // prevent removing unintended auras at Unit::RemoveGameObject
2909
2910 if (fishingPool)
2911 {
2912 fishingPool->Use(player);
2914 }
2915 else
2916 {
2917 m_loot.reset(GetFishLoot(player));
2918 player->SendLoot(*m_loot);
2919 }
2920 }
2921 else // If fishing skill is too low, send junk loot.
2922 {
2923 m_loot.reset(GetFishLootJunk(player));
2924 player->SendLoot(*m_loot);
2925 }
2926 break;
2927 }
2928 case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update
2929 break;
2930 default:
2931 {
2934 break;
2935 }
2936 }
2937
2939 return;
2940 }
2941
2942 case GAMEOBJECT_TYPE_RITUAL: //18
2943 {
2944 if (user->GetTypeId() != TYPEID_PLAYER)
2945 return;
2946
2947 Player* player = user->ToPlayer();
2948
2949 Unit* owner = GetOwner();
2950
2951 GameObjectTemplate const* info = GetGOInfo();
2952
2953 Player* m_ritualOwner = nullptr;
2956
2957 // ritual owner is set for GO's without owner (not summoned)
2958 if (!m_ritualOwner && !owner)
2959 {
2960 m_ritualOwnerGUID = player->GetGUID();
2961 m_ritualOwner = player;
2962 }
2963
2964 if (owner)
2965 {
2966 if (owner->GetTypeId() != TYPEID_PLAYER)
2967 return;
2968
2969 // accept only use by player from same group as owner, excluding owner itself (unique use already added in spell effect)
2970 if (player == owner->ToPlayer() || (info->ritual.castersGrouped && !player->IsInSameRaidWith(owner->ToPlayer())))
2971 return;
2972
2973 // expect owner to already be channeling, so if not...
2975 return;
2976
2977 // in case summoning ritual caster is GO creator
2978 spellCaster = owner;
2979 }
2980 else
2981 {
2982 if (player != m_ritualOwner && (info->ritual.castersGrouped && !player->IsInSameRaidWith(m_ritualOwner)))
2983 return;
2984
2985 spellCaster = player;
2986 }
2987
2988 AddUniqueUse(player);
2989
2990 if (info->ritual.animSpell)
2991 {
2992 player->CastSpell(player, info->ritual.animSpell, true);
2993
2994 // for this case, summoningRitual.spellId is always triggered
2995 triggered = true;
2996 }
2997
2998 // full amount unique participants including original summoner
2999 if (GetUniqueUseCount() == info->ritual.casters)
3000 {
3001 if (m_ritualOwner)
3002 spellCaster = m_ritualOwner;
3003
3004 spellId = info->ritual.spell;
3005
3006 if (spellId == 62330) // GO store nonexistent spell, replace by expected
3007 {
3008 // spell have reagent and mana cost but it not expected use its
3009 // it triggered spell in fact cast at currently channeled GO
3010 spellId = 61993;
3011 triggered = true;
3012 }
3013
3014 // Cast casterTargetSpell at a random GO user
3015 // on the current DB there is only one gameobject that uses this (Ritual of Doom)
3016 // and its required target number is 1 (outter for loop will run once)
3017 if (info->ritual.casterTargetSpell && info->ritual.casterTargetSpell != 1) // No idea why this field is a bool in some cases
3018 for (uint32 i = 0; i < info->ritual.casterTargetSpellTargets; i++)
3019 // m_unique_users can contain only player GUIDs
3021 spellCaster->CastSpell(target, info->ritual.casterTargetSpell, true);
3022
3023 // finish owners spell
3024 if (owner)
3026
3027 // can be deleted now, if
3028 if (!info->ritual.ritualPersistent)
3030 else
3031 {
3032 // reset ritual for this GO
3034 m_unique_users.clear();
3035 m_usetimes = 0;
3036 }
3037 }
3038 else
3039 return;
3040
3041 // go to end function to spell casting
3042 break;
3043 }
3045 {
3046 GameObjectTemplate const* info = GetGOInfo();
3047 if (!info)
3048 return;
3049
3050 if (info->spellCaster.partyOnly)
3051 {
3052 Unit* caster = GetOwner();
3053 if (!caster || caster->GetTypeId() != TYPEID_PLAYER)
3054 return;
3055
3056 if (user->GetTypeId() != TYPEID_PLAYER || !user->ToPlayer()->IsInSameRaidWith(caster->ToPlayer()))
3057 return;
3058 }
3059
3061 spellId = info->spellCaster.spell;
3062
3063 AddUse();
3064 break;
3065 }
3067 {
3068 GameObjectTemplate const* info = GetGOInfo();
3069
3070 if (user->GetTypeId() != TYPEID_PLAYER)
3071 return;
3072
3073 Player* player = user->ToPlayer();
3074
3075 Player* targetPlayer = ObjectAccessor::FindPlayer(player->GetTarget());
3076
3077 // accept only use by player from same raid as caster, except caster itself
3078 if (!targetPlayer || targetPlayer == player || !targetPlayer->IsInSameRaidWith(player))
3079 return;
3080
3081 //required lvl checks!
3082 if (Optional<ContentTuningLevels> userLevels = sDB2Manager.GetContentTuningData(info->ContentTuningId, player->m_playerData->CtrOptions->ContentTuningConditionMask))
3083 if (player->GetLevel() < userLevels->MaxLevel)
3084 return;
3085
3086 if (Optional<ContentTuningLevels> targetLevels = sDB2Manager.GetContentTuningData(info->ContentTuningId, targetPlayer->m_playerData->CtrOptions->ContentTuningConditionMask))
3087 if (targetPlayer->GetLevel() < targetLevels->MaxLevel)
3088 return;
3089
3090 if (info->entry == 194097)
3091 spellId = 61994; // Ritual of Summoning
3092 else
3093 spellId = 59782; // Summoning Stone Effect
3094
3095 break;
3096 }
3097
3098 case GAMEOBJECT_TYPE_FLAGSTAND: // 24
3099 {
3100 if (user->GetTypeId() != TYPEID_PLAYER)
3101 return;
3102
3103 Player* player = user->ToPlayer();
3104
3105 if (player->CanUseBattlegroundObject(this))
3106 {
3107 // in battleground check
3108 Battleground* bg = player->GetBattleground();
3109 if (!bg)
3110 return;
3111
3112 if (player->GetVehicle())
3113 return;
3114
3117 // BG flag click
3118 // AB:
3119 // 15001
3120 // 15002
3121 // 15003
3122 // 15004
3123 // 15005
3124 bg->EventPlayerClickedOnFlag(player, this);
3125 return; //we don;t need to delete flag ... it is despawned!
3126 }
3127 break;
3128 }
3129
3130 case GAMEOBJECT_TYPE_FISHINGHOLE: // 25
3131 {
3132 if (user->GetTypeId() != TYPEID_PLAYER)
3133 return;
3134
3135 Player* player = user->ToPlayer();
3136
3137 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_FISHINGHOLE, nullptr);
3138 loot->FillLoot(GetGOInfo()->GetLootId(), LootTemplates_Gameobject, player, true, false, LOOT_MODE_DEFAULT, ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
3139 m_personalLoot[player->GetGUID()].reset(loot);
3140
3141 player->SendLoot(*loot);
3143 return;
3144 }
3145
3146 case GAMEOBJECT_TYPE_FLAGDROP: // 26
3147 {
3148 if (user->GetTypeId() != TYPEID_PLAYER)
3149 return;
3150
3151 Player* player = user->ToPlayer();
3152
3153 if (player->CanUseBattlegroundObject(this))
3154 {
3155 // in battleground check
3156 Battleground* bg = player->GetBattleground();
3157 if (!bg)
3158 return;
3159
3160 if (player->GetVehicle())
3161 return;
3162
3165 // BG flag dropped
3166 // WS:
3167 // 179785 - Silverwing Flag
3168 // 179786 - Warsong Flag
3169 // EotS:
3170 // 184142 - Netherstorm Flag
3171 GameObjectTemplate const* info = GetGOInfo();
3172 if (info)
3173 {
3174 switch (info->entry)
3175 {
3176 case 179785: // Silverwing Flag
3177 case 179786: // Warsong Flag
3178 if (bg->GetTypeID() == BATTLEGROUND_WS)
3179 bg->EventPlayerClickedOnFlag(player, this);
3180 break;
3181 case 184142: // Netherstorm Flag
3182 if (bg->GetTypeID() == BATTLEGROUND_EY)
3183 bg->EventPlayerClickedOnFlag(player, this);
3184 break;
3185 }
3186
3187 if (info->flagDrop.eventID)
3188 GameEvents::Trigger(info->flagDrop.eventID, player, this);
3189 }
3190 //this cause to call return, all flags must be deleted here!!
3191 spellId = 0;
3192 Delete();
3193 }
3194 break;
3195 }
3197 {
3198 GameObjectTemplate const* info = GetGOInfo();
3199 if (!info)
3200 return;
3201
3202 if (user->GetTypeId() != TYPEID_PLAYER)
3203 return;
3204
3205 Player* player = user->ToPlayer();
3206
3207 WorldPackets::Misc::EnableBarberShop enableBarberShop;
3208 enableBarberShop.CustomizationScope = info->barberChair.CustomizationScope;
3209 player->SendDirectMessage(enableBarberShop.Write());
3210
3211 // fallback, will always work
3213
3215 return;
3216 }
3218 {
3219 GameObjectTemplate const* info = GetGOInfo();
3220 if (!info)
3221 return;
3222
3223 Player* player = user->ToPlayer();
3224 if (!player)
3225 return;
3226
3227 if (!player->CanUseBattlegroundObject(this))
3228 return;
3229
3230 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
3231 if (!newFlag)
3232 return;
3233
3234 if (newFlag->GetState() != FlagState::InBase)
3235 return;
3236
3237 spellId = info->newflag.pickupSpell;
3238 spellCaster = nullptr;
3239 break;
3240 }
3242 {
3243 GameObjectTemplate const* info = GetGOInfo();
3244 if (!info)
3245 return;
3246
3247 if (user->GetTypeId() != TYPEID_PLAYER)
3248 return;
3249
3250 if (!user->IsAlive())
3251 return;
3252
3253 if (GameObject* owner = GetMap()->GetGameObject(GetOwnerGUID()))
3254 {
3255 if (owner->GetGoType() == GAMEOBJECT_TYPE_NEW_FLAG)
3256 {
3257 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(owner->m_goTypeImpl.get());
3258 if (!newFlag)
3259 return;
3260
3261 if (newFlag->GetState() != FlagState::Dropped)
3262 return;
3263
3264 // friendly with enemy flag means you're taking it
3265 bool defenderInteract = !owner->IsFriendlyTo(user);
3266 if (defenderInteract && owner->GetGOInfo()->newflag.ReturnonDefenderInteract)
3267 {
3268 Delete();
3269 owner->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::InBase, user->ToPlayer()));
3270 return;
3271 }
3272 else
3273 {
3274 // we let the owner cast the spell for now
3275 // so that caster guid is set correctly
3276 SpellCastResult result = owner->CastSpell(user, owner->GetGOInfo()->newflag.pickupSpell, CastSpellExtraArgs(TRIGGERED_FULL_MASK));
3277 if (result == SPELL_CAST_OK)
3278 {
3279 Delete();
3280 owner->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::Taken, user->ToPlayer()));
3281 return;
3282 }
3283 }
3284 }
3285 }
3286
3287 Delete();
3288 return;
3289 }
3291 {
3292 Player* player = user->ToPlayer();
3293 if (!player)
3294 return;
3295
3296 AssaultCapturePoint(player);
3297 return;
3298 }
3300 {
3301 GameObjectTemplate const* info = GetGOInfo();
3302 if (!info)
3303 return;
3304
3305 if (user->GetTypeId() != TYPEID_PLAYER)
3306 return;
3307
3308 Player* player = user->ToPlayer();
3309 if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(info->itemForge.conditionID1))
3310 if (!sConditionMgr->IsPlayerMeetingCondition(player, playerCondition))
3311 return;
3312
3313 switch (info->itemForge.ForgeType)
3314 {
3315 case 0: // Artifact Forge
3316 case 1: // Relic Forge
3317 {
3319 Item const* item = artifactAura ? player->GetItemByGuid(artifactAura->GetCastItemGUID()) : nullptr;
3320 if (!item)
3321 {
3323 return;
3324 }
3325
3327 openArtifactForge.ArtifactGUID = item->GetGUID();
3328 openArtifactForge.ForgeGUID = GetGUID();
3329 player->SendDirectMessage(openArtifactForge.Write());
3330 break;
3331 }
3332 case 2: // Heart Forge
3333 {
3335 if (!item)
3336 return;
3337
3339 openHeartForge.ObjectGUID = GetGUID();
3341 player->SendDirectMessage(openHeartForge.Write());
3342 break;
3343 }
3344 default:
3345 break;
3346 }
3347 return;
3348 }
3350 {
3351 Player* player = user->ToPlayer();
3352 if (!player)
3353 return;
3354
3356 gameObjectUILink.ObjectGUID = GetGUID();
3357 switch (GetGOInfo()->UILink.UILinkType)
3358 {
3359 case 0:
3361 break;
3362 case 1:
3364 break;
3365 case 2:
3367 break;
3368 case 3:
3370 break;
3371 default:
3372 break;
3373 }
3374 player->SendDirectMessage(gameObjectUILink.Write());
3375 return;
3376 }
3378 {
3379 Player* player = user->ToPlayer();
3380 if (!player)
3381 return;
3382
3383 GameObjectTemplate const* info = GetGOInfo();
3384 if (!m_personalLoot.count(player->GetGUID()))
3385 {
3386 if (info->gatheringNode.chestLoot)
3387 {
3388 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr);
3389 m_personalLoot[player->GetGUID()].reset(loot);
3390
3391 loot->FillLoot(info->gatheringNode.chestLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
3392 }
3393
3394 if (info->gatheringNode.triggeredEvent)
3396
3397 // triggering linked GO
3398 if (uint32 trapEntry = info->gatheringNode.linkedTrap)
3399 TriggeringLinkedGameObject(trapEntry, player);
3400
3401 if (info->gatheringNode.xpDifficulty && info->gatheringNode.xpDifficulty < 10)
3402 if (QuestXPEntry const* questXp = sQuestXPStore.LookupEntry(player->GetLevel()))
3403 if (uint32 xp = Quest::RoundXPValue(questXp->Difficulty[info->gatheringNode.xpDifficulty]))
3404 player->GiveXP(xp, nullptr);
3405
3406 spellId = info->gatheringNode.spell;
3407 }
3408
3409 if (m_personalLoot.size() >= info->gatheringNode.MaxNumberofLoots)
3410 {
3413 }
3414
3415 if (getLootState() != GO_ACTIVATED)
3416 {
3417 SetLootState(GO_ACTIVATED, player);
3420 }
3421
3422 // Send loot
3423 if (Loot* loot = GetLootForPlayer(player))
3424 player->SendLoot(*loot);
3425 break;
3426 }
3427 default:
3429 TC_LOG_ERROR("misc", "GameObject::Use(): unit ({}, name: {}) tries to use object ({}, name: {}) of unknown type ({})",
3430 user->GetGUID().ToString(), user->GetName(), GetGUID().ToString(), GetGOInfo()->name, GetGoType());
3431 break;
3432 }
3433
3434 if (m_vignette)
3435 {
3436 if (Player* player = user->ToPlayer())
3437 {
3438 if (Quest const* reward = sObjectMgr->GetQuestTemplate(m_vignette->Data->RewardQuestID))
3439 if (!player->GetQuestRewardStatus(m_vignette->Data->RewardQuestID))
3440 player->RewardQuest(reward, LootItemType::Item, 0, this, false);
3441
3442 if (m_vignette->Data->VisibleTrackingQuestID)
3443 player->SetRewardedQuest(m_vignette->Data->VisibleTrackingQuestID);
3444 }
3445
3446 // only unregister it from visibility (need to keep vignette for other gameobject users in case its usable by multiple players
3447 // to flag their quest completion
3448 if (GetGOInfo()->ClearObjectVignetteonOpening())
3450 }
3451
3452 if (!spellId)
3453 return;
3454
3455 if (!sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID()))
3456 {
3457 if (user->GetTypeId() != TYPEID_PLAYER || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this))
3458 TC_LOG_ERROR("misc", "WORLD: unknown spell id {} at use action for gameobject (Entry: {} GoType: {})", spellId, GetEntry(), GetGoType());
3459 else
3460 TC_LOG_DEBUG("outdoorpvp", "WORLD: {} non-dbc spell was handled by OutdoorPvP", spellId);
3461 return;
3462 }
3463
3464 if (Player* player = user->ToPlayer())
3465 sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this);
3466
3467 if (spellCaster)
3468 spellCaster->CastSpell(user, spellId, triggered);
3469 else
3470 {
3471 SpellCastResult castResult = CastSpell(user, spellId);
3472 if (castResult == SPELL_FAILED_SUCCESS)
3473 {
3476 }
3477 }
3478}
3479
3481{
3483 customAnim.ObjectGUID = GetGUID();
3484 customAnim.CustomAnim = anim;
3485 SendMessageToSet(customAnim.Write(), true);
3486}
3487
3488bool GameObject::IsInRange(float x, float y, float z, float radius) const
3489{
3491 if (!info)
3492 return IsWithinDist3d(x, y, z, radius);
3493
3494 float sinA = std::sin(GetOrientation());
3495 float cosA = std::cos(GetOrientation());
3496 float dx = x - GetPositionX();
3497 float dy = y - GetPositionY();
3498 float dz = z - GetPositionZ();
3499 float dist = std::sqrt(dx*dx + dy*dy);
3502 if (G3D::fuzzyEq(dist, 0.0f))
3503 return true;
3504
3505 float sinB = dx / dist;
3506 float cosB = dy / dist;
3507 dx = dist * (cosA * cosB + sinA * sinB);
3508 dy = dist * (cosA * sinB - sinA * cosB);
3509 return dx < info->GeoBoxMax.X + radius && dx > info->GeoBoxMin.X - radius
3510 && dy < info->GeoBoxMax.Y + radius && dy > info->GeoBoxMin.Y - radius
3511 && dz < info->GeoBoxMax.Z + radius && dz > info->GeoBoxMin.Z - radius;
3512}
3513
3515{
3517 if (uint32 scriptId = gameObjectData->scriptId)
3518 return scriptId;
3519
3520 return GetGOInfo()->ScriptId;
3521}
3522
3523bool GameObject::HasStringId(std::string_view id) const
3524{
3525 return std::find(m_stringIds.begin(), m_stringIds.end(), id) != m_stringIds.end();
3526}
3527
3529{
3530 if (!id.empty())
3531 {
3532 m_scriptStringId.emplace(std::move(id));
3534 }
3535 else
3536 {
3537 m_scriptStringId.reset();
3538 m_stringIds[2] = {};
3539 }
3540}
3541
3542// overwrite WorldObject function for proper name localization
3544{
3545 if (locale != DEFAULT_LOCALE)
3546 if (GameObjectLocale const* cl = sObjectMgr->GetGameObjectLocale(GetEntry()))
3547 if (cl->Name.size() > locale && !cl->Name[locale].empty())
3548 return cl->Name[locale];
3549
3550 return GetName();
3551}
3552
3554{
3555 static const int32 PACK_YZ = 1 << 20;
3556 static const int32 PACK_X = PACK_YZ << 1;
3557
3558 static const int32 PACK_YZ_MASK = (PACK_YZ << 1) - 1;
3559 static const int32 PACK_X_MASK = (PACK_X << 1) - 1;
3560
3561 int8 w_sign = (m_localRotation.w >= 0.f ? 1 : -1);
3562 int64 x = int32(m_localRotation.x * PACK_X) * w_sign & PACK_X_MASK;
3563 int64 y = int32(m_localRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK;
3564 int64 z = int32(m_localRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK;
3565 m_packedRotation = z | (y << 21) | (x << 42);
3566}
3567
3568void GameObject::SetLocalRotation(float qx, float qy, float qz, float qw)
3569{
3570 G3D::Quat rotation(qx, qy, qz, qw);
3571 rotation.unitize();
3572 m_localRotation.x = rotation.x;
3573 m_localRotation.y = rotation.y;
3574 m_localRotation.z = rotation.z;
3575 m_localRotation.w = rotation.w;
3577}
3578
3580{
3582}
3583
3584void GameObject::SetLocalRotationAngles(float z_rot, float y_rot, float x_rot)
3585{
3586 G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot));
3587 SetLocalRotation(quat.x, quat.y, quat.z, quat.w);
3588}
3589
3591{
3592 QuaternionData localRotation = GetLocalRotation();
3593 if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
3594 {
3595 QuaternionData worldRotation = transport->GetWorldRotation();
3596
3597 G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w);
3598 G3D::Quat localRotationQuat(localRotation.x, localRotation.y, localRotation.z, localRotation.w);
3599
3600 G3D::Quat resultRotation = localRotationQuat * worldRotationQuat;
3601
3602 return QuaternionData(resultRotation.x, resultRotation.y, resultRotation.z, resultRotation.w);
3603 }
3604 return localRotation;
3605}
3606
3607void GameObject::ModifyHealth(int32 change, WorldObject* attackerOrHealer /*= nullptr*/, uint32 spellId /*= 0*/)
3608{
3609 if (!m_goValue.Building.MaxHealth || !change)
3610 return;
3611
3612 // prevent double destructions of the same object
3613 if (change < 0 && !m_goValue.Building.Health)
3614 return;
3615
3616 if (int32(m_goValue.Building.Health) + change <= 0)
3620 else
3621 m_goValue.Building.Health += change;
3622
3623 // Set the health bar, value = 255 * healthPct;
3625
3626 // dealing damage, send packet
3627 if (Player* player = attackerOrHealer ? attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr)
3628 {
3630 packet.Caster = attackerOrHealer->GetGUID(); // todo: this can be a GameObject
3631 packet.Target = GetGUID();
3632 packet.Damage = -change;
3633 packet.Owner = player->GetGUID();
3634 packet.SpellID = spellId;
3635 player->SendDirectMessage(packet.Write());
3636 }
3637
3638 if (change < 0 && GetGOInfo()->destructibleBuilding.DamageEvent)
3639 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DamageEvent, attackerOrHealer, this);
3640
3642
3644 newState = GO_DESTRUCTIBLE_DESTROYED;
3645 else if (m_goValue.Building.Health <= 10000/*GetGOInfo()->destructibleBuilding.damagedNumHits*/) // TODO: Get health somewhere
3646 newState = GO_DESTRUCTIBLE_DAMAGED;
3648 newState = GO_DESTRUCTIBLE_INTACT;
3649
3650 if (newState == GetDestructibleState())
3651 return;
3652
3653 SetDestructibleState(newState, attackerOrHealer, false);
3654}
3655
3656void GameObject::SetDestructibleState(GameObjectDestructibleState state, WorldObject* attackerOrHealer /*= nullptr*/, bool setHealth /*= false*/)
3657{
3658 // the user calling this must know he is already operating on destructible gameobject
3660
3661 switch (state)
3662 {
3666 if (setHealth)
3667 {
3669 SetGoAnimProgress(255);
3670 }
3671 EnableCollision(true);
3672 break;
3674 {
3675 if (GetGOInfo()->destructibleBuilding.DamagedEvent && attackerOrHealer)
3676 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DamagedEvent, attackerOrHealer, this);
3677 AI()->Damaged(attackerOrHealer, m_goInfo->destructibleBuilding.DamagedEvent);
3678
3681
3682 uint32 modelId = m_goInfo->displayId;
3684 if (modelData->State1Wmo)
3685 modelId = modelData->State1Wmo;
3686 SetDisplayId(modelId);
3687
3688 if (setHealth)
3689 {
3690 m_goValue.Building.Health = 10000/*m_goInfo->destructibleBuilding.damagedNumHits*/;
3691 uint32 maxHealth = m_goValue.Building.MaxHealth;
3692 // in this case current health is 0 anyway so just prevent crashing here
3693 if (!maxHealth)
3694 maxHealth = 1;
3695 SetGoAnimProgress(m_goValue.Building.Health * 255 / maxHealth);
3696 }
3697 break;
3698 }
3700 {
3701 if (GetGOInfo()->destructibleBuilding.DestroyedEvent && attackerOrHealer)
3702 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DestroyedEvent, attackerOrHealer, this);
3704
3705 if (Player* player = attackerOrHealer ? attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr)
3706 if (Battleground* bg = player->GetBattleground())
3707 bg->DestroyGate(player, this);
3708
3711
3712 uint32 modelId = m_goInfo->displayId;
3714 if (modelData->State2Wmo)
3715 modelId = modelData->State2Wmo;
3716 SetDisplayId(modelId);
3717
3718 if (setHealth)
3719 {
3722 }
3723 EnableCollision(false);
3724 break;
3725 }
3727 {
3728 if (GetGOInfo()->destructibleBuilding.RebuildingEvent && attackerOrHealer)
3729 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.RebuildingEvent, attackerOrHealer, this);
3731
3732 uint32 modelId = m_goInfo->displayId;
3734 if (modelData->State3Wmo)
3735 modelId = modelData->State3Wmo;
3736 SetDisplayId(modelId);
3737
3738 // restores to full health
3739 if (setHealth)
3740 {
3742 SetGoAnimProgress(255);
3743 }
3744 EnableCollision(true);
3745 break;
3746 }
3747 }
3748}
3749
3751{
3752 m_lootState = state;
3753 if (unit)
3754 m_lootStateUnitGUID = unit->GetGUID();
3755 else
3757
3758 AI()->OnLootStateChanged(state, unit);
3759
3760 // Start restock timer if the chest is partially looted or not looted at all
3761 if (GetGoType() == GAMEOBJECT_TYPE_CHEST && state == GO_ACTIVATED && GetGOInfo()->chest.chestRestockTime > 0 && m_restockTime == 0 && m_loot && m_loot->IsChanged())
3763
3764 if (GetGoType() == GAMEOBJECT_TYPE_DOOR) // only set collision for doors on SetGoState
3765 return;
3766
3767 if (m_model)
3768 {
3769 bool collision = false;
3770 // Use the current go state
3771 if ((GetGoState() != GO_STATE_READY && (state == GO_ACTIVATED || state == GO_JUST_DEACTIVATED)) || state == GO_READY)
3772 collision = !collision;
3773
3774 EnableCollision(collision);
3775 }
3776}
3777
3779{
3780 // Unlink loot objects from this GameObject before destroying to avoid accessing freed memory from Loot destructor
3781 std::unique_ptr<Loot> loot(std::move(m_loot));
3782 std::unordered_map<ObjectGuid, std::unique_ptr<Loot>> personalLoot(std::move(m_personalLoot));
3783
3784 loot.reset();
3785 personalLoot.clear();
3786 m_unique_users.clear();
3787 m_usetimes = 0;
3788}
3789
3791{
3792 if (m_loot && !m_loot->isLooted())
3793 return false;
3794
3795 for (auto const& [_, loot] : m_personalLoot)
3796 if (!loot->isLooted())
3797 return false;
3798
3799 return true;
3800}
3801
3803{
3804 switch (GetGoType())
3805 {
3807 {
3808 GameObjectTemplate const* goInfo = GetGOInfo();
3809 if (!goInfo->chest.consumable && goInfo->chest.chestPersonalLoot)
3810 {
3812 ? Seconds(goInfo->chest.chestRestockTime)
3813 : Seconds(m_respawnDelayTime)); // not hiding this object permanently to prevent infinite growth of m_perPlayerState
3814 // while also maintaining some sort of cheater protection (not getting rid of entries on logout)
3815 }
3816 break;
3817 }
3819 {
3821
3822 UF::ObjectData::Base objMask;
3825
3826 UpdateData udata(GetMapId());
3827 BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), looter);
3828 WorldPacket packet;
3829 udata.BuildPacket(&packet);
3830 looter->SendDirectMessage(&packet);
3831 break;
3832 }
3833 default:
3834 break;
3835 }
3836}
3837
3839{
3840 GOState oldState = GetGoState();
3842 if (AI())
3843 AI()->OnStateChanged(state);
3844
3845 if (m_goTypeImpl)
3846 m_goTypeImpl->OnStateChanged(oldState, state);
3847
3848 if (m_model && !IsTransport())
3849 {
3850 if (!IsInWorld())
3851 return;
3852
3853 // startOpen determines whether we are going to add or remove the LoS on activation
3854 bool collision = false;
3855 if (state == GO_STATE_READY)
3856 collision = !collision;
3857
3858 EnableCollision(collision);
3859 }
3860}
3861
3863{
3864 if (m_perPlayerState)
3866 if (state->State)
3867 return *state->State;
3868
3869 return GetGoState();
3870}
3871
3872void GameObject::SetGoStateFor(GOState state, Player const* viewer)
3873{
3874 PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[viewer->GetGUID()];
3876 perPlayerState.State = state;
3877
3879 setStateLocal.ObjectGUID = GetGUID();
3880 setStateLocal.State = state;
3881 viewer->SendDirectMessage(setStateLocal.Write());
3882}
3883
3885{
3887 UpdateModel();
3888}
3889
3891{
3892 switch (GetGoType())
3893 {
3896 {
3897 switch (GetDestructibleState())
3898 {
3900 return modelData->State0NameSet;
3902 return modelData->State1NameSet;
3904 return modelData->State2NameSet;
3906 return modelData->State3NameSet;
3907 default:
3908 break;
3909 }
3910 }
3911 break;
3915 return ((*m_gameObjectData->Flags) >> 8) & 0xF;
3916 default:
3917 break;
3918 }
3919
3920 return 0;
3921}
3922
3924{
3925 if (!m_model)
3926 return;
3927
3928 /*if (enable && !GetMap()->ContainsGameObjectModel(*m_model))
3929 GetMap()->InsertGameObjectModel(*m_model);*/
3930
3931 m_model->enableCollision(enable);
3932}
3933
3935{
3936 if (!IsInWorld())
3937 return;
3938 if (m_model)
3939 if (GetMap()->ContainsGameObjectModel(*m_model))
3942 delete m_model;
3943 m_model = nullptr;
3944 CreateModel();
3945 if (m_model)
3947}
3948
3949bool GameObject::IsLootAllowedFor(Player const* player) const
3950{
3951 if (Loot const* loot = GetLootForPlayer(player)) // check only if loot was already generated
3952 {
3953 if (loot->isLooted()) // nothing to loot or everything looted.
3954 return false;
3955 if (!loot->HasAllowedLooter(GetGUID()) || (!loot->hasItemForAll() && !loot->hasItemFor(player))) // no loot in chest for this player
3956 return false;
3957 }
3958
3959 if (HasLootRecipient())
3960 return m_tapList.find(player->GetGUID()) != m_tapList.end();
3961
3962 return true;
3963}
3964
3966{
3967 if (m_personalLoot.empty())
3968 return m_loot.get();
3969
3970 if (std::unique_ptr<Loot> const* loot = Trinity::Containers::MapGetValuePtr(m_personalLoot, player->GetGUID()))
3971 return loot->get();
3972
3973 return nullptr;
3974}
3975
3977{
3979}
3980
3981void GameObject::BuildValuesCreate(ByteBuffer* data, Player const* target) const
3982{
3984 std::size_t sizePos = data->wpos();
3985 *data << uint32(0);
3986 *data << uint8(flags);
3987 m_objectData->WriteCreate(*data, flags, this, target);
3988 m_gameObjectData->WriteCreate(*data, flags, this, target);
3989 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
3990}
3991
3992void GameObject::BuildValuesUpdate(ByteBuffer* data, Player const* target) const
3993{
3995 std::size_t sizePos = data->wpos();
3996 *data << uint32(0);
3998
4000 m_objectData->WriteUpdate(*data, flags, this, target);
4001
4003 m_gameObjectData->WriteUpdate(*data, flags, this, target);
4004
4005 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
4006}
4007
4009 UF::GameObjectData::Mask const& requestedGameObjectMask, Player const* target) const
4010{
4012 if (requestedObjectMask.IsAnySet())
4013 valuesMask.Set(TYPEID_OBJECT);
4014
4015 if (requestedGameObjectMask.IsAnySet())
4016 valuesMask.Set(TYPEID_GAMEOBJECT);
4017
4018 ByteBuffer& buffer = PrepareValuesUpdateBuffer(data);
4019 std::size_t sizePos = buffer.wpos();
4020 buffer << uint32(0);
4021 buffer << uint32(valuesMask.GetBlock(0));
4022
4023 if (valuesMask[TYPEID_OBJECT])
4024 m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
4025
4026 if (valuesMask[TYPEID_GAMEOBJECT])
4027 m_gameObjectData->WriteUpdate(buffer, requestedGameObjectMask, true, this, target);
4028
4029 buffer.put<uint32>(sizePos, buffer.wpos() - sizePos - 4);
4030
4031 data->AddUpdateBlock();
4032}
4033
4035{
4036 UpdateData udata(Owner->GetMapId());
4037 WorldPacket packet;
4038
4040
4041 udata.BuildPacket(&packet);
4042 player->SendDirectMessage(&packet);
4043}
4044
4046{
4049}
4050
4051std::vector<uint32> const* GameObject::GetPauseTimes() const
4052{
4053 if (GameObjectType::Transport const* transport = dynamic_cast<GameObjectType::Transport const*>(m_goTypeImpl.get()))
4054 return transport->GetPauseTimes();
4055
4056 return nullptr;
4057}
4058
4060{
4062 {
4063 UF::ObjectData::Base dynflagMask;
4065 bool marked = (m_objectData->GetChangesMask() & dynflagMask.GetChangesMask()).IsAnySet();
4066
4067 uint32 dynamicFlags = GetDynamicFlags();
4068 dynamicFlags &= 0xFFFF; // remove high bits
4069 dynamicFlags |= uint32(progress * 65535.0f) << 16;
4070 ReplaceAllDynamicFlags(dynamicFlags);
4071
4072 if (!marked)
4073 const_cast<UF::ObjectData&>(*m_objectData).ClearChanged(&UF::ObjectData::DynamicFlags);
4074 });
4075}
4076
4077void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const
4078{
4079 if (m_goData)
4080 {
4081 if (ori)
4082 m_goData->spawnPoint.GetPosition(x, y, z, *ori);
4083 else
4085 }
4086 else
4087 {
4088 if (ori)
4089 GetPosition(x, y, z, *ori);
4090 else
4091 GetPosition(x, y, z);
4092 }
4093}
4094
4096{
4097 switch (GetGoType())
4098 {
4100 return static_cast<GameObjectType::Transport const*>(m_goTypeImpl.get());
4102 return static_cast<Transport const*>(this);
4103 default:
4104 break;
4105 }
4106
4107 return nullptr;
4108}
4109
4111{
4114 if (m_goTypeImpl)
4115 m_goTypeImpl->OnRelocated();
4116
4117 // TODO: on heartbeat
4118 if (m_vignette)
4120
4122}
4123
4125{
4126 if (GetGOInfo()->GetInteractRadiusOverride())
4127 return float(GetGOInfo()->GetInteractRadiusOverride()) / 100.0f;
4128
4129 switch (GetGoType())
4130 {
4132 return 0.0f;
4138 return 5.5555553f;
4140 return 10.0f;
4143 return 3.0f;
4145 return 100.0f;
4147 return 20.0f + CONTACT_DISTANCE; // max spell range
4153 return 5.0f;
4154 // Following values are not blizzlike
4157 // Successful mailbox interaction is rather critical to the client, failing it will start a minute-long cooldown until the next mail query may be executed.
4158 // And since movement info update is not sent with mailbox interaction query, server may find the player outside of interaction range. Thus we increase it.
4159 return 10.0f; // 5.0f is blizzlike
4160 default:
4161 return INTERACTION_DISTANCE;
4162 }
4163}
4164
4166{
4167 if (!m_model)
4168 return;
4169
4170 if (GetMap()->ContainsGameObjectModel(*m_model))
4171 {
4175 }
4176}
4177
4178void GameObject::SetAnimKitId(uint16 animKitId, bool oneshot)
4179{
4180 if (_animKitId == animKitId)
4181 return;
4182
4183 if (animKitId && !sAnimKitStore.LookupEntry(animKitId))
4184 return;
4185
4186 if (!oneshot)
4187 _animKitId = animKitId;
4188 else
4189 _animKitId = 0;
4190
4192 activateAnimKit.ObjectGUID = GetGUID();
4193 activateAnimKit.AnimKitID = animKitId;
4194 activateAnimKit.Maintain = !oneshot;
4195 SendMessageToSet(activateAnimKit.Write(), true);
4196}
4197
4199{
4200 if (m_vignette)
4201 {
4202 if (m_vignette->Data->ID == vignetteId)
4203 return;
4204
4206 m_vignette = nullptr;
4207 }
4208
4209 if (VignetteEntry const* vignette = sVignetteStore.LookupEntry(vignetteId))
4210 m_vignette = Vignettes::Create(vignette, this);
4211}
4212
4213void GameObject::SetSpellVisualId(int32 spellVisualId, ObjectGuid activatorGuid)
4214{
4216
4218 packet.ObjectGUID = GetGUID();
4219 packet.ActivatorGUID = activatorGuid;
4220 packet.SpellVisualID = spellVisualId;
4221 SendMessageToSet(packet.Write(), true);
4222}
4223
4225{
4226 if (!CanInteractWithCapturePoint(player))
4227 return;
4228
4229 if (GameObjectAI* ai = AI())
4230 if (ai->OnCapturePointAssaulted(player))
4231 return;
4232
4233 // only supported in battlegrounds
4234 Battleground* battleground = nullptr;
4235 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
4236 if (Battleground* bg = map->GetBG())
4237 battleground = bg;
4238
4239 if (!battleground)
4240 return;
4241
4242 // Cancel current timer
4244
4245 if (player->GetBGTeam() == HORDE)
4246 {
4248 {
4249 // defended. capture instantly.
4251 battleground->SendBroadcastText(GetGOInfo()->capturePoint.DefendedBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE, player);
4253 if (GetGOInfo()->capturePoint.DefendedEventHorde)
4254 GameEvents::Trigger(GetGOInfo()->capturePoint.DefendedEventHorde, player, this);
4255 return;
4256 }
4257
4259 {
4264 battleground->SendBroadcastText(GetGOInfo()->capturePoint.AssaultBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE, player);
4266 if (GetGOInfo()->capturePoint.ContestedEventHorde)
4267 GameEvents::Trigger(GetGOInfo()->capturePoint.ContestedEventHorde, player, this);
4269 break;
4270 default:
4271 break;
4272 }
4273 }
4274 else
4275 {
4277 {
4278 // defended. capture instantly.
4282 if (GetGOInfo()->capturePoint.DefendedEventAlliance)
4283 GameEvents::Trigger(GetGOInfo()->capturePoint.DefendedEventAlliance, player, this);
4284 return;
4285 }
4286
4288 {
4295 if (GetGOInfo()->capturePoint.ContestedEventAlliance)
4296 GameEvents::Trigger(GetGOInfo()->capturePoint.ContestedEventAlliance, player, this);
4298 break;
4299 default:
4300 break;
4301 }
4302 }
4303}
4304
4306{
4308 return;
4309
4310 if (GameObjectAI* ai = AI())
4311 if (ai->OnCapturePointUpdated(m_goValue.CapturePoint.State))
4312 return;
4313
4314 uint32 spellVisualId = 0;
4315 uint32 customAnim = 0;
4316
4318 {
4320 spellVisualId = GetGOInfo()->capturePoint.SpellVisual1;
4321 break;
4323 customAnim = 1;
4324 spellVisualId = GetGOInfo()->capturePoint.SpellVisual2;
4325 break;
4327 customAnim = 2;
4328 spellVisualId = GetGOInfo()->capturePoint.SpellVisual3;
4329 break;
4331 customAnim = 3;
4332 spellVisualId = GetGOInfo()->capturePoint.SpellVisual4;
4333 break;
4335 customAnim = 4;
4336 spellVisualId = GetGOInfo()->capturePoint.SpellVisual5;
4337 break;
4338 default:
4339 break;
4340 }
4341
4342 if (customAnim != 0)
4343 SendCustomAnim(customAnim);
4344
4345 SetSpellVisualId(spellVisualId);
4347
4348 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
4349 {
4350 if (Battleground* bg = map->GetBG())
4351 {
4354 packet.CapturePointInfo.Pos = GetPosition();
4355 packet.CapturePointInfo.Guid = GetGUID();
4358 bg->SendPacketToAll(packet.Write());
4359 bg->UpdateWorldState(GetGOInfo()->capturePoint.worldState1, AsUnderlyingType(m_goValue.CapturePoint.State));
4360 }
4361 }
4362
4364}
4365
4367{
4369 return false;
4370
4372 return true;
4373
4374 if (target->GetBGTeam() == HORDE)
4375 {
4378 }
4379
4380 // For Alliance players
4383}
4384
4386{
4388 return FlagState(0);
4389
4390 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
4391 if (!newFlag)
4392 return FlagState(0);
4393
4394 return newFlag->GetState();
4395}
4396
4398{
4400 return ObjectGuid::Empty;
4401
4402 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
4403 if (!newFlag)
4404 return ObjectGuid::Empty;
4405
4406 return newFlag->GetCarrierGUID();
4407}
4408
4410{
4412 return time_t(0);
4413
4414 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
4415 if (!newFlag)
4416 return time_t(0);
4417
4418 return newFlag->GetTakenFromBaseTime();
4419}
4420
4422{
4423 if (GameObjectType::ControlZone const* controlZone = dynamic_cast<GameObjectType::ControlZone const*>(m_goTypeImpl.get()))
4424 return controlZone->GetInsidePlayers();
4425
4426 return nullptr;
4427}
4428
4430{
4431 if (!m_goInfo->GetConditionID1())
4432 return true;
4433
4434 if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(m_goInfo->GetConditionID1()))
4435 if (!ConditionMgr::IsPlayerMeetingCondition(user, playerCondition))
4436 return false;
4437
4438 return true;
4439}
4440
4441std::unordered_map<ObjectGuid, GameObject::PerPlayerState>& GameObject::GetOrCreatePerPlayerStates()
4442{
4443 if (!m_perPlayerState)
4444 m_perPlayerState = std::make_unique<std::unordered_map<ObjectGuid, PerPlayerState>>();
4445
4446 return *m_perPlayerState;
4447}
4448
4450{
4451public:
4452 explicit GameObjectModelOwnerImpl(GameObject* owner) : _owner(owner) { }
4453 virtual ~GameObjectModelOwnerImpl() = default;
4454
4455 bool IsSpawned() const override { return _owner->isSpawned(); }
4456 uint32 GetDisplayId() const override { return _owner->GetDisplayId(); }
4457 uint8 GetNameSetId() const override { return _owner->GetNameSetId(); }
4458 bool IsInPhase(PhaseShift const& phaseShift) const override { return _owner->GetPhaseShift().CanSee(phaseShift); }
4459 G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); }
4460 G3D::Quat GetRotation() const override { return G3D::Quat(_owner->GetLocalRotation().x, _owner->GetLocalRotation().y, _owner->GetLocalRotation().z, _owner->GetLocalRotation().w); }
4461 float GetScale() const override { return _owner->GetObjectScale(); }