Core/Transports
* Fixed transport orientation * Fixed transport position desynchronization for stoppable transports * Ignore spawnMask errors for objects spawned on transports
This commit is contained in:
@@ -397,6 +397,9 @@ bool BattlegroundIC::SetupBattleground()
|
||||
return false;
|
||||
}
|
||||
|
||||
gunshipHorde->EnableMovement(false);
|
||||
gunshipAlliance->EnableMovement(false);
|
||||
|
||||
// setting correct factions for Keep Cannons
|
||||
for (uint8 i = BG_IC_NPC_KEEP_CANNON_1; i < BG_IC_NPC_KEEP_CANNON_12; ++i)
|
||||
GetBGCreature(i)->setFaction(BG_IC_Factions[0]);
|
||||
|
||||
@@ -90,8 +90,7 @@ bool Transport::Create(uint32 guidlow, uint32 entry, uint32 mapid, float x, floa
|
||||
SetPeriod(tInfo->pathTime);
|
||||
SetEntry(goinfo->entry);
|
||||
SetDisplayId(goinfo->displayId);
|
||||
SetGoState(GO_STATE_READY);
|
||||
_pendingStop = goinfo->moTransport.canBeStopped != 0;
|
||||
SetGoState(!goinfo->moTransport.canBeStopped ? GO_STATE_READY : GO_STATE_ACTIVE);
|
||||
SetGoType(GAMEOBJECT_TYPE_MO_TRANSPORT);
|
||||
SetGoAnimProgress(animprogress);
|
||||
SetName(goinfo->name);
|
||||
@@ -111,7 +110,8 @@ void Transport::Update(uint32 diff)
|
||||
if (GetKeyFrames().size() <= 1)
|
||||
return;
|
||||
|
||||
m_goValue.Transport.PathProgress += diff;
|
||||
if (IsMoving() || !_pendingStop)
|
||||
m_goValue.Transport.PathProgress += diff;
|
||||
|
||||
uint32 timer = m_goValue.Transport.PathProgress % GetPeriod();
|
||||
|
||||
@@ -132,31 +132,23 @@ void Transport::Update(uint32 diff)
|
||||
if (timer < _currentFrame->DepartureTime)
|
||||
{
|
||||
SetMoving(false);
|
||||
if (_pendingStop)
|
||||
if (_pendingStop && GetGoState() != GO_STATE_READY)
|
||||
{
|
||||
SetGoState(GO_STATE_READY);
|
||||
m_goValue.Transport.PathProgress = (m_goValue.Transport.PathProgress / GetPeriod());
|
||||
m_goValue.Transport.PathProgress *= GetPeriod();
|
||||
m_goValue.Transport.PathProgress += _currentFrame->ArriveTime;
|
||||
}
|
||||
break; // its a stop frame and we are waiting
|
||||
}
|
||||
}
|
||||
|
||||
if (_pendingStop && timer >= _currentFrame->DepartureTime && GetGoState() == GO_STATE_READY)
|
||||
{
|
||||
m_goValue.Transport.PathProgress = (m_goValue.Transport.PathProgress / GetPeriod());
|
||||
m_goValue.Transport.PathProgress *= GetPeriod();
|
||||
m_goValue.Transport.PathProgress += _currentFrame->ArriveTime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (timer >= _currentFrame->DepartureTime && !_triggeredDepartureEvent)
|
||||
{
|
||||
DoEventIfAny(*_currentFrame, true); // departure event
|
||||
_triggeredDepartureEvent = true;
|
||||
}
|
||||
|
||||
if (timer >= _currentFrame->DepartureTime && timer < _currentFrame->NextArriveTime)
|
||||
break; // found current waypoint
|
||||
|
||||
MoveToNextWaypoint();
|
||||
|
||||
// not waiting anymore
|
||||
SetMoving(true);
|
||||
|
||||
@@ -164,13 +156,18 @@ void Transport::Update(uint32 diff)
|
||||
if (GetGOInfo()->moTransport.canBeStopped)
|
||||
SetGoState(GO_STATE_ACTIVE);
|
||||
|
||||
if (timer >= _currentFrame->DepartureTime && timer < _currentFrame->NextArriveTime)
|
||||
break; // found current waypoint
|
||||
|
||||
MoveToNextWaypoint();
|
||||
|
||||
sScriptMgr->OnRelocate(this, _currentFrame->Node->index, _currentFrame->Node->mapid, _currentFrame->Node->x, _currentFrame->Node->y, _currentFrame->Node->z);
|
||||
|
||||
TC_LOG_DEBUG("entities.transport", "Transport %u (%s) moved to node %u %u %f %f %f", GetEntry(), GetName().c_str(), _currentFrame->Node->index, _currentFrame->Node->mapid, _currentFrame->Node->x, _currentFrame->Node->y, _currentFrame->Node->z);
|
||||
|
||||
// Departure event
|
||||
if (_currentFrame->IsTeleportFrame())
|
||||
if (TeleportTransport(_nextFrame->Node->mapid, _nextFrame->Node->x, _nextFrame->Node->y, _nextFrame->Node->z))
|
||||
if (TeleportTransport(_nextFrame->Node->mapid, _nextFrame->Node->x, _nextFrame->Node->y, _nextFrame->Node->z, _nextFrame->InitialOrientation))
|
||||
return; // Update more in new map thread
|
||||
}
|
||||
|
||||
@@ -185,7 +182,18 @@ void Transport::Update(uint32 diff)
|
||||
G3D::Vector3 pos, dir;
|
||||
_currentFrame->Spline->evaluate_percent(_currentFrame->Index, t, pos);
|
||||
_currentFrame->Spline->evaluate_derivative(_currentFrame->Index, t, dir);
|
||||
UpdatePosition(pos.x, pos.y, pos.z, atan2(dir.x, dir.y));
|
||||
UpdatePosition(pos.x, pos.y, pos.z, atan2(dir.y, dir.x) + M_PI);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* There are four possible scenarios that trigger loading/unloading passengers:
|
||||
1. transport moves from inactive to active grid
|
||||
2. the grid that transport is currently in becomes active
|
||||
3. transport moves from active to inactive grid
|
||||
4. the grid that transport is currently in unloads
|
||||
*/
|
||||
if (_staticPassengers.empty() && GetMap()->IsGridLoaded(GetPositionX(), GetPositionY())) // 2.
|
||||
LoadStaticPassengers();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +322,7 @@ void Transport::UpdatePosition(float x, float y, float z, float o)
|
||||
3. transport moves from active to inactive grid
|
||||
4. the grid that transport is currently in unloads
|
||||
*/
|
||||
if (_staticPassengers.empty() && newActive) // 1. and 2.
|
||||
if (_staticPassengers.empty() && newActive) // 1.
|
||||
LoadStaticPassengers();
|
||||
else if (!_staticPassengers.empty() && !newActive && Cell(x, y).DiffGrid(Cell(GetPositionX(), GetPositionY()))) // 3.
|
||||
UnloadStaticPassengers();
|
||||
@@ -404,7 +412,7 @@ float Transport::CalculateSegmentPos(float now)
|
||||
return segmentPos / frame.NextDistFromPrev;
|
||||
}
|
||||
|
||||
bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z)
|
||||
bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z, float o)
|
||||
{
|
||||
Map const* oldMap = GetMap();
|
||||
|
||||
@@ -421,7 +429,7 @@ bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z)
|
||||
|
||||
float destX, destY, destZ, destO;
|
||||
obj->m_movementInfo.transport.pos.GetPosition(destX, destY, destZ, destO);
|
||||
TransportBase::CalculatePassengerPosition(destX, destY, destZ, &destO, x, y, z, GetOrientation());
|
||||
TransportBase::CalculatePassengerPosition(destX, destY, destZ, &destO, x, y, z, o);
|
||||
|
||||
switch (obj->GetTypeId())
|
||||
{
|
||||
@@ -447,7 +455,7 @@ bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z)
|
||||
}
|
||||
}
|
||||
|
||||
Relocate(x, y, z, GetOrientation());
|
||||
Relocate(x, y, z, o);
|
||||
GetMap()->AddToMap<Transport>(this);
|
||||
return true;
|
||||
}
|
||||
@@ -460,13 +468,13 @@ bool Transport::TeleportTransport(uint32 newMapid, float x, float y, float z)
|
||||
{
|
||||
float destX, destY, destZ, destO;
|
||||
(*itr)->m_movementInfo.transport.pos.GetPosition(destX, destY, destZ, destO);
|
||||
TransportBase::CalculatePassengerPosition(destX, destY, destZ, &destO, x, y, z, GetOrientation());
|
||||
TransportBase::CalculatePassengerPosition(destX, destY, destZ, &destO, x, y, z, o);
|
||||
|
||||
(*itr)->ToUnit()->NearTeleportTo(destX, destY, destZ, destO);
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(x, y, z, GetOrientation());
|
||||
UpdatePosition(x, y, z, o);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ class Transport : public GameObject, public TransportBase
|
||||
private:
|
||||
void MoveToNextWaypoint();
|
||||
float CalculateSegmentPos(float perc);
|
||||
bool TeleportTransport(uint32 newMapid, float x, float y, float z);
|
||||
bool TeleportTransport(uint32 newMapid, float x, float y, float z, float o);
|
||||
void UpdatePassengerPositions(std::set<WorldObject*>& passengers);
|
||||
void DoEventIfAny(KeyFrame const& node, bool departure);
|
||||
|
||||
|
||||
@@ -1601,7 +1601,8 @@ void ObjectMgr::LoadCreatures()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (data.spawnMask & ~spawnMasks[data.mapid])
|
||||
// Skip spawnMask check for transport maps
|
||||
if (!_transportMaps.count(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid])
|
||||
TC_LOG_ERROR("sql.sql", "Table `creature` have creature (GUID: %u) that have wrong spawn mask %u including not supported difficulty modes for map (Id: %u).", guid, data.spawnMask, data.mapid);
|
||||
|
||||
bool ok = true;
|
||||
@@ -1936,7 +1937,7 @@ void ObjectMgr::LoadGameobjects()
|
||||
|
||||
data.spawnMask = fields[14].GetUInt8();
|
||||
|
||||
if (data.spawnMask & ~spawnMasks[data.mapid])
|
||||
if (!_transportMaps.count(data.mapid) && data.spawnMask & ~spawnMasks[data.mapid])
|
||||
TC_LOG_ERROR("sql.sql", "Table `gameobject` has gameobject (GUID: %u Entry: %u) that has wrong spawn mask %u including not supported difficulty modes for map (Id: %u), skip", guid, data.id, data.spawnMask, data.mapid);
|
||||
|
||||
data.phaseMask = fields[15].GetUInt32();
|
||||
@@ -6480,6 +6481,8 @@ void ObjectMgr::LoadGameObjectTemplate()
|
||||
TC_LOG_ERROR("sql.sql", "GameObject (Entry: %u GoType: %u) have data0=%u but TaxiPath (Id: %u) not exist.",
|
||||
entry, got.type, got.moTransport.taxiPathId, got.moTransport.taxiPathId);
|
||||
}
|
||||
if (uint32 transportMap = got.moTransport.mapID)
|
||||
_transportMaps.insert(transportMap);
|
||||
break;
|
||||
}
|
||||
case GAMEOBJECT_TYPE_SUMMONING_RITUAL: //18
|
||||
|
||||
@@ -1384,6 +1384,8 @@ class ObjectMgr
|
||||
GO_TO_GO,
|
||||
GO_TO_CREATURE // GO is dependant on creature
|
||||
};
|
||||
|
||||
std::set<uint32> _transportMaps; // Helper container storing map ids that are for transports only, loaded from gameobject_template
|
||||
};
|
||||
|
||||
#define sObjectMgr ACE_Singleton<ObjectMgr, ACE_Null_Mutex>::instance()
|
||||
|
||||
@@ -87,28 +87,60 @@ void TransportMgr::LoadTransportTemplates()
|
||||
TC_LOG_INFO("server.loading", ">> Loaded %u transport templates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||||
}
|
||||
|
||||
class SplineRawInitializer
|
||||
{
|
||||
public:
|
||||
SplineRawInitializer(Movement::PointsArray& points) : _points(points) { }
|
||||
|
||||
void operator()(uint8& mode, bool& cyclic, Movement::PointsArray& points, int& lo, int& hi) const
|
||||
{
|
||||
mode = Movement::SplineBase::ModeCatmullrom;
|
||||
cyclic = false;
|
||||
points.assign(_points.begin(), _points.end());
|
||||
lo = 1;
|
||||
hi = points.size();
|
||||
}
|
||||
|
||||
Movement::PointsArray& _points;
|
||||
};
|
||||
|
||||
void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTemplate* transport)
|
||||
{
|
||||
uint32 pathId = goInfo->moTransport.taxiPathId;
|
||||
TaxiPathNodeList const& path = sTaxiPathNodesByPath[pathId];
|
||||
std::vector<KeyFrame>& keyFrames = transport->keyFrames;
|
||||
Movement::PointsArray splinePath;
|
||||
Movement::PointsArray splinePath, allPoints;
|
||||
bool mapChange = false;
|
||||
bool cyclic = true;
|
||||
for (size_t i = 0; i < path.size(); ++i)
|
||||
allPoints.push_back(G3D::Vector3(path[i].x, path[i].y, path[i].z));
|
||||
|
||||
// Add extra points to allow derivative calculations for all path nodes
|
||||
allPoints.insert(allPoints.begin(), allPoints.front().lerp(allPoints[1], -0.2f));
|
||||
allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -0.2f));
|
||||
allPoints.push_back(allPoints.back().lerp(allPoints[allPoints.size() - 2], -1.0f));
|
||||
|
||||
SplineRawInitializer initer(allPoints);
|
||||
TransportSpline orientationSpline;
|
||||
orientationSpline.init_spline_custom(initer);
|
||||
orientationSpline.initLengths();
|
||||
|
||||
for (size_t i = 0; i < path.size(); ++i)
|
||||
{
|
||||
if (!mapChange)
|
||||
{
|
||||
TaxiPathNodeEntry const& node_i = path[i];
|
||||
if (i != path.size() - 1 && (node_i.actionFlag == 1 || node_i.mapid != path[i + 1].mapid))
|
||||
if (i != path.size() - 1 && (node_i.actionFlag & 1 || node_i.mapid != path[i + 1].mapid))
|
||||
{
|
||||
cyclic = false;
|
||||
keyFrames.back().Teleport = true;
|
||||
mapChange = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyFrame k(node_i);
|
||||
G3D::Vector3 h;
|
||||
orientationSpline.evaluate_derivative(i + 1, 0.0f, h);
|
||||
k.InitialOrientation = Position::NormalizeOrientation(atan2(h.y, h.x) + M_PI);
|
||||
|
||||
keyFrames.push_back(k);
|
||||
splinePath.push_back(G3D::Vector3(node_i.x, node_i.y, node_i.z));
|
||||
transport->mapsUsed.insert(k.Node->mapid);
|
||||
@@ -118,16 +150,15 @@ void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTempl
|
||||
mapChange = false;
|
||||
}
|
||||
|
||||
// Not sure if data8 means the transport can be stopped or that its path in dbc does not contain extra spline points
|
||||
if (!goInfo->moTransport.canBeStopped && splinePath.size() >= 2)
|
||||
if (splinePath.size() >= 2)
|
||||
{
|
||||
// Remove special catmull-rom spline points
|
||||
splinePath.erase(splinePath.begin());
|
||||
keyFrames.erase(keyFrames.begin());
|
||||
splinePath.pop_back();
|
||||
keyFrames.pop_back();
|
||||
// Cyclic spline has one more extra point
|
||||
if (cyclic && !splinePath.empty())
|
||||
if (!keyFrames.front().IsStopFrame() && !keyFrames.front().Node->arrivalEventID && !keyFrames.front().Node->departureEventID)
|
||||
{
|
||||
splinePath.erase(splinePath.begin());
|
||||
keyFrames.erase(keyFrames.begin());
|
||||
}
|
||||
if (!keyFrames.back().IsStopFrame() && !keyFrames.back().Node->arrivalEventID && !keyFrames.back().Node->departureEventID)
|
||||
{
|
||||
splinePath.pop_back();
|
||||
keyFrames.pop_back();
|
||||
@@ -170,67 +201,41 @@ void TransportMgr::GeneratePath(GameObjectTemplate const* goInfo, TransportTempl
|
||||
|
||||
// find the rest of the distances between key points
|
||||
// Every path segment has its own spline
|
||||
if (cyclic)
|
||||
size_t start = 0;
|
||||
for (size_t i = 1; i < keyFrames.size(); ++i)
|
||||
{
|
||||
TransportSpline* spline = new TransportSpline();
|
||||
spline->init_cyclic_spline(&splinePath[0], splinePath.size(), Movement::SplineBase::ModeCatmullrom, 0);
|
||||
spline->initLengths();
|
||||
keyFrames[0].DistFromPrev = spline->length(spline->last() - 2, spline->last() - 1);
|
||||
keyFrames[0].Spline = spline;
|
||||
for (size_t i = 0; i < keyFrames.size(); ++i)
|
||||
if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.size())
|
||||
{
|
||||
keyFrames[i].Index = i + 1;
|
||||
keyFrames[i].DistFromPrev = spline->length(i, i + 1);
|
||||
if (i > 0)
|
||||
keyFrames[i - 1].NextDistFromPrev = keyFrames[i].DistFromPrev;
|
||||
keyFrames[i].Spline = spline;
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
size_t extra = !keyFrames[i - 1].Teleport ? 1 : 0;
|
||||
TransportSpline* spline = new TransportSpline();
|
||||
spline->init_spline(&splinePath[start], i - start + extra, Movement::SplineBase::ModeCatmullrom);
|
||||
spline->initLengths();
|
||||
for (size_t j = start; j < i + extra; ++j)
|
||||
{
|
||||
// remember first stop frame
|
||||
if (firstStop == -1)
|
||||
firstStop = i;
|
||||
lastStop = i;
|
||||
keyFrames[j].Index = j - start + 1;
|
||||
keyFrames[j].DistFromPrev = spline->length(j - start, j + 1 - start);
|
||||
if (j > 0)
|
||||
keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev;
|
||||
keyFrames[j].Spline = spline;
|
||||
}
|
||||
|
||||
if (keyFrames[i - 1].Teleport)
|
||||
{
|
||||
keyFrames[i].Index = i - start + 1;
|
||||
keyFrames[i].DistFromPrev = 0.0f;
|
||||
keyFrames[i - 1].NextDistFromPrev = 0.0f;
|
||||
keyFrames[i].Spline = spline;
|
||||
}
|
||||
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t start = 0;
|
||||
for (size_t i = 1; i < keyFrames.size(); ++i)
|
||||
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
{
|
||||
if (keyFrames[i - 1].Teleport || i + 1 == keyFrames.size())
|
||||
{
|
||||
size_t extra = !keyFrames[i - 1].Teleport ? 1 : 0;
|
||||
TransportSpline* spline = new TransportSpline();
|
||||
spline->init_spline(&splinePath[start], i - start + extra, Movement::SplineBase::ModeCatmullrom);
|
||||
spline->initLengths();
|
||||
for (size_t j = start; j < i + extra; ++j)
|
||||
{
|
||||
keyFrames[j].Index = j - start + 1;
|
||||
keyFrames[j].DistFromPrev = spline->length(j - start, j + 1 - start);
|
||||
if (j > 0)
|
||||
keyFrames[j - 1].NextDistFromPrev = keyFrames[j].DistFromPrev;
|
||||
keyFrames[j].Spline = spline;
|
||||
}
|
||||
|
||||
if (keyFrames[i - 1].Teleport)
|
||||
{
|
||||
keyFrames[i].Index = i - start + 1;
|
||||
keyFrames[i].DistFromPrev = 0.0f;
|
||||
keyFrames[i - 1].NextDistFromPrev = 0.0f;
|
||||
keyFrames[i].Spline = spline;
|
||||
}
|
||||
|
||||
start = i;
|
||||
}
|
||||
|
||||
if (keyFrames[i].IsStopFrame())
|
||||
{
|
||||
// remember first stop frame
|
||||
if (firstStop == -1)
|
||||
firstStop = i;
|
||||
lastStop = i;
|
||||
}
|
||||
// remember first stop frame
|
||||
if (firstStop == -1)
|
||||
firstStop = i;
|
||||
lastStop = i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,7 +378,7 @@ Transport* TransportMgr::CreateTransport(uint32 entry, uint32 guid /*= 0*/, Map*
|
||||
float x = startNode->x;
|
||||
float y = startNode->y;
|
||||
float z = startNode->z;
|
||||
float o = 0.0f;
|
||||
float o = tInfo->keyFrames.begin()->InitialOrientation;
|
||||
|
||||
// initialize the gameobject base
|
||||
uint32 guidLow = guid ? guid : sObjectMgr->GenerateLowGuid(HIGHGUID_MO_TRANSPORT);
|
||||
|
||||
@@ -38,7 +38,7 @@ typedef UNORDERED_MAP<uint32, std::set<uint32> > TransportInstanceMap;
|
||||
|
||||
struct KeyFrame
|
||||
{
|
||||
explicit KeyFrame(TaxiPathNodeEntry const& _node) : Index(0), Node(&_node),
|
||||
explicit KeyFrame(TaxiPathNodeEntry const& _node) : Index(0), Node(&_node), InitialOrientation(0.0f),
|
||||
DistSinceStop(-1.0f), DistUntilStop(-1.0f), DistFromPrev(-1.0f), TimeFrom(0.0f), TimeTo(0.0f),
|
||||
Teleport(false), ArriveTime(0), DepartureTime(0), Spline(NULL), NextDistFromPrev(0.0f), NextArriveTime(0)
|
||||
{
|
||||
@@ -46,6 +46,7 @@ struct KeyFrame
|
||||
|
||||
uint32 Index;
|
||||
TaxiPathNodeEntry const* Node;
|
||||
float InitialOrientation;
|
||||
float DistSinceStop;
|
||||
float DistUntilStop;
|
||||
float DistFromPrev;
|
||||
|
||||
@@ -156,7 +156,7 @@ float SplineBase::SegLengthLinear(index_type index) const
|
||||
return (points[index] - points[index+1]).length();
|
||||
}
|
||||
|
||||
float SplineBase::SegLengthCatmullRom( index_type index ) const
|
||||
float SplineBase::SegLengthCatmullRom(index_type index) const
|
||||
{
|
||||
ASSERT(index >= index_lo && index < index_hi);
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ public:
|
||||
|
||||
/** As i can see there are a lot of ways how spline can be initialized
|
||||
would be no harm to have some custom initializers. */
|
||||
template<class Init> inline void init_spline(Init& initializer)
|
||||
template<class Init> inline void init_spline_custom(Init& initializer)
|
||||
{
|
||||
initializer(m_mode, cyclic, points, index_lo, index_hi);
|
||||
}
|
||||
|
||||
@@ -1382,7 +1382,10 @@ public:
|
||||
}
|
||||
else
|
||||
{
|
||||
Position pos;
|
||||
transport->GetPosition(&pos);
|
||||
handler->PSendSysMessage("Transport %s is %s", transport->GetName().c_str(), transport->GetGoState() == GO_STATE_READY ? "stopped" : "moving");
|
||||
handler->PSendSysMessage("Transport position: %s", pos.ToString().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user