568 lines
18 KiB
C++
568 lines
18 KiB
C++
/**
|
|
* MaNGOS is a full featured server for World of Warcraft, supporting
|
|
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
|
*
|
|
* Copyright (C) 2005-2016 MaNGOS project <https://getmangos.eu>
|
|
*
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
|
|
* and lore are copyrighted by Blizzard Entertainment, Inc.
|
|
*/
|
|
|
|
#include "Common.h"
|
|
|
|
#include "Transports.h"
|
|
#include "MapManager.h"
|
|
#include "ObjectMgr.h"
|
|
#include "ObjectGuid.h"
|
|
#include "Path.h"
|
|
|
|
#include "WorldPacket.h"
|
|
#include "DBCStores.h"
|
|
#include "ProgressBar.h"
|
|
|
|
void MapManager::LoadTransports()
|
|
{
|
|
QueryResult* result = WorldDatabase.Query("SELECT entry, name, period FROM transports");
|
|
|
|
uint32 count = 0;
|
|
|
|
if (!result)
|
|
{
|
|
BarGoLink bar(1);
|
|
bar.step();
|
|
sLog.outString(">> Loaded %u transports", count);
|
|
sLog.outString();
|
|
return;
|
|
}
|
|
|
|
BarGoLink bar(result->GetRowCount());
|
|
|
|
do
|
|
{
|
|
bar.step();
|
|
|
|
Transport* t = new Transport;
|
|
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32 entry = fields[0].GetUInt32();
|
|
std::string name = fields[1].GetCppString();
|
|
t->m_period = fields[2].GetUInt32();
|
|
|
|
const GameObjectInfo* goinfo = ObjectMgr::GetGameObjectInfo(entry);
|
|
|
|
if (!goinfo)
|
|
{
|
|
sLog.outErrorDb("Transport ID:%u, Name: %s, will not be loaded, gameobject_template missing", entry, name.c_str());
|
|
delete t;
|
|
continue;
|
|
}
|
|
|
|
if (goinfo->type != GAMEOBJECT_TYPE_MO_TRANSPORT)
|
|
{
|
|
sLog.outErrorDb("Transport ID:%u, Name: %s, will not be loaded, gameobject_template type wrong", entry, name.c_str());
|
|
delete t;
|
|
continue;
|
|
}
|
|
|
|
// sLog.outString("Loading transport %d between %s, %s", entry, name.c_str(), goinfo->name);
|
|
|
|
std::set<uint32> mapsUsed;
|
|
|
|
if (!t->GenerateWaypoints(goinfo->moTransport.taxiPathId, mapsUsed))
|
|
// skip transports with empty waypoints list
|
|
{
|
|
sLog.outErrorDb("Transport (path id %u) path size = 0. Transport ignored, check DBC files or transport GO data0 field.", goinfo->moTransport.taxiPathId);
|
|
delete t;
|
|
continue;
|
|
}
|
|
|
|
float x, y, z, o;
|
|
uint32 mapid;
|
|
x = t->m_WayPoints[0].x; y = t->m_WayPoints[0].y; z = t->m_WayPoints[0].z; mapid = t->m_WayPoints[0].mapid; o = 1;
|
|
|
|
// current code does not support transports in dungeon!
|
|
const MapEntry* pMapInfo = sMapStore.LookupEntry(mapid);
|
|
if (!pMapInfo || pMapInfo->Instanceable())
|
|
{
|
|
delete t;
|
|
continue;
|
|
}
|
|
|
|
// creates the Gameobject
|
|
if (!t->Create(entry, mapid, x, y, z, o, GO_ANIMPROGRESS_DEFAULT))
|
|
{
|
|
delete t;
|
|
continue;
|
|
}
|
|
|
|
m_Transports.insert(t);
|
|
|
|
for (std::set<uint32>::const_iterator i = mapsUsed.begin(); i != mapsUsed.end(); ++i)
|
|
{ m_TransportsByMap[*i].insert(t); }
|
|
|
|
// If we someday decide to use the grid to track transports, here:
|
|
t->SetMap(sMapMgr.CreateMap(mapid, t));
|
|
|
|
// t->GetMap()->Add<GameObject>((GameObject *)t);
|
|
++count;
|
|
}
|
|
while (result->NextRow());
|
|
delete result;
|
|
|
|
// check transport data DB integrity
|
|
result = WorldDatabase.Query("SELECT gameobject.guid,gameobject.id,transports.name FROM gameobject,transports WHERE gameobject.id = transports.entry");
|
|
if (result) // wrong data found
|
|
{
|
|
do
|
|
{
|
|
Field* fields = result->Fetch();
|
|
|
|
uint32 guid = fields[0].GetUInt32();
|
|
uint32 entry = fields[1].GetUInt32();
|
|
std::string name = fields[2].GetCppString();
|
|
sLog.outErrorDb("Transport %u '%s' have record (GUID: %u) in `gameobject`. Transports DON'T must have any records in `gameobject` or its behavior will be unpredictable/bugged.", entry, name.c_str(), guid);
|
|
}
|
|
while (result->NextRow());
|
|
|
|
delete result;
|
|
}
|
|
|
|
sLog.outString(">> Loaded %u transports", count);
|
|
sLog.outString();
|
|
}
|
|
|
|
Transport::Transport() : GameObject()
|
|
{
|
|
m_updateFlag = (UPDATEFLAG_TRANSPORT | UPDATEFLAG_ALL | UPDATEFLAG_HAS_POSITION);
|
|
}
|
|
|
|
bool Transport::Create(uint32 guidlow, uint32 mapid, float x, float y, float z, float ang, uint32 animprogress)
|
|
{
|
|
Relocate(x, y, z, ang);
|
|
|
|
if (!IsPositionValid())
|
|
{
|
|
sLog.outError("Transport (GUID: %u) not created. Suggested coordinates isn't valid (X: %f Y: %f)",
|
|
guidlow, x, y);
|
|
return false;
|
|
}
|
|
|
|
Object::_Create(guidlow, 0, HIGHGUID_MO_TRANSPORT);
|
|
|
|
GameObjectInfo const* goinfo = ObjectMgr::GetGameObjectInfo(guidlow);
|
|
|
|
if (!goinfo)
|
|
{
|
|
sLog.outErrorDb("Transport not created: entry in `gameobject_template` not found, guidlow: %u map: %u (X: %f Y: %f Z: %f) ang: %f", guidlow, mapid, x, y, z, ang);
|
|
return false;
|
|
}
|
|
|
|
m_goInfo = goinfo;
|
|
|
|
SetObjectScale(goinfo->size);
|
|
|
|
SetUInt32Value(GAMEOBJECT_FACTION, goinfo->faction);
|
|
SetUInt32Value(GAMEOBJECT_FLAGS, goinfo->flags);
|
|
|
|
SetEntry(goinfo->id);
|
|
|
|
//SetDisplayId(goinfo->displayId);
|
|
// Use SetDisplayId only if we have the GO assigned to a proper map!
|
|
SetUInt32Value(GAMEOBJECT_DISPLAYID, goinfo->displayId);
|
|
|
|
SetGoState(GO_STATE_READY);
|
|
SetGoType(GameobjectTypes(goinfo->type));
|
|
|
|
SetGoAnimProgress(animprogress);
|
|
|
|
SetName(goinfo->name);
|
|
|
|
return true;
|
|
}
|
|
|
|
struct keyFrame
|
|
{
|
|
explicit keyFrame(TaxiPathNodeEntry const& _node) : node(&_node),
|
|
distSinceStop(-1.0f), distUntilStop(-1.0f), distFromPrev(-1.0f), tFrom(0.0f), tTo(0.0f)
|
|
{
|
|
}
|
|
|
|
TaxiPathNodeEntry const* node;
|
|
|
|
float distSinceStop;
|
|
float distUntilStop;
|
|
float distFromPrev;
|
|
float tFrom, tTo;
|
|
};
|
|
|
|
bool Transport::GenerateWaypoints(uint32 pathid, std::set<uint32>& mapids)
|
|
{
|
|
if (pathid >= sTaxiPathNodesByPath.size())
|
|
{ return false; }
|
|
|
|
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathid];
|
|
|
|
std::vector<keyFrame> keyFrames;
|
|
int mapChange = 0;
|
|
mapids.clear();
|
|
for (size_t i = 1; i < path.size() - 1; ++i)
|
|
{
|
|
if (mapChange == 0)
|
|
{
|
|
TaxiPathNodeEntry const& node_i = path[i];
|
|
if (node_i.mapid == path[i + 1].mapid)
|
|
{
|
|
keyFrame k(node_i);
|
|
keyFrames.push_back(k);
|
|
mapids.insert(k.node->mapid);
|
|
}
|
|
else
|
|
{
|
|
mapChange = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
--mapChange;
|
|
}
|
|
}
|
|
|
|
int lastStop = -1;
|
|
int firstStop = -1;
|
|
|
|
// first cell is arrived at by teleportation :S
|
|
keyFrames[0].distFromPrev = 0;
|
|
if (keyFrames[0].node->actionFlag == 2)
|
|
{
|
|
lastStop = 0;
|
|
}
|
|
|
|
// find the rest of the distances between key points
|
|
for (size_t i = 1; i < keyFrames.size(); ++i)
|
|
{
|
|
if ((keyFrames[i].node->actionFlag == 1) || (keyFrames[i].node->mapid != keyFrames[i - 1].node->mapid))
|
|
{
|
|
keyFrames[i].distFromPrev = 0;
|
|
}
|
|
else
|
|
{
|
|
keyFrames[i].distFromPrev =
|
|
sqrt(pow(keyFrames[i].node->x - keyFrames[i - 1].node->x, 2) +
|
|
pow(keyFrames[i].node->y - keyFrames[i - 1].node->y, 2) +
|
|
pow(keyFrames[i].node->z - keyFrames[i - 1].node->z, 2));
|
|
}
|
|
if (keyFrames[i].node->actionFlag == 2)
|
|
{
|
|
// remember first stop frame
|
|
if (firstStop == -1)
|
|
{ firstStop = i; }
|
|
lastStop = i;
|
|
}
|
|
}
|
|
|
|
float tmpDist = 0;
|
|
for (size_t i = 0; i < keyFrames.size(); ++i)
|
|
{
|
|
int j = (i + lastStop) % keyFrames.size();
|
|
if (keyFrames[j].node->actionFlag == 2)
|
|
{ tmpDist = 0; }
|
|
else
|
|
{ tmpDist += keyFrames[j].distFromPrev; }
|
|
keyFrames[j].distSinceStop = tmpDist;
|
|
}
|
|
|
|
for (int i = int(keyFrames.size()) - 1; i >= 0; --i)
|
|
{
|
|
int j = (i + (firstStop + 1)) % keyFrames.size();
|
|
tmpDist += keyFrames[(j + 1) % keyFrames.size()].distFromPrev;
|
|
keyFrames[j].distUntilStop = tmpDist;
|
|
if (keyFrames[j].node->actionFlag == 2)
|
|
{ tmpDist = 0; }
|
|
}
|
|
|
|
for (size_t i = 0; i < keyFrames.size(); ++i)
|
|
{
|
|
if (keyFrames[i].distSinceStop < (30 * 30 * 0.5f))
|
|
{ keyFrames[i].tFrom = sqrt(2 * keyFrames[i].distSinceStop); }
|
|
else
|
|
{ keyFrames[i].tFrom = ((keyFrames[i].distSinceStop - (30 * 30 * 0.5f)) / 30) + 30; }
|
|
|
|
if (keyFrames[i].distUntilStop < (30 * 30 * 0.5f))
|
|
{ keyFrames[i].tTo = sqrt(2 * keyFrames[i].distUntilStop); }
|
|
else
|
|
{ keyFrames[i].tTo = ((keyFrames[i].distUntilStop - (30 * 30 * 0.5f)) / 30) + 30; }
|
|
|
|
keyFrames[i].tFrom *= 1000;
|
|
keyFrames[i].tTo *= 1000;
|
|
}
|
|
|
|
// for (int i = 0; i < keyFrames.size(); ++i) {
|
|
// sLog.outString("%f, %f, %f, %f, %f, %f, %f", keyFrames[i].x, keyFrames[i].y, keyFrames[i].distUntilStop, keyFrames[i].distSinceStop, keyFrames[i].distFromPrev, keyFrames[i].tFrom, keyFrames[i].tTo);
|
|
// }
|
|
|
|
// Now we're completely set up; we can move along the length of each waypoint at 100 ms intervals
|
|
// speed = max(30, t) (remember x = 0.5s^2, and when accelerating, a = 1 unit/s^2
|
|
int t = 0;
|
|
bool teleport = false;
|
|
if (keyFrames[keyFrames.size() - 1].node->mapid != keyFrames[0].node->mapid)
|
|
{ teleport = true; }
|
|
|
|
WayPoint pos(keyFrames[0].node->mapid, keyFrames[0].node->x, keyFrames[0].node->y, keyFrames[0].node->z, teleport);
|
|
m_WayPoints[0] = pos;
|
|
t += keyFrames[0].node->delay * 1000;
|
|
|
|
uint32 cM = keyFrames[0].node->mapid;
|
|
for (size_t i = 0; i < keyFrames.size() - 1; ++i)
|
|
{
|
|
float d = 0;
|
|
float tFrom = keyFrames[i].tFrom;
|
|
float tTo = keyFrames[i].tTo;
|
|
|
|
// keep the generation of all these points; we use only a few now, but may need the others later
|
|
if (((d < keyFrames[i + 1].distFromPrev) && (tTo > 0)))
|
|
{
|
|
while ((d < keyFrames[i + 1].distFromPrev) && (tTo > 0))
|
|
{
|
|
tFrom += 100;
|
|
tTo -= 100;
|
|
|
|
if (d > 0)
|
|
{
|
|
float newX, newY, newZ;
|
|
newX = keyFrames[i].node->x + (keyFrames[i + 1].node->x - keyFrames[i].node->x) * d / keyFrames[i + 1].distFromPrev;
|
|
newY = keyFrames[i].node->y + (keyFrames[i + 1].node->y - keyFrames[i].node->y) * d / keyFrames[i + 1].distFromPrev;
|
|
newZ = keyFrames[i].node->z + (keyFrames[i + 1].node->z - keyFrames[i].node->z) * d / keyFrames[i + 1].distFromPrev;
|
|
|
|
bool teleport = false;
|
|
if (keyFrames[i].node->mapid != cM)
|
|
{
|
|
teleport = true;
|
|
cM = keyFrames[i].node->mapid;
|
|
}
|
|
|
|
// sLog.outString("T: %d, D: %f, x: %f, y: %f, z: %f", t, d, newX, newY, newZ);
|
|
WayPoint pos(keyFrames[i].node->mapid, newX, newY, newZ, teleport);
|
|
if (teleport)
|
|
{ m_WayPoints[t] = pos; }
|
|
}
|
|
|
|
if (tFrom < tTo) // caught in tFrom dock's "gravitational pull"
|
|
{
|
|
if (tFrom <= 30000)
|
|
{
|
|
d = 0.5f * (tFrom / 1000) * (tFrom / 1000);
|
|
}
|
|
else
|
|
{
|
|
d = 0.5f * 30 * 30 + 30 * ((tFrom - 30000) / 1000);
|
|
}
|
|
d = d - keyFrames[i].distSinceStop;
|
|
}
|
|
else
|
|
{
|
|
if (tTo <= 30000)
|
|
{
|
|
d = 0.5f * (tTo / 1000) * (tTo / 1000);
|
|
}
|
|
else
|
|
{
|
|
d = 0.5f * 30 * 30 + 30 * ((tTo - 30000) / 1000);
|
|
}
|
|
d = keyFrames[i].distUntilStop - d;
|
|
}
|
|
t += 100;
|
|
}
|
|
t -= 100;
|
|
}
|
|
|
|
if (keyFrames[i + 1].tFrom > keyFrames[i + 1].tTo)
|
|
{ t += 100 - ((long)keyFrames[i + 1].tTo % 100); }
|
|
else
|
|
{ t += (long)keyFrames[i + 1].tTo % 100; }
|
|
|
|
bool teleport = false;
|
|
if ((keyFrames[i + 1].node->actionFlag == 1) || (keyFrames[i + 1].node->mapid != keyFrames[i].node->mapid))
|
|
{
|
|
teleport = true;
|
|
cM = keyFrames[i + 1].node->mapid;
|
|
}
|
|
|
|
WayPoint pos(keyFrames[i + 1].node->mapid, keyFrames[i + 1].node->x, keyFrames[i + 1].node->y, keyFrames[i + 1].node->z, teleport);
|
|
|
|
// sLog.outString("T: %d, x: %f, y: %f, z: %f, t:%d", t, pos.x, pos.y, pos.z, teleport);
|
|
|
|
// if (teleport)
|
|
m_WayPoints[t] = pos;
|
|
|
|
t += keyFrames[i + 1].node->delay * 1000;
|
|
// sLog.outString("------");
|
|
}
|
|
|
|
uint32 timer = t;
|
|
|
|
// sLog.outDetail(" Generated %lu waypoints, total time %u.", (unsigned long)m_WayPoints.size(), timer);
|
|
|
|
m_next = m_WayPoints.begin(); // will used in MoveToNextWayPoint for init m_curr
|
|
MoveToNextWayPoint(); // m_curr -> first point
|
|
MoveToNextWayPoint(); // skip first point
|
|
|
|
m_pathTime = timer;
|
|
|
|
m_nextNodeTime = m_curr->first;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Transport::MoveToNextWayPoint()
|
|
{
|
|
m_curr = m_next;
|
|
|
|
++m_next;
|
|
if (m_next == m_WayPoints.end())
|
|
{ m_next = m_WayPoints.begin(); }
|
|
}
|
|
|
|
void Transport::TeleportTransport(uint32 newMapid, float x, float y, float z)
|
|
{
|
|
Map const* oldMap = GetMap();
|
|
Relocate(x, y, z);
|
|
|
|
for (PlayerSet::iterator itr = m_passengers.begin(); itr != m_passengers.end();)
|
|
{
|
|
PlayerSet::iterator it2 = itr;
|
|
++itr;
|
|
|
|
Player* plr = *it2;
|
|
if (!plr)
|
|
{
|
|
m_passengers.erase(it2);
|
|
continue;
|
|
}
|
|
|
|
if (plr->IsDead() && !plr->HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_GHOST))
|
|
{
|
|
plr->ResurrectPlayer(1.0);
|
|
}
|
|
plr->TeleportTo(newMapid, x, y, z, GetOrientation(), TELE_TO_NOT_LEAVE_TRANSPORT);
|
|
|
|
// WorldPacket data(SMSG_811, 4);
|
|
// data << uint32(0);
|
|
// plr->GetSession()->SendPacket(&data);
|
|
}
|
|
|
|
// we need to create and save new Map object with 'newMapid' because if not done -> lead to invalid Map object reference...
|
|
// player far teleport would try to create same instance, but we need it NOW for transport...
|
|
// correct me if I'm wrong O.o
|
|
Map* newMap = sMapMgr.CreateMap(newMapid, this);
|
|
SetMap(newMap);
|
|
|
|
if (oldMap != newMap)
|
|
{
|
|
UpdateForMap(oldMap);
|
|
UpdateForMap(newMap);
|
|
}
|
|
}
|
|
|
|
bool Transport::AddPassenger(Player* passenger)
|
|
{
|
|
if (m_passengers.find(passenger) == m_passengers.end())
|
|
{
|
|
DETAIL_LOG("Player %s boarded transport %s.", passenger->GetName(), GetName());
|
|
m_passengers.insert(passenger);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Transport::RemovePassenger(Player* passenger)
|
|
{
|
|
if (m_passengers.erase(passenger))
|
|
{ DETAIL_LOG("Player %s removed from transport %s.", passenger->GetName(), GetName()); }
|
|
return true;
|
|
}
|
|
|
|
void Transport::Update(uint32 /*update_diff*/, uint32 /*p_time*/)
|
|
{
|
|
if (m_WayPoints.size() <= 1)
|
|
{ return; }
|
|
|
|
m_timer = WorldTimer::getMSTime() % m_period;
|
|
while (((m_timer - m_curr->first) % m_pathTime) > ((m_next->first - m_curr->first) % m_pathTime))
|
|
{
|
|
MoveToNextWayPoint();
|
|
|
|
// first check help in case client-server transport coordinates de-synchronization
|
|
if (m_curr->second.mapid != GetMapId() || m_curr->second.teleport)
|
|
{
|
|
TeleportTransport(m_curr->second.mapid, m_curr->second.x, m_curr->second.y, m_curr->second.z);
|
|
}
|
|
else
|
|
{
|
|
Relocate(m_curr->second.x, m_curr->second.y, m_curr->second.z);
|
|
}
|
|
|
|
/*
|
|
for(PlayerSet::const_iterator itr = m_passengers.begin(); itr != m_passengers.end();)
|
|
{
|
|
PlayerSet::const_iterator it2 = itr;
|
|
++itr;
|
|
//(*it2)->SetPosition( m_curr->second.x + (*it2)->GetTransOffsetX(), m_curr->second.y + (*it2)->GetTransOffsetY(), m_curr->second.z + (*it2)->GetTransOffsetZ(), (*it2)->GetTransOffsetO() );
|
|
}
|
|
*/
|
|
|
|
m_nextNodeTime = m_curr->first;
|
|
|
|
if (m_curr == m_WayPoints.begin())
|
|
{ DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, " ************ BEGIN ************** %s", GetName()); }
|
|
|
|
DETAIL_FILTER_LOG(LOG_FILTER_TRANSPORT_MOVES, "%s moved to %f %f %f %d", GetName(), m_curr->second.x, m_curr->second.y, m_curr->second.z, m_curr->second.mapid);
|
|
}
|
|
}
|
|
|
|
void Transport::UpdateForMap(Map const* targetMap)
|
|
{
|
|
Map::PlayerList const& pl = targetMap->GetPlayers();
|
|
if (pl.isEmpty())
|
|
{ return; }
|
|
|
|
if (GetMapId() == targetMap->GetId())
|
|
{
|
|
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
|
|
{
|
|
if (this != itr->getSource()->GetTransport())
|
|
{
|
|
UpdateData transData;
|
|
BuildCreateUpdateBlockForPlayer(&transData, itr->getSource());
|
|
WorldPacket packet;
|
|
transData.BuildPacket(&packet, true);
|
|
itr->getSource()->SendDirectMessage(&packet);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UpdateData transData;
|
|
BuildOutOfRangeUpdateBlock(&transData);
|
|
WorldPacket out_packet;
|
|
transData.BuildPacket(&out_packet, true);
|
|
|
|
for (Map::PlayerList::const_iterator itr = pl.begin(); itr != pl.end(); ++itr)
|
|
if (this != itr->getSource()->GetTransport())
|
|
{ itr->getSource()->SendDirectMessage(&out_packet); }
|
|
}
|
|
}
|