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 {
67 if (!sWorld->getBoolConfig(CONFIG_LOAD_LOCALES) && loc != DEFAULT_LOCALE)
68 continue;
69
70 QueryData[loc] = BuildQueryData(static_cast<LocaleConstant>(loc));
71 }
72}
73
75{
77
78 queryTemp.GameObjectID = entry;
79
80 queryTemp.Allow = true;
82
83 stats.Type = type;
84 stats.DisplayID = displayId;
85
86 stats.Name[0] = name;
87 stats.IconName = IconName;
89 stats.UnkString = unk1;
90
91 if (loc != LOCALE_enUS)
92 if (GameObjectLocale const* gameObjectLocale = sObjectMgr->GetGameObjectLocale(entry))
93 {
94 ObjectMgr::GetLocaleString(gameObjectLocale->Name, loc, stats.Name[0]);
95 ObjectMgr::GetLocaleString(gameObjectLocale->CastBarCaption, loc, stats.CastBarCaption);
96 ObjectMgr::GetLocaleString(gameObjectLocale->Unk1, loc, stats.UnkString);
97 }
98
99 stats.Size = size;
100
101 if (std::vector<uint32> const* items = sObjectMgr->GetGameObjectQuestItemList(entry))
102 for (int32 item : *items)
103 stats.QuestItems.push_back(item);
104
105 memcpy(stats.Data.data(), raw.data, MAX_GAMEOBJECT_DATA * sizeof(int32));
107
108 queryTemp.Write();
109 queryTemp.ShrinkToFit();
110 return queryTemp.Move();
111}
112
114{
115 return fabs(x * x + y * y + z * z + w * w - 1.0f) < 1e-5f;
116}
117
118void QuaternionData::toEulerAnglesZYX(float& Z, float& Y, float& X) const
119{
120 G3D::Matrix3(G3D::Quat(x, y, z, w)).toEulerAnglesZYX(Z, Y, X);
121}
122
124{
125 G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(Z, Y, X));
126 return QuaternionData(quat.x, quat.y, quat.z, quat.w);
127}
128
130
132{
133//11 GAMEOBJECT_TYPE_TRANSPORT
135{
136public:
137 static constexpr Milliseconds PositionUpdateInterval = 50ms;
138
139 explicit Transport(GameObject& owner) : GameObjectTypeBase(owner), _animationInfo(sTransportMgr->GetTransportAnimInfo(owner.GetGOInfo()->entry)),
142 {
143 GameObjectTemplate const* goInfo = _owner.GetGOInfo();
144 if (goInfo->transport.Timeto2ndfloor > 0)
145 {
146 _stopFrames.push_back(goInfo->transport.Timeto2ndfloor);
147 if (goInfo->transport.Timeto3rdfloor > 0)
148 {
149 _stopFrames.push_back(goInfo->transport.Timeto3rdfloor);
150 if (goInfo->transport.Timeto4thfloor > 0)
151 {
152 _stopFrames.push_back(goInfo->transport.Timeto4thfloor);
153 if (goInfo->transport.Timeto5thfloor > 0)
154 {
155 _stopFrames.push_back(goInfo->transport.Timeto5thfloor);
156 if (goInfo->transport.Timeto6thfloor > 0)
157 {
158 _stopFrames.push_back(goInfo->transport.Timeto6thfloor);
159 if (goInfo->transport.Timeto7thfloor > 0)
160 {
161 _stopFrames.push_back(goInfo->transport.Timeto7thfloor);
162 if (goInfo->transport.Timeto8thfloor > 0)
163 {
164 _stopFrames.push_back(goInfo->transport.Timeto8thfloor);
165 if (goInfo->transport.Timeto9thfloor > 0)
166 {
167 _stopFrames.push_back(goInfo->transport.Timeto9thfloor);
168 if (goInfo->transport.Timeto10thfloor > 0)
169 _stopFrames.push_back(goInfo->transport.Timeto10thfloor);
170 }
171 }
172 }
173 }
174 }
175 }
176 }
177 }
178
179 if (!_stopFrames.empty())
180 {
181 _pathProgress = 0;
183 }
184
186 }
187
188 void Update(uint32 diff) override
189 {
190 if (!_animationInfo)
191 return;
192
195 return;
196
198
200 uint32 period = GetTransportPeriod();
201 uint32 newProgress = 0;
202 if (_stopFrames.empty())
203 newProgress = now % period;
204 else
205 {
206 int32 stopTargetTime = 0;
208 stopTargetTime = 0;
209 else
211
212 if (now < uint32(*_owner.m_gameObjectData->Level))
213 {
214 int32 timeToStop = _owner.m_gameObjectData->Level - _stateChangeTime;
215 float stopSourcePathPct = float(_stateChangeProgress) / float(period);
216 float stopTargetPathPct = float(stopTargetTime) / float(period);
217 float timeSinceStopProgressPct = float(now - _stateChangeTime) / float(timeToStop);
218
219 float progressPct;
221 {
223 stopTargetPathPct = 1.0f;
224
225 float pathPctBetweenStops = stopTargetPathPct - stopSourcePathPct;
226 if (pathPctBetweenStops < 0.0f)
227 pathPctBetweenStops += 1.0f;
228
229 progressPct = pathPctBetweenStops * timeSinceStopProgressPct + stopSourcePathPct;
230 if (progressPct > 1.0f)
231 progressPct = progressPct - 1.0f;
232 }
233 else
234 {
235 float pathPctBetweenStops = stopSourcePathPct - stopTargetPathPct;
236 if (pathPctBetweenStops < 0.0f)
237 pathPctBetweenStops += 1.0f;
238
239 progressPct = stopSourcePathPct - pathPctBetweenStops * timeSinceStopProgressPct;
240 if (progressPct < 0.0f)
241 progressPct += 1.0f;
242 }
243
244 newProgress = uint32(float(period) * progressPct) % period;
245 }
246 else
247 newProgress = stopTargetTime;
248
249 if (int32(newProgress) == stopTargetTime && newProgress != _pathProgress)
250 {
251 uint32 eventId = [&]()
252 {
254 {
255 case 0:
257 case 1:
259 case 2:
261 case 3:
263 case 4:
265 case 5:
267 case 6:
269 case 7:
271 case 8:
273 case 9:
275 default:
276 return 0u;
277 }
278 }();
279 if (eventId)
280 GameEvents::Trigger(eventId, &_owner, &_owner);
281
283 {
284 GOState currentState = _owner.GetGoState();
285 GOState newState;
286 if (currentState == GO_STATE_TRANSPORT_ACTIVE)
288 else if (currentState - GO_STATE_TRANSPORT_ACTIVE == int32(_stopFrames.size()))
289 newState = GOState(currentState - 1);
291 newState = GOState(currentState - 1);
292 else
293 newState = GOState(currentState + 1);
294
295 _owner.SetGoState(newState);
296 }
297 }
298 }
299
300 if (_pathProgress == newProgress)
301 return;
302
303 _pathProgress = newProgress;
304
305 TransportAnimationEntry const* oldAnimation = _animationInfo->GetPrevAnimNode(newProgress);
306 TransportAnimationEntry const* newAnimation = _animationInfo->GetNextAnimNode(newProgress);
307 if (oldAnimation && newAnimation)
308 {
309 G3D::Matrix3 pathRotation = G3D::Quat(_owner.m_gameObjectData->ParentRotation->x, _owner.m_gameObjectData->ParentRotation->y,
310 _owner.m_gameObjectData->ParentRotation->z, _owner.m_gameObjectData->ParentRotation->w).toRotationMatrix();
311
312 G3D::Vector3 prev(oldAnimation->Pos.X, oldAnimation->Pos.Y, oldAnimation->Pos.Z);
313 G3D::Vector3 next(newAnimation->Pos.X, newAnimation->Pos.Y, newAnimation->Pos.Z);
314
315 G3D::Vector3 dst = next;
316 if (prev != next)
317 {
318 float animProgress = float(newProgress - oldAnimation->TimeIndex) / float(newAnimation->TimeIndex - oldAnimation->TimeIndex);
319
320 dst = prev.lerp(next, animProgress);
321 }
322
323 dst = dst * pathRotation;
324 dst += PositionToVector3(&_owner.GetStationaryPosition());
325
326 _owner.GetMap()->GameObjectRelocation(&_owner, dst.x, dst.y, dst.z, _owner.GetOrientation());
327 }
328
329 TransportRotationEntry const* oldRotation = _animationInfo->GetPrevAnimRotation(newProgress);
330 TransportRotationEntry const* newRotation = _animationInfo->GetNextAnimRotation(newProgress);
331 if (oldRotation && newRotation)
332 {
333 G3D::Quat prev(oldRotation->Rot[0], oldRotation->Rot[1], oldRotation->Rot[2], oldRotation->Rot[3]);
334 G3D::Quat next(newRotation->Rot[0], newRotation->Rot[1], newRotation->Rot[2], newRotation->Rot[3]);
335
336 G3D::Quat rotation = next;
337
338 if (prev != next)
339 {
340 float animProgress = float(newProgress - oldRotation->TimeIndex) / float(newRotation->TimeIndex - oldRotation->TimeIndex);
341
342 rotation = prev.slerp(next, animProgress);
343 }
344
345 _owner.SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w);
347 }
348
349 // update progress marker for client
350 _owner.SetPathProgressForClient(float(_pathProgress) / float(period));
351 }
352
353 void OnStateChanged(GOState oldState, GOState newState) override
354 {
356
357 // transports without stop frames just keep animating in state 24
358 if (_stopFrames.empty())
359 {
360 if (newState != GO_STATE_TRANSPORT_ACTIVE)
362 return;
363 }
364
365 int32 stopPathProgress = 0;
366
367 if (newState != GO_STATE_TRANSPORT_ACTIVE)
368 {
370 uint32 stopFrame = newState - GO_STATE_TRANSPORT_STOPPED;
371 ASSERT(stopFrame < _stopFrames.size());
372 stopPathProgress = _stopFrames[stopFrame];
373 }
374
377 uint32 timeToStop = std::abs(int32(_pathProgress) - stopPathProgress);
380
381 if (oldState == GO_STATE_ACTIVE || oldState == newState)
382 {
383 // initialization
384 if (int32(_pathProgress) > stopPathProgress)
386 else
388
389 return;
390 }
391
392 int32 pauseTimesCount = _stopFrames.size();
393 int32 newToOldStateDelta = newState - oldState;
394 if (newToOldStateDelta < 0)
395 newToOldStateDelta += pauseTimesCount + 1;
396
397 int32 oldToNewStateDelta = oldState - newState;
398 if (oldToNewStateDelta < 0)
399 oldToNewStateDelta += pauseTimesCount + 1;
400
401 // this additional check is neccessary because client doesn't check dynamic flags on progress update
402 // instead it multiplies progress from dynamicflags field by -1 and then compares that against 0
403 // when calculating path progress while we simply check the flag if (!_owner.HasDynamicFlag(GO_DYNFLAG_LO_INVERTED_MOVEMENT))
404 bool isAtStartOfPath = _stateChangeProgress == 0;
405
406 if (oldToNewStateDelta < newToOldStateDelta && !isAtStartOfPath)
408 else
410 }
411
412 void OnRelocated() override
413 {
415 }
416
418 {
419 for (WorldObject* passenger : _passengers)
420 {
421 float x, y, z, o;
422 passenger->m_movementInfo.transport.pos.GetPosition(x, y, z, o);
423 CalculatePassengerPosition(x, y, z, &o);
424 UpdatePassengerPosition(_owner.GetMap(), passenger, x, y, z, o, true);
425 }
426 }
427
429 {
430 if (_animationInfo)
432
433 return 1;
434 }
435
436 std::vector<uint32> const* GetPauseTimes() const
437 {
438 return &_stopFrames;
439 }
440
441 ObjectGuid GetTransportGUID() const override { return _owner.GetGUID(); }
442
443 float GetTransportOrientation() const override { return _owner.GetOrientation(); }
444
445 void AddPassenger(WorldObject* passenger) override
446 {
447 if (!_owner.IsInWorld())
448 return;
449
450 if (_passengers.insert(passenger).second)
451 {
452 passenger->SetTransport(this);
454 TC_LOG_DEBUG("entities.transport", "Object {} boarded transport {}.", passenger->GetName(), _owner.GetName());
455 }
456 }
457
459 {
460 if (_passengers.erase(passenger) > 0)
461 {
462 passenger->SetTransport(nullptr);
463 passenger->m_movementInfo.transport.Reset();
464 TC_LOG_DEBUG("entities.transport", "Object {} removed from transport {}.", passenger->GetName(), _owner.GetName());
465
466 if (Player* plr = passenger->ToPlayer())
467 plr->SetFallInformation(0, plr->GetPositionZ());
468 }
469
470 return this;
471 }
472
473 void CalculatePassengerPosition(float& x, float& y, float& z, float* o) const override
474 {
476 }
477
478 void CalculatePassengerOffset(float& x, float& y, float& z, float* o) const override
479 {
481 }
482
483 int32 GetMapIdForSpawning() const override
484 {
486 }
487
489 {
491 }
492
493private:
498 std::vector<uint32> _stopFrames;
501 std::unordered_set<WorldObject*> _passengers;
502};
503
505{
506}
507
509{
510 if (Transport* transport = dynamic_cast<Transport*>(&type))
511 transport->SetAutoCycleBetweenStopFrames(_on);
512}
513
515{
516public:
518
519 void SetState(FlagState newState, Player* player)
520 {
521 if (_state == newState)
522 return;
523
524 FlagState oldState = _state;
525 _state = newState;
526
527 if (player && newState == FlagState::Taken)
528 _carrierGUID = player->GetGUID();
529 else
531
532 if (newState == FlagState::Taken && oldState == FlagState::InBase)
534 else if (newState == FlagState::InBase || newState == FlagState::Respawning)
536
538
539 if (newState == FlagState::Respawning)
541 else
542 _respawnTime = 0;
543
544 if (ZoneScript* zoneScript = _owner.GetZoneScript())
545 zoneScript->OnFlagStateChange(&_owner, oldState, _state, player);
546 }
547
548 void Update([[maybe_unused]] uint32 diff) override
549 {
551 SetState(FlagState::InBase, nullptr);
552 }
553
554 bool IsNeverVisibleFor([[maybe_unused]] WorldObject const* seer, [[maybe_unused]] bool allowServersideObjects) const override
555 {
556 return _state != FlagState::InBase;
557 }
558
559 FlagState GetState() const { return _state; }
560 ObjectGuid const& GetCarrierGUID() const { return _carrierGUID; }
561 time_t GetTakenFromBaseTime() const { return _takenFromBaseTime; }
562
563private:
568};
569
570SetNewFlagState::SetNewFlagState(FlagState state, Player* player) : _state(state), _player(player)
571{
572}
573
575{
576 if (NewFlag* newFlag = dynamic_cast<NewFlag*>(&type))
577 newFlag->SetState(_state, _player);
578}
579
581{
582public:
583 explicit ControlZone(GameObject& owner) : GameObjectTypeBase(owner), _value(static_cast<float>(owner.GetGOInfo()->controlZone.startingValue))
584 {
585 if (owner.GetMap()->Instanceable())
586 _heartbeatRate = 1s;
587 else if (owner.GetGOInfo()->controlZone.FrequentHeartbeat)
588 _heartbeatRate = 2500ms;
589 else
590 _heartbeatRate = 5s;
591
594 _contestedTriggered = false;
595 }
596
597 void Update(uint32 diff) override
598 {
600 return;
601
604 {
607 }
608 }
609
611 {
612 if (_value < GetMaxHordeValue())
613 return TEAM_HORDE;
614
616 return TEAM_ALLIANCE;
617
618 return TEAM_NEUTRAL;
619 }
620
622
623 void ActivateObject(GameObjectActions action, int32 /*param*/, WorldObject* /*spellCaster*/, uint32 /*spellId*/, int32 /*effectIndex*/) override
624 {
625 switch (action)
626 {
628 for (ObjectGuid const& guid : _insidePlayers)
629 if (Player* player = ObjectAccessor::GetPlayer(_owner, guid))
630 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0);
631
632 _insidePlayers.clear();
633 break;
634 default:
635 break;
636 }
637 }
638
639 void SetValue(float value)
640 {
641 _value = RoundToInterval<float>(value, 0.0f, 100.0f);
642 }
643
645 {
646 // update player list inside control zone
647 std::vector<Player*> targetList;
648 SearchTargets(targetList);
649
650 TeamId oldControllingTeam = GetControllingTeam();
651 float pointsGained = CalculatePointsPerSecond(targetList) * _heartbeatRate.count() / 1000.0f;
652 if (pointsGained == 0)
653 return;
654
655 int32 oldRoundedValue = static_cast<int32>(_value);
656 SetValue(_value + pointsGained);
657 int32 roundedValue = static_cast<int32>(_value);
658 if (oldRoundedValue == roundedValue)
659 return;
660
661 TeamId newControllingTeam = GetControllingTeam();
662 TeamId assaultingTeam = pointsGained > 0 ? TEAM_ALLIANCE : TEAM_HORDE;
663
664 if (oldControllingTeam != newControllingTeam)
665 _contestedTriggered = false;
666
667 if (oldControllingTeam != TEAM_ALLIANCE && newControllingTeam == TEAM_ALLIANCE)
669 else if (oldControllingTeam != TEAM_HORDE && newControllingTeam == TEAM_HORDE)
671 else if (oldControllingTeam == TEAM_HORDE && newControllingTeam == TEAM_NEUTRAL)
673 else if (oldControllingTeam == TEAM_ALLIANCE && newControllingTeam == TEAM_NEUTRAL)
675
676 if (roundedValue == 100 && newControllingTeam == TEAM_ALLIANCE && assaultingTeam == TEAM_ALLIANCE)
678 else if (roundedValue == 0 && newControllingTeam == TEAM_HORDE && assaultingTeam == TEAM_HORDE)
680
681 if (oldRoundedValue == 100 && assaultingTeam == TEAM_HORDE && !_contestedTriggered)
682 {
684 _contestedTriggered = true;
685 }
686 else if (oldRoundedValue == 0 && assaultingTeam == TEAM_ALLIANCE && !_contestedTriggered)
687 {
689 _contestedTriggered = true;
690 }
691
692 for (Player* player : targetList)
693 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, roundedValue);
694 }
695
696 void SearchTargets(std::vector<Player*>& targetList)
697 {
701 HandleUnitEnterExit(targetList);
702 }
703
704 float CalculatePointsPerSecond(std::vector<Player*> const& targetList) const
705 {
706 int32 hordePlayers = 0;
707 int32 alliancePlayers = 0;
708
709 for (Player const* player : targetList)
710 {
711 if (!player->IsOutdoorPvPActive())
712 continue;
713
714 if (player->GetTeamId() == TEAM_HORDE)
715 hordePlayers++;
716 else
717 alliancePlayers++;
718 }
719
720 int8 factionCoefficient = 0; // alliance superiority = 1; horde superiority = -1
721
722 if (alliancePlayers > hordePlayers)
723 factionCoefficient = 1;
724 else if (hordePlayers > alliancePlayers)
725 factionCoefficient = -1;
726
727 float const timeNeeded = CalculateTimeNeeded(hordePlayers, alliancePlayers);
728 if (timeNeeded == 0.0f)
729 return 0.0f;
730
731 return 100.0f / timeNeeded * static_cast<float>(factionCoefficient);
732 }
733
734 float CalculateTimeNeeded(int32 hordePlayers, int32 alliancePlayers) const
735 {
736 uint32 const uncontestedTime = _owner.GetGOInfo()->controlZone.UncontestedTime;
737 uint32 const delta = std::abs(alliancePlayers - hordePlayers);
738 uint32 const minSuperiority = _owner.GetGOInfo()->controlZone.minSuperiority;
739
740 if (delta < minSuperiority)
741 return 0.0f;
742
743 // return the uncontested time if controlzone is not contested
744 if (uncontestedTime && (hordePlayers == 0 || alliancePlayers == 0))
745 return static_cast<float>(uncontestedTime);
746
747 uint32 const minTime = _owner.GetGOInfo()->controlZone.minTime;
748 uint32 const maxTime = _owner.GetGOInfo()->controlZone.maxTime;
749 uint32 const maxSuperiority = _owner.GetGOInfo()->controlZone.maxSuperiority;
750
751 float const slope = static_cast<float>(minTime - maxTime) / static_cast<float>(std::max<uint32>(maxSuperiority - minSuperiority, 1));
752 float const intercept = static_cast<float>(maxTime) - slope * static_cast<float>(minSuperiority);
753 return slope * static_cast<float>(delta) + intercept;
754 }
755
756 void HandleUnitEnterExit(std::vector<Player*> const& newTargetList)
757 {
758 GuidUnorderedSet exitPlayers(std::move(_insidePlayers));
759
760 std::vector<Player*> enteringPlayers;
761
762 for (Player* unit : newTargetList)
763 {
764 if (exitPlayers.erase(unit->GetGUID()) == 0) // erase(key_type) returns number of elements erased
765 enteringPlayers.push_back(unit);
766
767 _insidePlayers.insert(unit->GetGUID());
768 }
769
770 for (Player* player : enteringPlayers)
771 {
772 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 1);
773 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldstate2, static_cast<int32>(_value));
775 }
776
777 for (ObjectGuid const& exitPlayerGuid : exitPlayers)
778 {
779 if (Player* player = ObjectAccessor::GetPlayer(_owner, exitPlayerGuid))
780 {
781 player->SendUpdateWorldState(_owner.GetGOInfo()->controlZone.worldState1, 0);
782 }
783 }
784 }
785
786 float GetMaxHordeValue() const
787 {
788 // ex: if neutralPercent is 40; then 0 - 30 is Horde Controlled
789 return 50.0f - _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f;
790 }
791
793 {
794 // ex: if neutralPercent is 40; then 70 - 100 is Alliance Controlled
795 return 50.0f + _owner.GetGOInfo()->controlZone.neutralPercent / 2.0f;
796 }
797
798 void TriggerEvent(uint32 eventId) const
799 {
800 if (eventId <= 0)
801 return;
802
804 GameEvents::Trigger(eventId, &_owner, nullptr);
805 }
806
808 {
810 }
811
812private:
817 float _value;
819};
820
822{
823}
824
826{
827 if (ControlZone* controlZone = dynamic_cast<ControlZone*>(&type))
828 {
829 uint32 value = controlZone->GetStartingValue();
830 if (_value.has_value())
831 value = *_value;
832
833 controlZone->SetValue(value);
834 }
835}
836}
837
839 m_model(nullptr), m_goValue(), m_stringIds(), m_AI(nullptr), m_respawnCompatibilityMode(false), _animKitId(0), _worldEffectID(0)
840{
843
845 m_updateFlag.Rotation = true;
846
847 m_respawnTime = 0;
848 m_respawnDelayTime = 300;
849 m_despawnDelay = 0;
851 m_restockTime = 0;
853 m_spawnedByDefault = true;
854 m_usetimes = 0;
855 m_spellId = 0;
856 m_cooldownTime = 0;
858 m_goInfo = nullptr;
859 m_goData = nullptr;
861 m_goTemplateAddon = nullptr;
862
863 m_spawnId = UI64LIT(0);
864
865 ResetLootMode(); // restore default loot mode
866 m_stationaryPosition.Relocate(0.0f, 0.0f, 0.0f, 0.0f);
867}
868
870{
871 delete m_AI;
872 delete m_model;
873}
874
876{
877 delete m_AI;
878 m_AI = nullptr;
879}
880
882{
883 AIM_Destroy();
884
886
887 if (!m_AI)
888 return false;
889
891 return true;
892}
893
894std::string const& GameObject::GetAIName() const
895{
896 return sObjectMgr->GetGameObjectTemplate(GetEntry())->AIName;
897}
898
899void GameObject::CleanupsBeforeDelete(bool finalCleanup)
900{
901 SetVignette(0);
902
904
906}
907
909{
910 ObjectGuid ownerGUID = GetOwnerGUID();
911 if (!ownerGUID)
912 return;
913
914 if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID))
915 {
916 owner->RemoveGameObject(this, false);
918 return;
919 }
920
921 // This happens when a mage portal is despawned after the caster changes map (for example using the portal)
922 TC_LOG_DEBUG("misc", "Removed GameObject ({} Entry: {} SpellId: {} LinkedGO: {}) that just lost any reference to the owner ({}) GO list",
923 GetGUID().ToString(), GetGOInfo()->entry, m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), ownerGUID.ToString());
925}
926
928{
930 if (!IsInWorld())
931 {
932 if (m_zoneScript)
934
936 if (m_spawnId)
937 GetMap()->GetGameObjectBySpawnIdStore().insert(std::make_pair(m_spawnId, this));
938
939 // The state can be changed after GameObject::Create but before GameObject::AddToWorld
940 bool toggledState = GetGoType() == GAMEOBJECT_TYPE_CHEST ? getLootState() == GO_READY : (GetGoState() == GO_STATE_READY || IsTransport());
941 if (m_model)
942 {
943 if (Transport* trans = ToTransport())
944 trans->SetDelayedAddModelToMap();
945 else
947 }
948
949 EnableCollision(toggledState);
951 }
952}
953
955{
957 if (IsInWorld())
958 {
959 if (m_zoneScript)
961
963 if (m_model)
964 if (GetMap()->ContainsGameObjectModel(*m_model))
966
967 // If linked trap exists, despawn it
968 if (GameObject* linkedTrap = GetLinkedTrap())
969 linkedTrap->DespawnOrUnsummon();
970
972
973 if (m_spawnId)
974 Trinity::Containers::MultimapErasePair(GetMap()->GetGameObjectBySpawnIdStore(), m_spawnId, this);
976 }
977}
978
979bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit, bool dynamic, ObjectGuid::LowType spawnid)
980{
981 ASSERT(map);
982 SetMap(map);
983
984 Relocate(pos);
986 if (!IsPositionValid())
987 {
988 TC_LOG_ERROR("misc", "Gameobject (Spawn id: {} Entry: {}) not created. Suggested coordinates isn't valid (X: {} Y: {})", GetSpawnId(), entry, pos.GetPositionX(), pos.GetPositionY());
989 return false;
990 }
991
992 // Set if this object can handle dynamic spawns
993 if (!dynamic)
995
997
999 if (m_zoneScript)
1000 {
1001 entry = m_zoneScript->GetGameObjectEntry(m_spawnId, entry);
1002 if (!entry)
1003 return false;
1004 }
1005
1006 GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry);
1007 if (!goInfo)
1008 {
1009 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());
1010 return false;
1011 }
1012
1014 {
1015 TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: {} Entry: {}) not created: gameobject type GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT cannot be manually created.", GetSpawnId(), entry);
1016 return false;
1017 }
1018
1019 ObjectGuid guid;
1020 if (goInfo->type != GAMEOBJECT_TYPE_TRANSPORT)
1021 guid = ObjectGuid::Create<HighGuid::GameObject>(map->GetId(), goInfo->entry, map->GenerateLowGuid<HighGuid::GameObject>());
1022 else
1023 {
1024 guid = ObjectGuid::Create<HighGuid::Transport>(map->GenerateLowGuid<HighGuid::Transport>());
1025 m_updateFlag.ServerTime = true;
1026 }
1027
1028 Object::_Create(guid);
1029
1030 m_goInfo = goInfo;
1031 m_goTemplateAddon = sObjectMgr->GetGameObjectTemplateAddon(entry);
1032
1033 if (goInfo->type >= MAX_GAMEOBJECT_TYPE)
1034 {
1035 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);
1036 return false;
1037 }
1038
1039 SetLocalRotation(rotation.x, rotation.y, rotation.z, rotation.w);
1040 GameObjectAddon const* gameObjectAddon = sObjectMgr->GetGameObjectAddon(GetSpawnId());
1041
1042 // For most of gameobjects is (0, 0, 0, 1) quaternion, there are only some transports with not standard rotation
1043 QuaternionData parentRotation;
1044 if (gameObjectAddon)
1045 parentRotation = gameObjectAddon->ParentRotation;
1046
1047 SetParentRotation(parentRotation);
1048
1049 SetObjectScale(goInfo->size);
1050
1051 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1052 {
1053 SetFaction(goOverride->Faction);
1054 ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
1055 }
1056
1058 {
1060 {
1061 m_updateFlag.GameObject = true;
1063 }
1064
1067 }
1068
1069 SetEntry(goInfo->entry);
1070
1071 // set name for logs usage, doesn't affect anything ingame
1072 SetName(goInfo->name);
1073
1074 SetDisplayId(goInfo->displayId);
1075
1076 CreateModel();
1077 // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3
1078 SetGoType(GameobjectTypes(goInfo->type));
1079 m_prevGoState = goState;
1080 SetGoState(goState);
1081 SetGoArtKit(artKit);
1082
1084
1085 switch (goInfo->type)
1086 {
1088 SetGoAnimProgress(animProgress);
1089 m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock);
1090 break;
1092 {
1093 m_goValue.Building.DestructibleHitpoint = sObjectMgr->GetDestructibleHitpoint(GetGOInfo()->destructibleBuilding.HealthRec);
1095 SetGoAnimProgress(255);
1096
1097 // yes, even after the updatefield rewrite this garbage hack is still in client
1098 QuaternionData reinterpretId;
1099 memcpy(&reinterpretId.x, &m_goInfo->destructibleBuilding.DestructibleModelRec, sizeof(float));
1101 break;
1102 }
1104 {
1105 m_goTypeImpl = std::make_unique<GameObjectType::Transport>(*this);
1106 if (goInfo->transport.startOpen)
1108 else
1110
1111 SetGoAnimProgress(animProgress);
1112 setActive(true);
1113 break;
1114 }
1116 SetLevel(0);
1117 SetGoAnimProgress(255);
1118 break;
1120 if (GetGOInfo()->trap.stealthed)
1121 {
1124 }
1125
1126 if (GetGOInfo()->trap.stealthAffected)
1127 {
1130 }
1131 break;
1133 m_goTypeImpl = std::make_unique<GameObjectType::ControlZone>(*this);
1134 setActive(true);
1135 break;
1137 m_goTypeImpl = std::make_unique<GameObjectType::NewFlag>(*this);
1138 if (map->Instanceable())
1139 setActive(true);
1140 break;
1142 if (map->Instanceable())
1143 setActive(true);
1144 break;
1148 break;
1155 if (map->Instanceable())
1156 setActive(true);
1157 break;
1158 default:
1159 SetGoAnimProgress(animProgress);
1160 break;
1161 }
1162
1163 if (gameObjectAddon)
1164 {
1165 if (gameObjectAddon->InvisibilityValue)
1166 {
1167 m_invisibility.AddFlag(gameObjectAddon->invisibilityType);
1168 m_invisibility.AddValue(gameObjectAddon->invisibilityType, gameObjectAddon->InvisibilityValue);
1169 }
1170
1171 if (gameObjectAddon->WorldEffectID)
1172 {
1173 m_updateFlag.GameObject = true;
1174 SetWorldEffectID(gameObjectAddon->WorldEffectID);
1175 }
1176
1177 if (gameObjectAddon->AIAnimKitID)
1178 _animKitId = gameObjectAddon->AIAnimKitID;
1179 }
1180
1181 if (uint32 vignetteId = GetGOInfo()->GetSpawnVignette())
1182 SetVignette(vignetteId);
1183
1185
1187
1189
1190 if (spawnid)
1191 m_spawnId = spawnid;
1192
1193 if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry())
1194 {
1195 if (GameObject* linkedGo = GameObject::CreateGameObject(linkedEntry, map, pos, rotation, 255, GO_STATE_READY))
1196 {
1197 SetLinkedTrap(linkedGo);
1198 if (!map->AddToMap(linkedGo))
1199 delete linkedGo;
1200 }
1201 }
1202
1203 // Check if GameObject is Infinite
1204 if (goInfo->IsInfiniteGameObject())
1206
1207 // Check if GameObject is Gigantic
1208 if (goInfo->IsGiganticGameObject())
1210
1211 // Check if GameObject is Large
1212 if (goInfo->IsLargeGameObject())
1214
1215 return true;
1216}
1217
1218GameObject* GameObject::CreateGameObject(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit /*= 0*/)
1219{
1220 GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry);
1221 if (!goInfo)
1222 return nullptr;
1223
1224 GameObject* go = new GameObject();
1225 if (!go->Create(entry, map, pos, rotation, animProgress, goState, artKit, false, 0))
1226 {
1227 delete go;
1228 return nullptr;
1229 }
1230
1231 return go;
1232}
1233
1235{
1236 GameObject* go = new GameObject();
1237 if (!go->LoadFromDB(spawnId, map, addToMap))
1238 {
1239 delete go;
1240 return nullptr;
1241 }
1242
1243 return go;
1244}
1245
1247{
1248 WorldObject::Update(diff);
1249
1250 if (AI())
1251 AI()->UpdateAI(diff);
1252 else if (!AIM_Initialize())
1253 TC_LOG_ERROR("misc", "Could not initialize GameObjectAI");
1254
1255 if (m_despawnDelay)
1256 {
1257 if (m_despawnDelay > diff)
1258 m_despawnDelay -= diff;
1259 else
1260 {
1261 m_despawnDelay = 0;
1263 }
1264 }
1265
1266 if (m_goTypeImpl)
1267 m_goTypeImpl->Update(diff);
1268
1269 if (m_perPlayerState)
1270 {
1271 for (auto itr = m_perPlayerState->begin(); itr != m_perPlayerState->end(); )
1272 {
1273 if (itr->second.ValidUntil > GameTime::GetSystemTime())
1274 {
1275 ++itr;
1276 continue;
1277 }
1278
1279 Player* seer = ObjectAccessor::GetPlayer(*this, itr->first);
1280 bool needsStateUpdate = itr->second.State != GetGoState();
1281 bool despawned = itr->second.Despawned;
1282
1283 itr = m_perPlayerState->erase(itr);
1284
1285 if (seer)
1286 {
1287 if (despawned)
1288 {
1289 seer->UpdateVisibilityOf(this);
1290 }
1291 else if (needsStateUpdate)
1292 {
1293 UF::ObjectData::Base objMask;
1296
1297 UpdateData udata(GetMapId());
1298 BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), seer);
1299 WorldPacket packet;
1300 udata.BuildPacket(&packet);
1301 seer->SendDirectMessage(&packet);
1302 }
1303 }
1304 }
1305 }
1306
1307 switch (m_lootState)
1308 {
1309 case GO_NOT_READY:
1310 {
1311 switch (GetGoType())
1312 {
1314 {
1315 // Arming Time for GAMEOBJECT_TYPE_TRAP (6)
1316 GameObjectTemplate const* goInfo = GetGOInfo();
1317 // Bombs
1318 if (goInfo->trap.charges == 2)
1319 // Hardcoded tooltip value
1321 else if (Unit* owner = GetOwner())
1322 if (owner->IsInCombat())
1324
1326 break;
1327 }
1329 {
1330 // fishing code (bobber ready)
1332 {
1333 // splash bobber (bobber ready now)
1334 Unit* caster = GetOwner();
1335 if (caster && caster->GetTypeId() == TYPEID_PLAYER)
1336 SendCustomAnim(0);
1337
1338 m_lootState = GO_READY; // can be successfully open with some chance
1339 }
1340 return;
1341 }
1344 return;
1345 // If there is no restock timer, or if the restock timer passed, the chest becomes ready to loot
1346 m_restockTime = 0;
1348 ClearLoot();
1350 break;
1351 default:
1352 m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY
1353 break;
1354 }
1355 [[fallthrough]];
1356 }
1357 case GO_READY:
1358 {
1360 {
1361 if (m_respawnTime > 0) // timer on
1362 {
1363 time_t now = GameTime::GetGameTime();
1364 if (m_respawnTime <= now) // timer expired
1365 {
1366 ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::GameObject>(GetMapId(), GetEntry(), m_spawnId);
1367 time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);
1368 if (linkedRespawntime) // Can't respawn, the master is dead
1369 {
1370 ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid);
1371 if (targetGuid == dbtableHighGuid) // if linking self, never respawn
1373 else
1374 m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little
1376 return;
1377 }
1378
1379 m_respawnTime = 0;
1380 m_SkillupList.clear();
1381 m_usetimes = 0;
1382
1383 switch (GetGoType())
1384 {
1385 case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now
1386 {
1387 Unit* caster = GetOwner();
1388 if (caster && caster->GetTypeId() == TYPEID_PLAYER)
1389 {
1390 caster->ToPlayer()->RemoveGameObject(this, false);
1391
1393 }
1394 // can be delete
1396 return;
1397 }
1400 // 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)
1401 if (GetGoState() != GO_STATE_READY)
1403 break;
1405 // Initialize a new max fish count on respawn
1406 m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock);
1407 break;
1408 default:
1409 break;
1410 }
1411
1412 // Despawn timer
1413 if (!m_spawnedByDefault)
1414 {
1415 // Can be despawned or destroyed
1417 return;
1418 }
1419
1420 // Call AI Reset (required for example in SmartAI to clear one time events)
1421 if (AI())
1422 AI()->Reset();
1423
1424 // Respawn timer
1426 if (poolid)
1427 sPoolMgr->UpdatePool<GameObject>(GetMap()->GetPoolData(), poolid, GetSpawnId());
1428 else
1429 GetMap()->AddToMap(this);
1430 }
1431 }
1432 }
1433
1434 // Set respawn timer
1437
1438 if (isSpawned())
1439 {
1440 GameObjectTemplate const* goInfo = GetGOInfo();
1441 if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
1442 {
1444 break;
1445
1446 // Type 2 (bomb) does not need to be triggered by a unit and despawns after casting its spell.
1447 if (goInfo->trap.charges == 2)
1448 {
1450 break;
1451 }
1452
1453 // Type 0 despawns after being triggered, type 1 does not.
1455 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.
1456 if (!radius)
1457 break;
1458
1459 // Pointer to appropriate target if found any
1460 Unit* target = nullptr;
1461
1462 if (GetOwner() || goInfo->trap.Checkallunits)
1463 {
1464 // Hunter trap: Search units which are unfriendly to the trap's owner
1467 Cell::VisitAllObjects(this, searcher, radius);
1468 }
1469 else
1470 {
1471 // Environmental trap: Any player
1472 Player* player = nullptr;
1473 Trinity::AnyPlayerInObjectRangeCheck checker(this, radius);
1475 Cell::VisitWorldObjects(this, searcher, radius);
1476 target = player;
1477 }
1478
1479 if (target)
1480 SetLootState(GO_ACTIVATED, target);
1481
1482 }
1483 else if (goInfo->type == GAMEOBJECT_TYPE_CAPTURE_POINT)
1484 {
1487 if (hordeCapturing || allianceCapturing)
1488 {
1490 {
1492 if (hordeCapturing)
1493 {
1495 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
1496 {
1497 if (Battleground* bg = map->GetBG())
1498 {
1499 if (goInfo->capturePoint.CaptureEventHorde)
1501 bg->SendBroadcastText(goInfo->capturePoint.CaptureBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE);
1502 }
1503 }
1504 }
1505 else
1506 {
1508 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
1509 {
1510 if (Battleground* bg = map->GetBG())
1511 {
1515 }
1516 }
1517 }
1518
1521 }
1522 else
1524 }
1525 }
1526 else if (uint32 max_charges = goInfo->GetCharges())
1527 {
1528 if (m_usetimes >= max_charges)
1529 {
1530 m_usetimes = 0;
1531 SetLootState(GO_JUST_DEACTIVATED); // can be despawned or destroyed
1532 }
1533 }
1534 }
1535
1536 break;
1537 }
1538 case GO_ACTIVATED:
1539 {
1540 switch (GetGoType())
1541 {
1546 break;
1549 {
1551
1553 m_cooldownTime = 0;
1554 }
1555 break;
1557 if (m_loot)
1558 {
1559 m_loot->Update();
1560
1561 // Non-consumable chest was partially looted and restock time passed, restock all loot now
1563 {
1564 m_restockTime = 0;
1566 ClearLoot();
1568 }
1569 }
1570
1571 for (auto&& [playerOwner, loot] : m_personalLoot)
1572 loot->Update();
1573 break;
1575 {
1576 GameObjectTemplate const* goInfo = GetGOInfo();
1577 if (goInfo->trap.charges == 2 && goInfo->trap.spell)
1578 {
1580 CastSpell(nullptr, goInfo->trap.spell);
1582 }
1583 else if (Unit* target = ObjectAccessor::GetUnit(*this, m_lootStateUnitGUID))
1584 {
1585 // Some traps do not have a spell but should be triggered
1586 CastSpellExtraArgs args;
1588 if (goInfo->trap.spell)
1589 CastSpell(target, goInfo->trap.spell, args);
1590
1591 // Template value or 4 seconds
1593
1594 if (goInfo->trap.charges == 1)
1596 else if (!goInfo->trap.charges)
1598 }
1599 break;
1600 }
1601 default:
1602 break;
1603 }
1604 break;
1605 }
1607 {
1608 // If nearby linked trap exists, despawn it
1609 if (GameObject* linkedTrap = GetLinkedTrap())
1610 linkedTrap->DespawnOrUnsummon();
1611
1612 //if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed
1614 {
1615 uint32 spellId = GetGOInfo()->goober.spell;
1616
1617 if (spellId)
1618 {
1619 for (GuidSet::const_iterator it = m_unique_users.begin(); it != m_unique_users.end(); ++it)
1620 // m_unique_users can contain only player GUIDs
1621 if (Player* owner = ObjectAccessor::GetPlayer(*this, *it))
1622 owner->CastSpell(owner, spellId, false);
1623
1624 m_unique_users.clear();
1625 m_usetimes = 0;
1626 }
1627
1628 // Only goobers with a lock id or a reset time may reset their go state
1629 if (GetGOInfo()->GetLockId() || GetGOInfo()->GetAutoCloseTime())
1631
1632 //any return here in case battleground traps
1633 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1634 if (goOverride->Flags & GO_FLAG_NODESPAWN)
1635 return;
1636 }
1637
1638 ClearLoot();
1639
1640 // Do not delete chests or goobers that are not consumed on loot, while still allowing them to despawn when they expire if summoned
1641 bool isSummonedAndExpired = (GetOwner() || GetSpellId()) && m_respawnTime == 0;
1642 if ((GetGoType() == GAMEOBJECT_TYPE_CHEST || GetGoType() == GAMEOBJECT_TYPE_GOOBER) && !GetGOInfo()->IsDespawnAtAction() && !isSummonedAndExpired)
1643 {
1645 {
1646 // Start restock timer when the chest is fully looted
1650 }
1651 else
1654 return;
1655 }
1656 else if (!GetOwnerGUID().IsEmpty() || GetSpellId())
1657 {
1658 SetRespawnTime(0);
1659
1661 {
1663 go->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::InBase, nullptr));
1664 }
1665
1666 Delete();
1667 return;
1668 }
1669
1671
1672 //burning flags in some battlegrounds, if you find better condition, just add it
1673 if (GetGOInfo()->IsDespawnAtAction() || GetGoAnimProgress() > 0)
1674 {
1676 //reset flags
1677 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1678 ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
1679 }
1680
1681 if (!m_respawnDelayTime)
1682 return;
1683
1684 if (!m_spawnedByDefault)
1685 {
1686 m_respawnTime = 0;
1687
1688 if (m_spawnId)
1690 else
1691 Delete();
1692
1693 return;
1694 }
1695
1696 uint32 respawnDelay = m_respawnDelayTime;
1697 if (uint32 scalingMode = sWorld->getIntConfig(CONFIG_RESPAWN_DYNAMICMODE))
1698 GetMap()->ApplyDynamicModeRespawnScaling(this, this->m_spawnId, respawnDelay, scalingMode);
1699 m_respawnTime = GameTime::GetGameTime() + respawnDelay;
1700
1701 // if option not set then object will be saved at grid unload
1702 // Otherwise just save respawn time to map object memory
1704
1707 else
1709
1710 break;
1711 }
1712 }
1713}
1714
1716{
1717 if (m_spawnId)
1718 {
1719 if (GameObjectOverride const* goOverride = sObjectMgr->GetGameObjectOverride(m_spawnId))
1720 return goOverride;
1721 }
1722
1723 return m_goTemplateAddon;
1724}
1725
1727{
1728 // Do not refresh despawned GO from spellcast (GO's from spellcast are destroyed after despawn)
1730 return;
1731
1732 if (isSpawned())
1733 GetMap()->AddToMap(this);
1734}
1735
1737{
1738 AddUse();
1739 m_unique_users.insert(player->GetGUID());
1740}
1741
1743{
1744 if (delay > 0ms)
1745 {
1746 if (!m_despawnDelay || m_despawnDelay > delay.count())
1747 {
1748 m_despawnDelay = delay.count();
1749 m_despawnRespawnTime = forceRespawnTime;
1750 }
1751 }
1752 else
1753 {
1754 if (m_goData)
1755 {
1756 uint32 const respawnDelay = (forceRespawnTime > 0s) ? forceRespawnTime.count() : m_goData->spawntimesecs;
1757 SaveRespawnTime(respawnDelay);
1758 }
1759 Delete();
1760 }
1761}
1762
1764{
1765 PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[seer->GetGUID()];
1766 perPlayerState.ValidUntil = GameTime::GetSystemTime() + respawnTime;
1767 perPlayerState.Despawned = true;
1768 seer->UpdateVisibilityOf(this);
1769}
1770
1772{
1775
1777 {
1779 SendMessageToSet(packet.Write(), true);
1780 }
1781
1783
1786
1787 if (GameObjectOverride const* goOverride = GetGameObjectOverride())
1788 ReplaceAllFlags(GameObjectFlags(goOverride->Flags));
1789
1791 if (m_respawnCompatibilityMode && poolid)
1792 sPoolMgr->UpdatePool<GameObject>(GetMap()->GetPoolData(), poolid, GetSpawnId());
1793 else
1795}
1796
1798{
1800 packet.ObjectGUID = GetGUID();
1801 SendMessageToSet(packet.Write(), true);
1802}
1803
1805{
1806 uint32 defaultzone = 1;
1807
1808 Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING, 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_DEFAULT, 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_DEFAULT, itemContext);
1823
1824 return fishLoot;
1825}
1826
1828{
1829 uint32 defaultzone = 1;
1830
1831 Loot* fishLoot = new Loot(GetMap(), GetGUID(), LOOT_FISHING_JUNK, nullptr);
1832
1833 uint32 areaId = GetAreaId();
1834 ItemContext itemContext = ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), lootOwner);
1835 while (AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(areaId))
1836 {
1837 fishLoot->FillLoot(areaId, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH, itemContext);
1838 if (!fishLoot->isLooted())
1839 break;
1840
1841 areaId = areaEntry->ParentAreaID;
1842 }
1843
1844 if (fishLoot->isLooted())
1845 fishLoot->FillLoot(defaultzone, LootTemplates_Fishing, lootOwner, true, true, LOOT_MODE_JUNK_FISH, itemContext);
1846
1847 return fishLoot;
1848}
1849
1851{
1852 // this should only be used when the gameobject has already been loaded
1853 // preferably after adding to map, because mapid may not be valid otherwise
1854 GameObjectData const* data = sObjectMgr->GetGameObjectData(m_spawnId);
1855 if (!data)
1856 {
1857 TC_LOG_ERROR("misc", "GameObject::SaveToDB failed, cannot get gameobject data!");
1858 return;
1859 }
1860
1861 uint32 mapId = GetMapId();
1862 if (TransportBase* transport = GetTransport())
1863 if (transport->GetMapIdForSpawning() >= 0)
1864 mapId = transport->GetMapIdForSpawning();
1865
1866 SaveToDB(mapId, data->spawnDifficulties);
1867}
1868
1869void GameObject::SaveToDB(uint32 mapid, std::vector<Difficulty> const& spawnDifficulties)
1870{
1871 GameObjectTemplate const* goI = GetGOInfo();
1872 if (!goI)
1873 return;
1874
1875 if (!m_spawnId)
1876 m_spawnId = sObjectMgr->GenerateGameObjectSpawnId();
1877
1878 // update in loaded data (changing data only in this place)
1879 GameObjectData& data = sObjectMgr->NewOrExistGameObjectData(m_spawnId);
1880
1881 if (!data.spawnId)
1882 data.spawnId = m_spawnId;
1883 ASSERT(data.spawnId == m_spawnId);
1884 data.id = GetEntry();
1885 data.mapId = GetMapId();
1886 data.spawnPoint.Relocate(this);
1890 data.goState = GetGoState();
1891 data.spawnDifficulties = spawnDifficulties;
1892 data.artKit = GetGoArtKit();
1893 if (!data.spawnGroupData)
1894 data.spawnGroupData = sObjectMgr->GetDefaultSpawnGroup();
1895
1896 data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : data.phaseId;
1897 data.phaseGroup = GetDBPhase() < 0 ? -GetDBPhase() : data.phaseGroup;
1898
1899 // Update in DB
1900 WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
1901
1902 uint8 index = 0;
1903
1905 stmt->setUInt64(0, m_spawnId);
1906 trans->Append(stmt);
1907
1908 stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT);
1909 stmt->setUInt64(index++, m_spawnId);
1910 stmt->setUInt32(index++, GetEntry());
1911 stmt->setUInt16(index++, uint16(mapid));
1912 stmt->setString(index++, [&data]() -> std::string
1913 {
1914 if (data.spawnDifficulties.empty())
1915 return "";
1916
1917 std::ostringstream os;
1918 auto itr = data.spawnDifficulties.begin();
1919 os << int32(*itr++);
1920
1921 for (; itr != data.spawnDifficulties.end(); ++itr)
1922 os << ',' << int32(*itr);
1923
1924 return os.str();
1925 }());
1926 stmt->setUInt32(index++, data.phaseId);
1927 stmt->setUInt32(index++, data.phaseGroup);
1928 stmt->setFloat(index++, GetPositionX());
1929 stmt->setFloat(index++, GetPositionY());
1930 stmt->setFloat(index++, GetPositionZ());
1931 stmt->setFloat(index++, GetOrientation());
1932 stmt->setFloat(index++, m_localRotation.x);
1933 stmt->setFloat(index++, m_localRotation.y);
1934 stmt->setFloat(index++, m_localRotation.z);
1935 stmt->setFloat(index++, m_localRotation.w);
1936 stmt->setInt32(index++, int32(m_respawnDelayTime));
1937 stmt->setUInt8(index++, GetGoAnimProgress());
1938 stmt->setUInt8(index++, uint8(GetGoState()));
1939 trans->Append(stmt);
1940
1941 WorldDatabase.CommitTransaction(trans);
1942}
1943
1944bool GameObject::LoadFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap, bool)
1945{
1946 GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
1947 if (!data)
1948 {
1949 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: {}) not found in table `gameobject`, can't load. ", spawnId);
1950 return false;
1951 }
1952
1953 uint32 entry = data->id;
1954 //uint32 map_id = data->mapid; // already used before call
1955
1956 uint32 animprogress = data->animprogress;
1957 GOState go_state = data->goState;
1958 uint32 artKit = data->artKit;
1959
1960 m_spawnId = spawnId;
1962 if (!Create(entry, map, data->spawnPoint, data->rotation, animprogress, go_state, artKit, !m_respawnCompatibilityMode, spawnId))
1963 return false;
1964
1967
1968 if (data->spawntimesecs >= 0)
1969 {
1970 m_spawnedByDefault = true;
1971
1972 if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction())
1973 {
1976 m_respawnTime = 0;
1977 }
1978 else
1979 {
1982
1983 // ready to respawn
1985 {
1986 m_respawnTime = 0;
1988 }
1989 }
1990 }
1991 else
1992 {
1994 {
1995 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);
1997 }
1998
1999 m_spawnedByDefault = false;
2001 m_respawnTime = 0;
2002 }
2003
2004 m_goData = data;
2005
2007
2008 if (addToMap && !GetMap()->AddToMap(this))
2009 return false;
2010
2011 return true;
2012}
2013
2015{
2016 GameObjectData const* data = sObjectMgr->GetGameObjectData(spawnId);
2017 if (!data)
2018 return false;
2019
2020 CharacterDatabaseTransaction charTrans = CharacterDatabase.BeginTransaction();
2021
2022 sMapMgr->DoForAllMapsWithMapId(data->mapId,
2023 [spawnId, charTrans](Map* map) -> void
2024 {
2025 // despawn all active objects, and remove their respawns
2026 std::vector<GameObject*> toUnload;
2027 for (auto const& pair : Trinity::Containers::MapEqualRange(map->GetGameObjectBySpawnIdStore(), spawnId))
2028 toUnload.push_back(pair.second);
2029 for (GameObject* obj : toUnload)
2030 map->AddObjectToRemoveList(obj);
2031 map->RemoveRespawnTime(SPAWN_TYPE_GAMEOBJECT, spawnId, charTrans);
2032 }
2033 );
2034
2035 // delete data from memory
2036 sObjectMgr->DeleteGameObjectData(spawnId);
2037
2038 CharacterDatabase.CommitTransaction(charTrans);
2039
2040 WorldDatabaseTransaction trans = WorldDatabase.BeginTransaction();
2041
2042 // ... and the database
2044 stmt->setUInt64(0, spawnId);
2045 trans->Append(stmt);
2046
2047 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_SPAWNGROUP_MEMBER);
2049 stmt->setUInt64(1, spawnId);
2050 trans->Append(stmt);
2051
2052 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT);
2053 stmt->setUInt64(0, spawnId);
2054 trans->Append(stmt);
2055
2056 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN);
2057 stmt->setUInt64(0, spawnId);
2059 trans->Append(stmt);
2060
2061 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN);
2062 stmt->setUInt64(0, spawnId);
2064 trans->Append(stmt);
2065
2066 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN_MASTER);
2067 stmt->setUInt64(0, spawnId);
2069 trans->Append(stmt);
2070
2071 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_LINKED_RESPAWN_MASTER);
2072 stmt->setUInt64(0, spawnId);
2074 trans->Append(stmt);
2075
2076 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT_ADDON);
2077 stmt->setUInt64(0, spawnId);
2078 trans->Append(stmt);
2079
2080 WorldDatabase.CommitTransaction(trans);
2081
2082 return true;
2083}
2084
2085/*********************************************************/
2086/*** QUEST SYSTEM ***/
2087/*********************************************************/
2088bool GameObject::hasQuest(uint32 quest_id) const
2089{
2090 return sObjectMgr->GetGOQuestRelations(GetEntry()).HasQuest(quest_id);
2091}
2092
2094{
2095 return sObjectMgr->GetGOQuestInvolvedRelations(GetEntry()).HasQuest(quest_id);
2096}
2097
2099{
2100 // If something is marked as a transport, don't transmit an out of range packet for it.
2101 GameObjectTemplate const* gInfo = GetGOInfo();
2102 if (!gInfo)
2103 return false;
2104
2106}
2107
2108// is Dynamic transport = non-stop Transport
2110{
2111 // If something is marked as a transport, don't transmit an out of range packet for it.
2112 GameObjectTemplate const* gInfo = GetGOInfo();
2113 if (!gInfo)
2114 return false;
2115
2117}
2118
2120{
2121 GameObjectTemplate const* gInfo = GetGOInfo();
2122 if (!gInfo)
2123 return false;
2124
2126}
2127
2129{
2130 if (m_goData && (forceDelay || m_respawnTime > GameTime::GetGameTime()) && m_spawnedByDefault)
2131 {
2133 {
2134 RespawnInfo ri;
2136 ri.spawnId = m_spawnId;
2139 return;
2140 }
2141
2142 uint32 thisRespawnTime = forceDelay ? GameTime::GetGameTime() + forceDelay : m_respawnTime;
2144 }
2145}
2146
2147bool GameObject::IsNeverVisibleFor(WorldObject const* seer, bool allowServersideObjects) const
2148{
2150 return true;
2151
2152 if (GetGOInfo()->GetServerOnly() && !allowServersideObjects)
2153 return true;
2154
2155 if (!GetDisplayId() && GetGOInfo()->IsDisplayMandatory())
2156 return true;
2157
2158 if (m_goTypeImpl)
2159 return m_goTypeImpl->IsNeverVisibleFor(seer, allowServersideObjects);
2160
2161 return false;
2162}
2163
2165{
2167 return true;
2168
2170 return true;
2171
2172 if (!seer)
2173 return false;
2174
2175 // Always seen by owner and friendly units
2176 if (!GetOwnerGUID().IsEmpty())
2177 {
2178 if (seer->GetGUID() == GetOwnerGUID())
2179 return true;
2180
2181 Unit* owner = GetOwner();
2182 if (Unit const* unitSeer = seer->ToUnit())
2183 if (owner && owner->IsFriendlyTo(unitSeer))
2184 return true;
2185 }
2186
2187 return false;
2188}
2189
2191{
2193 return true;
2194
2195 // Despawned
2196 if (!isSpawned())
2197 return true;
2198
2199 if (m_perPlayerState)
2201 if (state->Despawned)
2202 return true;
2203
2204 return false;
2205}
2206
2208{
2209 if (Unit* owner = GetOwner())
2210 return owner->GetLevelForTarget(target);
2211
2213 {
2214 if (Player const* player = target->ToPlayer())
2215 if (Optional<ContentTuningLevels> userLevels = sDB2Manager.GetContentTuningData(GetGOInfo()->ContentTuningId, player->m_playerData->CtrOptions->ContentTuningConditionMask))
2216 return uint8(std::clamp<int16>(player->GetLevel(), userLevels->MinLevel, userLevels->MaxLevel));
2217
2218 if (Unit const* targetUnit = target->ToUnit())
2219 return targetUnit->GetLevel();
2220 }
2221
2222 return 1;
2223}
2224
2226{
2227 time_t now = GameTime::GetGameTime();
2228 if (m_respawnTime > now)
2229 return m_respawnTime;
2230 else
2231 return now;
2232}
2233
2235{
2236 m_respawnTime = respawn > 0 ? GameTime::GetGameTime() + respawn : 0;
2237 m_respawnDelayTime = respawn > 0 ? respawn : 0;
2238 if (respawn && !m_spawnedByDefault)
2240}
2241
2243{
2245 {
2248 }
2249}
2250
2252{
2253 if (GetGOInfo()->GetQuestID())
2254 return true;
2255
2256 if (GetGoType() != GAMEOBJECT_TYPE_AURA_GENERATOR && GetGOInfo()->GetConditionID1())
2257 return true;
2258
2259 if (sObjectMgr->IsGameObjectForQuests(GetEntry()))
2260 return true;
2261
2262 return false;
2263}
2264
2266{
2267 if (!MeetsInteractCondition(target))
2268 return false;
2269
2270 if (!ActivateToQuest(target))
2271 return false;
2272
2273 return true;
2274}
2275
2276bool GameObject::ActivateToQuest(Player const* target) const
2277{
2278 if (target->HasQuestForGO(GetEntry()))
2279 return true;
2280
2281 if (!sObjectMgr->IsGameObjectForQuests(GetEntry()))
2282 return true;
2283
2284 switch (GetGoType())
2285 {
2287 {
2288 GameObject* go = const_cast<GameObject*>(this);
2289 QuestGiverStatus questStatus = const_cast<Player*>(target)->GetQuestDialogStatus(go);
2290 if (questStatus != QuestGiverStatus::None && questStatus != QuestGiverStatus::Future)
2291 return true;
2292 break;
2293 }
2295 {
2296 // Chests become inactive while not ready to be looted
2297 if (getLootState() == GO_NOT_READY)
2298 return false;
2299
2300 // scan GO chest with loot including quest items
2305 {
2306 return true;
2307 }
2308 break;
2309 }
2311 {
2313 return true;
2314 break;
2315 }
2317 {
2319 return true;
2320 break;
2321 }
2323 {
2325 return true;
2326 break;
2327 }
2329 {
2331 return true;
2332 break;
2333 }
2334 default:
2335 break;
2336 }
2337
2338 return false;
2339}
2340
2342{
2343 GameObjectTemplate const* trapInfo = sObjectMgr->GetGameObjectTemplate(trapEntry);
2344 if (!trapInfo || trapInfo->type != GAMEOBJECT_TYPE_TRAP)
2345 return;
2346
2347 SpellInfo const* trapSpell = sSpellMgr->GetSpellInfo(trapInfo->trap.spell, GetMap()->GetDifficultyID());
2348 if (!trapSpell) // checked at load already
2349 return;
2350
2351 if (GameObject* trapGO = GetLinkedTrap())
2352 trapGO->CastSpell(target, trapSpell->Id);
2353}
2354
2356{
2357 GameObject* ok = nullptr;
2358 Trinity::NearestGameObjectFishingHole u_check(*this, range);
2360 Cell::VisitGridObjects(this, checker, range);
2361 return ok;
2362}
2363
2365{
2367 return;
2368
2371
2373 m_cooldownTime = 0;
2374}
2375
2376void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = false */, Unit* user /*=nullptr*/)
2377{
2378 if (m_lootState != GO_READY)
2379 return;
2380
2381 if (!time_to_restore)
2382 time_to_restore = GetGOInfo()->GetAutoCloseTime();
2383
2384 SwitchDoorOrButton(true, alternative);
2386
2387 m_cooldownTime = time_to_restore ? (GameTime::GetGameTimeMS() + time_to_restore) : 0;
2388}
2389
2390void GameObject::ActivateObject(GameObjectActions action, int32 param, WorldObject* spellCaster /*= nullptr*/, uint32 spellId /*= 0*/, int32 effectIndex /*= -1*/)
2391{
2392 Unit* unitCaster = spellCaster ? spellCaster->ToUnit() : nullptr;
2393
2394 switch (action)
2395 {
2397 TC_LOG_FATAL("spell", "Spell {} has action type NONE in effect {}", spellId, effectIndex);
2398 break;
2404 break;
2405 case GameObjectActions::Disturb: // What's the difference with Open?
2406 if (unitCaster)
2407 Use(unitCaster);
2408 break;
2411 break;
2414 break;
2416 if (unitCaster)
2417 Use(unitCaster);
2418 break;
2420 if (unitCaster)
2421 UseDoorOrButton(0, false, unitCaster);
2423 break;
2426 break;
2428 // No use cases, implementation unknown
2429 break;
2431 if (unitCaster)
2432 UseDoorOrButton(0, true, unitCaster);
2433 break;
2436 break;
2438 // No use cases, implementation unknown
2439 break;
2442 break;
2445 break;
2448 break;
2452 break;
2458 {
2459 GameObjectTemplateAddon const* templateAddon = GetTemplateAddon();
2460
2461 uint32 artKitIndex = action != GameObjectActions::UseArtKit4 ? uint32(action) - uint32(GameObjectActions::UseArtKit0) : 4;
2462
2463 uint32 artKitValue = 0;
2464 if (templateAddon != nullptr)
2465 artKitValue = templateAddon->ArtKits[artKitIndex];
2466
2467 if (artKitValue == 0)
2468 TC_LOG_ERROR("sql.sql", "GameObject {} hit by spell {} needs `artkit{}` in `gameobject_template_addon`", GetEntry(), spellId, artKitIndex);
2469 else
2470 SetGoArtKit(artKitValue);
2471
2472 break;
2473 }
2486 SetGoState(GOState(action));
2487 else
2488 TC_LOG_ERROR("spell", "Spell {} targeted non-transport gameobject for transport only action \"Go to Floor\" {} in effect {}", spellId, int32(action), effectIndex);
2489 break;
2491 SetAnimKitId(param, false);
2492 break;
2494 if (unitCaster)
2495 UseDoorOrButton(0, false, unitCaster);
2496 SetAnimKitId(param, false);
2497 break;
2500 SetAnimKitId(param, false);
2501 break;
2503 SetAnimKitId(param, true);
2504 break;
2506 SetAnimKitId(0, false);
2507 break;
2509 if (unitCaster)
2510 UseDoorOrButton(0, false, unitCaster);
2511 SetAnimKitId(0, false);
2512 break;
2515 SetAnimKitId(0, false);
2516 break;
2518 SetSpellVisualId(param, Object::GetGUID(spellCaster));
2519 break;
2522 break;
2523 default:
2524 TC_LOG_ERROR("spell", "Spell {} has unhandled action {} in effect {}", spellId, int32(action), effectIndex);
2525 break;
2526 }
2527
2528 // Apply side effects of type
2529 if (m_goTypeImpl)
2530 m_goTypeImpl->ActivateObject(action, param, spellCaster, spellId, effectIndex);
2531}
2532
2534{
2536 GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGameObjectData(m_spawnId));
2537 if (data)
2538 data->artKit = kit;
2539}
2540
2542{
2543 GameObjectData const* data = nullptr;
2544 if (go)
2545 {
2546 go->SetGoArtKit(artkit);
2547 data = go->GetGameObjectData();
2548 }
2549 else if (lowguid)
2550 data = sObjectMgr->GetGameObjectData(lowguid);
2551
2552 if (data)
2553 const_cast<GameObjectData*>(data)->artKit = artkit;
2554}
2555
2556void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */)
2557{
2558 if (activate)
2560 else
2562
2563 if (GetGoState() == GO_STATE_READY) //if closed -> open
2565 else //if open -> close
2567}
2568
2570{
2571 // by default spell caster is user
2572 Unit* spellCaster = user;
2573 uint32 spellId = 0;
2574 bool triggered = false;
2575
2576 if (Player* playerUser = user->ToPlayer())
2577 {
2578 if (m_goInfo->GetNoDamageImmune() && playerUser->HasUnitFlag(UNIT_FLAG_IMMUNE))
2579 return;
2580
2581 if (!m_goInfo->IsUsableMounted())
2583
2584 playerUser->PlayerTalkClass->ClearMenus();
2585 if (AI()->OnGossipHello(playerUser))
2586 return;
2587 }
2588
2589 // If cooldown data present in template
2590 if (uint32 cooldown = GetGOInfo()->GetCooldown())
2591 {
2593 return;
2594
2596 }
2597
2598 switch (GetGoType())
2599 {
2600 case GAMEOBJECT_TYPE_DOOR: //0
2601 case GAMEOBJECT_TYPE_BUTTON: //1
2602 //doors/buttons never really despawn, only reset to default state/flags
2603 UseDoorOrButton(0, false, user);
2604 return;
2606 {
2607 if (user->GetTypeId() != TYPEID_PLAYER)
2608 return;
2609
2610 Player* player = user->ToPlayer();
2611
2612 player->PrepareGossipMenu(this, GetGOInfo()->questgiver.gossipID, true);
2613 player->SendPreparedGossip(this);
2614 return;
2615 }
2616 case GAMEOBJECT_TYPE_CHEST: //3
2617 {
2618 Player* player = user->ToPlayer();
2619 if (!player)
2620 return;
2621
2622 GameObjectTemplate const* info = GetGOInfo();
2623 if (!m_loot && info->GetLootId())
2624 {
2625 if (info->GetLootId())
2626 {
2627 Group const* group = player->GetGroup();
2628 bool groupRules = group && info->chest.usegrouplootrules;
2629
2630 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, groupRules ? group : nullptr);
2631 m_loot.reset(loot);
2632
2634 loot->FillLoot(info->GetLootId(), LootTemplates_Gameobject, player, !groupRules, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
2635
2636 if (GetLootMode() > 0)
2637 if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
2638 loot->generateMoneyLoot(addon->Mingold, addon->Maxgold);
2639 }
2640
2641 if (info->chest.triggeredEvent)
2642 GameEvents::Trigger(info->chest.triggeredEvent, player, this);
2643
2644 // triggering linked GO
2645 if (uint32 trapEntry = info->chest.linkedTrap)
2646 TriggeringLinkedGameObject(trapEntry, player);
2647 }
2648 else if (!m_personalLoot.count(player->GetGUID()))
2649 {
2650 if (info->chest.chestPersonalLoot)
2651 {
2653 if (info->chest.DungeonEncounter)
2654 {
2655 std::vector<Player*> tappers;
2656 for (ObjectGuid tapperGuid : GetTapList())
2657 if (Player* tapper = ObjectAccessor::GetPlayer(*this, tapperGuid))
2658 tappers.push_back(tapper);
2659
2660 if (tappers.empty())
2661 tappers.push_back(player);
2662
2664 LootTemplates_Gameobject, LOOT_CHEST, this, addon ? addon->Mingold : 0, addon ? addon->Maxgold : 0,
2665 GetLootMode(), GetMap()->GetMapDifficulty(), tappers);
2666 }
2667 else
2668 {
2669 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr);
2670 m_personalLoot[player->GetGUID()].reset(loot);
2671
2673 loot->FillLoot(info->chest.chestPersonalLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
2674
2675 if (GetLootMode() > 0 && addon)
2676 loot->generateMoneyLoot(addon->Mingold, addon->Maxgold);
2677 }
2678 }
2679 }
2680
2681 if (!m_unique_users.count(player->GetGUID()) && !info->GetLootId())
2682 {
2683 if (info->chest.chestPushLoot)
2684 {
2685 Loot pushLoot(GetMap(), GetGUID(), LOOT_CHEST, nullptr);
2686 pushLoot.FillLoot(info->chest.chestPushLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
2687 pushLoot.AutoStore(player, NULL_BAG, NULL_SLOT);
2688 }
2689
2690 if (info->chest.triggeredEvent)
2691 GameEvents::Trigger(info->chest.triggeredEvent, player, this);
2692
2693 // triggering linked GO
2694 if (uint32 trapEntry = info->chest.linkedTrap)
2695 TriggeringLinkedGameObject(trapEntry, player);
2696
2697 AddUniqueUse(player);
2698 }
2699
2700 if (getLootState() != GO_ACTIVATED)
2701 SetLootState(GO_ACTIVATED, player);
2702
2703 // Send loot
2704 if (Loot* loot = GetLootForPlayer(player))
2705 player->SendLoot(*loot);
2706 break;
2707 }
2708 case GAMEOBJECT_TYPE_TRAP: //6
2709 {
2710 GameObjectTemplate const* goInfo = GetGOInfo();
2711 if (goInfo->trap.spell)
2712 CastSpell(user, goInfo->trap.spell);
2713
2714 m_cooldownTime = GameTime::GetGameTimeMS() + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)) * IN_MILLISECONDS; // template or 4 seconds
2715
2716 if (goInfo->trap.charges == 1) // Deactivate after trigger
2718
2719 return;
2720 }
2721 //Sitting: Wooden bench, chairs enzz
2722 case GAMEOBJECT_TYPE_CHAIR: //7
2723 {
2724 GameObjectTemplate const* info = GetGOInfo();
2725 if (ChairListSlots.empty()) // this is called once at first chair use to make list of available slots
2726 {
2727 if (info->chair.chairslots > 0) // sometimes chairs in DB have error in fields and we dont know number of slots
2728 for (uint32 i = 0; i < info->chair.chairslots; ++i)
2729 ChairListSlots[i].Clear(); // Last user of current slot set to 0 (none sit here yet)
2730 else
2731 ChairListSlots[0].Clear(); // error in DB, make one default slot
2732 }
2733
2734 // a chair may have n slots. we have to calculate their positions and teleport the player to the nearest one
2735
2736 float lowestDist = DEFAULT_VISIBILITY_DISTANCE;
2737
2738 uint32 nearest_slot = 0;
2739 float x_lowest = GetPositionX();
2740 float y_lowest = GetPositionY();
2741
2742 // the object orientation + 1/2 pi
2743 // every slot will be on that straight line
2744 float orthogonalOrientation = GetOrientation() + float(M_PI) * 0.5f;
2745 // find nearest slot
2746 bool found_free_slot = false;
2747 for (auto& [slot, sittingUnit] : ChairListSlots)
2748 {
2749 // the distance between this slot and the center of the go - imagine a 1D space
2750 float relativeDistance = (info->size * slot) - (info->size * (info->chair.chairslots - 1) / 2.0f);
2751
2752 float x_i = GetPositionX() + relativeDistance * std::cos(orthogonalOrientation);
2753 float y_i = GetPositionY() + relativeDistance * std::sin(orthogonalOrientation);
2754
2755 if (!sittingUnit.IsEmpty())
2756 {
2757 if (Unit* chairUser = ObjectAccessor::GetUnit(*this, sittingUnit))
2758 {
2759 if (chairUser->IsSitState() && chairUser->GetStandState() != UNIT_STAND_STATE_SIT && chairUser->GetExactDist2d(x_i, y_i) < 0.1f)
2760 continue; // This seat is already occupied by ChairUser. NOTE: Not sure if the ChairUser->GetStandState() != UNIT_STAND_STATE_SIT check is required.
2761
2762 sittingUnit.Clear(); // This seat is unoccupied.
2763 }
2764 else
2765 sittingUnit.Clear(); // The seat may of had an occupant, but they're offline.
2766 }
2767
2768 found_free_slot = true;
2769
2770 // calculate the distance between the player and this slot
2771 float thisDistance = user->GetDistance2d(x_i, y_i);
2772
2773 if (thisDistance <= lowestDist)
2774 {
2775 nearest_slot = slot;
2776 lowestDist = thisDistance;
2777 x_lowest = x_i;
2778 y_lowest = y_i;
2779 }
2780 }
2781
2782 if (found_free_slot)
2783 {
2784 auto itr = ChairListSlots.find(nearest_slot);
2785 if (itr != ChairListSlots.end())
2786 {
2787 itr->second = user->GetGUID(); //this slot in now used by player
2788 user->NearTeleportTo(x_lowest, y_lowest, GetPositionZ(), GetOrientation());
2790 if (info->chair.triggeredEvent)
2791 GameEvents::Trigger(info->chair.triggeredEvent, user, this);
2792 return;
2793 }
2794 }
2795
2796 return;
2797 }
2799 // triggering linked GO
2800 if (uint32 trapEntry = GetGOInfo()->spellFocus.linkedTrap)
2801 TriggeringLinkedGameObject(trapEntry, user);
2802 break;
2803 //big gun, its a spell/aura
2804 case GAMEOBJECT_TYPE_GOOBER: //10
2805 {
2806 GameObjectTemplate const* info = GetGOInfo();
2807 Player* player = user->ToPlayer();
2808
2809 if (player)
2810 {
2811 if (info->goober.pageID) // show page...
2812 {
2814 data.GameObjectGUID = GetGUID();
2815 player->SendDirectMessage(data.Write());
2816 }
2817 else if (info->goober.gossipID)
2818 {
2819 player->PrepareGossipMenu(this, info->goober.gossipID);
2820 player->SendPreparedGossip(this);
2821 }
2822
2823 if (info->goober.eventID)
2824 {
2825 TC_LOG_DEBUG("maps.script", "Goober ScriptStart id {} for GO entry {} (GUID {}).", info->goober.eventID, GetEntry(), GetSpawnId());
2826 GameEvents::Trigger(info->goober.eventID, player, this);
2827 }
2828
2829 // possible quest objective for active quests
2830 if (info->goober.questID && sObjectMgr->GetQuestTemplate(info->goober.questID))
2831 {
2832 //Quest require to be active for GO using
2834 break;
2835 }
2836
2837 if (Group* group = player->GetGroup())
2838 {
2839 for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
2840 if (Player* member = itr->GetSource())
2841 if (member->IsAtGroupRewardDistance(this))
2842 member->KillCreditGO(info->entry, GetGUID());
2843 }
2844 else
2845 player->KillCreditGO(info->entry, GetGUID());
2846 }
2847
2848 if (uint32 trapEntry = info->goober.linkedTrap)
2849 TriggeringLinkedGameObject(trapEntry, user);
2850
2851 if (info->goober.AllowMultiInteract && player)
2852 {
2853 if (info->IsDespawnAtAction())
2855 else
2857 }
2858 else
2859 {
2862
2863 // this appear to be ok, however others exist in addition to this that should have custom (ex: 190510, 188692, 187389)
2864 if (info->goober.customAnim)
2866 else
2868
2870 }
2871
2872 // cast this spell later if provided
2873 spellId = info->goober.spell;
2874 if (!info->goober.playerCast)
2875 spellCaster = nullptr;
2876
2877 break;
2878 }
2879 case GAMEOBJECT_TYPE_CAMERA: //13
2880 {
2881 GameObjectTemplate const* info = GetGOInfo();
2882 if (!info)
2883 return;
2884
2885 if (user->GetTypeId() != TYPEID_PLAYER)
2886 return;
2887
2888 Player* player = user->ToPlayer();
2889
2890 if (info->camera.camera)
2891 player->SendCinematicStart(info->camera.camera);
2892
2893 if (info->camera.eventID)
2894 GameEvents::Trigger(info->camera.eventID, player, this);
2895
2896 return;
2897 }
2898 //fishing bobber
2900 {
2901 Player* player = user->ToPlayer();
2902 if (!player)
2903 return;
2904
2905 if (player->GetGUID() != GetOwnerGUID())
2906 return;
2907
2908 switch (getLootState())
2909 {
2910 case GO_READY: // ready for loot
2911 {
2912 SetLootState(GO_ACTIVATED, player);
2913
2916
2917 SendUpdateToPlayer(player);
2918
2919 AreaTableEntry const* areaEntry = sAreaTableStore.LookupEntry(GetAreaId());
2920 if (!areaEntry)
2921 {
2922 TC_LOG_ERROR("entities.gameobject", "Gameobject '{}' ({}) spawned in unknown area (x: {} y: {} z: {} map: {})",
2924 break;
2925 }
2926
2927 // Update the correct fishing skill according to the area's ContentTuning
2928 ContentTuningEntry const* areaContentTuning = DB2Manager::GetContentTuningForArea(areaEntry);
2929 if (!areaContentTuning)
2930 break;
2931
2932 player->UpdateFishingSkill(areaContentTuning->ExpansionID);
2933
2934 // Send loot
2935 int32 areaFishingLevel = sObjectMgr->GetFishingBaseSkillLevel(areaEntry);
2936
2937 uint32 playerFishingSkill = player->GetProfessionSkillForExp(SKILL_FISHING, areaContentTuning->ExpansionID);
2938 int32 playerFishingLevel = player->GetSkillValue(playerFishingSkill);
2939
2940 int32 roll = irand(1, 100);
2941 int32 chance = 100;
2942 if (playerFishingLevel < areaFishingLevel)
2943 {
2944 chance = int32(pow((double)playerFishingLevel / areaFishingLevel, 2) * 100);
2945 if (chance < 1)
2946 chance = 1;
2947 }
2948
2949 TC_LOG_DEBUG("misc", "Fishing check (skill {} level: {} area skill level: {} chance {} roll: {}", playerFishingSkill, playerFishingLevel, areaFishingLevel, chance, roll);
2950
2952 GameObject* fishingPool = LookupFishingHoleAround(20.0f + CONTACT_DISTANCE);
2953
2954 // If fishing skill is high enough, or if fishing on a pool, send correct loot.
2955 // Fishing pools have no skill requirement as of patch 3.3.0 (undocumented change).
2956 if (chance >= roll || fishingPool)
2957 {
2959 // prevent removing GO at spell cancel
2961 SetOwnerGUID(player->GetGUID());
2962 SetSpellId(0); // prevent removing unintended auras at Unit::RemoveGameObject
2963
2964 if (fishingPool)
2965 {
2966 fishingPool->Use(player);
2968 }
2969 else
2970 {
2971 m_loot.reset(GetFishLoot(player));
2972 player->SendLoot(*m_loot);
2973 }
2974 }
2975 else // If fishing skill is too low, send junk loot.
2976 {
2977 m_loot.reset(GetFishLootJunk(player));
2978 player->SendLoot(*m_loot);
2979 }
2980 break;
2981 }
2982 case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update
2983 break;
2984 default:
2985 {
2988 break;
2989 }
2990 }
2991
2993 return;
2994 }
2995
2996 case GAMEOBJECT_TYPE_RITUAL: //18
2997 {
2998 if (user->GetTypeId() != TYPEID_PLAYER)
2999 return;
3000
3001 Player* player = user->ToPlayer();
3002
3003 Unit* owner = GetOwner();
3004
3005 GameObjectTemplate const* info = GetGOInfo();
3006
3007 Player* m_ritualOwner = nullptr;
3010
3011 // ritual owner is set for GO's without owner (not summoned)
3012 if (!m_ritualOwner && !owner)
3013 {
3014 m_ritualOwnerGUID = player->GetGUID();
3015 m_ritualOwner = player;
3016 }
3017
3018 if (owner)
3019 {
3020 if (owner->GetTypeId() != TYPEID_PLAYER)
3021 return;
3022
3023 // accept only use by player from same group as owner, excluding owner itself (unique use already added in spell effect)
3024 if (player == owner->ToPlayer() || (info->ritual.castersGrouped && !player->IsInSameRaidWith(owner->ToPlayer())))
3025 return;
3026
3027 // expect owner to already be channeling, so if not...
3029 return;
3030
3031 // in case summoning ritual caster is GO creator
3032 spellCaster = owner;
3033 }
3034 else
3035 {
3036 if (player != m_ritualOwner && (info->ritual.castersGrouped && !player->IsInSameRaidWith(m_ritualOwner)))
3037 return;
3038
3039 spellCaster = player;
3040 }
3041
3042 AddUniqueUse(player);
3043
3044 if (info->ritual.animSpell)
3045 {
3046 player->CastSpell(player, info->ritual.animSpell, true);
3047
3048 // for this case, summoningRitual.spellId is always triggered
3049 triggered = true;
3050 }
3051
3052 // full amount unique participants including original summoner
3053 if (GetUniqueUseCount() == info->ritual.casters)
3054 {
3055 if (m_ritualOwner)
3056 spellCaster = m_ritualOwner;
3057
3058 spellId = info->ritual.spell;
3059
3060 if (spellId == 62330) // GO store nonexistent spell, replace by expected
3061 {
3062 // spell have reagent and mana cost but it not expected use its
3063 // it triggered spell in fact cast at currently channeled GO
3064 spellId = 61993;
3065 triggered = true;
3066 }
3067
3068 // Cast casterTargetSpell at a random GO user
3069 // on the current DB there is only one gameobject that uses this (Ritual of Doom)
3070 // and its required target number is 1 (outter for loop will run once)
3071 if (info->ritual.casterTargetSpell && info->ritual.casterTargetSpell != 1) // No idea why this field is a bool in some cases
3072 for (uint32 i = 0; i < info->ritual.casterTargetSpellTargets; i++)
3073 // m_unique_users can contain only player GUIDs
3075 spellCaster->CastSpell(target, info->ritual.casterTargetSpell, true);
3076
3077 // finish owners spell
3078 if (owner)
3080
3081 // can be deleted now, if
3082 if (!info->ritual.ritualPersistent)
3084 else
3085 {
3086 // reset ritual for this GO
3088 m_unique_users.clear();
3089 m_usetimes = 0;
3090 }
3091 }
3092 else
3093 return;
3094
3095 // go to end function to spell casting
3096 break;
3097 }
3099 {
3100 GameObjectTemplate const* info = GetGOInfo();
3101 if (!info)
3102 return;
3103
3104 if (info->spellCaster.partyOnly)
3105 {
3106 Unit* caster = GetOwner();
3107 if (!caster || caster->GetTypeId() != TYPEID_PLAYER)
3108 return;
3109
3110 if (user->GetTypeId() != TYPEID_PLAYER || !user->ToPlayer()->IsInSameRaidWith(caster->ToPlayer()))
3111 return;
3112 }
3113
3115 spellId = info->spellCaster.spell;
3116
3117 AddUse();
3118 break;
3119 }
3121 {
3122 GameObjectTemplate const* info = GetGOInfo();
3123
3124 if (user->GetTypeId() != TYPEID_PLAYER)
3125 return;
3126
3127 Player* player = user->ToPlayer();
3128
3129 Player* targetPlayer = ObjectAccessor::FindPlayer(player->GetTarget());
3130
3131 // accept only use by player from same raid as caster, except caster itself
3132 if (!targetPlayer || targetPlayer == player || !targetPlayer->IsInSameRaidWith(player))
3133 return;
3134
3135 //required lvl checks!
3136 if (Optional<ContentTuningLevels> userLevels = sDB2Manager.GetContentTuningData(info->ContentTuningId, player->m_playerData->CtrOptions->ContentTuningConditionMask))
3137 if (player->GetLevel() < userLevels->MaxLevel)
3138 return;
3139
3140 if (Optional<ContentTuningLevels> targetLevels = sDB2Manager.GetContentTuningData(info->ContentTuningId, targetPlayer->m_playerData->CtrOptions->ContentTuningConditionMask))
3141 if (targetPlayer->GetLevel() < targetLevels->MaxLevel)
3142 return;
3143
3144 if (info->entry == 194097)
3145 spellId = 61994; // Ritual of Summoning
3146 else
3147 spellId = 59782; // Summoning Stone Effect
3148
3149 break;
3150 }
3151
3152 case GAMEOBJECT_TYPE_FLAGSTAND: // 24
3153 {
3154 if (user->GetTypeId() != TYPEID_PLAYER)
3155 return;
3156
3157 Player* player = user->ToPlayer();
3158
3159 if (player->CanUseBattlegroundObject(this))
3160 {
3161 if (player->GetVehicle())
3162 return;
3163
3166 return; //we don;t need to delete flag ... it is despawned!
3167 }
3168 break;
3169 }
3170
3171 case GAMEOBJECT_TYPE_FISHINGHOLE: // 25
3172 {
3173 if (user->GetTypeId() != TYPEID_PLAYER)
3174 return;
3175
3176 Player* player = user->ToPlayer();
3177
3178 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_FISHINGHOLE, nullptr);
3179 loot->FillLoot(GetGOInfo()->GetLootId(), LootTemplates_Gameobject, player, true, false, LOOT_MODE_DEFAULT, ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
3180 m_personalLoot[player->GetGUID()].reset(loot);
3181
3182 player->SendLoot(*loot);
3184 return;
3185 }
3186
3187 case GAMEOBJECT_TYPE_FLAGDROP: // 26
3188 {
3189 if (user->GetTypeId() != TYPEID_PLAYER)
3190 return;
3191
3192 Player* player = user->ToPlayer();
3193
3194 if (player->CanUseBattlegroundObject(this))
3195 {
3196 if (player->GetVehicle())
3197 return;
3198
3201 // BG flag dropped
3202 // WS:
3203 // 179785 - Silverwing Flag
3204 // 179786 - Warsong Flag
3205 // EotS:
3206 // 184142 - Netherstorm Flag
3207 GameObjectTemplate const* info = GetGOInfo();
3208 if (info->flagDrop.eventID)
3209 GameEvents::Trigger(info->flagDrop.eventID, player, this);
3210 //this cause to call return, all flags must be deleted here!!
3211 spellId = 0;
3212 Delete();
3213 }
3214 break;
3215 }
3217 {
3218 GameObjectTemplate const* info = GetGOInfo();
3219 if (!info)
3220 return;
3221
3222 if (user->GetTypeId() != TYPEID_PLAYER)
3223 return;
3224
3225 Player* player = user->ToPlayer();
3226
3227 WorldPackets::Misc::EnableBarberShop enableBarberShop;
3228 enableBarberShop.CustomizationScope = info->barberChair.CustomizationScope;
3229 player->SendDirectMessage(enableBarberShop.Write());
3230
3231 // fallback, will always work
3233
3235 return;
3236 }
3238 {
3239 GameObjectTemplate const* info = GetGOInfo();
3240 if (!info)
3241 return;
3242
3243 Player* player = user->ToPlayer();
3244 if (!player)
3245 return;
3246
3247 if (!player->CanUseBattlegroundObject(this))
3248 return;
3249
3250 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
3251 if (!newFlag)
3252 return;
3253
3254 if (newFlag->GetState() != FlagState::InBase)
3255 return;
3256
3257 spellId = info->newflag.pickupSpell;
3258 spellCaster = nullptr;
3259 break;
3260 }
3262 {
3263 GameObjectTemplate const* info = GetGOInfo();
3264 if (!info)
3265 return;
3266
3267 if (user->GetTypeId() != TYPEID_PLAYER)
3268 return;
3269
3270 if (!user->IsAlive())
3271 return;
3272
3273 if (GameObject* owner = GetMap()->GetGameObject(GetOwnerGUID()))
3274 {
3275 if (owner->GetGoType() == GAMEOBJECT_TYPE_NEW_FLAG)
3276 {
3277 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(owner->m_goTypeImpl.get());
3278 if (!newFlag)
3279 return;
3280
3281 if (newFlag->GetState() != FlagState::Dropped)
3282 return;
3283
3284 // friendly with enemy flag means you're taking it
3285 bool defenderInteract = !owner->IsFriendlyTo(user);
3286 if (defenderInteract && owner->GetGOInfo()->newflag.ReturnonDefenderInteract)
3287 {
3288 Delete();
3289 owner->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::InBase, user->ToPlayer()));
3290 return;
3291 }
3292 else
3293 {
3294 // we let the owner cast the spell for now
3295 // so that caster guid is set correctly
3296 SpellCastResult result = owner->CastSpell(user, owner->GetGOInfo()->newflag.pickupSpell, CastSpellExtraArgs(TRIGGERED_FULL_MASK));
3297 if (result == SPELL_CAST_OK)
3298 {
3299 Delete();
3300 owner->HandleCustomTypeCommand(GameObjectType::SetNewFlagState(FlagState::Taken, user->ToPlayer()));
3301 return;
3302 }
3303 }
3304 }
3305 }
3306
3307 Delete();
3308 return;
3309 }
3311 {
3312 Player* player = user->ToPlayer();
3313 if (!player)
3314 return;
3315
3316 AssaultCapturePoint(player);
3317 return;
3318 }
3320 {
3321 GameObjectTemplate const* info = GetGOInfo();
3322 if (!info)
3323 return;
3324
3325 if (user->GetTypeId() != TYPEID_PLAYER)
3326 return;
3327
3328 Player* player = user->ToPlayer();
3329
3330 if (!MeetsInteractCondition(player))
3331 return;
3332
3333 switch (info->itemForge.ForgeType)
3334 {
3335 case 0: // Artifact Forge
3336 case 1: // Relic Forge
3337 {
3339 Item const* item = artifactAura ? player->GetItemByGuid(artifactAura->GetCastItemGUID()) : nullptr;
3340 if (!item)
3341 {
3343 return;
3344 }
3345
3347 openArtifactForge.ArtifactGUID = item->GetGUID();
3348 openArtifactForge.ForgeGUID = GetGUID();
3349 player->SendDirectMessage(openArtifactForge.Write());
3350 break;
3351 }
3352 case 2: // Heart Forge
3353 {
3355 if (!item)
3356 return;
3357
3359 openHeartForge.ObjectGUID = GetGUID();
3361 player->SendDirectMessage(openHeartForge.Write());
3362 break;
3363 }
3364 default:
3365 break;
3366 }
3367 return;
3368 }
3370 {
3371 Player* player = user->ToPlayer();
3372 if (!player)
3373 return;
3374
3376 gameObjectUILink.ObjectGUID = GetGUID();
3377 switch (GetGOInfo()->UILink.UILinkType)
3378 {
3379 case 0:
3381 break;
3382 case 1:
3384 break;
3385 case 2:
3387 break;
3388 case 3:
3390 break;
3391 default:
3392 break;
3393 }
3394 player->SendDirectMessage(gameObjectUILink.Write());
3395 return;
3396 }
3398 {
3399 Player* player = user->ToPlayer();
3400 if (!player)
3401 return;
3402
3403 GameObjectTemplate const* info = GetGOInfo();
3404 if (!m_personalLoot.count(player->GetGUID()))
3405 {
3406 if (info->gatheringNode.chestLoot)
3407 {
3408 Loot* loot = new Loot(GetMap(), GetGUID(), LOOT_CHEST, nullptr);
3409 m_personalLoot[player->GetGUID()].reset(loot);
3410
3411 loot->FillLoot(info->gatheringNode.chestLoot, LootTemplates_Gameobject, player, true, false, GetLootMode(), ItemBonusMgr::GetContextForPlayer(GetMap()->GetMapDifficulty(), player));
3412 }
3413
3414 if (info->gatheringNode.triggeredEvent)
3416
3417 // triggering linked GO
3418 if (uint32 trapEntry = info->gatheringNode.linkedTrap)
3419 TriggeringLinkedGameObject(trapEntry, player);
3420
3421 if (info->gatheringNode.xpDifficulty && info->gatheringNode.xpDifficulty < 10)
3422 if (QuestXPEntry const* questXp = sQuestXPStore.LookupEntry(player->GetLevel()))
3423 if (uint32 xp = Quest::RoundXPValue(questXp->Difficulty[info->gatheringNode.xpDifficulty]))
3424 player->GiveXP(xp, nullptr);
3425
3426 spellId = info->gatheringNode.spell;
3427 }
3428
3429 if (m_personalLoot.size() >= info->gatheringNode.MaxNumberofLoots)
3430 {
3433 }
3434
3435 if (getLootState() != GO_ACTIVATED)
3436 {
3437 SetLootState(GO_ACTIVATED, player);
3440 }
3441
3442 // Send loot
3443 if (Loot* loot = GetLootForPlayer(player))
3444 player->SendLoot(*loot);
3445 break;
3446 }
3447 default:
3449 TC_LOG_ERROR("misc", "GameObject::Use(): unit ({}, name: {}) tries to use object ({}, name: {}) of unknown type ({})",
3450 user->GetGUID().ToString(), user->GetName(), GetGUID().ToString(), GetGOInfo()->name, GetGoType());
3451 break;
3452 }
3453
3454 if (m_vignette)
3455 {
3456 if (Player* player = user->ToPlayer())
3457 {
3458 if (Quest const* reward = sObjectMgr->GetQuestTemplate(m_vignette->Data->RewardQuestID))
3459 if (!player->GetQuestRewardStatus(m_vignette->Data->RewardQuestID))
3460 player->RewardQuest(reward, LootItemType::Item, 0, this, false);
3461
3462 if (m_vignette->Data->VisibleTrackingQuestID)
3463 player->SetRewardedQuest(m_vignette->Data->VisibleTrackingQuestID);
3464 }
3465
3466 // only unregister it from visibility (need to keep vignette for other gameobject users in case its usable by multiple players
3467 // to flag their quest completion
3468 if (GetGOInfo()->ClearObjectVignetteonOpening())
3470 }
3471
3472 if (!spellId)
3473 return;
3474
3475 if (!sSpellMgr->GetSpellInfo(spellId, GetMap()->GetDifficultyID()))
3476 {
3477 if (user->GetTypeId() != TYPEID_PLAYER || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this))
3478 TC_LOG_ERROR("misc", "WORLD: unknown spell id {} at use action for gameobject (Entry: {} GoType: {})", spellId, GetEntry(), GetGoType());
3479 else
3480 TC_LOG_DEBUG("outdoorpvp", "WORLD: {} non-dbc spell was handled by OutdoorPvP", spellId);
3481 return;
3482 }
3483
3484 if (Player* player = user->ToPlayer())
3485 sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this);
3486
3487 if (spellCaster)
3488 spellCaster->CastSpell(user, spellId, triggered);
3489 else
3490 {
3491 SpellCastResult castResult = CastSpell(user, spellId);
3492 if (castResult == SPELL_FAILED_SUCCESS)
3493 {
3496 }
3497 }
3498}
3499
3501{
3503 customAnim.ObjectGUID = GetGUID();
3504 customAnim.CustomAnim = anim;
3505 SendMessageToSet(customAnim.Write(), true);
3506}
3507
3508bool GameObject::IsInRange(float x, float y, float z, float radius) const
3509{
3511 if (!info)
3512 return IsWithinDist3d(x, y, z, radius);
3513
3514 float sinA = std::sin(GetOrientation());
3515 float cosA = std::cos(GetOrientation());
3516 float dx = x - GetPositionX();
3517 float dy = y - GetPositionY();
3518 float dz = z - GetPositionZ();
3519 float dist = std::sqrt(dx*dx + dy*dy);
3522 if (G3D::fuzzyEq(dist, 0.0f))
3523 return true;
3524
3525 float sinB = dx / dist;
3526 float cosB = dy / dist;
3527 dx = dist * (cosA * cosB + sinA * sinB);
3528 dy = dist * (cosA * sinB - sinA * cosB);
3529 return dx < info->GeoBoxMax.X + radius && dx > info->GeoBoxMin.X - radius
3530 && dy < info->GeoBoxMax.Y + radius && dy > info->GeoBoxMin.Y - radius
3531 && dz < info->GeoBoxMax.Z + radius && dz > info->GeoBoxMin.Z - radius;
3532}
3533
3535{
3537 if (uint32 scriptId = gameObjectData->scriptId)
3538 return scriptId;
3539
3540 return GetGOInfo()->ScriptId;
3541}
3542
3544{
3545 // copy references to stringIds from template and spawn
3546 m_stringIds = parent->m_stringIds;
3547
3548 // then copy script stringId, not just its reference
3549 SetScriptStringId(std::string(parent->GetStringId(StringIdType::Script)));
3550}
3551
3552bool GameObject::HasStringId(std::string_view id) const
3553{
3554 return std::ranges::any_of(m_stringIds, [id](std::string const* stringId) { return stringId && *stringId == id; });
3555}
3556
3558{
3559 if (!id.empty())
3560 {
3561 m_scriptStringId.emplace(std::move(id));
3563 }
3564 else
3565 {
3566 m_scriptStringId.reset();
3568 }
3569}
3570
3571// overwrite WorldObject function for proper name localization
3573{
3574 if (locale != DEFAULT_LOCALE)
3575 if (GameObjectLocale const* cl = sObjectMgr->GetGameObjectLocale(GetEntry()))
3576 if (cl->Name.size() > locale && !cl->Name[locale].empty())
3577 return cl->Name[locale];
3578
3579 return GetName();
3580}
3581
3583{
3584 static const int32 PACK_YZ = 1 << 20;
3585 static const int32 PACK_X = PACK_YZ << 1;
3586
3587 static const int32 PACK_YZ_MASK = (PACK_YZ << 1) - 1;
3588 static const int32 PACK_X_MASK = (PACK_X << 1) - 1;
3589
3590 int8 w_sign = (m_localRotation.w >= 0.f ? 1 : -1);
3591 int64 x = int32(m_localRotation.x * PACK_X) * w_sign & PACK_X_MASK;
3592 int64 y = int32(m_localRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK;
3593 int64 z = int32(m_localRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK;
3594 m_packedRotation = z | (y << 21) | (x << 42);
3595}
3596
3597void GameObject::SetLocalRotation(float qx, float qy, float qz, float qw)
3598{
3599 G3D::Quat rotation(qx, qy, qz, qw);
3600 rotation.unitize();
3601 m_localRotation.x = rotation.x;
3602 m_localRotation.y = rotation.y;
3603 m_localRotation.z = rotation.z;
3604 m_localRotation.w = rotation.w;
3606}
3607
3609{
3611}
3612
3613void GameObject::SetLocalRotationAngles(float z_rot, float y_rot, float x_rot)
3614{
3615 G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot));
3616 SetLocalRotation(quat.x, quat.y, quat.z, quat.w);
3617}
3618
3620{
3621 QuaternionData localRotation = GetLocalRotation();
3622 if (Transport* transport = dynamic_cast<Transport*>(GetTransport()))
3623 {
3624 QuaternionData worldRotation = transport->GetWorldRotation();
3625
3626 G3D::Quat worldRotationQuat(worldRotation.x, worldRotation.y, worldRotation.z, worldRotation.w);
3627 G3D::Quat localRotationQuat(localRotation.x, localRotation.y, localRotation.z, localRotation.w);
3628
3629 G3D::Quat resultRotation = localRotationQuat * worldRotationQuat;
3630
3631 return QuaternionData(resultRotation.x, resultRotation.y, resultRotation.z, resultRotation.w);
3632 }
3633 return localRotation;
3634}
3635
3636void GameObject::ModifyHealth(int32 change, WorldObject* attackerOrHealer /*= nullptr*/, uint32 spellId /*= 0*/)
3637{
3638 if (!m_goValue.Building.DestructibleHitpoint || !change)
3639 return;
3640
3641 // prevent double destructions of the same object
3642 if (change < 0 && !m_goValue.Building.Health)
3643 return;
3644
3645 if (int32(m_goValue.Building.Health) + change <= 0)
3649 else
3650 m_goValue.Building.Health += change;
3651
3652 // Set the health bar, value = 255 * healthPct;
3654
3655 // dealing damage, send packet
3656 if (Player* player = attackerOrHealer ? attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr)
3657 {
3659 packet.Caster = attackerOrHealer->GetGUID(); // todo: this can be a GameObject
3660 packet.Target = GetGUID();
3661 packet.Damage = -change;
3662 packet.Owner = player->GetGUID();
3663 packet.SpellID = spellId;
3664 player->SendDirectMessage(packet.Write());
3665 }
3666
3667 if (change < 0 && GetGOInfo()->destructibleBuilding.DamageEvent)
3668 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DamageEvent, attackerOrHealer, this);
3669
3671
3673 newState = GO_DESTRUCTIBLE_DESTROYED;
3675 newState = GO_DESTRUCTIBLE_DAMAGED;
3677 newState = GO_DESTRUCTIBLE_INTACT;
3678
3679 if (newState == GetDestructibleState())
3680 return;
3681
3682 SetDestructibleState(newState, attackerOrHealer, false);
3683}
3684
3685void GameObject::SetDestructibleState(GameObjectDestructibleState state, WorldObject* attackerOrHealer /*= nullptr*/, bool setHealth /*= false*/)
3686{
3687 // the user calling this must know he is already operating on destructible gameobject
3689
3690 switch (state)
3691 {
3695 if (setHealth && m_goValue.Building.DestructibleHitpoint)
3696 {
3698 SetGoAnimProgress(255);
3699 }
3700 EnableCollision(true);
3701 break;
3703 {
3704 if (GetGOInfo()->destructibleBuilding.DamagedEvent && attackerOrHealer)
3705 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DamagedEvent, attackerOrHealer, this);
3706 AI()->Damaged(attackerOrHealer, m_goInfo->destructibleBuilding.DamagedEvent);
3707
3710
3711 uint32 modelId = m_goInfo->displayId;
3713 if (modelData->State1Wmo)
3714 modelId = modelData->State1Wmo;
3715 SetDisplayId(modelId);
3716
3717 if (setHealth && m_goValue.Building.DestructibleHitpoint)
3718 {
3721 // in this case current health is 0 anyway so just prevent crashing here
3722 if (!maxHealth)
3723 maxHealth = 1;
3724 SetGoAnimProgress(m_goValue.Building.Health * 255 / maxHealth);
3725 }
3726 break;
3727 }
3729 {
3730 if (GetGOInfo()->destructibleBuilding.DestroyedEvent && attackerOrHealer)
3731 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.DestroyedEvent, attackerOrHealer, this);
3733
3736
3737 uint32 modelId = m_goInfo->displayId;
3739 if (modelData->State2Wmo)
3740 modelId = modelData->State2Wmo;
3741 SetDisplayId(modelId);
3742
3743 if (setHealth)
3744 {
3747 }
3748 EnableCollision(false);
3749 break;
3750 }
3752 {
3753 if (GetGOInfo()->destructibleBuilding.RebuildingEvent && attackerOrHealer)
3754 GameEvents::Trigger(GetGOInfo()->destructibleBuilding.RebuildingEvent, attackerOrHealer, this);
3756
3757 uint32 modelId = m_goInfo->displayId;
3759 if (modelData->State3Wmo)
3760 modelId = modelData->State3Wmo;
3761 SetDisplayId(modelId);
3762
3763 // restores to full health
3764 if (setHealth && m_goValue.Building.DestructibleHitpoint)
3765 {
3767 SetGoAnimProgress(255);
3768 }
3769 EnableCollision(true);
3770 break;
3771 }
3772 }
3773}
3774
3776{
3777 m_lootState = state;
3778 if (unit)
3779 m_lootStateUnitGUID = unit->GetGUID();
3780 else
3782
3783 AI()->OnLootStateChanged(state, unit);
3784
3785 // Start restock timer if the chest is partially looted or not looted at all
3786 if (GetGoType() == GAMEOBJECT_TYPE_CHEST && state == GO_ACTIVATED && GetGOInfo()->chest.chestRestockTime > 0 && m_restockTime == 0 && m_loot && m_loot->IsChanged())
3788
3789 if (GetGoType() == GAMEOBJECT_TYPE_DOOR) // only set collision for doors on SetGoState
3790 return;
3791
3792 if (m_model)
3793 {
3794 bool collision = false;
3795 // Use the current go state
3796 if ((GetGoState() != GO_STATE_READY && (state == GO_ACTIVATED || state == GO_JUST_DEACTIVATED)) || state == GO_READY)
3797 collision = !collision;
3798
3799 EnableCollision(collision);
3800 }
3801}
3802
3804{
3805 // Unlink loot objects from this GameObject before destroying to avoid accessing freed memory from Loot destructor
3806 std::unique_ptr<Loot> loot(std::move(m_loot));
3807 std::unordered_map<ObjectGuid, std::unique_ptr<Loot>> personalLoot(std::move(m_personalLoot));
3808
3809 loot.reset();
3810 personalLoot.clear();
3811 m_unique_users.clear();
3812 m_usetimes = 0;
3813}
3814
3816{
3817 if (m_loot && !m_loot->isLooted())
3818 return false;
3819
3820 for (auto const& [_, loot] : m_personalLoot)
3821 if (!loot->isLooted())
3822 return false;
3823
3824 return true;
3825}
3826
3828{
3829 switch (GetGoType())
3830 {
3832 {
3833 GameObjectTemplate const* goInfo = GetGOInfo();
3834 if (!goInfo->chest.consumable && goInfo->chest.chestPersonalLoot)
3835 {
3837 ? Seconds(goInfo->chest.chestRestockTime)
3838 : Seconds(m_respawnDelayTime)); // not hiding this object permanently to prevent infinite growth of m_perPlayerState
3839 // while also maintaining some sort of cheater protection (not getting rid of entries on logout)
3840 }
3841 break;
3842 }
3844 {
3846
3847 UF::ObjectData::Base objMask;
3850
3851 UpdateData udata(GetMapId());
3852 BuildValuesUpdateForPlayerWithMask(&udata, objMask.GetChangesMask(), goMask.GetChangesMask(), looter);
3853 WorldPacket packet;
3854 udata.BuildPacket(&packet);
3855 looter->SendDirectMessage(&packet);
3856 break;
3857 }
3858 default:
3859 break;
3860 }
3861}
3862
3864{
3865 GOState oldState = GetGoState();
3867 if (AI())
3868 AI()->OnStateChanged(state);
3869
3870 if (m_goTypeImpl)
3871 m_goTypeImpl->OnStateChanged(oldState, state);
3872
3873 if (m_model && !IsTransport())
3874 {
3875 if (!IsInWorld())
3876 return;
3877
3878 // startOpen determines whether we are going to add or remove the LoS on activation
3879 bool collision = false;
3880 if (state == GO_STATE_READY)
3881 collision = !collision;
3882
3883 EnableCollision(collision);
3884 }
3885}
3886
3888{
3889 if (m_perPlayerState)
3891 if (state->State)
3892 return *state->State;
3893
3894 return GetGoState();
3895}
3896
3897void GameObject::SetGoStateFor(GOState state, Player const* viewer)
3898{
3899 PerPlayerState& perPlayerState = GetOrCreatePerPlayerStates()[viewer->GetGUID()];
3901 perPlayerState.State = state;
3902
3904 setStateLocal.ObjectGUID = GetGUID();
3905 setStateLocal.State = state;
3906 viewer->SendDirectMessage(setStateLocal.Write());
3907}
3908
3910{
3912 UpdateModel();
3913}
3914
3916{
3917 switch (GetGoType())
3918 {
3921 {
3922 switch (GetDestructibleState())
3923 {
3925 return modelData->State0NameSet;
3927 return modelData->State1NameSet;
3929 return modelData->State2NameSet;
3931 return modelData->State3NameSet;
3932 default:
3933 break;
3934 }
3935 }
3936 break;
3940 return ((*m_gameObjectData->Flags) >> 8) & 0xF;
3941 default:
3942 break;
3943 }
3944
3945 return 0;
3946}
3947
3949{
3950 if (!m_model)
3951 return;
3952
3953 /*if (enable && !GetMap()->ContainsGameObjectModel(*m_model))
3954 GetMap()->InsertGameObjectModel(*m_model);*/
3955
3956 m_model->EnableCollision(enable);
3957}
3958
3960{
3961 if (!IsInWorld())
3962 return;
3963 if (m_model)
3964 if (GetMap()->ContainsGameObjectModel(*m_model))
3967 delete m_model;
3968 m_model = nullptr;
3969 CreateModel();
3970 if (m_model)
3972}
3973
3974bool GameObject::IsLootAllowedFor(Player const* player) const
3975{
3976 if (Loot const* loot = GetLootForPlayer(player)) // check only if loot was already generated
3977 {
3978 if (loot->isLooted()) // nothing to loot or everything looted.
3979 return false;
3980 if (!loot->HasAllowedLooter(GetGUID()) || (!loot->hasItemForAll() && !loot->hasItemFor(player))) // no loot in chest for this player
3981 return false;
3982 }
3983
3984 if (HasLootRecipient())
3985 return m_tapList.find(player->GetGUID()) != m_tapList.end();
3986
3987 return true;
3988}
3989
3991{
3992 if (m_personalLoot.empty())
3993 return m_loot.get();
3994
3996 return loot;
3997
3998 return nullptr;
3999}
4000
4002{
4004}
4005
4006void GameObject::BuildValuesCreate(ByteBuffer* data, Player const* target) const
4007{
4009 std::size_t sizePos = data->wpos();
4010 *data << uint32(0);
4011 *data << uint8(flags);
4012 m_objectData->WriteCreate(*data, flags, this, target);
4013 m_gameObjectData->WriteCreate(*data, flags, this, target);
4014 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
4015}
4016
4017void GameObject::BuildValuesUpdate(ByteBuffer* data, Player const* target) const
4018{
4020 std::size_t sizePos = data->wpos();
4021 *data << uint32(0);
4023
4025 m_objectData->WriteUpdate(*data, flags, this, target);
4026
4028 m_gameObjectData->WriteUpdate(*data, flags, this, target);
4029
4030 data->put<uint32>(sizePos, data->wpos() - sizePos - 4);
4031}
4032
4034 UF::GameObjectData::Mask const& requestedGameObjectMask, Player const* target) const
4035{
4037 if (requestedObjectMask.IsAnySet())
4038 valuesMask.Set(TYPEID_OBJECT);
4039
4040 if (requestedGameObjectMask.IsAnySet())
4041 valuesMask.Set(TYPEID_GAMEOBJECT);
4042
4043 ByteBuffer& buffer = PrepareValuesUpdateBuffer(data);
4044 std::size_t sizePos = buffer.wpos();
4045 buffer << uint32(0);
4046 buffer << uint32(valuesMask.GetBlock(0));
4047
4048 if (valuesMask[TYPEID_OBJECT])
4049 m_objectData->WriteUpdate(buffer, requestedObjectMask, true, this, target);
4050
4051 if (valuesMask[TYPEID_GAMEOBJECT])
4052 m_gameObjectData->WriteUpdate(buffer, requestedGameObjectMask, true, this, target);
4053
4054 buffer.put<uint32>(sizePos, buffer.wpos() - sizePos - 4);
4055
4056 data->AddUpdateBlock();
4057}
4058
4060{
4061 UpdateData udata(Owner->GetMapId());
4062 WorldPacket packet;
4063
4065
4066 udata.BuildPacket(&packet);
4067 player->SendDirectMessage(&packet);
4068}
4069
4071{
4074}
4075
4076std::vector<uint32> const* GameObject::GetPauseTimes() const
4077{
4078 if (GameObjectType::Transport const* transport = dynamic_cast<GameObjectType::Transport const*>(m_goTypeImpl.get()))
4079 return transport->GetPauseTimes();
4080
4081 return nullptr;
4082}
4083
4085{
4087 {
4088 UF::ObjectData::Base dynflagMask;
4090 bool marked = (m_objectData->GetChangesMask() & dynflagMask.GetChangesMask()).IsAnySet();
4091
4092 uint32 dynamicFlags = GetDynamicFlags();
4093 dynamicFlags &= 0xFFFF; // remove high bits
4094 dynamicFlags |= uint32(progress * 65535.0f) << 16;
4095 ReplaceAllDynamicFlags(dynamicFlags);
4096
4097 if (!marked)
4098 const_cast<UF::ObjectData&>(*m_objectData).ClearChanged(&UF::ObjectData::DynamicFlags);
4099 });
4100}
4101
4102void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const
4103{
4104 if (m_goData)
4105 {
4106 if (ori)
4107 m_goData->spawnPoint.GetPosition(x, y, z, *ori);
4108 else
4110 }
4111 else
4112 {
4113 if (ori)
4114 GetPosition(x, y, z, *ori);
4115 else
4116 GetPosition(x, y, z);
4117 }
4118}
4119
4121{
4122 switch (GetGoType())
4123 {
4125 return static_cast<GameObjectType::Transport const*>(m_goTypeImpl.get());
4127 return static_cast<Transport const*>(this);
4128 default:
4129 break;
4130 }
4131
4132 return nullptr;
4133}
4134
4136{
4139 if (m_goTypeImpl)
4140 m_goTypeImpl->OnRelocated();
4141
4142 // TODO: on heartbeat
4143 if (m_vignette)
4145
4147}
4148
4150{
4151 if (GetGOInfo()->GetInteractRadiusOverride())
4152 return float(GetGOInfo()->GetInteractRadiusOverride()) / 100.0f;
4153
4154 switch (GetGoType())
4155 {
4157 return 0.0f;
4163 return 5.5555553f;
4165 return 10.0f;
4168 return 3.0f;
4170 return 100.0f;
4172 return 20.0f + CONTACT_DISTANCE; // max spell range
4178 return 5.0f;
4179 // Following values are not blizzlike
4182 // 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.
4183 // 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.
4184 return 10.0f; // 5.0f is blizzlike
4185 default:
4186 return INTERACTION_DISTANCE;
4187 }
4188}
4189
4191{
4192 if (!m_model)
4193 return;
4194
4195 if (GetMap()->ContainsGameObjectModel(*m_model))
4196 {
4200 }
4201}
4202
4203void GameObject::SetAnimKitId(uint16 animKitId, bool oneshot)
4204{
4205 if (_animKitId == animKitId)
4206 return;
4207
4208 if (animKitId && !sAnimKitStore.LookupEntry(animKitId))
4209 return;
4210
4211 if (!oneshot)
4212 _animKitId = animKitId;
4213 else
4214 _animKitId = 0;
4215
4217 activateAnimKit.ObjectGUID = GetGUID();
4218 activateAnimKit.AnimKitID = animKitId;
4219 activateAnimKit.Maintain = !oneshot;
4220 SendMessageToSet(activateAnimKit.Write(), true);
4221}
4222
4224{
4225 if (m_vignette)
4226 {
4227 if (m_vignette->Data->ID == vignetteId)
4228 return;
4229
4231 m_vignette = nullptr;
4232 }
4233
4234 if (VignetteEntry const* vignette = sVignetteStore.LookupEntry(vignetteId))
4235 m_vignette = Vignettes::Create(vignette, this);
4236}
4237
4238void GameObject::SetSpellVisualId(int32 spellVisualId, ObjectGuid activatorGuid)
4239{
4241
4243 packet.ObjectGUID = GetGUID();
4244 packet.ActivatorGUID = activatorGuid;
4245 packet.SpellVisualID = spellVisualId;
4246 SendMessageToSet(packet.Write(), true);
4247}
4248
4250{
4251 if (!CanInteractWithCapturePoint(player))
4252 return;
4253
4254 if (GameObjectAI* ai = AI())
4255 if (ai->OnCapturePointAssaulted(player))
4256 return;
4257
4258 // only supported in battlegrounds
4259 Battleground* battleground = nullptr;
4260 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
4261 if (Battleground* bg = map->GetBG())
4262 battleground = bg;
4263
4264 if (!battleground)
4265 return;
4266
4267 // Cancel current timer
4269
4270 if (player->GetBGTeam() == HORDE)
4271 {
4273 {
4274 // defended. capture instantly.
4276 battleground->SendBroadcastText(GetGOInfo()->capturePoint.DefendedBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE, player);
4278 if (GetGOInfo()->capturePoint.DefendedEventHorde)
4279 GameEvents::Trigger(GetGOInfo()->capturePoint.DefendedEventHorde, player, this);
4280 return;
4281 }
4282
4284 {
4289 battleground->SendBroadcastText(GetGOInfo()->capturePoint.AssaultBroadcastHorde, CHAT_MSG_BG_SYSTEM_HORDE, player);
4291 if (GetGOInfo()->capturePoint.ContestedEventHorde)
4292 GameEvents::Trigger(GetGOInfo()->capturePoint.ContestedEventHorde, player, this);
4294 break;
4295 default:
4296 break;
4297 }
4298 }
4299 else
4300 {
4302 {
4303 // defended. capture instantly.
4307 if (GetGOInfo()->capturePoint.DefendedEventAlliance)
4308 GameEvents::Trigger(GetGOInfo()->capturePoint.DefendedEventAlliance, player, this);
4309 return;
4310 }
4311
4313 {
4320 if (GetGOInfo()->capturePoint.ContestedEventAlliance)
4321 GameEvents::Trigger(GetGOInfo()->capturePoint.ContestedEventAlliance, player, this);
4323 break;
4324 default:
4325 break;
4326 }
4327 }
4328}
4329
4331{
4333 return;
4334
4335 if (GameObjectAI* ai = AI())
4336 if (ai->OnCapturePointUpdated(m_goValue.CapturePoint.State))
4337 return;
4338
4339 uint32 spellVisualId = 0;
4340 uint32 customAnim = 0;
4341
4343 {
4345 spellVisualId = GetGOInfo()->capturePoint.SpellVisual1;
4346 break;
4348 customAnim = 1;
4349 spellVisualId = GetGOInfo()->capturePoint.SpellVisual2;
4350 break;
4352 customAnim = 2;
4353 spellVisualId = GetGOInfo()->capturePoint.SpellVisual3;
4354 break;
4356 customAnim = 3;
4357 spellVisualId = GetGOInfo()->capturePoint.SpellVisual4;
4358 break;
4360 customAnim = 4;
4361 spellVisualId = GetGOInfo()->capturePoint.SpellVisual5;
4362 break;
4363 default:
4364 break;
4365 }
4366
4367 if (customAnim != 0)
4368 SendCustomAnim(customAnim);
4369
4370 SetSpellVisualId(spellVisualId);
4372
4373 if (BattlegroundMap* map = GetMap()->ToBattlegroundMap())
4374 {
4375 if (Battleground* bg = map->GetBG())
4376 {
4379 packet.CapturePointInfo.Pos = GetPosition();
4380 packet.CapturePointInfo.Guid = GetGUID();
4383 bg->SendPacketToAll(packet.Write());
4384 bg->UpdateWorldState(GetGOInfo()->capturePoint.worldState1, AsUnderlyingType(m_goValue.CapturePoint.State));
4385 }
4386 }
4387
4389}
4390
4392{
4394 return false;
4395
4397 return true;
4398
4399 if (target->GetBGTeam() == HORDE)
4400 {
4403 }
4404
4405 // For Alliance players
4408}
4409
4411{
4413 return FlagState(0);
4414
4415 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
4416 if (!newFlag)
4417 return FlagState(0);
4418
4419 return newFlag->GetState();
4420}
4421
4423{
4425 return ObjectGuid::Empty;
4426
4427 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
4428 if (!newFlag)
4429 return ObjectGuid::Empty;
4430
4431 return newFlag->GetCarrierGUID();
4432}
4433
4435{
4437 return time_t(0);
4438
4439 GameObjectType::NewFlag const* newFlag = dynamic_cast<GameObjectType::NewFlag const*>(m_goTypeImpl.get());
4440 if (!newFlag)
4441 return time_t(0);
4442
4443 return newFlag->GetTakenFromBaseTime();
4444}
4445