]> Raphaƫl G. Git Repositories - trinitycore/blob - src/server/game/Entities/GameObject/GameObject.cpp
185d2f1f781d41a38a112c9d7323ec0ace5f4be8
[trinitycore] / src / server / game / Entities / GameObject / GameObject.cpp
1 /*
2 * Copyright (C) 2008-2018 TrinityCore <https://www.trinitycore.org/>
3 * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the
7 * Free Software Foundation; either version 2 of the License, or (at your
8 * option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "GameObject.h"
20 #include "ArtifactPackets.h"
21 #include "Battleground.h"
22 #include "CellImpl.h"
23 #include "CreatureAISelector.h"
24 #include "DatabaseEnv.h"
25 #include "GameObjectAI.h"
26 #include "GameObjectModel.h"
27 #include "GameObjectPackets.h"
28 #include "GridNotifiersImpl.h"
29 #include "Group.h"
30 #include "GroupMgr.h"
31 #include "Item.h"
32 #include "Log.h"
33 #include "LootMgr.h"
34 #include "MiscPackets.h"
35 #include "ObjectAccessor.h"
36 #include "ObjectMgr.h"
37 #include "OutdoorPvPMgr.h"
38 #include "PhasingHandler.h"
39 #include "PoolMgr.h"
40 #include "ScriptMgr.h"
41 #include "SpellMgr.h"
42 #include "Transport.h"
43 #include "UpdateFieldFlags.h"
44 #include "World.h"
45 #include <G3D/Quat.h>
46
47 bool QuaternionData::isUnit() const
48 {
49 return fabs(x * x + y * y + z * z + w * w - 1.0f) < 1e-5;
50 }
51
52 QuaternionData QuaternionData::fromEulerAnglesZYX(float Z, float Y, float X)
53 {
54 G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(Z, Y, X));
55 return QuaternionData(quat.x, quat.y, quat.z, quat.w);
56 }
57
58 GameObject::GameObject() : WorldObject(false), MapObject(),
59 m_model(nullptr), m_goValue(), m_AI(nullptr), _animKitId(0), _worldEffectID(0)
60 {
61 m_objectType |= TYPEMASK_GAMEOBJECT;
62 m_objectTypeId = TYPEID_GAMEOBJECT;
63
64 m_updateFlag = (UPDATEFLAG_STATIONARY_POSITION | UPDATEFLAG_ROTATION);
65
66 m_valuesCount = GAMEOBJECT_END;
67 _dynamicValuesCount = GAMEOBJECT_DYNAMIC_END;
68 m_respawnTime = 0;
69 m_respawnDelayTime = 300;
70 m_lootState = GO_NOT_READY;
71 m_spawnedByDefault = true;
72 m_usetimes = 0;
73 m_spellId = 0;
74 m_cooldownTime = 0;
75 m_prevGoState = GO_STATE_ACTIVE;
76 m_goInfo = nullptr;
77 m_goData = nullptr;
78 m_packedRotation = 0;
79 m_goTemplateAddon = nullptr;
80
81 m_spawnId = UI64LIT(0);
82
83 m_groupLootTimer = 0;
84
85 ResetLootMode(); // restore default loot mode
86 m_stationaryPosition.Relocate(0.0f, 0.0f, 0.0f, 0.0f);
87 }
88
89 GameObject::~GameObject()
90 {
91 delete m_AI;
92 delete m_model;
93 if (m_goInfo && m_goInfo->type == GAMEOBJECT_TYPE_TRANSPORT)
94 delete m_goValue.Transport.StopFrames;
95 //if (m_uint32Values) // field array can be not exist if GameOBject not loaded
96 // CleanupsBeforeDelete();
97 }
98
99 void GameObject::AIM_Destroy()
100 {
101 delete m_AI;
102 m_AI = nullptr;
103 }
104
105 bool GameObject::AIM_Initialize()
106 {
107 AIM_Destroy();
108
109 m_AI = FactorySelector::SelectGameObjectAI(this);
110
111 if (!m_AI)
112 return false;
113
114 m_AI->InitializeAI();
115 return true;
116 }
117
118 std::string GameObject::GetAIName() const
119 {
120 if (GameObjectTemplate const* got = sObjectMgr->GetGameObjectTemplate(GetEntry()))
121 return got->AIName;
122
123 return "";
124 }
125
126 void GameObject::CleanupsBeforeDelete(bool finalCleanup)
127 {
128 WorldObject::CleanupsBeforeDelete(finalCleanup);
129
130 if (m_uint32Values) // field array can be not exist if GameOBject not loaded
131 RemoveFromOwner();
132 }
133
134 void GameObject::RemoveFromOwner()
135 {
136 ObjectGuid ownerGUID = GetOwnerGUID();
137 if (!ownerGUID)
138 return;
139
140 if (Unit* owner = ObjectAccessor::GetUnit(*this, ownerGUID))
141 {
142 owner->RemoveGameObject(this, false);
143 ASSERT(!GetOwnerGUID());
144 return;
145 }
146
147 // This happens when a mage portal is despawned after the caster changes map (for example using the portal)
148 TC_LOG_DEBUG("misc", "Removed GameObject (%s SpellId: %u LinkedGO: %u) that just lost any reference to the owner (%s) GO list",
149 GetGUID().ToString().c_str(), m_spellId, GetGOInfo()->GetLinkedGameObjectEntry(), ownerGUID.ToString().c_str());
150 SetOwnerGUID(ObjectGuid::Empty);
151 }
152
153 void GameObject::AddToWorld()
154 {
155 ///- Register the gameobject for guid lookup
156 if (!IsInWorld())
157 {
158 if (m_zoneScript)
159 m_zoneScript->OnGameObjectCreate(this);
160
161 GetMap()->GetObjectsStore().Insert<GameObject>(GetGUID(), this);
162 if (m_spawnId)
163 GetMap()->GetGameObjectBySpawnIdStore().insert(std::make_pair(m_spawnId, this));
164
165 // The state can be changed after GameObject::Create but before GameObject::AddToWorld
166 bool toggledState = GetGoType() == GAMEOBJECT_TYPE_CHEST ? getLootState() == GO_READY : (GetGoState() == GO_STATE_READY || IsTransport());
167 if (m_model)
168 {
169 if (Transport* trans = ToTransport())
170 trans->SetDelayedAddModelToMap();
171 else
172 GetMap()->InsertGameObjectModel(*m_model);
173 }
174
175 EnableCollision(toggledState);
176 WorldObject::AddToWorld();
177 }
178 }
179
180 void GameObject::RemoveFromWorld()
181 {
182 ///- Remove the gameobject from the accessor
183 if (IsInWorld())
184 {
185 if (m_zoneScript)
186 m_zoneScript->OnGameObjectRemove(this);
187
188 RemoveFromOwner();
189 if (m_model)
190 if (GetMap()->ContainsGameObjectModel(*m_model))
191 GetMap()->RemoveGameObjectModel(*m_model);
192
193 WorldObject::RemoveFromWorld();
194
195 if (m_spawnId)
196 Trinity::Containers::MultimapErasePair(GetMap()->GetGameObjectBySpawnIdStore(), m_spawnId, this);
197 GetMap()->GetObjectsStore().Remove<GameObject>(GetGUID());
198 }
199 }
200
201 bool GameObject::Create(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit)
202 {
203 ASSERT(map);
204 SetMap(map);
205
206 Relocate(pos);
207 m_stationaryPosition.Relocate(pos);
208 if (!IsPositionValid())
209 {
210 TC_LOG_ERROR("misc", "Gameobject (Spawn id: " UI64FMTD " Entry: %u) not created. Suggested coordinates isn't valid (X: %f Y: %f)", GetSpawnId(), entry, pos.GetPositionX(), pos.GetPositionY());
211 return false;
212 }
213
214 SetZoneScript();
215 if (m_zoneScript)
216 {
217 entry = m_zoneScript->GetGameObjectEntry(m_spawnId, entry);
218 if (!entry)
219 return false;
220 }
221
222 GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry);
223 if (!goInfo)
224 {
225 TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: " UI64FMTD " Entry: %u) not created: non-existing entry in `gameobject_template`. Map: %u (X: %f Y: %f Z: %f)", GetSpawnId(), entry, map->GetId(), pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ());
226 return false;
227 }
228
229 if (goInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT)
230 {
231 TC_LOG_ERROR("sql.sql", "Gameobject (Spawn id: " UI64FMTD " Entry: %u) not created: gameobject type GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT cannot be manually created.", GetSpawnId(), entry);
232 return false;
233 }
234
235 ObjectGuid guid;
236 if (goInfo->type != GAMEOBJECT_TYPE_TRANSPORT)
237 guid = ObjectGuid::Create<HighGuid::GameObject>(map->GetId(), goInfo->entry, map->GenerateLowGuid<HighGuid::GameObject>());
238 else
239 {
240 guid = ObjectGuid::Create<HighGuid::Transport>(map->GenerateLowGuid<HighGuid::Transport>());
241 m_updateFlag |= UPDATEFLAG_TRANSPORT;
242 }
243
244 Object::_Create(guid);
245
246 m_goInfo = goInfo;
247 m_goTemplateAddon = sObjectMgr->GetGameObjectTemplateAddon(entry);
248
249 if (goInfo->type >= MAX_GAMEOBJECT_TYPE)
250 {
251 TC_LOG_ERROR("sql.sql", "Gameobject (%s Spawn id: " UI64FMTD " Entry: %u) not created: non-existing GO type '%u' in `gameobject_template`. It will crash client if created.", guid.ToString().c_str(), GetSpawnId(), entry, goInfo->type);
252 return false;
253 }
254
255 SetWorldRotation(rotation.x, rotation.y, rotation.z, rotation.w);
256 GameObjectAddon const* gameObjectAddon = sObjectMgr->GetGameObjectAddon(GetSpawnId());
257
258 // For most of gameobjects is (0, 0, 0, 1) quaternion, there are only some transports with not standard rotation
259 QuaternionData parentRotation;
260 if (gameObjectAddon)
261 parentRotation = gameObjectAddon->ParentRotation;
262
263 SetParentRotation(parentRotation);
264
265 SetObjectScale(goInfo->size);
266
267 if (m_goTemplateAddon)
268 {
269 SetUInt32Value(GAMEOBJECT_FACTION, m_goTemplateAddon->faction);
270 SetUInt32Value(GAMEOBJECT_FLAGS, m_goTemplateAddon->flags);
271
272 if (m_goTemplateAddon->WorldEffectID)
273 {
274 m_updateFlag |= UPDATEFLAG_GAMEOBJECT;
275 SetWorldEffectID(m_goTemplateAddon->WorldEffectID);
276 }
277 }
278
279 SetEntry(goInfo->entry);
280
281 // set name for logs usage, doesn't affect anything ingame
282 SetName(goInfo->name);
283
284 SetDisplayId(goInfo->displayId);
285
286 m_model = CreateModel();
287 // GAMEOBJECT_BYTES_1, index at 0, 1, 2 and 3
288 SetGoType(GameobjectTypes(goInfo->type));
289 m_prevGoState = goState;
290 SetGoState(goState);
291 SetGoArtKit(artKit);
292
293 switch (goInfo->type)
294 {
295 case GAMEOBJECT_TYPE_FISHINGHOLE:
296 SetGoAnimProgress(animProgress);
297 m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock);
298 break;
299 case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
300 // TODO: Get the values somehow, no longer in gameobject_template
301 m_goValue.Building.Health = 20000/*goinfo->destructibleBuilding.intactNumHits + goinfo->destructibleBuilding.damagedNumHits*/;
302 m_goValue.Building.MaxHealth = m_goValue.Building.Health;
303 SetGoAnimProgress(255);
304 SetUInt32Value(GAMEOBJECT_PARENTROTATION, m_goInfo->destructibleBuilding.DestructibleModelRec);
305 break;
306 case GAMEOBJECT_TYPE_TRANSPORT:
307 {
308 m_goValue.Transport.AnimationInfo = sTransportMgr->GetTransportAnimInfo(goInfo->entry);
309 m_goValue.Transport.PathProgress = getMSTime();
310 if (m_goValue.Transport.AnimationInfo)
311 m_goValue.Transport.PathProgress -= m_goValue.Transport.PathProgress % GetTransportPeriod(); // align to period
312 m_goValue.Transport.CurrentSeg = 0;
313 m_goValue.Transport.StateUpdateTimer = 0;
314 m_goValue.Transport.StopFrames = new std::vector<uint32>();
315 if (goInfo->transport.Timeto2ndfloor > 0)
316 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto2ndfloor);
317 if (goInfo->transport.Timeto3rdfloor > 0)
318 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto3rdfloor);
319 if (goInfo->transport.Timeto4thfloor > 0)
320 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto4thfloor);
321 if (goInfo->transport.Timeto5thfloor > 0)
322 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto5thfloor);
323 if (goInfo->transport.Timeto6thfloor > 0)
324 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto6thfloor);
325 if (goInfo->transport.Timeto7thfloor > 0)
326 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto7thfloor);
327 if (goInfo->transport.Timeto8thfloor > 0)
328 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto8thfloor);
329 if (goInfo->transport.Timeto9thfloor > 0)
330 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto9thfloor);
331 if (goInfo->transport.Timeto10thfloor > 0)
332 m_goValue.Transport.StopFrames->push_back(goInfo->transport.Timeto10thfloor);
333 if (goInfo->transport.startOpen)
334 SetTransportState(GO_STATE_TRANSPORT_STOPPED, goInfo->transport.startOpen - 1);
335 else
336 SetTransportState(GO_STATE_TRANSPORT_ACTIVE);
337
338 SetGoAnimProgress(animProgress);
339 break;
340 }
341 case GAMEOBJECT_TYPE_FISHINGNODE:
342 SetUInt32Value(GAMEOBJECT_LEVEL, 1);
343 SetGoAnimProgress(255);
344 break;
345 case GAMEOBJECT_TYPE_TRAP:
346 if (GetGOInfo()->trap.stealthed)
347 {
348 m_stealth.AddFlag(STEALTH_TRAP);
349 m_stealth.AddValue(STEALTH_TRAP, 70);
350 }
351
352 if (GetGOInfo()->trap.stealthAffected)
353 {
354 m_invisibility.AddFlag(INVISIBILITY_TRAP);
355 m_invisibility.AddValue(INVISIBILITY_TRAP, 300);
356 }
357 break;
358 default:
359 SetGoAnimProgress(animProgress);
360 break;
361 }
362
363 if (gameObjectAddon && gameObjectAddon->InvisibilityValue)
364 {
365 m_invisibility.AddFlag(gameObjectAddon->invisibilityType);
366 m_invisibility.AddValue(gameObjectAddon->invisibilityType, gameObjectAddon->InvisibilityValue);
367 }
368
369 if (gameObjectAddon && gameObjectAddon->WorldEffectID)
370 {
371 m_updateFlag |= UPDATEFLAG_GAMEOBJECT;
372 SetWorldEffectID(gameObjectAddon->WorldEffectID);
373 }
374
375 LastUsedScriptID = GetGOInfo()->ScriptId;
376 AIM_Initialize();
377
378 // Initialize loot duplicate count depending on raid difficulty
379 if (map->Is25ManRaid())
380 loot.maxDuplicates = 3;
381
382 if (uint32 linkedEntry = GetGOInfo()->GetLinkedGameObjectEntry())
383 {
384 if (GameObject* linkedGo = GameObject::CreateGameObject(linkedEntry, map, pos, rotation, 255, GO_STATE_READY))
385 {
386 SetLinkedTrap(linkedGo);
387 if (!map->AddToMap(linkedGo))
388 delete linkedGo;
389 }
390 }
391
392 return true;
393 }
394
395 GameObject* GameObject::CreateGameObject(uint32 entry, Map* map, Position const& pos, QuaternionData const& rotation, uint32 animProgress, GOState goState, uint32 artKit /*= 0*/)
396 {
397 GameObjectTemplate const* goInfo = sObjectMgr->GetGameObjectTemplate(entry);
398 if (!goInfo)
399 return nullptr;
400
401 GameObject* go = new GameObject();
402 if (!go->Create(entry, map, pos, rotation, animProgress, goState, artKit))
403 {
404 delete go;
405 return nullptr;
406 }
407
408 return go;
409 }
410
411 GameObject* GameObject::CreateGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap /*= true*/)
412 {
413 GameObject* go = new GameObject();
414 if (!go->LoadGameObjectFromDB(spawnId, map, addToMap))
415 {
416 delete go;
417 return nullptr;
418 }
419
420 return go;
421 }
422
423 void GameObject::Update(uint32 diff)
424 {
425 if (AI())
426 AI()->UpdateAI(diff);
427 else if (!AIM_Initialize())
428 TC_LOG_ERROR("misc", "Could not initialize GameObjectAI");
429
430 switch (m_lootState)
431 {
432 case GO_NOT_READY:
433 {
434 switch (GetGoType())
435 {
436 case GAMEOBJECT_TYPE_TRAP:
437 {
438 // Arming Time for GAMEOBJECT_TYPE_TRAP (6)
439 GameObjectTemplate const* goInfo = GetGOInfo();
440 // Bombs
441 if (goInfo->trap.charges == 2)
442 // Hardcoded tooltip value
443 m_cooldownTime = time(NULL) + 10;
444 else if (Unit* owner = GetOwner())
445 if (owner->IsInCombat())
446 m_cooldownTime = time(NULL) + goInfo->trap.startDelay;
447
448 SetLootState(GO_READY);
449 break;
450 }
451 case GAMEOBJECT_TYPE_TRANSPORT:
452 {
453 if (!m_goValue.Transport.AnimationInfo)
454 break;
455
456 if (GetGoState() == GO_STATE_TRANSPORT_ACTIVE)
457 {
458 m_goValue.Transport.PathProgress += diff;
459 /* TODO: Fix movement in unloaded grid - currently GO will just disappear
460 uint32 timer = m_goValue.Transport.PathProgress % GetTransportPeriod();
461 TransportAnimationEntry const* node = m_goValue.Transport.AnimationInfo->GetAnimNode(timer);
462 if (node && m_goValue.Transport.CurrentSeg != node->TimeSeg)
463 {
464 m_goValue.Transport.CurrentSeg = node->TimeSeg;
465
466 G3D::Quat rotation;
467 if (TransportRotationEntry const* rot = m_goValue.Transport.AnimationInfo->GetAnimRotation(timer))
468 rotation = G3D::Quat(rot->X, rot->Y, rot->Z, rot->W);
469
470 G3D::Vector3 pos = rotation.toRotationMatrix()
471 * G3D::Matrix3::fromEulerAnglesZYX(GetOrientation(), 0.0f, 0.0f)
472 * G3D::Vector3(node->X, node->Y, node->Z);
473
474 pos += G3D::Vector3(GetStationaryX(), GetStationaryY(), GetStationaryZ());
475
476 G3D::Vector3 src(GetPositionX(), GetPositionY(), GetPositionZ());
477
478 TC_LOG_DEBUG("misc", "Src: %s Dest: %s", src.toString().c_str(), pos.toString().c_str());
479
480 GetMap()->GameObjectRelocation(this, pos.x, pos.y, pos.z, GetOrientation());
481 }
482 */
483
484 if (!m_goValue.Transport.StopFrames->empty())
485 {
486 uint32 visualStateBefore = (m_goValue.Transport.StateUpdateTimer / 20000) & 1;
487 m_goValue.Transport.StateUpdateTimer += diff;
488 uint32 visualStateAfter = (m_goValue.Transport.StateUpdateTimer / 20000) & 1;
489 if (visualStateBefore != visualStateAfter)
490 {
491 ForceValuesUpdateAtIndex(GAMEOBJECT_LEVEL);
492 ForceValuesUpdateAtIndex(GAMEOBJECT_BYTES_1);
493 }
494 }
495 }
496 break;
497 }
498 case GAMEOBJECT_TYPE_FISHINGNODE:
499 {
500 // fishing code (bobber ready)
501 if (time(NULL) > m_respawnTime - FISHING_BOBBER_READY_TIME)
502 {
503 // splash bobber (bobber ready now)
504 Unit* caster = GetOwner();
505 if (caster && caster->GetTypeId() == TYPEID_PLAYER)
506 {
507 SetGoState(GO_STATE_ACTIVE);
508 SetUInt32Value(GAMEOBJECT_FLAGS, GO_FLAG_NODESPAWN);
509
510 UpdateData udata(caster->GetMapId());
511 WorldPacket packet;
512 BuildValuesUpdateBlockForPlayer(&udata, caster->ToPlayer());
513 udata.BuildPacket(&packet);
514 caster->ToPlayer()->SendDirectMessage(&packet);
515
516 SendCustomAnim(GetGoAnimProgress());
517 }
518
519 m_lootState = GO_READY; // can be successfully open with some chance
520 }
521 return;
522 }
523 default:
524 m_lootState = GO_READY; // for other GOis same switched without delay to GO_READY
525 break;
526 }
527 // NO BREAK for switch (m_lootState)
528 }
529 case GO_READY:
530 {
531 if (m_respawnTime > 0) // timer on
532 {
533 time_t now = time(NULL);
534 if (m_respawnTime <= now) // timer expired
535 {
536 ObjectGuid dbtableHighGuid = ObjectGuid::Create<HighGuid::GameObject>(GetMapId(), GetEntry(), m_spawnId);
537 time_t linkedRespawntime = GetMap()->GetLinkedRespawnTime(dbtableHighGuid);
538 if (linkedRespawntime) // Can't respawn, the master is dead
539 {
540 ObjectGuid targetGuid = sObjectMgr->GetLinkedRespawnGuid(dbtableHighGuid);
541 if (targetGuid == dbtableHighGuid) // if linking self, never respawn (check delayed to next day)
542 SetRespawnTime(DAY);
543 else
544 m_respawnTime = (now > linkedRespawntime ? now : linkedRespawntime) + urand(5, MINUTE); // else copy time from master and add a little
545 SaveRespawnTime(); // also save to DB immediately
546 return;
547 }
548
549 m_respawnTime = 0;
550 m_SkillupList.clear();
551 m_usetimes = 0;
552
553 // If nearby linked trap exists, respawn it
554 if (GameObject* linkedTrap = GetLinkedTrap())
555 linkedTrap->SetLootState(GO_READY);
556
557 switch (GetGoType())
558 {
559 case GAMEOBJECT_TYPE_FISHINGNODE: // can't fish now
560 {
561 Unit* caster = GetOwner();
562 if (caster && caster->GetTypeId() == TYPEID_PLAYER)
563 {
564 caster->ToPlayer()->RemoveGameObject(this, false);
565 caster->ToPlayer()->SendDirectMessage(WorldPackets::GameObject::FishEscaped().Write());
566 }
567 // can be delete
568 m_lootState = GO_JUST_DEACTIVATED;
569 return;
570 }
571 case GAMEOBJECT_TYPE_DOOR:
572 case GAMEOBJECT_TYPE_BUTTON:
573 // 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)
574 if (GetGoState() != GO_STATE_READY)
575 ResetDoorOrButton();
576 break;
577 case GAMEOBJECT_TYPE_FISHINGHOLE:
578 // Initialize a new max fish count on respawn
579 m_goValue.FishingHole.MaxOpens = urand(GetGOInfo()->fishingHole.minRestock, GetGOInfo()->fishingHole.maxRestock);
580 break;
581 default:
582 break;
583 }
584
585 // Despawn timer
586 if (!m_spawnedByDefault)
587 {
588 // Can be despawned or destroyed
589 SetLootState(GO_JUST_DEACTIVATED);
590 return;
591 }
592
593 // Respawn timer
594 uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0;
595 if (poolid)
596 sPoolMgr->UpdatePool<GameObject>(poolid, GetSpawnId());
597 else
598 GetMap()->AddToMap(this);
599 }
600 }
601
602 if (isSpawned())
603 {
604 GameObjectTemplate const* goInfo = GetGOInfo();
605 if (goInfo->type == GAMEOBJECT_TYPE_TRAP)
606 {
607 if (m_cooldownTime >= time(NULL))
608 break;
609
610 // Type 2 (bomb) does not need to be triggered by a unit and despawns after casting its spell.
611 if (goInfo->trap.charges == 2)
612 {
613 SetLootState(GO_ACTIVATED);
614 break;
615 }
616
617 // Type 0 despawns after being triggered, type 1 does not.
618 /// @todo This is activation radius. Casting radius must be selected from spell data.
619 float radius;
620 if (!goInfo->trap.radius)
621 {
622 // Battleground traps: data2 == 0 && data5 == 3
623 if (goInfo->trap.cooldown != 3)
624 break;
625
626 radius = 3.f;
627 }
628 else
629 radius = goInfo->trap.radius / 2.f;
630
631 // Pointer to appropriate target if found any
632 Unit* target = nullptr;
633
634 /// @todo this hack with search required until GO casting not implemented
635 if (Unit* owner = GetOwner())
636 {
637 // Hunter trap: Search units which are unfriendly to the trap's owner
638 Trinity::NearestAttackableNoTotemUnitInObjectRangeCheck checker(this, owner, radius);
639 Trinity::UnitLastSearcher<Trinity::NearestAttackableNoTotemUnitInObjectRangeCheck> searcher(this, target, checker);
640 Cell::VisitAllObjects(this, searcher, radius);
641 }
642 else
643 {
644 // Environmental trap: Any player
645 Player* player = nullptr;
646 Trinity::AnyPlayerInObjectRangeCheck checker(this, radius);
647 Trinity::PlayerSearcher<Trinity::AnyPlayerInObjectRangeCheck> searcher(this, player, checker);
648 Cell::VisitWorldObjects(this, searcher, radius);
649 target = player;
650 }
651
652 if (target)
653 SetLootState(GO_ACTIVATED, target);
654
655 }
656 else if (uint32 max_charges = goInfo->GetCharges())
657 {
658 if (m_usetimes >= max_charges)
659 {
660 m_usetimes = 0;
661 SetLootState(GO_JUST_DEACTIVATED); // can be despawned or destroyed
662 }
663 }
664 }
665
666 break;
667 }
668 case GO_ACTIVATED:
669 {
670 switch (GetGoType())
671 {
672 case GAMEOBJECT_TYPE_DOOR:
673 case GAMEOBJECT_TYPE_BUTTON:
674 if (m_cooldownTime && (m_cooldownTime < time(NULL)))
675 ResetDoorOrButton();
676 break;
677 case GAMEOBJECT_TYPE_GOOBER:
678 if (m_cooldownTime < time(NULL))
679 {
680 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
681
682 SetLootState(GO_JUST_DEACTIVATED);
683 m_cooldownTime = 0;
684 }
685 break;
686 case GAMEOBJECT_TYPE_CHEST:
687 if (m_groupLootTimer)
688 {
689 if (m_groupLootTimer <= diff)
690 {
691 if (Group* group = sGroupMgr->GetGroupByGUID(lootingGroupLowGUID))
692 group->EndRoll(&loot);
693
694 m_groupLootTimer = 0;
695 lootingGroupLowGUID.Clear();
696 }
697 else
698 m_groupLootTimer -= diff;
699 }
700 break;
701 case GAMEOBJECT_TYPE_TRAP:
702 {
703 GameObjectTemplate const* goInfo = GetGOInfo();
704 if (goInfo->trap.charges == 2 && goInfo->trap.spell)
705 {
706 /// @todo nullptr target won't work for target type 1
707 CastSpell(nullptr, goInfo->trap.spell);
708 SetLootState(GO_JUST_DEACTIVATED);
709 }
710 else if (Unit* target = ObjectAccessor::GetUnit(*this, m_lootStateUnitGUID))
711 {
712 // Some traps do not have a spell but should be triggered
713 if (goInfo->trap.spell)
714 CastSpell(target, goInfo->trap.spell);
715
716 // Template value or 4 seconds
717 m_cooldownTime = time(NULL) + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4));
718
719 if (goInfo->trap.charges == 1)
720 SetLootState(GO_JUST_DEACTIVATED);
721 else if (!goInfo->trap.charges)
722 SetLootState(GO_READY);
723
724 // Battleground gameobjects have data2 == 0 && data5 == 3
725 if (!goInfo->trap.radius && goInfo->trap.cooldown == 3)
726 if (Player* player = target->ToPlayer())
727 if (Battleground* bg = player->GetBattleground())
728 bg->HandleTriggerBuff(GetGUID());
729 }
730 break;
731 }
732 default:
733 break;
734 }
735 break;
736 }
737 case GO_JUST_DEACTIVATED:
738 {
739 // If nearby linked trap exists, despawn it
740 if (GameObject* linkedTrap = GetLinkedTrap())
741 linkedTrap->SetLootState(GO_JUST_DEACTIVATED);
742
743 //if Gameobject should cast spell, then this, but some GOs (type = 10) should be destroyed
744 if (GetGoType() == GAMEOBJECT_TYPE_GOOBER)
745 {
746 uint32 spellId = GetGOInfo()->goober.spell;
747
748 if (spellId)
749 {
750 for (GuidSet::const_iterator it = m_unique_users.begin(); it != m_unique_users.end(); ++it)
751 // m_unique_users can contain only player GUIDs
752 if (Player* owner = ObjectAccessor::GetPlayer(*this, *it))
753 owner->CastSpell(owner, spellId, false);
754
755 m_unique_users.clear();
756 m_usetimes = 0;
757 }
758
759 SetGoState(GO_STATE_READY);
760
761 //any return here in case battleground traps
762 if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
763 if (addon->flags & GO_FLAG_NODESPAWN)
764 return;
765 }
766
767 loot.clear();
768
769 //! If this is summoned by a spell with ie. SPELL_EFFECT_SUMMON_OBJECT_WILD, with or without owner, we check respawn criteria based on spell
770 //! The GetOwnerGUID() check is mostly for compatibility with hacky scripts - 99% of the time summoning should be done trough spells.
771 if (GetSpellId() || !GetOwnerGUID().IsEmpty())
772 {
773 SetRespawnTime(0);
774 Delete();
775 return;
776 }
777
778 SetLootState(GO_READY);
779
780 //burning flags in some battlegrounds, if you find better condition, just add it
781 if (GetGOInfo()->IsDespawnAtAction() || GetGoAnimProgress() > 0)
782 {
783 SendGameObjectDespawn();
784 //reset flags
785 if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
786 SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags);
787 }
788
789 if (!m_respawnDelayTime)
790 return;
791
792 if (!m_spawnedByDefault)
793 {
794 m_respawnTime = 0;
795 UpdateObjectVisibility();
796 return;
797 }
798
799 m_respawnTime = time(NULL) + m_respawnDelayTime;
800
801 // if option not set then object will be saved at grid unload
802 if (sWorld->getBoolConfig(CONFIG_SAVE_RESPAWN_TIME_IMMEDIATELY))
803 SaveRespawnTime();
804
805 UpdateObjectVisibility();
806
807 break;
808 }
809 }
810 sScriptMgr->OnGameObjectUpdate(this, diff);
811 }
812
813 void GameObject::Refresh()
814 {
815 // Do not refresh despawned GO from spellcast (GO's from spellcast are destroyed after despawn)
816 if (m_respawnTime > 0 && m_spawnedByDefault)
817 return;
818
819 if (isSpawned())
820 GetMap()->AddToMap(this);
821 }
822
823 void GameObject::AddUniqueUse(Player* player)
824 {
825 AddUse();
826 m_unique_users.insert(player->GetGUID());
827 }
828
829 void GameObject::Delete()
830 {
831 SetLootState(GO_NOT_READY);
832 RemoveFromOwner();
833
834 SendGameObjectDespawn();
835
836 SetGoState(GO_STATE_READY);
837
838 if (GameObjectTemplateAddon const* addon = GetTemplateAddon())
839 SetUInt32Value(GAMEOBJECT_FLAGS, addon->flags);
840
841 uint32 poolid = GetSpawnId() ? sPoolMgr->IsPartOfAPool<GameObject>(GetSpawnId()) : 0;
842 if (poolid)
843 sPoolMgr->UpdatePool<GameObject>(poolid, GetSpawnId());
844 else
845 AddObjectToRemoveList();
846 }
847
848 void GameObject::SendGameObjectDespawn()
849 {
850 WorldPackets::GameObject::GameObjectDespawn packet;
851 packet.ObjectGUID = GetGUID();
852 SendMessageToSet(packet.Write(), true);
853 }
854
855 void GameObject::getFishLoot(Loot* fishloot, Player* loot_owner)
856 {
857 fishloot->clear();
858
859 uint32 zone, subzone;
860 uint32 defaultzone = 1;
861 GetZoneAndAreaId(zone, subzone);
862
863 // if subzone loot exist use it
864 fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true);
865 if (fishloot->empty()) //use this becase if zone or subzone has set LOOT_MODE_JUNK_FISH,Even if no normal drop, fishloot->FillLoot return true. it wrong.
866 {
867 //subzone no result,use zone loot
868 fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true);
869 //use zone 1 as default, somewhere fishing got nothing,becase subzone and zone not set, like Off the coast of Storm Peaks.
870 if (fishloot->empty())
871 fishloot->FillLoot(defaultzone, LootTemplates_Fishing, loot_owner, true, true);
872 }
873 }
874
875 void GameObject::getFishLootJunk(Loot* fishloot, Player* loot_owner)
876 {
877 fishloot->clear();
878
879 uint32 zone, subzone;
880 uint32 defaultzone = 1;
881 GetZoneAndAreaId(zone, subzone);
882
883 // if subzone loot exist use it
884 fishloot->FillLoot(subzone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
885 if (fishloot->empty()) //use this becase if zone or subzone has normal mask drop, then fishloot->FillLoot return true.
886 {
887 //use zone loot
888 fishloot->FillLoot(zone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
889 if (fishloot->empty())
890 //use zone 1 as default
891 fishloot->FillLoot(defaultzone, LootTemplates_Fishing, loot_owner, true, true, LOOT_MODE_JUNK_FISH);
892 }
893 }
894
895 void GameObject::SaveToDB()
896 {
897 // this should only be used when the gameobject has already been loaded
898 // preferably after adding to map, because mapid may not be valid otherwise
899 GameObjectData const* data = sObjectMgr->GetGOData(m_spawnId);
900 if (!data)
901 {
902 TC_LOG_ERROR("misc", "GameObject::SaveToDB failed, cannot get gameobject data!");
903 return;
904 }
905
906 SaveToDB(GetMapId(), data->spawnMask);
907 }
908
909 void GameObject::SaveToDB(uint32 mapid, uint64 spawnMask)
910 {
911 const GameObjectTemplate* goI = GetGOInfo();
912
913 if (!goI)
914 return;
915
916 if (!m_spawnId)
917 m_spawnId = sObjectMgr->GenerateGameObjectSpawnId();
918
919 // update in loaded data (changing data only in this place)
920 GameObjectData& data = sObjectMgr->NewGOData(m_spawnId);
921
922 // data->guid = guid must not be updated at save
923 data.id = GetEntry();
924 data.mapid = mapid;
925 data.posX = GetPositionX();
926 data.posY = GetPositionY();
927 data.posZ = GetPositionZ();
928 data.orientation = GetOrientation();
929 data.rotation = m_worldRotation;
930 data.spawntimesecs = m_spawnedByDefault ? m_respawnDelayTime : -(int32)m_respawnDelayTime;
931 data.animprogress = GetGoAnimProgress();
932 data.go_state = GetGoState();
933 data.spawnMask = spawnMask;
934 data.artKit = GetGoArtKit();
935
936 data.phaseId = GetDBPhase() > 0 ? GetDBPhase() : data.phaseId;
937 data.phaseGroup = GetDBPhase() < 0 ? -GetDBPhase() : data.phaseGroup;
938
939 // Update in DB
940 SQLTransaction trans = WorldDatabase.BeginTransaction();
941
942 uint8 index = 0;
943
944 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT);
945 stmt->setUInt64(0, m_spawnId);
946 trans->Append(stmt);
947
948 stmt = WorldDatabase.GetPreparedStatement(WORLD_INS_GAMEOBJECT);
949 stmt->setUInt64(index++, m_spawnId);
950 stmt->setUInt32(index++, GetEntry());
951 stmt->setUInt16(index++, uint16(mapid));
952 stmt->setUInt64(index++, spawnMask);
953 stmt->setUInt32(index++, data.phaseId);
954 stmt->setUInt32(index++, data.phaseGroup);
955 stmt->setFloat(index++, GetPositionX());
956 stmt->setFloat(index++, GetPositionY());
957 stmt->setFloat(index++, GetPositionZ());
958 stmt->setFloat(index++, GetOrientation());
959 stmt->setFloat(index++, m_worldRotation.x);
960 stmt->setFloat(index++, m_worldRotation.y);
961 stmt->setFloat(index++, m_worldRotation.z);
962 stmt->setFloat(index++, m_worldRotation.w);
963 stmt->setInt32(index++, int32(m_respawnDelayTime));
964 stmt->setUInt8(index++, GetGoAnimProgress());
965 stmt->setUInt8(index++, uint8(GetGoState()));
966 trans->Append(stmt);
967
968 WorldDatabase.CommitTransaction(trans);
969 }
970
971 bool GameObject::LoadGameObjectFromDB(ObjectGuid::LowType spawnId, Map* map, bool addToMap)
972 {
973 GameObjectData const* data = sObjectMgr->GetGOData(spawnId);
974 if (!data)
975 {
976 TC_LOG_ERROR("sql.sql", "Gameobject (GUID: " UI64FMTD ") not found in table `gameobject`, can't load. ", spawnId);
977 return false;
978 }
979
980 uint32 entry = data->id;
981 //uint32 map_id = data->mapid; // already used before call
982 Position pos(data->posX, data->posY, data->posZ, data->orientation);
983
984 uint32 animprogress = data->animprogress;
985 GOState go_state = data->go_state;
986 uint32 artKit = data->artKit;
987
988 m_spawnId = spawnId;
989 if (!Create(entry, map, pos, data->rotation, animprogress, go_state, artKit))
990 return false;
991
992 PhasingHandler::InitDbPhaseShift(GetPhaseShift(), data->phaseUseFlags, data->phaseId, data->phaseGroup);
993 PhasingHandler::InitDbVisibleMapId(GetPhaseShift(), data->terrainSwapMap);
994
995 if (data->spawntimesecs >= 0)
996 {
997 m_spawnedByDefault = true;
998
999 if (!GetGOInfo()->GetDespawnPossibility() && !GetGOInfo()->IsDespawnAtAction())
1000 {
1001 SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_NODESPAWN);
1002 m_respawnDelayTime = 0;
1003 m_respawnTime = 0;
1004 }
1005 else
1006 {
1007 m_respawnDelayTime = data->spawntimesecs;
1008 m_respawnTime = GetMap()->GetGORespawnTime(m_spawnId);
1009
1010 // ready to respawn
1011 if (m_respawnTime && m_respawnTime <= time(nullptr))
1012 {
1013 m_respawnTime = 0;
1014 GetMap()->RemoveGORespawnTime(m_spawnId);
1015 }
1016 }
1017 }
1018 else
1019 {
1020 m_spawnedByDefault = false;
1021 m_respawnDelayTime = -data->spawntimesecs;
1022 m_respawnTime = 0;
1023 }
1024
1025 m_goData = data;
1026
1027 if (addToMap && !GetMap()->AddToMap(this))
1028 return false;
1029
1030 return true;
1031 }
1032
1033 void GameObject::DeleteFromDB()
1034 {
1035 GetMap()->RemoveGORespawnTime(m_spawnId);
1036 sObjectMgr->DeleteGOData(m_spawnId);
1037
1038 PreparedStatement* stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_GAMEOBJECT);
1039
1040 stmt->setUInt64(0, m_spawnId);
1041
1042 WorldDatabase.Execute(stmt);
1043
1044 stmt = WorldDatabase.GetPreparedStatement(WORLD_DEL_EVENT_GAMEOBJECT);
1045
1046 stmt->setUInt64(0, m_spawnId);
1047
1048 WorldDatabase.Execute(stmt);
1049 }
1050
1051 /*********************************************************/
1052 /*** QUEST SYSTEM ***/
1053 /*********************************************************/
1054 bool GameObject::hasQuest(uint32 quest_id) const
1055 {
1056 QuestRelationBounds qr = sObjectMgr->GetGOQuestRelationBounds(GetEntry());
1057 for (QuestRelations::const_iterator itr = qr.first; itr != qr.second; ++itr)
1058 {
1059 if (itr->second == quest_id)
1060 return true;
1061 }
1062 return false;
1063 }
1064
1065 bool GameObject::hasInvolvedQuest(uint32 quest_id) const
1066 {
1067 QuestRelationBounds qir = sObjectMgr->GetGOQuestInvolvedRelationBounds(GetEntry());
1068 for (QuestRelations::const_iterator itr = qir.first; itr != qir.second; ++itr)
1069 {
1070 if (itr->second == quest_id)
1071 return true;
1072 }
1073 return false;
1074 }
1075
1076 bool GameObject::IsTransport() const
1077 {
1078 // If something is marked as a transport, don't transmit an out of range packet for it.
1079 GameObjectTemplate const* gInfo = GetGOInfo();
1080 if (!gInfo)
1081 return false;
1082
1083 return gInfo->type == GAMEOBJECT_TYPE_TRANSPORT || gInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT;
1084 }
1085
1086 // is Dynamic transport = non-stop Transport
1087 bool GameObject::IsDynTransport() const
1088 {
1089 // If something is marked as a transport, don't transmit an out of range packet for it.
1090 GameObjectTemplate const* gInfo = GetGOInfo();
1091 if (!gInfo)
1092 return false;
1093
1094 return gInfo->type == GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT || (gInfo->type == GAMEOBJECT_TYPE_TRANSPORT && m_goValue.Transport.StopFrames->empty());
1095 }
1096
1097 bool GameObject::IsDestructibleBuilding() const
1098 {
1099 GameObjectTemplate const* gInfo = GetGOInfo();
1100 if (!gInfo)
1101 return false;
1102
1103 return gInfo->type == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING;
1104 }
1105
1106 Unit* GameObject::GetOwner() const
1107 {
1108 return ObjectAccessor::GetUnit(*this, GetOwnerGUID());
1109 }
1110
1111 void GameObject::SaveRespawnTime()
1112 {
1113 if (m_goData && m_goData->dbData && m_respawnTime > time(NULL) && m_spawnedByDefault)
1114 GetMap()->SaveGORespawnTime(m_spawnId, m_respawnTime);
1115 }
1116
1117 bool GameObject::IsNeverVisibleFor(WorldObject const* seer) const
1118 {
1119 if (WorldObject::IsNeverVisibleFor(seer))
1120 return true;
1121
1122 if (GetGoType() == GAMEOBJECT_TYPE_SPELL_FOCUS && GetGOInfo()->spellFocus.serverOnly == 1)
1123 return true;
1124
1125 if (!GetUInt32Value(GAMEOBJECT_DISPLAYID))
1126 return true;
1127
1128 return false;
1129 }
1130
1131 bool GameObject::IsAlwaysVisibleFor(WorldObject const* seer) const
1132 {
1133 if (WorldObject::IsAlwaysVisibleFor(seer))
1134 return true;
1135
1136 if (IsTransport() || IsDestructibleBuilding())
1137 return true;
1138
1139 if (!seer)
1140 return false;
1141
1142 // Always seen by owner and friendly units
1143 if (!GetOwnerGUID().IsEmpty())
1144 {
1145 if (seer->GetGUID() == GetOwnerGUID())
1146 return true;
1147
1148 Unit* owner = GetOwner();
1149 if (Unit const* unitSeer = seer->ToUnit())
1150 if (owner && owner->IsFriendlyTo(unitSeer))
1151 return true;
1152 }
1153
1154 return false;
1155 }
1156
1157 bool GameObject::IsInvisibleDueToDespawn() const
1158 {
1159 if (WorldObject::IsInvisibleDueToDespawn())
1160 return true;
1161
1162 // Despawned
1163 if (!isSpawned())
1164 return true;
1165
1166 return false;
1167 }
1168
1169 uint8 GameObject::GetLevelForTarget(WorldObject const* target) const
1170 {
1171 if (Unit* owner = GetOwner())
1172 return owner->GetLevelForTarget(target);
1173
1174 return 1;
1175 }
1176
1177 void GameObject::Respawn()
1178 {
1179 if (m_spawnedByDefault && m_respawnTime > 0)
1180 {
1181 m_respawnTime = time(NULL);
1182 GetMap()->RemoveGORespawnTime(m_spawnId);
1183 }
1184 }
1185
1186 bool GameObject::ActivateToQuest(Player* target) const
1187 {
1188 if (target->HasQuestForGO(GetEntry()))
1189 return true;
1190
1191 if (!sObjectMgr->IsGameObjectForQuests(GetEntry()))
1192 return false;
1193
1194 switch (GetGoType())
1195 {
1196 case GAMEOBJECT_TYPE_QUESTGIVER:
1197 {
1198 GameObject* go = const_cast<GameObject*>(this);
1199 QuestGiverStatus questStatus = target->GetQuestDialogStatus(go);
1200 if (questStatus > DIALOG_STATUS_UNAVAILABLE)
1201 return true;
1202 break;
1203 }
1204 case GAMEOBJECT_TYPE_CHEST:
1205 {
1206 // scan GO chest with loot including quest items
1207 if (LootTemplates_Gameobject.HaveQuestLootForPlayer(GetGOInfo()->GetLootId(), target))
1208 {
1209 if (Battleground const* bg = target->GetBattleground())
1210 return bg->CanActivateGO(GetEntry(), target->GetTeam());
1211 return true;
1212 }
1213 break;
1214 }
1215 case GAMEOBJECT_TYPE_GENERIC:
1216 {
1217 if (target->GetQuestStatus(GetGOInfo()->generic.questID) == QUEST_STATUS_INCOMPLETE)
1218 return true;
1219 break;
1220 }
1221 case GAMEOBJECT_TYPE_GOOBER:
1222 {
1223 if (target->GetQuestStatus(GetGOInfo()->goober.questID) == QUEST_STATUS_INCOMPLETE)
1224 return true;
1225 break;
1226 }
1227 default:
1228 break;
1229 }
1230
1231 return false;
1232 }
1233
1234 void GameObject::TriggeringLinkedGameObject(uint32 trapEntry, Unit* target)
1235 {
1236 GameObjectTemplate const* trapInfo = sObjectMgr->GetGameObjectTemplate(trapEntry);
1237 if (!trapInfo || trapInfo->type != GAMEOBJECT_TYPE_TRAP)
1238 return;
1239
1240 SpellInfo const* trapSpell = sSpellMgr->GetSpellInfo(trapInfo->trap.spell);
1241 if (!trapSpell) // checked at load already
1242 return;
1243
1244 if (GameObject* trapGO = GetLinkedTrap())
1245 trapGO->CastSpell(target, trapSpell->Id);
1246 }
1247
1248 GameObject* GameObject::LookupFishingHoleAround(float range)
1249 {
1250 GameObject* ok = nullptr;
1251 Trinity::NearestGameObjectFishingHole u_check(*this, range);
1252 Trinity::GameObjectSearcher<Trinity::NearestGameObjectFishingHole> checker(this, ok, u_check);
1253 Cell::VisitGridObjects(this, checker, range);
1254 return ok;
1255 }
1256
1257 void GameObject::ResetDoorOrButton()
1258 {
1259 if (m_lootState == GO_READY || m_lootState == GO_JUST_DEACTIVATED)
1260 return;
1261
1262 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
1263 SetGoState(m_prevGoState);
1264
1265 SetLootState(GO_JUST_DEACTIVATED);
1266 m_cooldownTime = 0;
1267 }
1268
1269 void GameObject::UseDoorOrButton(uint32 time_to_restore, bool alternative /* = false */, Unit* user /*=nullptr*/)
1270 {
1271 if (m_lootState != GO_READY)
1272 return;
1273
1274 if (!time_to_restore)
1275 time_to_restore = GetGOInfo()->GetAutoCloseTime();
1276
1277 SwitchDoorOrButton(true, alternative);
1278 SetLootState(GO_ACTIVATED, user);
1279
1280 m_cooldownTime = time_to_restore ? (time(NULL) + time_to_restore) : 0;
1281 }
1282
1283 void GameObject::SetGoArtKit(uint8 kit)
1284 {
1285 SetByteValue(GAMEOBJECT_BYTES_1, 2, kit);
1286 GameObjectData* data = const_cast<GameObjectData*>(sObjectMgr->GetGOData(m_spawnId));
1287 if (data)
1288 data->artKit = kit;
1289 }
1290
1291 void GameObject::SetGoArtKit(uint8 artkit, GameObject* go, ObjectGuid::LowType lowguid)
1292 {
1293 const GameObjectData* data = nullptr;
1294 if (go)
1295 {
1296 go->SetGoArtKit(artkit);
1297 data = go->GetGOData();
1298 }
1299 else if (lowguid)
1300 data = sObjectMgr->GetGOData(lowguid);
1301
1302 if (data)
1303 const_cast<GameObjectData*>(data)->artKit = artkit;
1304 }
1305
1306 void GameObject::SwitchDoorOrButton(bool activate, bool alternative /* = false */)
1307 {
1308 if (activate)
1309 SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
1310 else
1311 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
1312
1313 if (GetGoState() == GO_STATE_READY) //if closed -> open
1314 SetGoState(alternative ? GO_STATE_ACTIVE_ALTERNATIVE : GO_STATE_ACTIVE);
1315 else //if open -> close
1316 SetGoState(GO_STATE_READY);
1317 }
1318
1319 void GameObject::Use(Unit* user)
1320 {
1321 // by default spell caster is user
1322 Unit* spellCaster = user;
1323 uint32 spellId = 0;
1324 bool triggered = false;
1325
1326 if (Player* playerUser = user->ToPlayer())
1327 {
1328 if (sScriptMgr->OnGossipHello(playerUser, this))
1329 return;
1330
1331 if (AI()->GossipHello(playerUser, true))
1332 return;
1333 }
1334
1335 // If cooldown data present in template
1336 if (uint32 cooldown = GetGOInfo()->GetCooldown())
1337 {
1338 if (m_cooldownTime > sWorld->GetGameTime())
1339 return;
1340
1341 m_cooldownTime = sWorld->GetGameTime() + cooldown;
1342 }
1343
1344 switch (GetGoType())
1345 {
1346 case GAMEOBJECT_TYPE_DOOR: //0
1347 case GAMEOBJECT_TYPE_BUTTON: //1
1348 //doors/buttons never really despawn, only reset to default state/flags
1349 UseDoorOrButton(0, false, user);
1350 return;
1351 case GAMEOBJECT_TYPE_QUESTGIVER: //2
1352 {
1353 if (user->GetTypeId() != TYPEID_PLAYER)
1354 return;
1355
1356 Player* player = user->ToPlayer();
1357
1358 player->PrepareGossipMenu(this, GetGOInfo()->questgiver.gossipID, true);
1359 player->SendPreparedGossip(this);
1360 return;
1361 }
1362 case GAMEOBJECT_TYPE_TRAP: //6
1363 {
1364 GameObjectTemplate const* goInfo = GetGOInfo();
1365 if (goInfo->trap.spell)
1366 CastSpell(user, goInfo->trap.spell);
1367
1368 m_cooldownTime = time(NULL) + (goInfo->trap.cooldown ? goInfo->trap.cooldown : uint32(4)); // template or 4 seconds
1369
1370 if (goInfo->trap.charges == 1) // Deactivate after trigger
1371 SetLootState(GO_JUST_DEACTIVATED);
1372
1373 return;
1374 }
1375 //Sitting: Wooden bench, chairs enzz
1376 case GAMEOBJECT_TYPE_CHAIR: //7
1377 {
1378 GameObjectTemplate const* info = GetGOInfo();
1379 if (!info)
1380 return;
1381
1382 if (user->GetTypeId() != TYPEID_PLAYER)
1383 return;
1384
1385 if (ChairListSlots.empty()) // this is called once at first chair use to make list of available slots
1386 {
1387 if (info->chair.chairslots > 0) // sometimes chairs in DB have error in fields and we dont know number of slots
1388 for (uint32 i = 0; i < info->chair.chairslots; ++i)
1389 ChairListSlots[i].Clear(); // Last user of current slot set to 0 (none sit here yet)
1390 else
1391 ChairListSlots[0].Clear(); // error in DB, make one default slot
1392 }
1393
1394 Player* player = user->ToPlayer();
1395
1396 // a chair may have n slots. we have to calculate their positions and teleport the player to the nearest one
1397
1398 float lowestDist = DEFAULT_VISIBILITY_DISTANCE;
1399
1400 uint32 nearest_slot = 0;
1401 float x_lowest = GetPositionX();
1402 float y_lowest = GetPositionY();
1403
1404 // the object orientation + 1/2 pi
1405 // every slot will be on that straight line
1406 float orthogonalOrientation = GetOrientation() + float(M_PI) * 0.5f;
1407 // find nearest slot
1408 bool found_free_slot = false;
1409 for (ChairSlotAndUser::iterator itr = ChairListSlots.begin(); itr != ChairListSlots.end(); ++itr)
1410 {
1411 // the distance between this slot and the center of the go - imagine a 1D space
1412 float relativeDistance = (info->size*itr->first) - (info->size*(info->chair.chairslots - 1) / 2.0f);
1413
1414 float x_i = GetPositionX() + relativeDistance * std::cos(orthogonalOrientation);
1415 float y_i = GetPositionY() + relativeDistance * std::sin(orthogonalOrientation);
1416
1417 if (!itr->second.IsEmpty())
1418 {
1419 if (Player* ChairUser = ObjectAccessor::FindPlayer(itr->second))
1420 {
1421 if (ChairUser->IsSitState() && ChairUser->GetStandState() != UNIT_STAND_STATE_SIT && ChairUser->GetExactDist2d(x_i, y_i) < 0.1f)
1422 continue; // This seat is already occupied by ChairUser. NOTE: Not sure if the ChairUser->GetStandState() != UNIT_STAND_STATE_SIT check is required.
1423 else
1424 itr->second.Clear(); // This seat is unoccupied.
1425 }
1426 else
1427 itr->second.Clear(); // The seat may of had an occupant, but they're offline.
1428 }
1429
1430 found_free_slot = true;
1431
1432 // calculate the distance between the player and this slot
1433 float thisDistance = player->GetDistance2d(x_i, y_i);
1434
1435 if (thisDistance <= lowestDist)
1436 {
1437 nearest_slot = itr->first;
1438 lowestDist = thisDistance;
1439 x_lowest = x_i;
1440 y_lowest = y_i;
1441 }
1442 }
1443
1444 if (found_free_slot)
1445 {
1446 ChairSlotAndUser::iterator itr = ChairListSlots.find(nearest_slot);
1447 if (itr != ChairListSlots.end())
1448 {
1449 itr->second = player->GetGUID(); //this slot in now used by player
1450 player->TeleportTo(GetMapId(), x_lowest, y_lowest, GetPositionZ(), GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET);
1451 player->SetStandState(UnitStandStateType(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->chair.chairheight));
1452 return;
1453 }
1454 }
1455
1456 return;
1457 }
1458 //big gun, its a spell/aura
1459 case GAMEOBJECT_TYPE_GOOBER: //10
1460 {
1461 GameObjectTemplate const* info = GetGOInfo();
1462
1463 if (Player* player = user->ToPlayer())
1464 {
1465 if (info->goober.pageID) // show page...
1466 {
1467 WorldPackets::GameObject::PageText data;
1468 data.GameObjectGUID = GetGUID();
1469 player->SendDirectMessage(data.Write());
1470 }
1471 else if (info->goober.gossipID)
1472 {
1473 player->PrepareGossipMenu(this, info->goober.gossipID);
1474 player->SendPreparedGossip(this);
1475 }
1476
1477 if (info->goober.eventID)
1478 {
1479 TC_LOG_DEBUG("maps.script", "Goober ScriptStart id %u for GO entry %u (GUID " UI64FMTD ").", info->goober.eventID, GetEntry(), GetSpawnId());
1480 GetMap()->ScriptsStart(sEventScripts, info->goober.eventID, player, this);
1481 EventInform(info->goober.eventID, user);
1482 }
1483
1484 // possible quest objective for active quests
1485 if (info->goober.questID && sObjectMgr->GetQuestTemplate(info->goober.questID))
1486 {
1487 //Quest require to be active for GO using
1488 if (player->GetQuestStatus(info->goober.questID) != QUEST_STATUS_INCOMPLETE)
1489 break;
1490 }
1491
1492 if (Group* group = player->GetGroup())
1493 {
1494 for (GroupReference const* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
1495 if (Player* member = itr->GetSource())
1496 if (member->IsAtGroupRewardDistance(this))
1497 member->KillCreditGO(info->entry, GetGUID());
1498 }
1499 else
1500 player->KillCreditGO(info->entry, GetGUID());
1501 }
1502
1503 if (uint32 trapEntry = info->goober.linkedTrap)
1504 TriggeringLinkedGameObject(trapEntry, user);
1505
1506 SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_IN_USE);
1507 SetLootState(GO_ACTIVATED, user);
1508
1509 // this appear to be ok, however others exist in addition to this that should have custom (ex: 190510, 188692, 187389)
1510 if (info->goober.customAnim)
1511 SendCustomAnim(GetGoAnimProgress());
1512 else
1513 SetGoState(GO_STATE_ACTIVE);
1514
1515 m_cooldownTime = time(NULL) + info->GetAutoCloseTime();
1516
1517 // cast this spell later if provided
1518 spellId = info->goober.spell;
1519 spellCaster = nullptr;
1520
1521 break;
1522 }
1523 case GAMEOBJECT_TYPE_CAMERA: //13
1524 {
1525 GameObjectTemplate const* info = GetGOInfo();
1526 if (!info)
1527 return;
1528
1529 if (user->GetTypeId() != TYPEID_PLAYER)
1530 return;
1531
1532 Player* player = user->ToPlayer();
1533
1534 if (info->camera.camera)
1535 player->SendCinematicStart(info->camera.camera);
1536
1537 if (info->camera.eventID)
1538 {
1539 GetMap()->ScriptsStart(sEventScripts, info->camera.eventID, player, this);
1540 EventInform(info->camera.eventID, user);
1541 }
1542
1543 return;
1544 }
1545 //fishing bobber
1546 case GAMEOBJECT_TYPE_FISHINGNODE: //17
1547 {
1548 Player* player = user->ToPlayer();
1549 if (!player)
1550 return;
1551
1552 if (player->GetGUID() != GetOwnerGUID())
1553 return;
1554
1555 switch (getLootState())
1556 {
1557 case GO_READY: // ready for loot
1558 {
1559 uint32 zone, subzone;
1560 GetZoneAndAreaId(zone, subzone);
1561
1562 int32 zone_skill = sObjectMgr->GetFishingBaseSkillLevel(subzone);
1563 if (!zone_skill)
1564 zone_skill = sObjectMgr->GetFishingBaseSkillLevel(zone);
1565
1566 //provide error, no fishable zone or area should be 0
1567 if (!zone_skill)
1568 TC_LOG_ERROR("sql.sql", "Fishable areaId %u are not properly defined in `skill_fishing_base_level`.", subzone);
1569
1570 int32 skill = player->GetSkillValue(SKILL_FISHING);
1571
1572 int32 chance;
1573 if (skill < zone_skill)
1574 {
1575 chance = int32(pow((double)skill/zone_skill, 2) * 100);
1576 if (chance < 1)
1577 chance = 1;
1578 }
1579 else
1580 chance = 100;
1581
1582 int32 roll = irand(1, 100);
1583
1584 TC_LOG_DEBUG("misc", "Fishing check (skill: %i zone min skill: %i chance %i roll: %i", skill, zone_skill, chance, roll);
1585
1586 player->UpdateFishingSkill();
1587
1588 /// @todo find reasonable value for fishing hole search
1589 GameObject* fishingPool = LookupFishingHoleAround(20.0f + CONTACT_DISTANCE);
1590
1591 // If fishing skill is high enough, or if fishing on a pool, send correct loot.
1592 // Fishing pools have no skill requirement as of patch 3.3.0 (undocumented change).
1593 if (chance >= roll || fishingPool)
1594 {
1595 /// @todo I do not understand this hack. Need some explanation.
1596 // prevent removing GO at spell cancel
1597 RemoveFromOwner();
1598 SetOwnerGUID(player->GetGUID());
1599 SetSpellId(0); // prevent removing unintended auras at Unit::RemoveGameObject
1600
1601 if (fishingPool)
1602 {
1603 fishingPool->Use(player);
1604 SetLootState(GO_JUST_DEACTIVATED);
1605 }
1606 else
1607 player->SendLoot(GetGUID(), LOOT_FISHING);
1608 }
1609 else // If fishing skill is too low, send junk loot.
1610 player->SendLoot(GetGUID(), LOOT_FISHING_JUNK);
1611 break;
1612 }
1613 case GO_JUST_DEACTIVATED: // nothing to do, will be deleted at next update
1614 break;
1615 default:
1616 {
1617 SetLootState(GO_JUST_DEACTIVATED);
1618 player->SendDirectMessage(WorldPackets::GameObject::FishNotHooked().Write());
1619 break;
1620 }
1621 }
1622
1623 player->FinishSpell(CURRENT_CHANNELED_SPELL);
1624 return;
1625 }
1626
1627 case GAMEOBJECT_TYPE_RITUAL: //18
1628 {
1629 if (user->GetTypeId() != TYPEID_PLAYER)
1630 return;
1631
1632 Player* player = user->ToPlayer();
1633
1634 Unit* owner = GetOwner();
1635
1636 GameObjectTemplate const* info = GetGOInfo();
1637
1638 Player* m_ritualOwner = nullptr;
1639 if (!m_ritualOwnerGUID.IsEmpty())
1640 m_ritualOwner = ObjectAccessor::FindPlayer(m_ritualOwnerGUID);
1641
1642 // ritual owner is set for GO's without owner (not summoned)
1643 if (!m_ritualOwner && !owner)
1644 {
1645 m_ritualOwnerGUID = player->GetGUID();
1646 m_ritualOwner = player;
1647 }
1648
1649 if (owner)
1650 {
1651 if (owner->GetTypeId() != TYPEID_PLAYER)
1652 return;
1653
1654 // accept only use by player from same group as owner, excluding owner itself (unique use already added in spell effect)
1655 if (player == owner->ToPlayer() || (info->ritual.castersGrouped && !player->IsInSameRaidWith(owner->ToPlayer())))
1656 return;
1657
1658 // expect owner to already be channeling, so if not...
1659 if (!owner->GetCurrentSpell(CURRENT_CHANNELED_SPELL))
1660 return;
1661
1662 // in case summoning ritual caster is GO creator
1663 spellCaster = owner;
1664 }
1665 else
1666 {
1667 if (player != m_ritualOwner && (info->ritual.castersGrouped && !player->IsInSameRaidWith(m_ritualOwner)))
1668 return;
1669
1670 spellCaster = player;
1671 }
1672
1673 AddUniqueUse(player);
1674
1675 if (info->ritual.animSpell)
1676 {
1677 player->CastSpell(player, info->ritual.animSpell, true);
1678
1679 // for this case, summoningRitual.spellId is always triggered
1680 triggered = true;
1681 }
1682
1683 // full amount unique participants including original summoner
1684 if (GetUniqueUseCount() == info->ritual.casters)
1685 {
1686 if (m_ritualOwner)
1687 spellCaster = m_ritualOwner;
1688
1689 spellId = info->ritual.spell;
1690
1691 if (spellId == 62330) // GO store nonexistent spell, replace by expected
1692 {
1693 // spell have reagent and mana cost but it not expected use its
1694 // it triggered spell in fact cast at currently channeled GO
1695 spellId = 61993;
1696 triggered = true;
1697 }
1698
1699 // Cast casterTargetSpell at a random GO user
1700 // on the current DB there is only one gameobject that uses this (Ritual of Doom)
1701 // and its required target number is 1 (outter for loop will run once)
1702 if (info->ritual.casterTargetSpell && info->ritual.casterTargetSpell != 1) // No idea why this field is a bool in some cases
1703 for (uint32 i = 0; i < info->ritual.casterTargetSpellTargets; i++)
1704 // m_unique_users can contain only player GUIDs
1705 if (Player* target = ObjectAccessor::GetPlayer(*this, Trinity::Containers::SelectRandomContainerElement(m_unique_users)))
1706 spellCaster->CastSpell(target, info->ritual.casterTargetSpell, true);
1707
1708 // finish owners spell
1709 if (owner)
1710 owner->FinishSpell(CURRENT_CHANNELED_SPELL);
1711
1712 // can be deleted now, if
1713 if (!info->ritual.ritualPersistent)
1714 SetLootState(GO_JUST_DEACTIVATED);
1715 else
1716 {
1717 // reset ritual for this GO
1718 m_ritualOwnerGUID.Clear();
1719 m_unique_users.clear();
1720 m_usetimes = 0;
1721 }
1722 }
1723 else
1724 return;
1725
1726 // go to end function to spell casting
1727 break;
1728 }
1729 case GAMEOBJECT_TYPE_SPELLCASTER: //22
1730 {
1731 GameObjectTemplate const* info = GetGOInfo();
1732 if (!info)
1733 return;
1734
1735 if (info->spellCaster.partyOnly)
1736 {
1737 Unit* caster = GetOwner();
1738 if (!caster || caster->GetTypeId() != TYPEID_PLAYER)
1739 return;
1740
1741 if (user->GetTypeId() != TYPEID_PLAYER || !user->ToPlayer()->IsInSameRaidWith(caster->ToPlayer()))
1742 return;
1743 }
1744
1745 user->RemoveAurasByType(SPELL_AURA_MOUNTED);
1746 spellId = info->spellCaster.spell;
1747
1748 AddUse();
1749 break;
1750 }
1751 case GAMEOBJECT_TYPE_MEETINGSTONE: //23
1752 {
1753 GameObjectTemplate const* info = GetGOInfo();
1754
1755 if (user->GetTypeId() != TYPEID_PLAYER)
1756 return;
1757
1758 Player* player = user->ToPlayer();
1759
1760 Player* targetPlayer = ObjectAccessor::FindPlayer(player->GetTarget());
1761
1762 // accept only use by player from same raid as caster, except caster itself
1763 if (!targetPlayer || targetPlayer == player || !targetPlayer->IsInSameRaidWith(player))
1764 return;
1765
1766 //required lvl checks!
1767 uint8 level = player->getLevel();
1768 if (level < info->meetingStone.minLevel)
1769 return;
1770 level = targetPlayer->getLevel();
1771 if (level < info->meetingStone.minLevel)
1772 return;
1773
1774 if (info->entry == 194097)
1775 spellId = 61994; // Ritual of Summoning
1776 else
1777 spellId = 59782; // Summoning Stone Effect
1778
1779 break;
1780 }
1781
1782 case GAMEOBJECT_TYPE_FLAGSTAND: // 24
1783 {
1784 if (user->GetTypeId() != TYPEID_PLAYER)
1785 return;
1786
1787 Player* player = user->ToPlayer();
1788
1789 if (player->CanUseBattlegroundObject(this))
1790 {
1791 // in battleground check
1792 Battleground* bg = player->GetBattleground();
1793 if (!bg)
1794 return;
1795
1796 if (player->GetVehicle())
1797 return;
1798
1799 player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
1800 player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY);
1801 // BG flag click
1802 // AB:
1803 // 15001
1804 // 15002
1805 // 15003
1806 // 15004
1807 // 15005
1808 bg->EventPlayerClickedOnFlag(player, this);
1809 return; //we don;t need to delete flag ... it is despawned!
1810 }
1811 break;
1812 }
1813
1814 case GAMEOBJECT_TYPE_FISHINGHOLE: // 25
1815 {
1816 if (user->GetTypeId() != TYPEID_PLAYER)
1817 return;
1818
1819 Player* player = user->ToPlayer();
1820
1821 player->SendLoot(GetGUID(), LOOT_FISHINGHOLE);
1822 player->UpdateCriteria(CRITERIA_TYPE_FISH_IN_GAMEOBJECT, GetGOInfo()->entry);
1823 return;
1824 }
1825
1826 case GAMEOBJECT_TYPE_FLAGDROP: // 26
1827 {
1828 if (user->GetTypeId() != TYPEID_PLAYER)
1829 return;
1830
1831 Player* player = user->ToPlayer();
1832
1833 if (player->CanUseBattlegroundObject(this))
1834 {
1835 // in battleground check
1836 Battleground* bg = player->GetBattleground();
1837 if (!bg)
1838 return;
1839
1840 if (player->GetVehicle())
1841 return;
1842
1843 player->RemoveAurasByType(SPELL_AURA_MOD_STEALTH);
1844 player->RemoveAurasByType(SPELL_AURA_MOD_INVISIBILITY);
1845 // BG flag dropped
1846 // WS:
1847 // 179785 - Silverwing Flag
1848 // 179786 - Warsong Flag
1849 // EotS:
1850 // 184142 - Netherstorm Flag
1851 GameObjectTemplate const* info = GetGOInfo();
1852 if (info)
1853 {
1854 switch (info->entry)
1855 {
1856 case 179785: // Silverwing Flag
1857 case 179786: // Warsong Flag
1858 if (bg->GetTypeID(true) == BATTLEGROUND_WS)
1859 bg->EventPlayerClickedOnFlag(player, this);
1860 break;
1861 case 184142: // Netherstorm Flag
1862 if (bg->GetTypeID(true) == BATTLEGROUND_EY)
1863 bg->EventPlayerClickedOnFlag(player, this);
1864 break;
1865 }
1866 }
1867 //this cause to call return, all flags must be deleted here!!
1868 spellId = 0;
1869 Delete();
1870 }
1871 break;
1872 }
1873 case GAMEOBJECT_TYPE_BARBER_CHAIR: //32
1874 {
1875 GameObjectTemplate const* info = GetGOInfo();
1876 if (!info)
1877 return;
1878
1879 if (user->GetTypeId() != TYPEID_PLAYER)
1880 return;
1881
1882 Player* player = user->ToPlayer();
1883
1884 WorldPackets::Misc::EnableBarberShop packet;
1885 player->SendDirectMessage(packet.Write());
1886
1887 // fallback, will always work
1888 player->TeleportTo(GetMapId(), GetPositionX(), GetPositionY(), GetPositionZ(), GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT | TELE_TO_NOT_LEAVE_COMBAT | TELE_TO_NOT_UNSUMMON_PET);
1889
1890 player->SetStandState(UnitStandStateType(UNIT_STAND_STATE_SIT_LOW_CHAIR + info->barberChair.chairheight), info->barberChair.SitAnimKit);
1891 return;
1892 }
1893 case GAMEOBJECT_TYPE_ARTIFACT_FORGE:
1894 {
1895 GameObjectTemplate const* info = GetGOInfo();
1896 if (!info)
1897 return;
1898
1899 if (user->GetTypeId() != TYPEID_PLAYER)
1900 return;
1901
1902 Player* player = user->ToPlayer();
1903 if (PlayerConditionEntry const* playerCondition = sPlayerConditionStore.LookupEntry(info->artifactForge.conditionID1))
1904 if (!sConditionMgr->IsPlayerMeetingCondition(player, playerCondition))
1905 return;
1906
1907 Aura const* artifactAura = player->GetAura(ARTIFACTS_ALL_WEAPONS_GENERAL_WEAPON_EQUIPPED_PASSIVE);
1908 Item const* item = artifactAura ? player->GetItemByGuid(artifactAura->GetCastItemGUID()) : nullptr;
1909 if (!item)
1910 {
1911 player->SendDirectMessage(WorldPackets::Misc::DisplayGameError(GameError::ERR_MUST_EQUIP_ARTIFACT).Write());
1912 return;
1913 }
1914
1915 WorldPackets::Artifact::ArtifactForgeOpened artifactForgeOpened;
1916 artifactForgeOpened.ArtifactGUID = item->GetGUID();
1917 artifactForgeOpened.ForgeGUID = GetGUID();
1918 player->SendDirectMessage(artifactForgeOpened.Write());
1919 return;
1920 }
1921 case GAMEOBJECT_TYPE_UI_LINK:
1922 {
1923 Player* player = user->ToPlayer();
1924 if (!player)
1925 return;
1926
1927 WorldPackets::GameObject::GameObjectUIAction gameObjectUIAction;
1928 gameObjectUIAction.ObjectGUID = GetGUID();
1929 gameObjectUIAction.UILink = GetGOInfo()->UILink.UILinkType;
1930 player->SendDirectMessage(gameObjectUIAction.Write());
1931 return;
1932 }
1933 default:
1934 if (GetGoType() >= MAX_GAMEOBJECT_TYPE)
1935 TC_LOG_ERROR("misc", "GameObject::Use(): unit (type: %u, %s, name: %s) tries to use object (%s, name: %s) of unknown type (%u)",
1936 user->GetTypeId(), user->GetGUID().ToString().c_str(), user->GetName().c_str(), GetGUID().ToString().c_str(), GetGOInfo()->name.c_str(), GetGoType());
1937 break;
1938 }
1939
1940 if (!spellId)
1941 return;
1942
1943 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
1944 if (!spellInfo)
1945 {
1946 if (user->GetTypeId() != TYPEID_PLAYER || !sOutdoorPvPMgr->HandleCustomSpell(user->ToPlayer(), spellId, this))
1947 TC_LOG_ERROR("misc", "WORLD: unknown spell id %u at use action for gameobject (Entry: %u GoType: %u)", spellId, GetEntry(), GetGoType());
1948 else
1949 TC_LOG_DEBUG("outdoorpvp", "WORLD: %u non-dbc spell was handled by OutdoorPvP", spellId);
1950 return;
1951 }
1952
1953 if (Player* player = user->ToPlayer())
1954 sOutdoorPvPMgr->HandleCustomSpell(player, spellId, this);
1955
1956 if (spellCaster)
1957 spellCaster->CastSpell(user, spellInfo, triggered);
1958 else
1959 CastSpell(user, spellId);
1960 }
1961
1962 void GameObject::CastSpell(Unit* target, uint32 spellId, bool triggered /* = true*/)
1963 {
1964 CastSpell(target, spellId, triggered ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
1965 }
1966
1967 void GameObject::CastSpell(Unit* target, uint32 spellId, TriggerCastFlags triggered)
1968 {
1969 SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
1970 if (!spellInfo)
1971 return;
1972
1973 bool self = false;
1974 for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(GetMap()->GetDifficultyID()))
1975 {
1976 if (effect && effect->TargetA.GetTarget() == TARGET_UNIT_CASTER)
1977 {
1978 self = true;
1979 break;
1980 }
1981 }
1982
1983 if (self)
1984 {
1985 if (target)
1986 target->CastSpell(target, spellInfo, triggered);
1987 return;
1988 }
1989
1990 //summon world trigger
1991 Creature* trigger = SummonTrigger(GetPositionX(), GetPositionY(), GetPositionZ(), 0, spellInfo->CalcCastTime() + 100);
1992 if (!trigger)
1993 return;
1994
1995 // remove immunity flags, to allow spell to target anything
1996 trigger->RemoveFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC | UNIT_FLAG_IMMUNE_TO_PC);
1997
1998 if (Unit* owner = GetOwner())
1999 {
2000 trigger->setFaction(owner->getFaction());
2001 if (owner->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE))
2002 trigger->SetFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE);
2003 // copy pvp state flags from owner
2004 trigger->SetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG, owner->GetByteValue(UNIT_FIELD_BYTES_2, UNIT_BYTES_2_OFFSET_PVP_FLAG));
2005 // needed for GO casts for proper target validation checks
2006 trigger->SetOwnerGUID(owner->GetGUID());
2007 trigger->CastSpell(target ? target : trigger, spellInfo, triggered, nullptr, nullptr, owner->GetGUID());
2008 }
2009 else
2010 {
2011 trigger->setFaction(spellInfo->IsPositive() ? 35 : 14);
2012 // Set owner guid for target if no owner available - needed by trigger auras
2013 // - trigger gets despawned and there's no caster avalible (see AuraEffect::TriggerSpell())
2014 trigger->CastSpell(target ? target : trigger, spellInfo, triggered, nullptr, nullptr, target ? target->GetGUID() : ObjectGuid::Empty);
2015 }
2016 }
2017
2018 void GameObject::SendCustomAnim(uint32 anim)
2019 {
2020 WorldPackets::GameObject::GameObjectCustomAnim customAnim;
2021 customAnim.ObjectGUID = GetGUID();
2022 customAnim.CustomAnim = anim;
2023 SendMessageToSet(customAnim.Write(), true);
2024 }
2025
2026 bool GameObject::IsInRange(float x, float y, float z, float radius) const
2027 {
2028 GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(m_goInfo->displayId);
2029 if (!info)
2030 return IsWithinDist3d(x, y, z, radius);
2031
2032 float sinA = std::sin(GetOrientation());
2033 float cosA = std::cos(GetOrientation());
2034 float dx = x - GetPositionX();
2035 float dy = y - GetPositionY();
2036 float dz = z - GetPositionZ();
2037 float dist = std::sqrt(dx*dx + dy*dy);
2038 //! Check if the distance between the 2 objects is 0, can happen if both objects are on the same position.
2039 //! The code below this check wont crash if dist is 0 because 0/0 in float operations is valid, and returns infinite
2040 if (G3D::fuzzyEq(dist, 0.0f))
2041 return true;
2042
2043 float sinB = dx / dist;
2044 float cosB = dy / dist;
2045 dx = dist * (cosA * cosB + sinA * sinB);
2046 dy = dist * (cosA * sinB - sinA * cosB);
2047 return dx < info->GeoBoxMax.X + radius && dx > info->GeoBoxMin.X - radius
2048 && dy < info->GeoBoxMax.Y + radius && dy > info->GeoBoxMin.Y - radius
2049 && dz < info->GeoBoxMax.Z + radius && dz > info->GeoBoxMin.Z - radius;
2050 }
2051
2052 void GameObject::EventInform(uint32 eventId, WorldObject* invoker /*= nullptr*/)
2053 {
2054 if (!eventId)
2055 return;
2056
2057 if (AI())
2058 AI()->EventInform(eventId);
2059
2060 if (GetZoneScript())
2061 GetZoneScript()->ProcessEvent(this, eventId);
2062
2063 if (BattlegroundMap* bgMap = GetMap()->ToBattlegroundMap())
2064 if (bgMap->GetBG())
2065 bgMap->GetBG()->ProcessEvent(this, eventId, invoker);
2066 }
2067
2068 uint32 GameObject::GetScriptId() const
2069 {
2070 if (GameObjectData const* gameObjectData = GetGOData())
2071 return gameObjectData->ScriptId;
2072
2073 return GetGOInfo()->ScriptId;
2074 }
2075
2076 // overwrite WorldObject function for proper name localization
2077 std::string const & GameObject::GetNameForLocaleIdx(LocaleConstant loc_idx) const
2078 {
2079 if (loc_idx != DEFAULT_LOCALE)
2080 {
2081 uint8 uloc_idx = uint8(loc_idx);
2082 if (GameObjectLocale const* cl = sObjectMgr->GetGameObjectLocale(GetEntry()))
2083 if (cl->Name.size() > uloc_idx && !cl->Name[uloc_idx].empty())
2084 return cl->Name[uloc_idx];
2085 }
2086
2087 return GetName();
2088 }
2089
2090 void GameObject::UpdatePackedRotation()
2091 {
2092 static const int32 PACK_YZ = 1 << 20;
2093 static const int32 PACK_X = PACK_YZ << 1;
2094
2095 static const int32 PACK_YZ_MASK = (PACK_YZ << 1) - 1;
2096 static const int32 PACK_X_MASK = (PACK_X << 1) - 1;
2097
2098 int8 w_sign = (m_worldRotation.w >= 0.f ? 1 : -1);
2099 int64 x = int32(m_worldRotation.x * PACK_X) * w_sign & PACK_X_MASK;
2100 int64 y = int32(m_worldRotation.y * PACK_YZ) * w_sign & PACK_YZ_MASK;
2101 int64 z = int32(m_worldRotation.z * PACK_YZ) * w_sign & PACK_YZ_MASK;
2102 m_packedRotation = z | (y << 21) | (x << 42);
2103 }
2104
2105 void GameObject::SetWorldRotation(float qx, float qy, float qz, float qw)
2106 {
2107 G3D::Quat rotation(qx, qy, qz, qw);
2108 rotation.unitize();
2109 m_worldRotation.x = rotation.x;
2110 m_worldRotation.y = rotation.y;
2111 m_worldRotation.z = rotation.z;
2112 m_worldRotation.w = rotation.w;
2113 UpdatePackedRotation();
2114 }
2115
2116 void GameObject::SetParentRotation(QuaternionData const& rotation)
2117 {
2118 SetFloatValue(GAMEOBJECT_PARENTROTATION + 0, rotation.x);
2119 SetFloatValue(GAMEOBJECT_PARENTROTATION + 1, rotation.y);
2120 SetFloatValue(GAMEOBJECT_PARENTROTATION + 2, rotation.z);
2121 SetFloatValue(GAMEOBJECT_PARENTROTATION + 3, rotation.w);
2122 }
2123
2124 void GameObject::SetWorldRotationAngles(float z_rot, float y_rot, float x_rot)
2125 {
2126 G3D::Quat quat(G3D::Matrix3::fromEulerAnglesZYX(z_rot, y_rot, x_rot));
2127 SetWorldRotation(quat.x, quat.y, quat.z, quat.w);
2128 }
2129
2130 void GameObject::ModifyHealth(int32 change, Unit* attackerOrHealer /*= nullptr*/, uint32 spellId /*= 0*/)
2131 {
2132 if (!m_goValue.Building.MaxHealth || !change)
2133 return;
2134
2135 // prevent double destructions of the same object
2136 if (change < 0 && !m_goValue.Building.Health)
2137 return;
2138
2139 if (int32(m_goValue.Building.Health) + change <= 0)
2140 m_goValue.Building.Health = 0;
2141 else if (int32(m_goValue.Building.Health) + change >= int32(m_goValue.Building.MaxHealth))
2142 m_goValue.Building.Health = m_goValue.Building.MaxHealth;
2143 else
2144 m_goValue.Building.Health += change;
2145
2146 // Set the health bar, value = 255 * healthPct;
2147 SetGoAnimProgress(m_goValue.Building.Health * 255 / m_goValue.Building.MaxHealth);
2148
2149 Player* player = attackerOrHealer ? attackerOrHealer->GetCharmerOrOwnerPlayerOrPlayerItself() : nullptr;
2150
2151 // dealing damage, send packet
2152 if (player)
2153 {
2154 WorldPackets::GameObject::DestructibleBuildingDamage packet;
2155 packet.Caster = attackerOrHealer->GetGUID(); // todo: this can be a GameObject
2156 packet.Target = GetGUID();
2157 packet.Damage = -change;
2158 packet.Owner = player->GetGUID();
2159 packet.SpellID = spellId;
2160 player->SendDirectMessage(packet.Write());
2161 }
2162
2163 GameObjectDestructibleState newState = GetDestructibleState();
2164
2165 if (!m_goValue.Building.Health)
2166 newState = GO_DESTRUCTIBLE_DESTROYED;
2167 else if (m_goValue.Building.Health <= 10000/*GetGOInfo()->destructibleBuilding.damagedNumHits*/) // TODO: Get health somewhere
2168 newState = GO_DESTRUCTIBLE_DAMAGED;
2169 else if (m_goValue.Building.Health == m_goValue.Building.MaxHealth)
2170 newState = GO_DESTRUCTIBLE_INTACT;
2171
2172 if (newState == GetDestructibleState())
2173 return;
2174
2175 /// @todo: pass attackerOrHealer instead of player
2176 SetDestructibleState(newState, player, false);
2177 }
2178
2179 void GameObject::SetDestructibleState(GameObjectDestructibleState state, Player* eventInvoker /*= nullptr*/, bool setHealth /*= false*/)
2180 {
2181 // the user calling this must know he is already operating on destructible gameobject
2182 ASSERT(GetGoType() == GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING);
2183
2184 switch (state)
2185 {
2186 case GO_DESTRUCTIBLE_INTACT:
2187 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
2188 SetDisplayId(m_goInfo->displayId);
2189 if (setHealth)
2190 {
2191 m_goValue.Building.Health = m_goValue.Building.MaxHealth;
2192 SetGoAnimProgress(255);
2193 }
2194 EnableCollision(true);
2195 break;
2196 case GO_DESTRUCTIBLE_DAMAGED:
2197 {
2198 EventInform(m_goInfo->destructibleBuilding.DamagedEvent, eventInvoker);
2199 sScriptMgr->OnGameObjectDamaged(this, eventInvoker);
2200
2201 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DESTROYED);
2202 SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED);
2203
2204 uint32 modelId = m_goInfo->displayId;
2205 if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec))
2206 if (modelData->State1Wmo)
2207 modelId = modelData->State1Wmo;
2208 SetDisplayId(modelId);
2209
2210 if (setHealth)
2211 {
2212 m_goValue.Building.Health = 10000/*m_goInfo->destructibleBuilding.damagedNumHits*/;
2213 uint32 maxHealth = m_goValue.Building.MaxHealth;
2214 // in this case current health is 0 anyway so just prevent crashing here
2215 if (!maxHealth)
2216 maxHealth = 1;
2217 SetGoAnimProgress(m_goValue.Building.Health * 255 / maxHealth);
2218 }
2219 break;
2220 }
2221 case GO_DESTRUCTIBLE_DESTROYED:
2222 {
2223 sScriptMgr->OnGameObjectDestroyed(this, eventInvoker);
2224 EventInform(m_goInfo->destructibleBuilding.DestroyedEvent, eventInvoker);
2225 if (eventInvoker)
2226 if (Battleground* bg = eventInvoker->GetBattleground())
2227 bg->DestroyGate(eventInvoker, this);
2228
2229 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED);
2230 SetFlag(GAMEOBJECT_FLAGS, GO_FLAG_DESTROYED);
2231
2232 uint32 modelId = m_goInfo->displayId;
2233 if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec))
2234 if (modelData->State2Wmo)
2235 modelId = modelData->State2Wmo;
2236 SetDisplayId(modelId);
2237
2238 if (setHealth)
2239 {
2240 m_goValue.Building.Health = 0;
2241 SetGoAnimProgress(0);
2242 }
2243 EnableCollision(false);
2244 break;
2245 }
2246 case GO_DESTRUCTIBLE_REBUILDING:
2247 {
2248 EventInform(m_goInfo->destructibleBuilding.RebuildingEvent, eventInvoker);
2249 RemoveFlag(GAMEOBJECT_FLAGS, GO_FLAG_DAMAGED | GO_FLAG_DESTROYED);
2250
2251 uint32 modelId = m_goInfo->displayId;
2252 if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec))
2253 if (modelData->State3Wmo)
2254 modelId = modelData->State3Wmo;
2255 SetDisplayId(modelId);
2256
2257 // restores to full health
2258 if (setHealth)
2259 {
2260 m_goValue.Building.Health = m_goValue.Building.MaxHealth;
2261 SetGoAnimProgress(255);
2262 }
2263 EnableCollision(true);
2264 break;
2265 }
2266 }
2267 }
2268
2269 void GameObject::SetLootState(LootState state, Unit* unit)
2270 {
2271 m_lootState = state;
2272 if (unit)
2273 m_lootStateUnitGUID = unit->GetGUID();
2274 else
2275 m_lootStateUnitGUID.Clear();
2276
2277 AI()->OnStateChanged(state, unit);
2278 sScriptMgr->OnGameObjectLootStateChanged(this, state, unit);
2279
2280 if (GetGoType() == GAMEOBJECT_TYPE_DOOR) // only set collision for doors on SetGoState
2281 return;
2282
2283 if (m_model)
2284 {
2285 bool collision = false;
2286 // Use the current go state
2287 if ((GetGoState() != GO_STATE_READY && (state == GO_ACTIVATED || state == GO_JUST_DEACTIVATED)) || state == GO_READY)
2288 collision = !collision;
2289
2290 EnableCollision(collision);
2291 }
2292 }
2293
2294 void GameObject::SetGoState(GOState state)
2295 {
2296 SetByteValue(GAMEOBJECT_BYTES_1, 0, state);
2297 sScriptMgr->OnGameObjectStateChanged(this, state);
2298 if (m_model && !IsTransport())
2299 {
2300 if (!IsInWorld())
2301 return;
2302
2303 // startOpen determines whether we are going to add or remove the LoS on activation
2304 bool collision = false;
2305 if (state == GO_STATE_READY)
2306 collision = !collision;
2307
2308 EnableCollision(collision);
2309 }
2310 }
2311
2312 uint32 GameObject::GetTransportPeriod() const
2313 {
2314 ASSERT(GetGOInfo()->type == GAMEOBJECT_TYPE_TRANSPORT);
2315 if (m_goValue.Transport.AnimationInfo)
2316 return m_goValue.Transport.AnimationInfo->TotalTime;
2317
2318 return 0;
2319 }
2320
2321 void GameObject::SetTransportState(GOState state, uint32 stopFrame /*= 0*/)
2322 {
2323 if (GetGoState() == state)
2324 return;
2325
2326 ASSERT(GetGOInfo()->type == GAMEOBJECT_TYPE_TRANSPORT);
2327 ASSERT(state >= GO_STATE_TRANSPORT_ACTIVE);
2328 if (state == GO_STATE_TRANSPORT_ACTIVE)
2329 {
2330 m_goValue.Transport.StateUpdateTimer = 0;
2331 m_goValue.Transport.PathProgress = getMSTime();
2332 if (GetGoState() >= GO_STATE_TRANSPORT_STOPPED)
2333 m_goValue.Transport.PathProgress += m_goValue.Transport.StopFrames->at(GetGoState() - GO_STATE_TRANSPORT_STOPPED);
2334 SetGoState(GO_STATE_TRANSPORT_ACTIVE);
2335 }
2336 else
2337 {
2338 ASSERT(state < GOState(GO_STATE_TRANSPORT_STOPPED + MAX_GO_STATE_TRANSPORT_STOP_FRAMES));
2339 ASSERT(stopFrame < m_goValue.Transport.StopFrames->size());
2340 m_goValue.Transport.PathProgress = getMSTime() + m_goValue.Transport.StopFrames->at(stopFrame);
2341 SetGoState(GOState(GO_STATE_TRANSPORT_STOPPED + stopFrame));
2342 }
2343 }
2344
2345 void GameObject::SetDisplayId(uint32 displayid)
2346 {
2347 SetUInt32Value(GAMEOBJECT_DISPLAYID, displayid);
2348 UpdateModel();
2349 }
2350
2351 uint8 GameObject::GetNameSetId() const
2352 {
2353 switch (GetGoType())
2354 {
2355 case GAMEOBJECT_TYPE_DESTRUCTIBLE_BUILDING:
2356 if (DestructibleModelDataEntry const* modelData = sDestructibleModelDataStore.LookupEntry(m_goInfo->destructibleBuilding.DestructibleModelRec))
2357 {
2358 switch (GetDestructibleState())
2359 {
2360 case GO_DESTRUCTIBLE_INTACT:
2361 return modelData->State0NameSet;
2362 case GO_DESTRUCTIBLE_DAMAGED:
2363 return modelData->State1NameSet;
2364 case GO_DESTRUCTIBLE_DESTROYED:
2365 return modelData->State2NameSet;
2366 case GO_DESTRUCTIBLE_REBUILDING:
2367 return modelData->State3NameSet;
2368 default:
2369 break;
2370 }
2371 }
2372 break;
2373 case GAMEOBJECT_TYPE_GARRISON_BUILDING:
2374 case GAMEOBJECT_TYPE_GARRISON_PLOT:
2375 case GAMEOBJECT_TYPE_PHASEABLE_MO:
2376 return GetByteValue(GAMEOBJECT_FLAGS, 1) & 0xF;
2377 default:
2378 break;
2379 }
2380
2381 return 0;
2382 }
2383
2384 void GameObject::EnableCollision(bool enable)
2385 {
2386 if (!m_model)
2387 return;
2388
2389 /*if (enable && !GetMap()->ContainsGameObjectModel(*m_model))
2390 GetMap()->InsertGameObjectModel(*m_model);*/
2391
2392 m_model->enableCollision(enable);
2393 }
2394
2395 void GameObject::UpdateModel()
2396 {
2397 if (!IsInWorld())
2398 return;
2399 if (m_model)
2400 if (GetMap()->ContainsGameObjectModel(*m_model))
2401 GetMap()->RemoveGameObjectModel(*m_model);
2402 delete m_model;
2403 m_model = CreateModel();
2404 if (m_model)
2405 GetMap()->InsertGameObjectModel(*m_model);
2406 }
2407
2408 Player* GameObject::GetLootRecipient() const
2409 {
2410 if (!m_lootRecipient)
2411 return nullptr;
2412 return ObjectAccessor::FindConnectedPlayer(m_lootRecipient);
2413 }
2414
2415 Group* GameObject::GetLootRecipientGroup() const
2416 {
2417 if (!m_lootRecipientGroup)
2418 return nullptr;
2419 return sGroupMgr->GetGroupByGUID(m_lootRecipientGroup);
2420 }
2421
2422 void GameObject::SetLootRecipient(Unit* unit, Group* group)
2423 {
2424 // set the player whose group should receive the right
2425 // to loot the creature after it dies
2426 // should be set to nullptr after the loot disappears
2427
2428 if (!unit)
2429 {
2430 m_lootRecipient.Clear();
2431 m_lootRecipientGroup = group ? group->GetGUID() : ObjectGuid::Empty;
2432 return;
2433 }
2434
2435 if (unit->GetTypeId() != TYPEID_PLAYER && !unit->IsVehicle())
2436 return;
2437
2438 Player* player = unit->GetCharmerOrOwnerPlayerOrPlayerItself();
2439 if (!player) // normal creature, no player involved
2440 return;
2441
2442 m_lootRecipient = player->GetGUID();
2443
2444 // either get the group from the passed parameter or from unit's one
2445 if (group)
2446 m_lootRecipientGroup = group->GetGUID();
2447 else if (Group* unitGroup = player->GetGroup())
2448 m_lootRecipientGroup = unitGroup->GetGUID();
2449 }
2450
2451 bool GameObject::IsLootAllowedFor(Player const* player) const
2452 {
2453 if (!m_lootRecipient && !m_lootRecipientGroup)
2454 return true;
2455
2456 if (player->GetGUID() == m_lootRecipient)
2457 return true;
2458
2459 Group const* playerGroup = player->GetGroup();
2460 if (!playerGroup || playerGroup != GetLootRecipientGroup()) // if we dont have a group we arent the recipient
2461 return false; // if go doesnt have group bound it means it was solo killed by someone else
2462
2463 return true;
2464 }
2465
2466 GameObject* GameObject::GetLinkedTrap()
2467 {
2468 return ObjectAccessor::GetGameObject(*this, m_linkedTrap);
2469 }
2470
2471 void GameObject::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const
2472 {
2473 if (!target)
2474 return;
2475
2476 bool isStoppableTransport = GetGoType() == GAMEOBJECT_TYPE_TRANSPORT && !m_goValue.Transport.StopFrames->empty();
2477 bool forcedFlags = GetGoType() == GAMEOBJECT_TYPE_CHEST && GetGOInfo()->chest.usegrouplootrules && HasLootRecipient();
2478 bool targetIsGM = target->IsGameMaster();
2479
2480 std::size_t blockCount = UpdateMask::GetBlockCount(m_valuesCount);
2481
2482 uint32* flags = GameObjectUpdateFieldFlags;
2483 uint32 visibleFlag = UF_FLAG_PUBLIC;
2484 if (GetOwnerGUID() == target->GetGUID())
2485 visibleFlag |= UF_FLAG_OWNER;
2486
2487 *data << uint8(blockCount);
2488 std::size_t maskPos = data->wpos();
2489 data->resize(data->size() + blockCount * sizeof(UpdateMask::BlockType));
2490
2491 for (uint16 index = 0; index < m_valuesCount; ++index)
2492 {
2493 if (_fieldNotifyFlags & flags[index] ||
2494 ((updateType == UPDATETYPE_VALUES ? _changesMask[index] : m_uint32Values[index]) && (flags[index] & visibleFlag)) ||
2495 (index == GAMEOBJECT_FLAGS && forcedFlags))
2496 {
2497 UpdateMask::SetUpdateBit(data->contents() + maskPos, index);
2498
2499 if (index == OBJECT_DYNAMIC_FLAGS)
2500 {
2501 uint16 dynFlags = 0;
2502 int16 pathProgress = -1;
2503 switch (GetGoType())
2504 {
2505 case GAMEOBJECT_TYPE_QUESTGIVER:
2506 if (ActivateToQuest(target))
2507 dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
2508 break;
2509 case GAMEOBJECT_TYPE_CHEST:
2510 case GAMEOBJECT_TYPE_GOOBER:
2511 if (ActivateToQuest(target))
2512 dynFlags |= GO_DYNFLAG_LO_ACTIVATE | GO_DYNFLAG_LO_SPARKLE;
2513 else if (targetIsGM)
2514 dynFlags |= GO_DYNFLAG_LO_ACTIVATE;
2515 break;
2516 case GAMEOBJECT_TYPE_GENERIC:
2517 if (ActivateToQuest(target))
2518 dynFlags |= GO_DYNFLAG_LO_SPARKLE;
2519 break;
2520 case GAMEOBJECT_TYPE_TRANSPORT:
2521 case GAMEOBJECT_TYPE_MAP_OBJ_TRANSPORT:
2522 {
2523 if (uint32 transportPeriod = GetTransportPeriod())
2524 {
2525 float timer = float(m_goValue.Transport.PathProgress % transportPeriod);
2526 pathProgress = int16(timer / float(transportPeriod) * 65535.0f);
2527 }
2528 break;
2529 }
2530 default:
2531 break;
2532 }
2533
2534 *data << uint16(dynFlags);
2535 *data << int16(pathProgress);
2536 }
2537 else if (index == GAMEOBJECT_FLAGS)
2538 {
2539 uint32 goFlags = m_uint32Values[GAMEOBJECT_FLAGS];
2540 if (GetGoType() == GAMEOBJECT_TYPE_CHEST)
2541 if (GetGOInfo()->chest.usegrouplootrules && !IsLootAllowedFor(target))
2542 goFlags |= GO_FLAG_LOCKED | GO_FLAG_NOT_SELECTABLE;
2543
2544 *data << goFlags;
2545 }
2546 else if (index == GAMEOBJECT_LEVEL)
2547 {
2548 if (isStoppableTransport)
2549 *data << uint32(m_goValue.Transport.PathProgress);
2550 else
2551 *data << m_uint32Values[index];
2552 }
2553 else if (index == GAMEOBJECT_BYTES_1)
2554 {
2555 uint32 bytes1 = m_uint32Values[index];
2556 if (isStoppableTransport && GetGoState() == GO_STATE_TRANSPORT_ACTIVE)
2557 {
2558 if ((m_goValue.Transport.StateUpdateTimer / 20000) & 1)
2559 {
2560 bytes1 &= 0xFFFFFF00;
2561 bytes1 |= GO_STATE_TRANSPORT_STOPPED;
2562 }
2563 }
2564
2565 *data << bytes1;
2566 }
2567 else
2568 *data << m_uint32Values[index]; // other cases
2569 }
2570 }
2571 }
2572
2573 void GameObject::GetRespawnPosition(float &x, float &y, float &z, float* ori /* = nullptr*/) const
2574 {
2575 if (m_spawnId)
2576 {
2577 if (GameObjectData const* data = sObjectMgr->GetGOData(GetSpawnId()))
2578 {
2579 x = data->posX;
2580 y = data->posY;
2581 z = data->posZ;
2582 if (ori)
2583 *ori = data->orientation;
2584 return;
2585 }
2586 }
2587
2588 x = GetPositionX();
2589 y = GetPositionY();
2590 z = GetPositionZ();
2591 if (ori)
2592 *ori = GetOrientation();
2593 }
2594
2595 float GameObject::GetInteractionDistance() const
2596 {
2597 switch (GetGoType())
2598 {
2599 /// @todo find out how the client calculates the maximal usage distance to spellless working
2600 // gameobjects like guildbanks and mailboxes - 10.0 is a just an abitrary choosen number
2601 case GAMEOBJECT_TYPE_GUILD_BANK:
2602 case GAMEOBJECT_TYPE_MAILBOX:
2603 return 10.0f;
2604 case GAMEOBJECT_TYPE_FISHINGHOLE:
2605 case GAMEOBJECT_TYPE_FISHINGNODE:
2606 return 20.0f + CONTACT_DISTANCE; // max spell range
2607 default:
2608 return INTERACTION_DISTANCE;
2609 }
2610 }
2611
2612 void GameObject::UpdateModelPosition()
2613 {
2614 if (!m_model)
2615 return;
2616
2617 if (GetMap()->ContainsGameObjectModel(*m_model))
2618 {
2619 GetMap()->RemoveGameObjectModel(*m_model);
2620 m_model->UpdatePosition();
2621 GetMap()->InsertGameObjectModel(*m_model);
2622 }
2623 }
2624
2625 void GameObject::SetAnimKitId(uint16 animKitId, bool oneshot)
2626 {
2627 if (_animKitId == animKitId)
2628 return;
2629
2630 if (animKitId && !sAnimKitStore.LookupEntry(animKitId))
2631 return;
2632
2633 if (!oneshot)
2634 _animKitId = animKitId;
2635 else
2636 _animKitId = 0;
2637
2638 WorldPackets::GameObject::GameObjectActivateAnimKit activateAnimKit;
2639 activateAnimKit.ObjectGUID = GetGUID();
2640 activateAnimKit.AnimKitID = animKitId;
2641 activateAnimKit.Maintain = !oneshot;
2642 SendMessageToSet(activateAnimKit.Write(), true);
2643 }
2644
2645 class GameObjectModelOwnerImpl : public GameObjectModelOwnerBase
2646 {
2647 public:
2648 explicit GameObjectModelOwnerImpl(GameObject const* owner) : _owner(owner) { }
2649 virtual ~GameObjectModelOwnerImpl() = default;
2650
2651 bool IsSpawned() const override { return _owner->isSpawned(); }
2652 uint32 GetDisplayId() const override { return _owner->GetDisplayId(); }
2653 uint8 GetNameSetId() const override { return _owner->GetNameSetId(); }
2654 bool IsInPhase(PhaseShift const& phaseShift) const override { return _owner->GetPhaseShift().CanSee(phaseShift); }
2655 G3D::Vector3 GetPosition() const override { return G3D::Vector3(_owner->GetPositionX(), _owner->GetPositionY(), _owner->GetPositionZ()); }
2656 float GetOrientation() const override { return _owner->GetOrientation(); }
2657 float GetScale() const override { return _owner->GetObjectScale(); }
2658 void DebugVisualizeCorner(G3D::Vector3 const& corner) const override { _owner->SummonCreature(1, corner.x, corner.y, corner.z, 0, TEMPSUMMON_MANUAL_DESPAWN); }
2659
2660 private:
2661 GameObject const* _owner;
2662 };
2663
2664 GameObjectModel* GameObject::CreateModel()
2665 {
2666 return GameObjectModel::Create(Trinity::make_unique<GameObjectModelOwnerImpl>(this), sWorld->GetDataPath());
2667 }