/* * Copyright (C) * Copyright (C) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "Common.h" #include "SharedDefines.h" #include "WorldPacket.h" #include "Opcodes.h" #include "Log.h" #include "World.h" #include "Object.h" #include "Creature.h" #include "Player.h" #include "Vehicle.h" #include "ObjectMgr.h" #include "UpdateData.h" #include "UpdateMask.h" #include "Util.h" #include "MapManager.h" #include "ObjectAccessor.h" #include "Log.h" #include "Transport.h" #include "TargetedMovementGenerator.h" #include "WaypointMovementGenerator.h" #include "VMapFactory.h" #include "CellImpl.h" #include "GridNotifiers.h" #include "GridNotifiersImpl.h" #include "SpellAuraEffects.h" #include "Battlefield.h" #include "BattlefieldMgr.h" #include "UpdateFieldFlags.h" #include "TemporarySummon.h" #include "Totem.h" #include "OutdoorPvPMgr.h" #include "MovementPacketBuilder.h" #include "DynamicTree.h" #include "Group.h" #include "Chat.h" #include "DynamicVisibility.h" uint32 GuidHigh2TypeId(uint32 guid_hi) { switch (guid_hi) { case HIGHGUID_ITEM: return TYPEID_ITEM; //case HIGHGUID_CONTAINER: return TYPEID_CONTAINER; HIGHGUID_CONTAINER == HIGHGUID_ITEM currently case HIGHGUID_UNIT: return TYPEID_UNIT; case HIGHGUID_PET: return TYPEID_UNIT; case HIGHGUID_PLAYER: return TYPEID_PLAYER; case HIGHGUID_GAMEOBJECT: return TYPEID_GAMEOBJECT; case HIGHGUID_DYNAMICOBJECT:return TYPEID_DYNAMICOBJECT; case HIGHGUID_CORPSE: return TYPEID_CORPSE; case HIGHGUID_MO_TRANSPORT: return TYPEID_GAMEOBJECT; case HIGHGUID_VEHICLE: return TYPEID_UNIT; } return NUM_CLIENT_OBJECT_TYPES; // unknown } Object::Object() : m_PackGUID(sizeof(uint64)+1) { m_objectTypeId = TYPEID_OBJECT; m_objectType = TYPEMASK_OBJECT; m_uint32Values = NULL; m_valuesCount = 0; _fieldNotifyFlags = UF_FLAG_DYNAMIC; m_inWorld = false; m_objectUpdated = false; m_PackGUID.appendPackGUID(0); } WorldObject::~WorldObject() { // this may happen because there are many !create/delete if (IsWorldObject() && m_currMap) { if (GetTypeId() == TYPEID_CORPSE) { sLog->outCrash("Object::~Object Corpse guid=" UI64FMTD ", type=%d, entry=%u deleted but still in map!!", GetGUID(), ((Corpse*)this)->GetType(), GetEntry()); ASSERT(false); } ResetMap(); } } Object::~Object() { if (IsInWorld()) { sLog->outCrash("Object::~Object - guid=" UI64FMTD ", typeid=%d, entry=%u deleted but still in world!!", GetGUID(), GetTypeId(), GetEntry()); if (isType(TYPEMASK_ITEM)) sLog->outCrash("Item slot %u", ((Item*)this)->GetSlot()); ASSERT(false); RemoveFromWorld(); } if (m_objectUpdated) { sLog->outCrash("Object::~Object - guid=" UI64FMTD ", typeid=%d, entry=%u deleted but still in update list!!", GetGUID(), GetTypeId(), GetEntry()); ASSERT(false); sObjectAccessor->RemoveUpdateObject(this); } delete [] m_uint32Values; m_uint32Values = 0; } void Object::_InitValues() { m_uint32Values = new uint32[m_valuesCount]; memset(m_uint32Values, 0, m_valuesCount*sizeof(uint32)); _changesMask.SetCount(m_valuesCount); m_objectUpdated = false; } void Object::_Create(uint32 guidlow, uint32 entry, HighGuid guidhigh) { if (!m_uint32Values) _InitValues(); uint64 guid = MAKE_NEW_GUID(guidlow, entry, guidhigh); SetUInt64Value(OBJECT_FIELD_GUID, guid); SetUInt32Value(OBJECT_FIELD_TYPE, m_objectType); m_PackGUID.wpos(0); m_PackGUID.appendPackGUID(GetGUID()); } std::string Object::_ConcatFields(uint16 startIndex, uint16 size) const { std::ostringstream ss; for (uint16 index = 0; index < size; ++index) ss << GetUInt32Value(index + startIndex) << ' '; return ss.str(); } void Object::AddToWorld() { if (m_inWorld) return; ASSERT(m_uint32Values); m_inWorld = true; // synchronize values mirror with values array (changes will send in updatecreate opcode any way ClearUpdateMask(true); } void Object::RemoveFromWorld() { if (!m_inWorld) return; m_inWorld = false; // if we remove from world then sending changes not required ClearUpdateMask(true); } void Object::BuildMovementUpdateBlock(UpdateData* data, uint32 flags) const { ByteBuffer buf(500); buf << uint8(UPDATETYPE_MOVEMENT); buf.append(GetPackGUID()); BuildMovementUpdate(&buf, flags); data->AddUpdateBlock(buf); } void Object::BuildCreateUpdateBlockForPlayer(UpdateData* data, Player* target) const { if (!target) return; uint8 updatetype = UPDATETYPE_CREATE_OBJECT; uint16 flags = m_updateFlag; /** lower flag1 **/ if (target == this) // building packet for yourself flags |= UPDATEFLAG_SELF; if (flags & UPDATEFLAG_STATIONARY_POSITION) { // UPDATETYPE_CREATE_OBJECT2 dynamic objects, corpses... if (isType(TYPEMASK_DYNAMICOBJECT) || isType(TYPEMASK_CORPSE) || isType(TYPEMASK_PLAYER)) updatetype = UPDATETYPE_CREATE_OBJECT2; // UPDATETYPE_CREATE_OBJECT2 for pets... if (target->GetPetGUID() == GetGUID()) updatetype = UPDATETYPE_CREATE_OBJECT2; // UPDATETYPE_CREATE_OBJECT2 for some gameobject types... if (isType(TYPEMASK_GAMEOBJECT)) { switch (((GameObject*)this)->GetGoType()) { case GAMEOBJECT_TYPE_TRAP: case GAMEOBJECT_TYPE_DUEL_ARBITER: case GAMEOBJECT_TYPE_FLAGSTAND: case GAMEOBJECT_TYPE_FLAGDROP: updatetype = UPDATETYPE_CREATE_OBJECT2; break; default: break; } } if (isType(TYPEMASK_UNIT)) { if (((Unit*)this)->GetVictim()) flags |= UPDATEFLAG_HAS_TARGET; } } //sLog->outDebug("BuildCreateUpdate: update-type: %u, object-type: %u got flags: %X, flags2: %X", updatetype, m_objectTypeId, flags, flags2); ByteBuffer buf(500); buf << (uint8)updatetype; buf.append(GetPackGUID()); buf << (uint8)m_objectTypeId; BuildMovementUpdate(&buf, flags); BuildValuesUpdate(updatetype, &buf, target); data->AddUpdateBlock(buf); } void Object::SendUpdateToPlayer(Player* player) { // send create update to player UpdateData upd; WorldPacket packet; BuildCreateUpdateBlockForPlayer(&upd, player); upd.BuildPacket(&packet); player->GetSession()->SendPacket(&packet); } void Object::BuildValuesUpdateBlockForPlayer(UpdateData* data, Player* target) const { ByteBuffer buf(500); buf << (uint8) UPDATETYPE_VALUES; buf.append(GetPackGUID()); BuildValuesUpdate(UPDATETYPE_VALUES, &buf, target); data->AddUpdateBlock(buf); } void Object::BuildOutOfRangeUpdateBlock(UpdateData* data) const { data->AddOutOfRangeGUID(GetGUID()); } void Object::DestroyForPlayer(Player* target, bool onDeath) const { ASSERT(target); if (isType(TYPEMASK_UNIT) || isType(TYPEMASK_PLAYER)) { if (Battleground* bg = target->GetBattleground()) { if (bg->isArena()) { WorldPacket data(SMSG_ARENA_UNIT_DESTROYED, 8); data << uint64(GetGUID()); target->GetSession()->SendPacket(&data); } } } WorldPacket data(SMSG_DESTROY_OBJECT, 8 + 1); data << uint64(GetGUID()); //! If the following bool is true, the client will call "void CGUnit_C::OnDeath()" for this object. //! OnDeath() does for eg trigger death animation and interrupts certain spells/missiles/auras/sounds... data << uint8(onDeath ? 1 : 0); target->GetSession()->SendPacket(&data); } void Object::BuildMovementUpdate(ByteBuffer* data, uint16 flags) const { Unit const* unit = NULL; WorldObject const* object = NULL; if (isType(TYPEMASK_UNIT)) unit = ToUnit(); else object = ((WorldObject*)this); *data << uint16(flags); // update flags // 0x20 if (flags & UPDATEFLAG_LIVING) { unit->BuildMovementPacket(data); *data << unit->GetSpeed(MOVE_WALK) << unit->GetSpeed(MOVE_RUN) << unit->GetSpeed(MOVE_RUN_BACK) << unit->GetSpeed(MOVE_SWIM) << unit->GetSpeed(MOVE_SWIM_BACK) << unit->GetSpeed(MOVE_FLIGHT) << unit->GetSpeed(MOVE_FLIGHT_BACK) << unit->GetSpeed(MOVE_TURN_RATE) << unit->GetSpeed(MOVE_PITCH_RATE); // 0x08000000 if (unit->m_movementInfo.GetMovementFlags() & MOVEMENTFLAG_SPLINE_ENABLED) { if (unit->movespline->_Spline().getPoints(true).empty() || (!unit->movespline->_Spline().getPoints(true).empty() && &unit->movespline->_Spline().getPoints(true)[0] == NULL)) const_cast(unit)->DisableSpline(); else Movement::PacketBuilder::WriteCreate(*unit->movespline, *data); } } else { if (flags & UPDATEFLAG_POSITION) { Transport* transport = object->GetTransport(); if (transport) data->append(transport->GetPackGUID()); else *data << uint8(0); *data << object->GetPositionX(); *data << object->GetPositionY(); *data << object->GetPositionZ() + (unit ? unit->GetHoverHeight() : 0.0f); if (transport) { *data << object->GetTransOffsetX(); *data << object->GetTransOffsetY(); *data << object->GetTransOffsetZ(); } else { *data << object->GetPositionX(); *data << object->GetPositionY(); *data << object->GetPositionZ() + (unit ? unit->GetHoverHeight() : 0.0f); } *data << object->GetOrientation(); if (GetTypeId() == TYPEID_CORPSE) *data << float(object->GetOrientation()); else *data << float(0); } else { // 0x40 if (flags & UPDATEFLAG_STATIONARY_POSITION) { *data << object->GetStationaryX(); *data << object->GetStationaryY(); *data << object->GetStationaryZ() + (unit ? unit->GetHoverHeight() : 0.0f); *data << object->GetStationaryO(); } } } // 0x8 if (flags & UPDATEFLAG_UNKNOWN) { *data << uint32(0); } // 0x10 if (flags & UPDATEFLAG_LOWGUID) { switch (GetTypeId()) { case TYPEID_OBJECT: case TYPEID_ITEM: case TYPEID_CONTAINER: case TYPEID_GAMEOBJECT: case TYPEID_DYNAMICOBJECT: case TYPEID_CORPSE: *data << uint32(GetGUIDLow()); // GetGUIDLow() break; //! Unit, Player and default here are sending wrong values. /// @todo Research the proper formula case TYPEID_UNIT: *data << uint32(0x0000000B); // unk break; case TYPEID_PLAYER: if (flags & UPDATEFLAG_SELF) *data << uint32(0x0000002F); // unk else *data << uint32(0x00000008); // unk break; default: *data << uint32(0x00000000); // unk break; } } // 0x4 if (flags & UPDATEFLAG_HAS_TARGET) { if (Unit* victim = unit->GetVictim()) data->append(victim->GetPackGUID()); else *data << uint8(0); } // 0x2 if (flags & UPDATEFLAG_TRANSPORT) { GameObject const* go = ToGameObject(); if (go && go->ToTransport()) *data << uint32(go->ToTransport()->GetPathProgress()); else *data << uint32(0); } // 0x80 if (flags & UPDATEFLAG_VEHICLE) { /// @todo Allow players to aquire this updateflag. *data << uint32(unit->GetVehicleKit()->GetVehicleInfo()->m_ID); if (unit->HasUnitMovementFlag(MOVEMENTFLAG_ONTRANSPORT)) *data << float(unit->GetTransOffsetO()); else *data << float(unit->GetOrientation()); } // 0x200 if (flags & UPDATEFLAG_ROTATION) *data << int64(ToGameObject()->GetPackedWorldRotation()); } void Object::BuildValuesUpdate(uint8 updateType, ByteBuffer* data, Player* target) const { if (!target) return; ByteBuffer fieldBuffer; UpdateMask updateMask; updateMask.SetCount(m_valuesCount); uint32* flags = NULL; uint32 visibleFlag = GetUpdateFieldData(target, flags); for (uint16 index = 0; index < m_valuesCount; ++index) { if (_fieldNotifyFlags & flags[index] || ((updateType == UPDATETYPE_VALUES ? _changesMask.GetBit(index) : m_uint32Values[index]) && (flags[index] & visibleFlag))) { updateMask.SetBit(index); fieldBuffer << m_uint32Values[index]; } } *data << uint8(updateMask.GetBlockCount()); updateMask.AppendToPacket(data); data->append(fieldBuffer); } void Object::ClearUpdateMask(bool remove) { _changesMask.Clear(); if (m_objectUpdated) { if (remove) sObjectAccessor->RemoveUpdateObject(this); m_objectUpdated = false; } } void Object::BuildFieldsUpdate(Player* player, UpdateDataMapType& data_map) const { UpdateDataMapType::iterator iter = data_map.find(player); if (iter == data_map.end()) { std::pair p = data_map.insert(UpdateDataMapType::value_type(player, UpdateData())); ASSERT(p.second); iter = p.first; } BuildValuesUpdateBlockForPlayer(&iter->second, iter->first); } uint32 Object::GetUpdateFieldData(Player const* target, uint32*& flags) const { uint32 visibleFlag = UF_FLAG_PUBLIC; if (target == this) visibleFlag |= UF_FLAG_PRIVATE; switch (GetTypeId()) { case TYPEID_ITEM: case TYPEID_CONTAINER: flags = ItemUpdateFieldFlags; if (((Item*)this)->GetOwnerGUID() == target->GetGUID()) visibleFlag |= UF_FLAG_OWNER | UF_FLAG_ITEM_OWNER; break; case TYPEID_UNIT: case TYPEID_PLAYER: { Player* plr = ToUnit()->GetCharmerOrOwnerPlayerOrPlayerItself(); flags = UnitUpdateFieldFlags; if (ToUnit()->GetOwnerGUID() == target->GetGUID()) visibleFlag |= UF_FLAG_OWNER; if (HasFlag(UNIT_DYNAMIC_FLAGS, UNIT_DYNFLAG_SPECIALINFO)) if (ToUnit()->HasAuraTypeWithCaster(SPELL_AURA_EMPATHY, target->GetGUID())) visibleFlag |= UF_FLAG_SPECIAL_INFO; if (plr && plr->IsInSameRaidWith(target)) visibleFlag |= UF_FLAG_PARTY_MEMBER; break; } case TYPEID_GAMEOBJECT: flags = GameObjectUpdateFieldFlags; if (ToGameObject()->GetOwnerGUID() == target->GetGUID()) visibleFlag |= UF_FLAG_OWNER; break; case TYPEID_DYNAMICOBJECT: flags = DynamicObjectUpdateFieldFlags; if (((DynamicObject*)this)->GetCasterGUID() == target->GetGUID()) visibleFlag |= UF_FLAG_OWNER; break; case TYPEID_CORPSE: flags = CorpseUpdateFieldFlags; if (ToCorpse()->GetOwnerGUID() == target->GetGUID()) visibleFlag |= UF_FLAG_OWNER; break; case TYPEID_OBJECT: break; } return visibleFlag; } void Object::_LoadIntoDataField(std::string const& data, uint32 startOffset, uint32 count) { if (data.empty()) return; Tokenizer tokens(data, ' ', count); if (tokens.size() != count) return; for (uint32 index = 0; index < count; ++index) { m_uint32Values[startOffset + index] = atol(tokens[index]); _changesMask.SetBit(startOffset + index); } } void Object::SetInt32Value(uint16 index, int32 value) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (m_int32Values[index] != value) { m_int32Values[index] = value; _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::SetUInt32Value(uint16 index, uint32 value) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (m_uint32Values[index] != value) { m_uint32Values[index] = value; _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::UpdateUInt32Value(uint16 index, uint32 value) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); m_uint32Values[index] = value; _changesMask.SetBit(index); } void Object::SetUInt64Value(uint16 index, uint64 value) { ASSERT(index + 1 < m_valuesCount || PrintIndexError(index, true)); if (*((uint64*)&(m_uint32Values[index])) != value) { m_uint32Values[index] = PAIR64_LOPART(value); m_uint32Values[index + 1] = PAIR64_HIPART(value); _changesMask.SetBit(index); _changesMask.SetBit(index + 1); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } bool Object::AddUInt64Value(uint16 index, uint64 value) { ASSERT(index + 1 < m_valuesCount || PrintIndexError(index, true)); if (value && !*((uint64*)&(m_uint32Values[index]))) { m_uint32Values[index] = PAIR64_LOPART(value); m_uint32Values[index + 1] = PAIR64_HIPART(value); _changesMask.SetBit(index); _changesMask.SetBit(index + 1); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } return true; } return false; } bool Object::RemoveUInt64Value(uint16 index, uint64 value) { ASSERT(index + 1 < m_valuesCount || PrintIndexError(index, true)); if (value && *((uint64*)&(m_uint32Values[index])) == value) { m_uint32Values[index] = 0; m_uint32Values[index + 1] = 0; _changesMask.SetBit(index); _changesMask.SetBit(index + 1); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } return true; } return false; } void Object::SetFloatValue(uint16 index, float value) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (m_floatValues[index] != value) { m_floatValues[index] = value; _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::SetByteValue(uint16 index, uint8 offset, uint8 value) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (offset > 3) { sLog->outError("Object::SetByteValue: wrong offset %u", offset); return; } if (uint8(m_uint32Values[index] >> (offset * 8)) != value) { m_uint32Values[index] &= ~uint32(uint32(0xFF) << (offset * 8)); m_uint32Values[index] |= uint32(uint32(value) << (offset * 8)); _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::SetUInt16Value(uint16 index, uint8 offset, uint16 value) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (offset > 1) { sLog->outError("Object::SetUInt16Value: wrong offset %u", offset); return; } if (uint16(m_uint32Values[index] >> (offset * 16)) != value) { m_uint32Values[index] &= ~uint32(uint32(0xFFFF) << (offset * 16)); m_uint32Values[index] |= uint32(uint32(value) << (offset * 16)); _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::SetStatFloatValue(uint16 index, float value) { if (value < 0) value = 0.0f; SetFloatValue(index, value); } void Object::SetStatInt32Value(uint16 index, int32 value) { if (value < 0) value = 0; SetUInt32Value(index, uint32(value)); } void Object::ApplyModUInt32Value(uint16 index, int32 val, bool apply) { int32 cur = GetUInt32Value(index); cur += (apply ? val : -val); if (cur < 0) cur = 0; SetUInt32Value(index, cur); } void Object::ApplyModInt32Value(uint16 index, int32 val, bool apply) { int32 cur = GetInt32Value(index); cur += (apply ? val : -val); SetInt32Value(index, cur); } void Object::ApplyModSignedFloatValue(uint16 index, float val, bool apply) { float cur = GetFloatValue(index); cur += (apply ? val : -val); SetFloatValue(index, cur); } void Object::ApplyModPositiveFloatValue(uint16 index, float val, bool apply) { float cur = GetFloatValue(index); cur += (apply ? val : -val); if (cur < 0) cur = 0; SetFloatValue(index, cur); } void Object::SetFlag(uint16 index, uint32 newFlag) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); uint32 oldval = m_uint32Values[index]; uint32 newval = oldval | newFlag; if (oldval != newval) { m_uint32Values[index] = newval; _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::RemoveFlag(uint16 index, uint32 oldFlag) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); ASSERT(m_uint32Values); uint32 oldval = m_uint32Values[index]; uint32 newval = oldval & ~oldFlag; if (oldval != newval) { m_uint32Values[index] = newval; _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::SetByteFlag(uint16 index, uint8 offset, uint8 newFlag) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (offset > 3) { sLog->outError("Object::SetByteFlag: wrong offset %u", offset); return; } if (!(uint8(m_uint32Values[index] >> (offset * 8)) & newFlag)) { m_uint32Values[index] |= uint32(uint32(newFlag) << (offset * 8)); _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } void Object::RemoveByteFlag(uint16 index, uint8 offset, uint8 oldFlag) { ASSERT(index < m_valuesCount || PrintIndexError(index, true)); if (offset > 3) { sLog->outError("Object::RemoveByteFlag: wrong offset %u", offset); return; } if (uint8(m_uint32Values[index] >> (offset * 8)) & oldFlag) { m_uint32Values[index] &= ~uint32(uint32(oldFlag) << (offset * 8)); _changesMask.SetBit(index); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } } bool Object::PrintIndexError(uint32 index, bool set) const { sLog->outString("Attempt %s non-existed value field: %u (count: %u) for object typeid: %u type mask: %u", (set ? "set value to" : "get value from"), index, m_valuesCount, GetTypeId(), m_objectType); // ASSERT must fail after function call return false; } bool Position::HasInLine(WorldObject const* target, float width) const { if (!HasInArc(M_PI, target)) return false; width += target->GetObjectSize(); float angle = GetRelativeAngle(target); return fabs(sin(angle)) * GetExactDist2d(target->GetPositionX(), target->GetPositionY()) < width; } std::string Position::ToString() const { std::stringstream sstr; sstr << "X: " << m_positionX << " Y: " << m_positionY << " Z: " << m_positionZ << " O: " << m_orientation; return sstr.str(); } ByteBuffer& operator>>(ByteBuffer& buf, Position::PositionXYZOStreamer const& streamer) { float x, y, z, o; buf >> x >> y >> z >> o; streamer.m_pos->Relocate(x, y, z, o); return buf; } ByteBuffer& operator<<(ByteBuffer& buf, Position::PositionXYZStreamer const& streamer) { float x, y, z; streamer.m_pos->GetPosition(x, y, z); buf << x << y << z; return buf; } ByteBuffer& operator>>(ByteBuffer& buf, Position::PositionXYZStreamer const& streamer) { float x, y, z; buf >> x >> y >> z; streamer.m_pos->Relocate(x, y, z); return buf; } ByteBuffer& operator<<(ByteBuffer& buf, Position::PositionXYZOStreamer const& streamer) { float x, y, z, o; streamer.m_pos->GetPosition(x, y, z, o); buf << x << y << z << o; return buf; } void MovementInfo::OutDebug() { sLog->outString("MOVEMENT INFO"); sLog->outString("guid " UI64FMTD, guid); sLog->outString("flags %u", flags); sLog->outString("flags2 %u", flags2); sLog->outString("time %u current time " UI64FMTD "", flags2, uint64(::time(NULL))); sLog->outString("position: `%s`", pos.ToString().c_str()); if (flags & MOVEMENTFLAG_ONTRANSPORT) { sLog->outString("TRANSPORT:"); sLog->outString("guid: " UI64FMTD, transport.guid); sLog->outString("position: `%s`", transport.pos.ToString().c_str()); sLog->outString("seat: %i", transport.seat); sLog->outString("time: %u", transport.time); if (flags2 & MOVEMENTFLAG2_INTERPOLATED_MOVEMENT) sLog->outString("time2: %u", transport.time2); } if ((flags & (MOVEMENTFLAG_SWIMMING | MOVEMENTFLAG_FLYING)) || (flags2 & MOVEMENTFLAG2_ALWAYS_ALLOW_PITCHING)) sLog->outString("pitch: %f", pitch); sLog->outString("fallTime: %u", fallTime); if (flags & MOVEMENTFLAG_FALLING) sLog->outString("j_zspeed: %f j_sinAngle: %f j_cosAngle: %f j_xyspeed: %f", jump.zspeed, jump.sinAngle, jump.cosAngle, jump.xyspeed); if (flags & MOVEMENTFLAG_SPLINE_ELEVATION) sLog->outString("splineElevation: %f", splineElevation); } WorldObject::WorldObject(bool isWorldObject) : WorldLocation(), LastUsedScriptID(0), m_name(""), m_isActive(false), m_isWorldObject(isWorldObject), m_zoneScript(NULL), m_transport(NULL), m_currMap(NULL), m_InstanceId(0), m_phaseMask(PHASEMASK_NORMAL), m_notifyflags(0), m_executed_notifies(0) { m_serverSideVisibility.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE | GHOST_VISIBILITY_GHOST); m_serverSideVisibilityDetect.SetValue(SERVERSIDE_VISIBILITY_GHOST, GHOST_VISIBILITY_ALIVE); } void WorldObject::SetWorldObject(bool on) { if (!IsInWorld()) return; GetMap()->AddObjectToSwitchList(this, on); } bool WorldObject::IsWorldObject() const { if (m_isWorldObject) return true; if (ToCreature() && ToCreature()->m_isTempWorldObject) return true; return false; } void WorldObject::setActive(bool on) { if (m_isActive == on) return; if (GetTypeId() == TYPEID_PLAYER) return; m_isActive = on; if (!IsInWorld()) return; Map* map = FindMap(); if (!map) return; if (on) { if (GetTypeId() == TYPEID_UNIT) map->AddToActive(this->ToCreature()); else if (GetTypeId() == TYPEID_DYNAMICOBJECT) map->AddToActive((DynamicObject*)this); else if (GetTypeId() == TYPEID_GAMEOBJECT) map->AddToActive((GameObject*)this); } else { if (GetTypeId() == TYPEID_UNIT) map->RemoveFromActive(this->ToCreature()); else if (GetTypeId() == TYPEID_DYNAMICOBJECT) map->RemoveFromActive((DynamicObject*)this); else if (GetTypeId() == TYPEID_GAMEOBJECT) map->RemoveFromActive((GameObject*)this); } } void WorldObject::CleanupsBeforeDelete(bool /*finalCleanup*/) { if (IsInWorld()) RemoveFromWorld(); } void WorldObject::_Create(uint32 guidlow, HighGuid guidhigh, uint32 phaseMask) { Object::_Create(guidlow, 0, guidhigh); m_phaseMask = phaseMask; } uint32 WorldObject::GetZoneId(bool /*forceRecalc*/) const { return GetBaseMap()->GetZoneId(m_positionX, m_positionY, m_positionZ); } uint32 WorldObject::GetAreaId(bool /*forceRecalc*/) const { return GetBaseMap()->GetAreaId(m_positionX, m_positionY, m_positionZ); } void WorldObject::GetZoneAndAreaId(uint32& zoneid, uint32& areaid, bool /*forceRecalc*/) const { GetBaseMap()->GetZoneAndAreaId(zoneid, areaid, m_positionX, m_positionY, m_positionZ); } InstanceScript* WorldObject::GetInstanceScript() { Map* map = GetMap(); return map->IsDungeon() ? map->ToInstanceMap()->GetInstanceScript() : NULL; } float WorldObject::GetDistanceZ(const WorldObject* obj) const { float dz = fabs(GetPositionZ() - obj->GetPositionZ()); float sizefactor = GetObjectSize() + obj->GetObjectSize(); float dist = dz - sizefactor; return (dist > 0 ? dist : 0); } bool WorldObject::_IsWithinDist(WorldObject const* obj, float dist2compare, bool is3D) const { float sizefactor = GetObjectSize() + obj->GetObjectSize(); float maxdist = dist2compare + sizefactor; if (m_transport && obj->GetTransport() && obj->GetTransport()->GetGUIDLow() == m_transport->GetGUIDLow()) { float dtx = m_movementInfo.transport.pos.m_positionX - obj->m_movementInfo.transport.pos.m_positionX; float dty = m_movementInfo.transport.pos.m_positionY - obj->m_movementInfo.transport.pos.m_positionY; float disttsq = dtx * dtx + dty * dty; if (is3D) { float dtz = m_movementInfo.transport.pos.m_positionZ - obj->m_movementInfo.transport.pos.m_positionZ; disttsq += dtz * dtz; } return disttsq < (maxdist * maxdist); } float dx = GetPositionX() - obj->GetPositionX(); float dy = GetPositionY() - obj->GetPositionY(); float distsq = dx*dx + dy*dy; if (is3D) { float dz = GetPositionZ() - obj->GetPositionZ(); distsq += dz*dz; } return distsq < maxdist * maxdist; } bool WorldObject::IsWithinLOSInMap(const WorldObject* obj) const { if (!IsInMap(obj)) return false; float ox, oy, oz; obj->GetPosition(ox, oy, oz); return IsWithinLOS(ox, oy, oz); } bool WorldObject::IsWithinLOS(float ox, float oy, float oz) const { /*float x, y, z; GetPosition(x, y, z); VMAP::IVMapManager* vMapManager = VMAP::VMapFactory::createOrGetVMapManager(); return vMapManager->isInLineOfSight(GetMapId(), x, y, z+2.0f, ox, oy, oz+2.0f);*/ if (IsInWorld()) return GetMap()->isInLineOfSight(GetPositionX(), GetPositionY(), GetPositionZ()+2.f, ox, oy, oz+2.f, GetPhaseMask()); return true; } bool WorldObject::GetDistanceOrder(WorldObject const* obj1, WorldObject const* obj2, bool is3D /* = true */) const { float dx1 = GetPositionX() - obj1->GetPositionX(); float dy1 = GetPositionY() - obj1->GetPositionY(); float distsq1 = dx1*dx1 + dy1*dy1; if (is3D) { float dz1 = GetPositionZ() - obj1->GetPositionZ(); distsq1 += dz1*dz1; } float dx2 = GetPositionX() - obj2->GetPositionX(); float dy2 = GetPositionY() - obj2->GetPositionY(); float distsq2 = dx2*dx2 + dy2*dy2; if (is3D) { float dz2 = GetPositionZ() - obj2->GetPositionZ(); distsq2 += dz2*dz2; } return distsq1 < distsq2; } bool WorldObject::IsInRange(WorldObject const* obj, float minRange, float maxRange, bool is3D /* = true */) const { float dx = GetPositionX() - obj->GetPositionX(); float dy = GetPositionY() - obj->GetPositionY(); float distsq = dx*dx + dy*dy; if (is3D) { float dz = GetPositionZ() - obj->GetPositionZ(); distsq += dz*dz; } float sizefactor = GetObjectSize() + obj->GetObjectSize(); // check only for real range if (minRange > 0.0f) { float mindist = minRange + sizefactor; if (distsq < mindist * mindist) return false; } float maxdist = maxRange + sizefactor; return distsq < maxdist * maxdist; } bool WorldObject::IsInRange2d(float x, float y, float minRange, float maxRange) const { float dx = GetPositionX() - x; float dy = GetPositionY() - y; float distsq = dx*dx + dy*dy; float sizefactor = GetObjectSize(); // check only for real range if (minRange > 0.0f) { float mindist = minRange + sizefactor; if (distsq < mindist * mindist) return false; } float maxdist = maxRange + sizefactor; return distsq < maxdist * maxdist; } bool WorldObject::IsInRange3d(float x, float y, float z, float minRange, float maxRange) const { float dx = GetPositionX() - x; float dy = GetPositionY() - y; float dz = GetPositionZ() - z; float distsq = dx*dx + dy*dy + dz*dz; float sizefactor = GetObjectSize(); // check only for real range if (minRange > 0.0f) { float mindist = minRange + sizefactor; if (distsq < mindist * mindist) return false; } float maxdist = maxRange + sizefactor; return distsq < maxdist * maxdist; } void Position::RelocateOffset(const Position & offset) { m_positionX = GetPositionX() + (offset.GetPositionX() * cos(GetOrientation()) + offset.GetPositionY() * sin(GetOrientation() + M_PI)); m_positionY = GetPositionY() + (offset.GetPositionY() * cos(GetOrientation()) + offset.GetPositionX() * sin(GetOrientation())); m_positionZ = GetPositionZ() + offset.GetPositionZ(); m_orientation = GetOrientation() + offset.GetOrientation(); } void Position::GetPositionOffsetTo(const Position & endPos, Position & retOffset) const { float dx = endPos.GetPositionX() - GetPositionX(); float dy = endPos.GetPositionY() - GetPositionY(); retOffset.m_positionX = dx * cos(GetOrientation()) + dy * sin(GetOrientation()); retOffset.m_positionY = dy * cos(GetOrientation()) - dx * sin(GetOrientation()); retOffset.m_positionZ = endPos.GetPositionZ() - GetPositionZ(); retOffset.m_orientation = endPos.GetOrientation() - GetOrientation(); } float Position::GetAngle(const Position* obj) const { if (!obj) return 0; return GetAngle(obj->GetPositionX(), obj->GetPositionY()); } // Return angle in range 0..2*pi float Position::GetAngle(const float x, const float y) const { float dx = x - GetPositionX(); float dy = y - GetPositionY(); float ang = atan2(dy, dx); ang = (ang >= 0) ? ang : 2 * M_PI + ang; return ang; } void Position::GetSinCos(const float x, const float y, float &vsin, float &vcos) const { float dx = GetPositionX() - x; float dy = GetPositionY() - y; if (fabs(dx) < 0.001f && fabs(dy) < 0.001f) { float angle = (float)rand_norm()*static_cast(2*M_PI); vcos = cos(angle); vsin = sin(angle); } else { float dist = sqrt((dx*dx) + (dy*dy)); vcos = dx / dist; vsin = dy / dist; } } bool Position::IsWithinBox(const Position& center, float xradius, float yradius, float zradius) const { // rotate the WorldObject position instead of rotating the whole cube, that way we can make a simplified // is-in-cube check and we have to calculate only one point instead of 4 // 2PI = 360*, keep in mind that ingame orientation is counter-clockwise double rotation = 2 * M_PI - center.GetOrientation(); double sinVal = std::sin(rotation); double cosVal = std::cos(rotation); float BoxDistX = GetPositionX() - center.GetPositionX(); float BoxDistY = GetPositionY() - center.GetPositionY(); float rotX = float(center.GetPositionX() + BoxDistX * cosVal - BoxDistY*sinVal); float rotY = float(center.GetPositionY() + BoxDistY * cosVal + BoxDistX*sinVal); // box edges are parallel to coordiante axis, so we can treat every dimension independently :D float dz = GetPositionZ() - center.GetPositionZ(); float dx = rotX - center.GetPositionX(); float dy = rotY - center.GetPositionY(); if ((std::fabs(dx) > xradius) || (std::fabs(dy) > yradius) || (std::fabs(dz) > zradius)) return false; return true; } bool Position::HasInArc(float arc, const Position* obj, float targetRadius) const { // always have self in arc if (obj == this) return true; // move arc to range 0.. 2*pi arc = Position::NormalizeOrientation(arc); float angle = GetAngle(obj); angle -= m_orientation; // move angle to range -pi ... +pi angle = Position::NormalizeOrientation(angle); if (angle > M_PI) angle -= 2.0f*M_PI; float lborder = -1 * (arc/2.0f); // in range -pi..0 float rborder = (arc/2.0f); // in range 0..pi // pussywizard: take into consideration target size if (targetRadius > 0.0f) { float distSq = GetExactDist2dSq(obj); // pussywizard: at least a part of target's model is in every direction if (distSq < targetRadius*targetRadius) return true; float angularRadius = 2.0f * atan(targetRadius / (2.0f * sqrt(distSq))); lborder -= angularRadius; rborder += angularRadius; } return ((angle >= lborder) && (angle <= rborder)); } bool WorldObject::IsInBetween(const WorldObject* obj1, const WorldObject* obj2, float size) const { if (!obj1 || !obj2) return false; if (!size) size = GetObjectSize() / 2; float pdist = obj1->GetExactDist2dSq(obj2) + size / 2.0f; if (GetExactDist2dSq(obj1) >= pdist || GetExactDist2dSq(obj2) >= pdist) return false; if (G3D::fuzzyEq(obj1->GetPositionX(), obj2->GetPositionX())) return GetPositionX() >= obj1->GetPositionX()-size && GetPositionX() <= obj1->GetPositionX()+size; float A = (obj2->GetPositionY()-obj1->GetPositionY())/(obj2->GetPositionX()-obj1->GetPositionX()); float B = -1; float C = obj1->GetPositionY() - A*obj1->GetPositionX(); float dist = fabs(A*GetPositionX()+B*GetPositionY()+C)/sqrt(A*A+B*B); return dist <= size; } bool WorldObject::isInFront(WorldObject const* target, float arc) const { return HasInArc(arc, target); } bool WorldObject::isInBack(WorldObject const* target, float arc) const { return !HasInArc(2 * M_PI - arc, target); } void WorldObject::GetRandomPoint(const Position &pos, float distance, float &rand_x, float &rand_y, float &rand_z) const { if (!distance) { pos.GetPosition(rand_x, rand_y, rand_z); return; } // angle to face `obj` to `this` float angle = (float)rand_norm()*static_cast(2*M_PI); float new_dist = (float)rand_norm()*static_cast(distance); rand_x = pos.m_positionX + new_dist * cos(angle); rand_y = pos.m_positionY + new_dist * sin(angle); rand_z = pos.m_positionZ; Trinity::NormalizeMapCoord(rand_x); Trinity::NormalizeMapCoord(rand_y); UpdateGroundPositionZ(rand_x, rand_y, rand_z); // update to LOS height if available } void WorldObject::UpdateGroundPositionZ(float x, float y, float &z) const { float new_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z /*+ 2.0f*/, true); // pussywizard: +2.0f is added in all inner functions if (new_z > INVALID_HEIGHT) z = new_z + 0.05f; // just to be sure that we are not a few pixel under the surface } void WorldObject::UpdateAllowedPositionZ(float x, float y, float &z) const { // TODO: Allow transports to be part of dynamic vmap tree //if (GetTransport()) // return; switch (GetTypeId()) { case TYPEID_UNIT: { // non fly unit don't must be in air // non swim unit must be at ground (mostly speedup, because it don't must be in water and water level check less fast if (!ToCreature()->CanFly()) { bool canSwim = ToCreature()->CanSwim(); float ground_z = z; float max_z = canSwim ? GetMap()->GetWaterOrGroundLevel(x, y, z, &ground_z, !ToUnit()->HasAuraType(SPELL_AURA_WATER_WALK)) : ((ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true))); if (max_z > INVALID_HEIGHT) { if (z > max_z) z = max_z; else if (z < ground_z) z = ground_z; } } else { float ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true); if (z < ground_z) z = ground_z; } break; } case TYPEID_PLAYER: { // for server controlled moves playr work same as creature (but it can always swim) if (!ToPlayer()->CanFly()) { float ground_z = z; float max_z = GetMap()->GetWaterOrGroundLevel(x, y, z, &ground_z, !ToUnit()->HasAuraType(SPELL_AURA_WATER_WALK)); if (max_z > INVALID_HEIGHT) { if (z > max_z) z = max_z; else if (z < ground_z) z = ground_z; } } else { float ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true); if (z < ground_z) z = ground_z; } break; } default: { float ground_z = GetMap()->GetHeight(GetPhaseMask(), x, y, z, true); if (ground_z > INVALID_HEIGHT) z = ground_z; break; } } } bool Position::IsPositionValid() const { return Trinity::IsValidMapCoord(m_positionX, m_positionY, m_positionZ, m_orientation); } float WorldObject::GetGridActivationRange() const { if (ToPlayer()) return IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); else if (ToCreature()) return ToCreature()->m_SightDistance; else if (GetTypeId() == TYPEID_GAMEOBJECT && ToGameObject()->IsTransport()) return GetMap()->GetVisibilityRange(); else return 0.0f; } float WorldObject::GetVisibilityRange() const { if (isActiveObject() && !ToPlayer()) return MAX_VISIBILITY_DISTANCE; else if (GetTypeId() == TYPEID_GAMEOBJECT) return IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP+VISIBILITY_INC_FOR_GOBJECTS : GetMap()->GetVisibilityRange()+VISIBILITY_INC_FOR_GOBJECTS; else return IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); } float WorldObject::GetSightRange(const WorldObject* target) const { if (ToUnit()) { if (ToPlayer()) { if (target) { if (target->isActiveObject() && !target->ToPlayer()) return MAX_VISIBILITY_DISTANCE; else if (target->GetTypeId() == TYPEID_GAMEOBJECT) return IsInWintergrasp() && target->IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP+VISIBILITY_INC_FOR_GOBJECTS : GetMap()->GetVisibilityRange()+VISIBILITY_INC_FOR_GOBJECTS; return IsInWintergrasp() && target->IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); } return IsInWintergrasp() ? VISIBILITY_DIST_WINTERGRASP : GetMap()->GetVisibilityRange(); } else if (ToCreature()) return ToCreature()->m_SightDistance; else return SIGHT_RANGE_UNIT; } return 0.0f; } bool WorldObject::CanSeeOrDetect(WorldObject const* obj, bool ignoreStealth, bool distanceCheck) const { if (this == obj) return true; if (obj->IsNeverVisible() || CanNeverSee(obj)) return false; if (obj->IsAlwaysVisibleFor(this) || CanAlwaysSee(obj)) return true; // Creature scripts if (Creature const* cObj = obj->ToCreature()) if (cObj->IsAIEnabled && this->ToPlayer() && !cObj->AI()->CanBeSeen(this->ToPlayer())) return false; // pussywizard: arena spectator if (obj->GetTypeId() == TYPEID_PLAYER) if (((const Player*)obj)->IsSpectator() && ((const Player*)obj)->FindMap()->IsBattleArena()) return false; bool corpseVisibility = false; if (distanceCheck) { bool corpseCheck = false; WorldObject const* viewpoint = this; if (Player const* thisPlayer = ToPlayer()) { if (thisPlayer->isDead() && thisPlayer->GetHealth() > 0 && // Cheap way to check for ghost state !(obj->m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GHOST) & m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GHOST) & GHOST_VISIBILITY_GHOST)) { if (Corpse* corpse = thisPlayer->GetCorpse()) { corpseCheck = true; if (corpse->IsWithinDist(thisPlayer, GetSightRange(obj), false)) if (corpse->IsWithinDist(obj, GetSightRange(obj), false)) corpseVisibility = true; } } // our additional checks if (Unit const* target = obj->ToUnit()) { // xinef: don't allow to detect vehicle accessory if you can't see vehicle base! if (Unit const* vehicle = target->GetVehicleBase()) if (!thisPlayer->HaveAtClient(vehicle)) return false; // pussywizard: during arena preparation, don't allow to detect pets if can't see its owner (spoils enemy arena frames) if (target->IsPet() && target->GetOwnerGUID() && target->FindMap()->IsBattleArena() && GetGUID() != target->GetOwnerGUID()) if (BattlegroundMap* bgmap = target->FindMap()->ToBattlegroundMap()) if (Battleground* bg = bgmap->GetBG()) if (bg->GetStatus() < STATUS_IN_PROGRESS && !thisPlayer->HaveAtClient(target->GetOwnerGUID())) return false; } if (thisPlayer->GetViewpoint()) viewpoint = thisPlayer->GetViewpoint(); } // Xinef: check reversely obj vs viewpoint, object could be a gameObject which overrides _IsWithinDist function to include gameobject size if (!corpseCheck && !viewpoint->IsWithinDist(obj, GetSightRange(obj), true)) return false; } // GM visibility off or hidden NPC if (!obj->m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM)) { // Stop checking other things for GMs if (m_serverSideVisibilityDetect.GetValue(SERVERSIDE_VISIBILITY_GM)) return true; } else return m_serverSideVisibilityDetect.GetValue(SERVERSIDE_VISIBILITY_GM) >= obj->m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GM); // Ghost players, Spirit Healers, and some other NPCs if (!corpseVisibility && !(obj->m_serverSideVisibility.GetValue(SERVERSIDE_VISIBILITY_GHOST) & m_serverSideVisibilityDetect.GetValue(SERVERSIDE_VISIBILITY_GHOST))) { // Alive players can see dead players in some cases, but other objects can't do that if (Player const* thisPlayer = ToPlayer()) { if (Player const* objPlayer = obj->ToPlayer()) { if (thisPlayer->GetTeamId() != objPlayer->GetTeamId() || !thisPlayer->IsGroupVisibleFor(objPlayer)) return false; } else return false; } else return false; } if (obj->IsInvisibleDueToDespawn()) return false; // pussywizard: arena spectator if (this->GetTypeId() == TYPEID_PLAYER) if (((const Player*)this)->IsSpectator() && ((const Player*)this)->FindMap()->IsBattleArena() && (obj->m_invisibility.GetFlags() || obj->m_stealth.GetFlags())) return false; if (!CanDetect(obj, ignoreStealth, !distanceCheck)) return false; return true; } bool WorldObject::CanNeverSee(WorldObject const* obj) const { if (GetTypeId() == TYPEID_UNIT && obj->GetTypeId() == TYPEID_UNIT) return GetMap() != obj->GetMap() || (!InSamePhase(obj) && ToUnit()->GetVehicleBase() != obj && this != obj->ToUnit()->GetVehicleBase()); return GetMap() != obj->GetMap() || !InSamePhase(obj); } bool WorldObject::CanDetect(WorldObject const* obj, bool ignoreStealth, bool checkClient) const { const WorldObject* seer = this; // Pets don't have detection, they use the detection of their masters if (const Unit* thisUnit = ToUnit()) if (Unit* controller = thisUnit->GetCharmerOrOwner()) seer = controller; if (obj->IsAlwaysDetectableFor(seer) || GetEntry() == WORLD_TRIGGER) // xinef: World Trigger can detect all objects, used for wild gameobjects without owner! return true; if (!ignoreStealth) { if (!seer->CanDetectInvisibilityOf(obj)) // xinef: added ignoreStealth, allow AoE spells to hit invisible targets! return false; if (!seer->CanDetectStealthOf(obj)) { // xinef: ignore units players have at client, this cant be cheated! if (checkClient) { if (GetTypeId() != TYPEID_PLAYER || !ToPlayer()->HaveAtClient(obj)) return false; } else return false; } } return true; } bool WorldObject::CanDetectInvisibilityOf(WorldObject const* obj) const { uint32 mask = obj->m_invisibility.GetFlags() & m_invisibilityDetect.GetFlags(); // xinef: include invisible flags of caster in the mask, 2 invisible objects should be able to detect eachother mask |= obj->m_invisibility.GetFlags() & m_invisibility.GetFlags(); // Check for not detected types if (mask != obj->m_invisibility.GetFlags()) return false; // It isn't possible in invisibility to detect something that can't detect the invisible object // (it's at least true for spell: 66) // It seems like that only Units are affected by this check (couldn't see arena doors with preparation invisibility) if (obj->ToUnit()) { uint32 objMask = m_invisibility.GetFlags() & obj->m_invisibilityDetect.GetFlags(); // xinef: include invisible flags of caster in the mask, 2 invisible objects should be able to detect eachother objMask |= m_invisibility.GetFlags() & obj->m_invisibility.GetFlags(); if (objMask != m_invisibility.GetFlags()) return false; } for (uint32 i = 0; i < TOTAL_INVISIBILITY_TYPES; ++i) { if (!(mask & (1 << i))) continue; // xinef: visible for the same invisibility type: if (m_invisibility.GetValue(InvisibilityType(i)) && obj->m_invisibility.GetValue(InvisibilityType(i))) continue; int32 objInvisibilityValue = obj->m_invisibility.GetValue(InvisibilityType(i)); int32 ownInvisibilityDetectValue = m_invisibilityDetect.GetValue(InvisibilityType(i)); // Too low value to detect if (ownInvisibilityDetectValue < objInvisibilityValue) return false; } return true; } bool WorldObject::CanDetectStealthOf(WorldObject const* obj) const { // Combat reach is the minimal distance (both in front and behind), // and it is also used in the range calculation. // One stealth point increases the visibility range by 0.3 yard. if (!obj->m_stealth.GetFlags()) return true; // dead players shouldnt be able to detect stealth on arenas if (isType(TYPEMASK_PLAYER)) if (!ToPlayer()->IsAlive()) return false; float distance = GetExactDist(obj); float combatReach = 0.0f; if (isType(TYPEMASK_UNIT)) combatReach = ((Unit*)this)->GetCombatReach(); if (distance < combatReach) return true; if (!HasInArc(M_PI, obj)) return false; for (uint32 i = 0; i < TOTAL_STEALTH_TYPES; ++i) { if (!(obj->m_stealth.GetFlags() & (1 << i))) continue; if (isType(TYPEMASK_UNIT)) if (((Unit*)this)->HasAuraTypeWithMiscvalue(SPELL_AURA_DETECT_STEALTH, i)) return true; // Starting points int32 detectionValue = 30; // Level difference: 5 point / level, starting from level 1. // There may be spells for this and the starting points too, but // not in the DBCs of the client. detectionValue += int32(getLevelForTarget(obj) - 1) * 5; // Apply modifiers detectionValue += m_stealthDetect.GetValue(StealthType(i)); if (obj->isType(TYPEMASK_GAMEOBJECT)) { detectionValue += 30; // pussywizard: increase detection range for gameobjects (ie. traps) if (Unit* owner = ((GameObject*)obj)->GetOwner()) detectionValue -= int32(owner->getLevelForTarget(this) - 1) * 5; } detectionValue -= obj->m_stealth.GetValue(StealthType(i)); // Calculate max distance float visibilityRange = float(detectionValue) * 0.3f + combatReach; if (visibilityRange > MAX_PLAYER_STEALTH_DETECT_RANGE) visibilityRange = MAX_PLAYER_STEALTH_DETECT_RANGE; if (distance > visibilityRange) return false; } return true; } void WorldObject::SendPlaySound(uint32 Sound, bool OnlySelf) { WorldPacket data(SMSG_PLAY_SOUND, 4); data << Sound; if (OnlySelf && GetTypeId() == TYPEID_PLAYER) this->ToPlayer()->GetSession()->SendPacket(&data); else SendMessageToSet(&data, true); // ToSelf ignored in this case } void Object::ForceValuesUpdateAtIndex(uint32 i) { _changesMask.SetBit(i); if (m_inWorld && !m_objectUpdated) { sObjectAccessor->AddUpdateObject(this); m_objectUpdated = true; } } namespace Trinity { class MonsterChatBuilder { public: MonsterChatBuilder(WorldObject const* obj, ChatMsg msgtype, int32 textId, uint32 language, WorldObject const* target) : i_object(obj), i_msgtype(msgtype), i_textId(textId), i_language(Language(language)), i_target(target) { } void operator()(WorldPacket& data, LocaleConstant loc_idx) { char const* text = sObjectMgr->GetTrinityString(i_textId, loc_idx); ChatHandler::BuildChatPacket(data, i_msgtype, i_language, i_object, i_target, text, 0, "", loc_idx); } private: WorldObject const* i_object; ChatMsg i_msgtype; int32 i_textId; Language i_language; WorldObject const* i_target; }; class MonsterCustomChatBuilder { public: MonsterCustomChatBuilder(WorldObject const* obj, ChatMsg msgtype, const char* text, uint32 language, WorldObject const* target) : i_object(obj), i_msgtype(msgtype), i_text(text), i_language(Language(language)), i_target(target) {} void operator()(WorldPacket& data, LocaleConstant loc_idx) { ChatHandler::BuildChatPacket(data, i_msgtype, i_language, i_object, i_target, i_text, 0, "", loc_idx); } private: WorldObject const* i_object; ChatMsg i_msgtype; const char* i_text; Language i_language; WorldObject const* i_target; }; } // namespace Trinity void WorldObject::MonsterSay(const char* text, uint32 language, WorldObject const* target) { CellCoord p = Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()); Cell cell(p); cell.SetNoCreate(); Trinity::MonsterCustomChatBuilder say_build(this, CHAT_MSG_MONSTER_SAY, text, language, target); Trinity::LocalizedPacketDo say_do(say_build); Trinity::PlayerDistWorker > say_worker(this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), say_do); TypeContainerVisitor >, WorldTypeMapContainer > message(say_worker); cell.Visit(p, message, *GetMap(), *this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY)); } void WorldObject::MonsterSay(int32 textId, uint32 language, WorldObject const* target) { CellCoord p = Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()); Cell cell(p); cell.SetNoCreate(); Trinity::MonsterChatBuilder say_build(this, CHAT_MSG_MONSTER_SAY, textId, language, target); Trinity::LocalizedPacketDo say_do(say_build); Trinity::PlayerDistWorker > say_worker(this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY), say_do); TypeContainerVisitor >, WorldTypeMapContainer > message(say_worker); cell.Visit(p, message, *GetMap(), *this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_SAY)); } void WorldObject::MonsterYell(const char* text, uint32 language, WorldObject const* target) { CellCoord p = Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()); Cell cell(p); cell.SetNoCreate(); Trinity::MonsterCustomChatBuilder say_build(this, CHAT_MSG_MONSTER_YELL, text, language, target); Trinity::LocalizedPacketDo say_do(say_build); Trinity::PlayerDistWorker > say_worker(this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), say_do); TypeContainerVisitor >, WorldTypeMapContainer > message(say_worker); cell.Visit(p, message, *GetMap(), *this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL)); } void WorldObject::MonsterYell(int32 textId, uint32 language, WorldObject const* target) { CellCoord p = Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()); Cell cell(p); cell.SetNoCreate(); Trinity::MonsterChatBuilder say_build(this, CHAT_MSG_MONSTER_YELL, textId, language, target); Trinity::LocalizedPacketDo say_do(say_build); Trinity::PlayerDistWorker > say_worker(this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL), say_do); TypeContainerVisitor >, WorldTypeMapContainer > message(say_worker); cell.Visit(p, message, *GetMap(), *this, sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_YELL)); } void WorldObject::MonsterTextEmote(const char* text, WorldObject const* target, bool IsBossEmote) { WorldPacket data; ChatHandler::BuildChatPacket(data, IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, LANG_UNIVERSAL, this, target, text); SendMessageToSetInRange(&data, (IsBossEmote ? 200.0f : sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)), true); } void WorldObject::MonsterTextEmote(int32 textId, WorldObject const* target, bool IsBossEmote) { CellCoord p = Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()); Cell cell(p); cell.SetNoCreate(); Trinity::MonsterChatBuilder say_build(this, IsBossEmote ? CHAT_MSG_RAID_BOSS_EMOTE : CHAT_MSG_MONSTER_EMOTE, textId, LANG_UNIVERSAL, target); Trinity::LocalizedPacketDo say_do(say_build); Trinity::PlayerDistWorker > say_worker(this, (IsBossEmote ? 200.0f : sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE)), say_do); TypeContainerVisitor >, WorldTypeMapContainer > message(say_worker); cell.Visit(p, message, *GetMap(), *this, (IsBossEmote ? 200.0f : sWorld->getFloatConfig(CONFIG_LISTEN_RANGE_TEXTEMOTE))); } void WorldObject::MonsterWhisper(const char* text, Player const* target, bool IsBossWhisper) { if (!target) return; LocaleConstant loc_idx = target->GetSession()->GetSessionDbLocaleIndex(); WorldPacket data; ChatHandler::BuildChatPacket(data, IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, LANG_UNIVERSAL, this, target, text, 0, "", loc_idx); target->GetSession()->SendPacket(&data); } void WorldObject::MonsterWhisper(int32 textId, Player const* target, bool IsBossWhisper) { if (!target) return; LocaleConstant loc_idx = target->GetSession()->GetSessionDbLocaleIndex(); char const* text = sObjectMgr->GetTrinityString(textId, loc_idx); WorldPacket data; ChatHandler::BuildChatPacket(data, IsBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, LANG_UNIVERSAL, this, target, text, 0, "", loc_idx); target->GetSession()->SendPacket(&data); } void Unit::BuildHeartBeatMsg(WorldPacket* data) const { data->Initialize(MSG_MOVE_HEARTBEAT, 32); data->append(GetPackGUID()); BuildMovementPacket(data); } // pussywizard! void WorldObject::SendMessageToSetInRange(WorldPacket* data, float dist, bool /*self*/, bool includeMargin, Player const* skipped_rcvr) { dist += GetObjectSize(); if (includeMargin) dist += VISIBILITY_COMPENSATION; // pussywizard: to ensure everyone receives all important packets Trinity::MessageDistDeliverer notifier(this, data, dist, false, skipped_rcvr); VisitNearbyWorldObject(dist, notifier); } void WorldObject::SendObjectDeSpawnAnim(uint64 guid) { WorldPacket data(SMSG_GAMEOBJECT_DESPAWN_ANIM, 8); data << uint64(guid); SendMessageToSet(&data, true); } void WorldObject::SetMap(Map* map) { ASSERT(map); ASSERT(!IsInWorld() || GetTypeId() == TYPEID_CORPSE); if (m_currMap == map) // command add npc: first create, than loadfromdb return; if (m_currMap) { sLog->outCrash("WorldObject::SetMap: obj %u new map %u %u, old map %u %u", (uint32)GetTypeId(), map->GetId(), map->GetInstanceId(), m_currMap->GetId(), m_currMap->GetInstanceId()); ASSERT(false); } m_currMap = map; m_mapId = map->GetId(); m_InstanceId = map->GetInstanceId(); if (IsWorldObject()) m_currMap->AddWorldObject(this); } void WorldObject::ResetMap() { ASSERT(m_currMap); ASSERT(!IsInWorld()); if (IsWorldObject()) m_currMap->RemoveWorldObject(this); m_currMap = NULL; //maybe not for corpse //m_mapId = 0; //m_InstanceId = 0; } Map const* WorldObject::GetBaseMap() const { ASSERT(m_currMap); return m_currMap->GetParent(); } void WorldObject::AddObjectToRemoveList() { ASSERT(m_uint32Values); Map* map = FindMap(); if (!map) { sLog->outError("Object (TypeId: %u Entry: %u GUID: %u) at attempt add to move list not have valid map (Id: %u).", GetTypeId(), GetEntry(), GetGUIDLow(), GetMapId()); return; } map->AddObjectToRemoveList(this); } TempSummon* Map::SummonCreature(uint32 entry, Position const& pos, SummonPropertiesEntry const* properties /*= NULL*/, uint32 duration /*= 0*/, Unit* summoner /*= NULL*/, uint32 spellId /*= 0*/, uint32 vehId /*= 0*/) { uint32 mask = UNIT_MASK_SUMMON; if (properties) { switch (properties->Category) { case SUMMON_CATEGORY_PET: mask = UNIT_MASK_GUARDIAN; break; case SUMMON_CATEGORY_PUPPET: mask = UNIT_MASK_PUPPET; break; case SUMMON_CATEGORY_VEHICLE: mask = UNIT_MASK_MINION; break; case SUMMON_CATEGORY_WILD: case SUMMON_CATEGORY_ALLY: case SUMMON_CATEGORY_UNK: { switch (properties->Type) { case SUMMON_TYPE_MINION: case SUMMON_TYPE_GUARDIAN: case SUMMON_TYPE_GUARDIAN2: mask = UNIT_MASK_GUARDIAN; break; case SUMMON_TYPE_TOTEM: case SUMMON_TYPE_LIGHTWELL: mask = UNIT_MASK_TOTEM; break; case SUMMON_TYPE_VEHICLE: case SUMMON_TYPE_VEHICLE2: mask = UNIT_MASK_SUMMON; break; case SUMMON_TYPE_MINIPET: case SUMMON_TYPE_JEEVES: mask = UNIT_MASK_MINION; break; default: if (properties->Flags & 512) // Mirror Image, Summon Gargoyle mask = UNIT_MASK_GUARDIAN; break; } break; } default: return NULL; } } uint32 phase = PHASEMASK_NORMAL; if (summoner) phase = summoner->GetPhaseMask(); TempSummon* summon = NULL; switch (mask) { case UNIT_MASK_SUMMON: summon = new TempSummon(properties, summoner ? summoner->GetGUID() : 0, false); break; case UNIT_MASK_GUARDIAN: summon = new Guardian(properties, summoner ? summoner->GetGUID() : 0, false); break; case UNIT_MASK_PUPPET: summon = new Puppet(properties, summoner ? summoner->GetGUID() : 0); break; case UNIT_MASK_TOTEM: summon = new Totem(properties, summoner ? summoner->GetGUID() : 0); break; case UNIT_MASK_MINION: summon = new Minion(properties, summoner ? summoner->GetGUID() : 0, false); break; default: return NULL; } EnsureGridLoaded(Cell(pos.GetPositionX(), pos.GetPositionY())); if (!summon->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_UNIT), this, phase, entry, vehId, pos.GetPositionX(), pos.GetPositionY(), pos.GetPositionZ(), pos.GetOrientation())) { delete summon; return NULL; } summon->SetUInt32Value(UNIT_CREATED_BY_SPELL, spellId); summon->SetHomePosition(pos); summon->InitStats(duration); AddToMap(summon->ToCreature(), (IS_PLAYER_GUID(summon->GetOwnerGUID()) || summoner && summoner->GetTransport())); summon->InitSummon(); //ObjectAccessor::UpdateObjectVisibility(summon); return summon; } /** * Summons group of creatures. * * @param group Id of group to summon. * @param list List to store pointers to summoned creatures. */ void Map::SummonCreatureGroup(uint8 group, std::list* list /*= NULL*/) { std::vector const* data = sObjectMgr->GetSummonGroup(GetId(), SUMMONER_TYPE_MAP, group); if (!data) return; for (std::vector::const_iterator itr = data->begin(); itr != data->end(); ++itr) if (TempSummon* summon = SummonCreature(itr->entry, itr->pos, NULL, itr->time)) if (list) list->push_back(summon); } GameObject* Map::SummonGameObject(uint32 entry, float x, float y, float z, float ang, float rotation0, float rotation1, float rotation2, float rotation3, uint32 respawnTime, bool checkTransport) { GameObjectTemplate const* goinfo = sObjectMgr->GetGameObjectTemplate(entry); if (!goinfo) { sLog->outErrorDb("Gameobject template %u not found in database!", entry); return NULL; } GameObject* go = sObjectMgr->IsGameObjectStaticTransport(entry) ? new StaticTransport() : new GameObject(); if (!go->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_GAMEOBJECT), entry, this, PHASEMASK_NORMAL, x, y, z, ang, G3D::Quat(rotation0, rotation1, rotation2, rotation3), 100, GO_STATE_READY)) { delete go; return NULL; } // Xinef: if gameobject is temporary, set custom spellid if (respawnTime) go->SetSpellId(1); go->SetRespawnTime(respawnTime); go->SetSpawnedByDefault(false); AddToMap(go, checkTransport); return go; } void WorldObject::SetZoneScript() { if (Map* map = FindMap()) { if (map->IsDungeon()) m_zoneScript = (ZoneScript*)map->ToInstanceMap()->GetInstanceScript(); else if (!map->IsBattlegroundOrArena()) { uint32 zoneId = GetZoneId(true); if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(zoneId)) m_zoneScript = bf; else m_zoneScript = sOutdoorPvPMgr->GetZoneScript(zoneId); } } } TempSummon* WorldObject::SummonCreature(uint32 entry, const Position &pos, TempSummonType spwtype, uint32 duration, uint32 vehId, SummonPropertiesEntry const *properties) const { if (Map* map = FindMap()) { if (TempSummon* summon = map->SummonCreature(entry, pos, properties, duration, isType(TYPEMASK_UNIT) ? (Unit*)this : NULL)) { summon->SetTempSummonType(spwtype); return summon; } } return NULL; } GameObject* WorldObject::SummonGameObject(uint32 entry, float x, float y, float z, float ang, float rotation0, float rotation1, float rotation2, float rotation3, uint32 respawnTime, bool checkTransport) { if (!IsInWorld()) return NULL; GameObjectTemplate const* goinfo = sObjectMgr->GetGameObjectTemplate(entry); if (!goinfo) { sLog->outErrorDb("Gameobject template %u not found in database!", entry); return NULL; } Map* map = GetMap(); GameObject* go = sObjectMgr->IsGameObjectStaticTransport(entry) ? new StaticTransport() : new GameObject(); if (!go->Create(sObjectMgr->GenerateLowGuid(HIGHGUID_GAMEOBJECT), entry, map, GetPhaseMask(), x, y, z, ang, G3D::Quat(rotation0, rotation1, rotation2, rotation3), 100, GO_STATE_READY)) { delete go; return NULL; } go->SetRespawnTime(respawnTime); // Xinef: if gameobject is temporary, set custom spellid if (respawnTime) go->SetSpellId(1); if (GetTypeId() == TYPEID_PLAYER || GetTypeId() == TYPEID_UNIT) //not sure how to handle this ToUnit()->AddGameObject(go); else go->SetSpawnedByDefault(false); map->AddToMap(go, checkTransport); return go; } Creature* WorldObject::SummonTrigger(float x, float y, float z, float ang, uint32 duration, bool setLevel, CreatureAI* (*GetAI)(Creature*)) { TempSummonType summonType = (duration == 0) ? TEMPSUMMON_DEAD_DESPAWN : TEMPSUMMON_TIMED_DESPAWN; Creature* summon = SummonCreature(WORLD_TRIGGER, x, y, z, ang, summonType, duration); if (!summon) return NULL; //summon->SetName(GetName()); if (setLevel && (GetTypeId() == TYPEID_PLAYER || GetTypeId() == TYPEID_UNIT)) { summon->setFaction(((Unit*)this)->getFaction()); summon->SetLevel(((Unit*)this)->getLevel()); } // Xinef: correctly set phase mask in case of gameobjects summon->SetPhaseMask(GetPhaseMask(), false); if (GetAI) summon->AIM_Initialize(GetAI(summon)); return summon; } /** * Summons group of creatures. Should be called only by instances of Creature and GameObject classes. * * @param group Id of group to summon. * @param list List to store pointers to summoned creatures. */ void WorldObject::SummonCreatureGroup(uint8 group, std::list* list /*= NULL*/) { ASSERT((GetTypeId() == TYPEID_GAMEOBJECT || GetTypeId() == TYPEID_UNIT) && "Only GOs and creatures can summon npc groups!"); std::vector const* data = sObjectMgr->GetSummonGroup(GetEntry(), GetTypeId() == TYPEID_GAMEOBJECT ? SUMMONER_TYPE_GAMEOBJECT : SUMMONER_TYPE_CREATURE, group); if (!data) return; for (std::vector::const_iterator itr = data->begin(); itr != data->end(); ++itr) if (TempSummon* summon = SummonCreature(itr->entry, itr->pos, itr->type, itr->time)) if (list) list->push_back(summon); } Creature* WorldObject::FindNearestCreature(uint32 entry, float range, bool alive) const { Creature* creature = NULL; Trinity::NearestCreatureEntryWithLiveStateInObjectRangeCheck checker(*this, entry, alive, range); Trinity::CreatureLastSearcher searcher(this, creature, checker); VisitNearbyObject(range, searcher); return creature; } GameObject* WorldObject::FindNearestGameObject(uint32 entry, float range) const { GameObject* go = NULL; Trinity::NearestGameObjectEntryInObjectRangeCheck checker(*this, entry, range); Trinity::GameObjectLastSearcher searcher(this, go, checker); VisitNearbyGridObject(range, searcher); return go; } GameObject* WorldObject::FindNearestGameObjectOfType(GameobjectTypes type, float range) const { GameObject* go = NULL; Trinity::NearestGameObjectTypeInObjectRangeCheck checker(*this, type, range); Trinity::GameObjectLastSearcher searcher(this, go, checker); VisitNearbyGridObject(range, searcher); return go; } Player* WorldObject::SelectNearestPlayer(float distance) const { Player* target = NULL; Trinity::NearestPlayerInObjectRangeCheck checker(this, distance); Trinity::PlayerLastSearcher searcher(this, target, checker); VisitNearbyObject(distance, searcher); return target; } void WorldObject::GetGameObjectListWithEntryInGrid(std::list& gameobjectList, uint32 entry, float maxSearchRange) const { CellCoord pair(Trinity::ComputeCellCoord(this->GetPositionX(), this->GetPositionY())); Cell cell(pair); cell.SetNoCreate(); Trinity::AllGameObjectsWithEntryInRange check(this, entry, maxSearchRange); Trinity::GameObjectListSearcher searcher(this, gameobjectList, check); TypeContainerVisitor, GridTypeMapContainer> visitor(searcher); cell.Visit(pair, visitor, *(this->GetMap()), *this, maxSearchRange); } void WorldObject::GetCreatureListWithEntryInGrid(std::list& creatureList, uint32 entry, float maxSearchRange) const { CellCoord pair(Trinity::ComputeCellCoord(this->GetPositionX(), this->GetPositionY())); Cell cell(pair); cell.SetNoCreate(); Trinity::AllCreaturesOfEntryInRange check(this, entry, maxSearchRange); Trinity::CreatureListSearcher searcher(this, creatureList, check); TypeContainerVisitor, GridTypeMapContainer> visitor(searcher); cell.Visit(pair, visitor, *(this->GetMap()), *this, maxSearchRange); } /* namespace Trinity { class NearUsedPosDo { public: NearUsedPosDo(WorldObject const& obj, WorldObject const* searcher, float angle, ObjectPosSelector& selector) : i_object(obj), i_searcher(searcher), i_angle(angle), i_selector(selector) {} void operator()(Corpse*) const {} void operator()(DynamicObject*) const {} void operator()(Creature* c) const { // skip self or target if (c == i_searcher || c == &i_object) return; float x, y, z; if (!c->IsAlive() || c->HasUnitState(UNIT_STATE_ROOT | UNIT_STATE_STUNNED | UNIT_STATE_DISTRACTED) || !c->GetMotionMaster()->GetDestination(x, y, z)) { x = c->GetPositionX(); y = c->GetPositionY(); } add(c, x, y); } template void operator()(T* u) const { // skip self or target if (u == i_searcher || u == &i_object) return; float x, y; x = u->GetPositionX(); y = u->GetPositionY(); add(u, x, y); } // we must add used pos that can fill places around center void add(WorldObject* u, float x, float y) const { // u is too nearest/far away to i_object if (!i_object.IsInRange2d(x, y, i_selector.m_dist - i_selector.m_size, i_selector.m_dist + i_selector.m_size)) return; float angle = i_object.GetAngle(u)-i_angle; // move angle to range -pi ... +pi while (angle > M_PI) angle -= 2.0f * M_PI; while (angle < -M_PI) angle += 2.0f * M_PI; // dist include size of u float dist2d = i_object.GetDistance2d(x, y); i_selector.AddUsedPos(u->GetObjectSize(), angle, dist2d + i_object.GetObjectSize()); } private: WorldObject const& i_object; WorldObject const* i_searcher; float i_angle; ObjectPosSelector& i_selector; }; } // namespace Trinity */ //=================================================================================================== void WorldObject::GetNearPoint2D(float &x, float &y, float distance2d, float absAngle) const { x = GetPositionX() + (GetObjectSize() + distance2d) * cos(absAngle); y = GetPositionY() + (GetObjectSize() + distance2d) * sin(absAngle); Trinity::NormalizeMapCoord(x); Trinity::NormalizeMapCoord(y); } void WorldObject::GetNearPoint(WorldObject const* searcher, float &x, float &y, float &z, float searcher_size, float distance2d, float absAngle) const { GetNearPoint2D(x, y, distance2d+searcher_size, absAngle); z = GetPositionZ(); if (searcher) searcher->UpdateAllowedPositionZ(x, y, z); else UpdateAllowedPositionZ(x, y, z); /* // if detection disabled, return first point if (!sWorld->getIntConfig(CONFIG_DETECT_POS_COLLISION)) { UpdateGroundPositionZ(x, y, z); // update to LOS height if available return; } // or remember first point float first_x = x; float first_y = y; bool first_los_conflict = false; // first point LOS problems // prepare selector for work ObjectPosSelector selector(GetPositionX(), GetPositionY(), GetObjectSize(), distance2d+searcher_size); // adding used positions around object { CellCoord p(Trinity::ComputeCellCoord(GetPositionX(), GetPositionY())); Cell cell(p); cell.SetNoCreate(); Trinity::NearUsedPosDo u_do(*this, searcher, absAngle, selector); Trinity::WorldObjectWorker worker(this, u_do); TypeContainerVisitor, GridTypeMapContainer > grid_obj_worker(worker); TypeContainerVisitor, WorldTypeMapContainer > world_obj_worker(worker); CellLock cell_lock(cell, p); cell_lock->Visit(cell_lock, grid_obj_worker, *GetMap(), *this, distance2d); cell_lock->Visit(cell_lock, world_obj_worker, *GetMap(), *this, distance2d); } // maybe can just place in primary position if (selector.CheckOriginal()) { UpdateGroundPositionZ(x, y, z); // update to LOS height if available if (IsWithinLOS(x, y, z)) return; first_los_conflict = true; // first point have LOS problems } float angle; // candidate of angle for free pos // special case when one from list empty and then empty side preferred if (selector.FirstAngle(angle)) { GetNearPoint2D(x, y, distance2d, absAngle+angle); z = GetPositionZ(); UpdateGroundPositionZ(x, y, z); // update to LOS height if available if (IsWithinLOS(x, y, z)) return; } // set first used pos in lists selector.InitializeAngle(); // select in positions after current nodes (selection one by one) while (selector.NextAngle(angle)) // angle for free pos { GetNearPoint2D(x, y, distance2d, absAngle+angle); z = GetPositionZ(); UpdateGroundPositionZ(x, y, z); // update to LOS height if available if (IsWithinLOS(x, y, z)) return; } // BAD NEWS: not free pos (or used or have LOS problems) // Attempt find _used_ pos without LOS problem if (!first_los_conflict) { x = first_x; y = first_y; UpdateGroundPositionZ(x, y, z); // update to LOS height if available return; } // special case when one from list empty and then empty side preferred if (selector.IsNonBalanced()) { if (!selector.FirstAngle(angle)) // _used_ pos { GetNearPoint2D(x, y, distance2d, absAngle+angle); z = GetPositionZ(); UpdateGroundPositionZ(x, y, z); // update to LOS height if available if (IsWithinLOS(x, y, z)) return; } } // set first used pos in lists selector.InitializeAngle(); // select in positions after current nodes (selection one by one) while (selector.NextUsedAngle(angle)) // angle for used pos but maybe without LOS problem { GetNearPoint2D(x, y, distance2d, absAngle+angle); z = GetPositionZ(); UpdateGroundPositionZ(x, y, z); // update to LOS height if available if (IsWithinLOS(x, y, z)) return; } // BAD BAD NEWS: all found pos (free and used) have LOS problem :( x = first_x; y = first_y; UpdateGroundPositionZ(x, y, z); // update to LOS height if available */ } bool WorldObject::GetClosePoint(float &x, float &y, float &z, float size, float distance2d, float angle, const WorldObject* forWho, bool force) const { // angle calculated from current orientation GetNearPoint(forWho, x, y, z, size, distance2d, GetOrientation() + angle); if (fabs(this->GetPositionZ()-z) > 3.0f || !IsWithinLOS(x, y, z)) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); if (forWho) if (const Unit* u = forWho->ToUnit()) u->UpdateAllowedPositionZ(x, y, z); } float maxDist = GetObjectSize() + size + distance2d + 1.0f; if (GetExactDistSq(x, y, z) >= maxDist*maxDist) { if (force) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); return true; } return false; } return true; } void WorldObject::GetContactPoint(const WorldObject* obj, float &x, float &y, float &z, float distance2d) const { // angle to face `obj` to `this` using distance includes size of `obj` GetNearPoint(obj, x, y, z, obj->GetObjectSize(), distance2d, GetAngle(obj)); if (fabs(this->GetPositionZ()-z) > 3.0f || !IsWithinLOS(x, y, z)) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); obj->UpdateAllowedPositionZ(x, y, z); } } void WorldObject::GetChargeContactPoint(const WorldObject* obj, float &x, float &y, float &z, float distance2d) const { // angle to face `obj` to `this` using distance includes size of `obj` GetNearPoint(obj, x, y, z, obj->GetObjectSize(), distance2d, GetAngle(obj)); if (fabs(this->GetPositionZ()-z) > 3.0f || !IsWithinLOS(x, y, z)) { x = this->GetPositionX(); y = this->GetPositionY(); z = this->GetPositionZ(); obj->UpdateGroundPositionZ(x, y, z); } } void WorldObject::MovePosition(Position &pos, float dist, float angle) { angle += m_orientation; float destx, desty, destz, ground, floor; destx = pos.m_positionX + dist * cos(angle); desty = pos.m_positionY + dist * sin(angle); // Prevent invalid coordinates here, position is unchanged if (!Trinity::IsValidMapCoord(destx, desty)) { sLog->outCrash("WorldObject::MovePosition invalid coordinates X: %f and Y: %f were passed!", destx, desty); return; } ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; float step = dist/10.0f; for (uint8 j = 0; j < 10; ++j) { // do not allow too big z changes if (fabs(pos.m_positionZ - destz) > 6.0f) { destx -= step * cos(angle); desty -= step * sin(angle); ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; } // we have correct destz now else break; } pos.Relocate(destx, desty, destz); Trinity::NormalizeMapCoord(pos.m_positionX); Trinity::NormalizeMapCoord(pos.m_positionY); UpdateGroundPositionZ(pos.m_positionX, pos.m_positionY, pos.m_positionZ); pos.m_orientation = m_orientation; } void WorldObject::MovePositionToFirstCollision(Position &pos, float dist, float angle) { angle += m_orientation; float destx, desty, destz; destx = pos.m_positionX + dist * cos(angle); desty = pos.m_positionY + dist * sin(angle); destz = pos.m_positionZ; if (isType(TYPEMASK_UNIT|TYPEMASK_PLAYER) && !ToUnit()->IsInWater()) destz += 2.0f; // Prevent invalid coordinates here, position is unchanged if (!Trinity::IsValidMapCoord(destx, desty)) { sLog->outCrash("WorldObject::MovePositionToFirstCollision invalid coordinates X: %f and Y: %f were passed!", destx, desty); return; } // Xinef: ugly hack for dalaran arena float selfAddition = 1.5f; float allowedDiff = 6.0f; float newDist = dist; if (GetMapId() == 617) { allowedDiff = 3.5f; selfAddition = 1.0f; destz = pos.m_positionZ + 1.0f; } else UpdateAllowedPositionZ(destx, desty, destz); bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(GetMapId(), pos.m_positionX, pos.m_positionY, pos.m_positionZ+selfAddition, destx, desty, destz+0.5f, destx, desty, destz, -0.5f); // collision occured if (col) { // move back a bit if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) { destx -= CONTACT_DISTANCE * cos(angle); desty -= CONTACT_DISTANCE * sin(angle); } newDist = sqrt((pos.m_positionX - destx)*(pos.m_positionX - destx) + (pos.m_positionY - desty)*(pos.m_positionY - desty)); } // check dynamic collision col = GetMap()->getObjectHitPos(GetPhaseMask(), pos.m_positionX, pos.m_positionY, pos.m_positionZ+selfAddition, destx, desty, destz+0.5f, destx, desty, destz, -0.5f); // Collided with a gameobject if (col) { // move back a bit if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) { destx -= CONTACT_DISTANCE * cos(angle); desty -= CONTACT_DISTANCE * sin(angle); } newDist = sqrt((pos.m_positionX - destx)*(pos.m_positionX - destx) + (pos.m_positionY - desty)*(pos.m_positionY - desty)); } float step = newDist / 10.0f; for (uint8 j = 0; j < 10; ++j) { // do not allow too big z changes if (fabs(pos.m_positionZ - destz) > allowedDiff) { destx -= step * cos(angle); desty -= step * sin(angle); UpdateAllowedPositionZ(destx, desty, destz); } // we have correct destz now else break; } Trinity::NormalizeMapCoord(destx); Trinity::NormalizeMapCoord(desty); UpdateAllowedPositionZ(destx, desty, destz); float ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); float floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, destz, true); ground = fabs(ground - destz) <= fabs(floor - pos.m_positionZ) ? ground : floor; if (destz < ground) destz = ground; // Xinef: check if last z updates did not move z too far away //newDist = pos.GetExactDist(destx, desty, destz); //float ratio = newDist / dist; //if (ratio > 1.3f) //{ // ratio = (1 / ratio) + (0.3f / ratio); // destx = pos.GetPositionX() + (fabs(destx - pos.GetPositionX()) * cos(angle) * ratio); // desty = pos.GetPositionY() + (fabs(desty - pos.GetPositionY()) * sin(angle) * ratio); // destz = pos.GetPositionZ() + (fabs(destz - pos.GetPositionZ()) * ratio * (destz < pos.GetPositionZ() ? -1.0f : 1.0f)); //} pos.Relocate(destx, desty, destz); pos.m_orientation = m_orientation; } void WorldObject::MovePositionToFirstCollisionForTotem(Position &pos, float dist, float angle, bool forGameObject) { angle += m_orientation; float destx, desty, destz, ground, floor; pos.m_positionZ += 2.0f; destx = pos.m_positionX + dist * cos(angle); desty = pos.m_positionY + dist * sin(angle); destz = pos.GetPositionZ(); // Prevent invalid coordinates here, position is unchanged if (!Trinity::IsValidMapCoord(destx, desty)) { sLog->outCrash("WorldObject::MovePositionToFirstCollision invalid coordinates X: %f and Y: %f were passed!", destx, desty); return; } bool col = VMAP::VMapFactory::createOrGetVMapManager()->getObjectHitPos(GetMapId(), pos.m_positionX, pos.m_positionY, pos.m_positionZ, destx, desty, destz, destx, desty, destz, -0.5f); // collision occured if (col) { // move back a bit if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) { destx -= CONTACT_DISTANCE * cos(angle); desty -= CONTACT_DISTANCE * sin(angle); } dist = sqrt((pos.m_positionX - destx)*(pos.m_positionX - destx) + (pos.m_positionY - desty)*(pos.m_positionY - desty)); } // check dynamic collision col = GetMap()->getObjectHitPos(GetPhaseMask(), pos.m_positionX, pos.m_positionY, pos.m_positionZ+0.5f, destx, desty, destz+0.5f, destx, desty, destz, -0.5f); // Collided with a gameobject if (col) { // move back a bit if (pos.GetExactDist2d(destx, desty) > CONTACT_DISTANCE) { destx -= CONTACT_DISTANCE * cos(angle); desty -= CONTACT_DISTANCE * sin(angle); } dist = sqrt((pos.m_positionX - destx)*(pos.m_positionX - destx) + (pos.m_positionY - desty)*(pos.m_positionY - desty)); } float prevdx = destx, prevdy = desty, prevdz = destz; bool anyvalid = false; ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; // xinef: if we have gameobject, store last valid ground position // xinef: I assume you wanted to spawn totem in air and allow it to fall down if no valid position was found if (forGameObject) prevdz = destz; float step = dist/10.0f; for (uint8 j = 0; j < 10; ++j) { // do not allow too big z changes if (fabs(pos.m_positionZ - destz) > 4.0f) { destx -= step * cos(angle); desty -= step * sin(angle); ground = GetMap()->GetHeight(GetPhaseMask(), destx, desty, MAX_HEIGHT, true); floor = GetMap()->GetHeight(GetPhaseMask(), destx, desty, pos.m_positionZ, true); destz = fabs(ground - pos.m_positionZ) <= fabs(floor - pos.m_positionZ) ? ground : floor; if (j == 9 && fabs(pos.m_positionZ - destz) <= 4.0f) anyvalid = true; } // we have correct destz now else { anyvalid = true; break; } } if (!anyvalid) { destx = prevdx; desty = prevdy; destz = prevdz; } Trinity::NormalizeMapCoord(destx); Trinity::NormalizeMapCoord(desty); pos.Relocate(destx, desty, destz); pos.m_orientation = m_orientation; } void WorldObject::SetPhaseMask(uint32 newPhaseMask, bool update) { m_phaseMask = newPhaseMask; if (update && IsInWorld()) UpdateObjectVisibility(); } void WorldObject::PlayDistanceSound(uint32 sound_id, Player* target /*= NULL*/) { WorldPacket data(SMSG_PLAY_OBJECT_SOUND, 4+8); data << uint32(sound_id); data << uint64(GetGUID()); if (target) target->SendDirectMessage(&data); else SendMessageToSet(&data, true); } void WorldObject::PlayDirectSound(uint32 sound_id, Player* target /*= NULL*/) { WorldPacket data(SMSG_PLAY_SOUND, 4); data << uint32(sound_id); if (target) target->SendDirectMessage(&data); else SendMessageToSet(&data, true); } void WorldObject::DestroyForNearbyPlayers() { if (!IsInWorld()) return; std::list targets; Trinity::AnyPlayerInObjectRangeCheck check(this, GetVisibilityRange()+VISIBILITY_COMPENSATION, false); Trinity::PlayerListSearcherWithSharedVision searcher(this, targets, check); VisitNearbyWorldObject(GetVisibilityRange()+VISIBILITY_COMPENSATION, searcher); for (std::list::const_iterator iter = targets.begin(); iter != targets.end(); ++iter) { Player* player = (*iter); if (player == this) continue; if (!player->HaveAtClient(this)) continue; if (isType(TYPEMASK_UNIT) && ((Unit*)this)->GetCharmerGUID() == player->GetGUID()) // TODO: this is for puppet continue; DestroyForPlayer(player); player->m_clientGUIDs.erase(GetGUID()); } } void WorldObject::UpdateObjectVisibility(bool /*forced*/, bool /*fromUpdate*/) { //updates object's visibility for nearby players Trinity::VisibleChangesNotifier notifier(*this); VisitNearbyWorldObject(GetVisibilityRange()+VISIBILITY_COMPENSATION, notifier); } void WorldObject::AddToNotify(uint16 f) { if (!(m_notifyflags & f)) if (Unit* u = ToUnit()) { if (f & NOTIFY_VISIBILITY_CHANGED) { uint32 EVENT_VISIBILITY_DELAY = u->FindMap() ? DynamicVisibilityMgr::GetVisibilityNotifyDelay(u->FindMap()->GetEntry()->map_type) : 1000; uint32 diff = getMSTimeDiff(u->m_last_notify_mstime, World::GetGameTimeMS()); if (diff >= EVENT_VISIBILITY_DELAY/2) EVENT_VISIBILITY_DELAY /= 2; else EVENT_VISIBILITY_DELAY -= diff; u->m_delayed_unit_relocation_timer = EVENT_VISIBILITY_DELAY; u->m_last_notify_mstime = World::GetGameTimeMS()+EVENT_VISIBILITY_DELAY-1; } else if (f & NOTIFY_AI_RELOCATION) { u->m_delayed_unit_ai_notify_timer = u->FindMap() ? DynamicVisibilityMgr::GetAINotifyDelay(u->FindMap()->GetEntry()->map_type) : 500; } m_notifyflags |= f; } } struct WorldObjectChangeAccumulator { UpdateDataMapType& i_updateDatas; UpdatePlayerSet& i_playerSet; WorldObject& i_object; WorldObjectChangeAccumulator(WorldObject &obj, UpdateDataMapType &d, UpdatePlayerSet &p) : i_updateDatas(d), i_playerSet(p), i_object(obj) { i_playerSet.clear(); } void Visit(PlayerMapType &m) { Player* source = NULL; for (PlayerMapType::iterator iter = m.begin(); iter != m.end(); ++iter) { source = iter->GetSource(); BuildPacket(source); if (source->HasSharedVision()) { SharedVisionList::const_iterator it = source->GetSharedVisionList().begin(); for (; it != source->GetSharedVisionList().end(); ++it) BuildPacket(*it); } } } void Visit(CreatureMapType &m) { Creature* source = NULL; for (CreatureMapType::iterator iter = m.begin(); iter != m.end(); ++iter) { source = iter->GetSource(); if (source->HasSharedVision()) { SharedVisionList::const_iterator it = source->GetSharedVisionList().begin(); for (; it != source->GetSharedVisionList().end(); ++it) BuildPacket(*it); } } } void Visit(DynamicObjectMapType &m) { DynamicObject* source = NULL; for (DynamicObjectMapType::iterator iter = m.begin(); iter != m.end(); ++iter) { source = iter->GetSource(); uint64 guid = source->GetCasterGUID(); if (IS_PLAYER_GUID(guid)) { //Caster may be NULL if DynObj is in removelist if (Player* caster = ObjectAccessor::FindPlayer(guid)) if (caster->GetUInt64Value(PLAYER_FARSIGHT) == source->GetGUID()) BuildPacket(caster); } } } void BuildPacket(Player* player) { // Only send update once to a player if (i_playerSet.find(player->GetGUIDLow()) == i_playerSet.end() && player->HaveAtClient(&i_object)) { i_object.BuildFieldsUpdate(player, i_updateDatas); i_playerSet.insert(player->GetGUIDLow()); } } template void Visit(GridRefManager &) {} }; void WorldObject::BuildUpdate(UpdateDataMapType& data_map, UpdatePlayerSet& player_set) { CellCoord p = Trinity::ComputeCellCoord(GetPositionX(), GetPositionY()); Cell cell(p); cell.SetNoCreate(); WorldObjectChangeAccumulator notifier(*this, data_map, player_set); TypeContainerVisitor player_notifier(notifier); Map& map = *GetMap(); //we must build packets for all visible players cell.Visit(p, player_notifier, map, *this, GetVisibilityRange()+VISIBILITY_COMPENSATION); ClearUpdateMask(false); } void WorldObject::GetCreaturesWithEntryInRange(std::list &creatureList, float radius, uint32 entry) { CellCoord pair(Trinity::ComputeCellCoord(this->GetPositionX(), this->GetPositionY())); Cell cell(pair); cell.SetNoCreate(); Trinity::AllCreaturesOfEntryInRange check(this, entry, radius); Trinity::CreatureListSearcher searcher(this, creatureList, check); TypeContainerVisitor, WorldTypeMapContainer> world_visitor(searcher); cell.Visit(pair, world_visitor, *(this->GetMap()), *this, radius); TypeContainerVisitor, GridTypeMapContainer> grid_visitor(searcher); cell.Visit(pair, grid_visitor, *(this->GetMap()), *this, radius); } uint64 WorldObject::GetTransGUID() const { if (GetTransport()) return GetTransport()->GetGUID(); return 0; }