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