mirror of
https://bitbucket.org/theswgsource/src-1.2.git
synced 2026-07-14 00:01:30 -04:00
1125 lines
41 KiB
C++
1125 lines
41 KiB
C++
// ======================================================================
|
|
//
|
|
// CreatureObject_Mounts.cpp
|
|
// Copyright 2003 Sony Online Entertainment, Inc.
|
|
// All Rights Reserved.
|
|
//
|
|
// ======================================================================
|
|
|
|
#include "serverGame/FirstServerGame.h"
|
|
#include "serverGame/CreatureObject.h"
|
|
|
|
#include "serverGame/Chat.h"
|
|
#include "serverGame/ConfigServerGame.h"
|
|
#include "serverGame/ContainerInterface.h"
|
|
#include "serverGame/GameServer.h"
|
|
#include "serverGame/ServerTangibleObjectTemplate.h"
|
|
#include "serverGame/ServerWorld.h"
|
|
#include "sharedGame/SharedSaddleManager.h"
|
|
#include "sharedCollision/CollisionProperty.h"
|
|
#include "sharedCollision/CollisionWorld.h"
|
|
#include "sharedFoundation/ConstCharCrcLowerString.h"
|
|
#include "sharedFoundation/ConstCharCrcString.h"
|
|
#include "sharedGame/MountValidScaleRangeTable.h"
|
|
#include "sharedGame/SharedSaddleManager.h"
|
|
#include "sharedLog/Log.h"
|
|
#include "sharedNetworkMessages/MessageQueueGenericValueType.h"
|
|
#include "sharedObject/Appearance.h"
|
|
#include "sharedObject/AppearanceTemplate.h"
|
|
#include "sharedObject/AlterScheduler.h"
|
|
#include "sharedObject/NetworkIdManager.h"
|
|
#include "sharedObject/SlotIdManager.h"
|
|
#include "sharedObject/SlottedContainer.h"
|
|
#include "sharedObject/SlottedContainmentProperty.h"
|
|
#include "swgSharedUtility/Postures.h"
|
|
#include "UnicodeUtils.h"
|
|
|
|
#include <string>
|
|
|
|
// ======================================================================
|
|
|
|
namespace CreatureObjectNamespace
|
|
{
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
std::string const cs_petControlDeviceObjVarName("pet.controlDevice");
|
|
char const *const cs_riderSlotName = "rider";
|
|
|
|
char const *const cs_mountErrorChannelName = "mounts-error";
|
|
char const *const cs_mountInfoChannelName = "mounts-info";
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
int const cs_totalNumberOfRiders = 8;
|
|
|
|
SlotId s_riderSlotId[cs_totalNumberOfRiders] =
|
|
{
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
SlotId(SlotId::invalid),
|
|
};
|
|
|
|
CreatureObject * realGetMountingRider(CreatureObject const * mount, SlotId const & slot);
|
|
CrcString const * getLogicalSaddleNameForMount(Object const * mount);
|
|
int getSaddleSeatingCapacity(CreatureObject const * mount);
|
|
bool realDetachRider(CreatureObject * mount, SlotId const & slot);
|
|
void LogMovement(CreatureObject const * mount);
|
|
}
|
|
|
|
// ======================================================================
|
|
|
|
CreatureObject * CreatureObjectNamespace::realGetMountingRider(CreatureObject const * const mount, SlotId const & slot)
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
return NULL;
|
|
|
|
//-- Get the rider element from the slotted container.
|
|
SlottedContainer const * const slottedContainer = ContainerInterface::getSlottedContainer(*mount);
|
|
if (!slottedContainer)
|
|
return NULL;
|
|
|
|
//-- Check for a rider.
|
|
Container::ContainerErrorCode errorCode = Container::CEC_Success;
|
|
CachedNetworkId riderId = slottedContainer->getObjectInSlot(slot, errorCode);
|
|
if (errorCode != Container::CEC_Success)
|
|
return NULL;
|
|
|
|
Object * const riderObject = riderId.getObject();
|
|
|
|
ServerObject * const riderServerObject = (riderObject != 0) ? riderObject->asServerObject() : 0;
|
|
|
|
CreatureObject * const riderCreatureObject = (riderServerObject != 0) ? riderServerObject->asCreatureObject() : 0;
|
|
|
|
return riderCreatureObject;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void CreatureObjectNamespace::LogMovement(CreatureObject const * const mount)
|
|
{
|
|
NOT_NULL(mount);
|
|
|
|
typedef std::vector<CreatureObject const *> Riders;
|
|
Riders riders;
|
|
|
|
if (mount != 0)
|
|
{
|
|
mount->getMountingRiders(riders);
|
|
|
|
if (!riders.empty())
|
|
{
|
|
{
|
|
int const process = static_cast<int>(GameServer::getInstance().getProcessId());
|
|
char const * const id = mount->getNetworkId().getValueString().c_str();
|
|
Vector const location_w(mount->getPosition_w());
|
|
LOG("mounts-movement", ("server id=[%d], Vehicle id=[%s] is located at %f %f %f", process, id, location_w.x, location_w.y, location_w.z));
|
|
}
|
|
|
|
Riders::const_iterator ii = riders.begin();
|
|
Riders::const_iterator iiEnd = riders.end();
|
|
|
|
for (; ii != iiEnd; ++ii)
|
|
{
|
|
CreatureObject const *const rider = *ii;
|
|
|
|
if (rider != 0)
|
|
{
|
|
int const process = static_cast<int>(GameServer::getInstance().getProcessId());
|
|
char const * const id = rider->getNetworkId().getValueString().c_str();
|
|
Vector const location_w(rider->getPosition_w());
|
|
LOG("mounts-movement", ("server id=[%d], passenger id=[%s] is located at %f %f %f", process, id, location_w.x, location_w.y, location_w.z));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
CrcString const * CreatureObjectNamespace::getLogicalSaddleNameForMount(Object const * const mount)
|
|
{
|
|
NOT_NULL(mount);
|
|
|
|
ServerObject const * const serverObject = mount->asServerObject();
|
|
SharedObjectTemplate const * const sharedObjectTemplate = serverObject->getSharedTemplate();
|
|
|
|
std::string const appearanceFilename = (sharedObjectTemplate != 0) ? sharedObjectTemplate->getAppearanceFilename() : "None";
|
|
|
|
ConstCharCrcString const appearanceFilenameCrc(appearanceFilename.c_str());
|
|
|
|
CrcString const * const logicalSaddleName = SharedSaddleManager::getLogicalSaddleNameForMountAppearance(appearanceFilenameCrc);
|
|
|
|
return logicalSaddleName;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
int CreatureObjectNamespace::getSaddleSeatingCapacity(CreatureObject const * const mount)
|
|
{
|
|
NOT_NULL(mount);
|
|
|
|
// default to 1 seat...
|
|
int saddleSeatingCapacity = 1;
|
|
|
|
{
|
|
ServerObject const * const serverObject = mount->asServerObject();
|
|
SharedObjectTemplate const * const sharedObjectTemplate = serverObject->getSharedTemplate();
|
|
|
|
std::string const appearanceFilename = (sharedObjectTemplate != 0) ? sharedObjectTemplate->getAppearanceFilename() : "None";
|
|
|
|
//DEBUG_REPORT_LOG(true, ("sharedObjectTemplate appearanceFilename = '%s'\n", appearanceFilename.c_str()));
|
|
}
|
|
|
|
CrcString const * const logicalSaddleName = getLogicalSaddleNameForMount(mount);
|
|
|
|
if (logicalSaddleName != 0)
|
|
{
|
|
saddleSeatingCapacity = SharedSaddleManager::getSaddleSeatingCapacityForLogicalSaddleName(*logicalSaddleName);
|
|
}
|
|
|
|
return saddleSeatingCapacity;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool CreatureObjectNamespace::realDetachRider(CreatureObject * const mount, SlotId const & slot)
|
|
{
|
|
NOT_NULL(mount);
|
|
|
|
bool detachedRider = false;
|
|
|
|
//-- If this object is a mount and has an object in its rider slot,
|
|
// remove that object from the container now. It will be a player and must
|
|
// be removed prior to the mount hitting ServerObject::permanentlyDestroyObject().
|
|
SlottedContainer * const slottedContainer = ContainerInterface::getSlottedContainer(*mount);
|
|
if (slottedContainer)
|
|
{
|
|
//-- Check for a rider.
|
|
Container::ContainerErrorCode errorCode = Container::CEC_Success;
|
|
CachedNetworkId riderId = slottedContainer->getObjectInSlot(slot, errorCode);
|
|
if (errorCode == Container::CEC_Success)
|
|
{
|
|
ServerObject *const rider = safe_cast<ServerObject*>(riderId.getObject());
|
|
if (rider)
|
|
{
|
|
// Turn off mounted state on rider.
|
|
CreatureObject *const creatureRider = rider->asCreatureObject();
|
|
if (creatureRider)
|
|
creatureRider->setState(States::RidingMount, false);
|
|
|
|
// Allow dismounted rider to participate in CollisionWorld.
|
|
CollisionProperty *const collisionProperty = rider->getCollisionProperty();
|
|
if (collisionProperty)
|
|
{
|
|
collisionProperty->setDisableCollisionWorldAddRemove(false);
|
|
if (mount->isInWorld())
|
|
CollisionWorld::addObject(rider);
|
|
}
|
|
|
|
// Rider should be authoritative if on the mount and the mount is authoritative.
|
|
if (!rider->isAuthoritative())
|
|
{
|
|
// Something really screwy happened here --- the mount was authoritative but the
|
|
// rider, contained by the mount, was not authoritative. Sounds like a container
|
|
// authority bug.
|
|
WARNING(true, ("CreatureObject::realDetachRider(): server id=[%d], mount id=[%s], rider id=[%s], not ejecting rider because rider is not authoritative.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
mount->getNetworkId().getValueString().c_str(),
|
|
rider->getNetworkId().getValueString().c_str()));
|
|
}
|
|
else
|
|
{
|
|
// Transfer rider to world.
|
|
// @todo: -TRF- transfer to mount's cell if we allow mounts inside cells.
|
|
IGNORE_RETURN(ContainerInterface::transferItemToWorld(*rider, mount->getTransform_o2w(), NULL, errorCode));
|
|
if (errorCode == Container::CEC_Success)
|
|
{
|
|
detachedRider = true;
|
|
|
|
// Add rider to CollisionWorld. We removed the rider from CollisionWorld
|
|
// when the rider mounted the creature.
|
|
CollisionWorld::addObject(rider);
|
|
}
|
|
else
|
|
{
|
|
DEBUG_WARNING(errorCode != Container::CEC_Success, ("mount id=[%s] failed to force unmount the rider when removed from the world, container error code [%d].", mount->getNetworkId().getValueString().c_str(), static_cast<int>(errorCode)));
|
|
}
|
|
rider->updatePositionOnPlanetServer(true); // Force an update to the Planet Server, in case we delete the mount next
|
|
|
|
if (detachedRider && !ServerWorld::isSpaceScene() && mount->isInWorldCell())
|
|
{
|
|
// force the dismounted rider to the *EXACT* location
|
|
// of the mount, because any slight little movement
|
|
// "twitch" because of dismount could put the rider in
|
|
// an unintended, exploitable location, like behind
|
|
// the wall of a building
|
|
rider->teleportObject(mount->getPosition_w(), NetworkId::cms_invalid, "", Vector(), "");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return detachedRider;
|
|
}
|
|
|
|
// ======================================================================
|
|
|
|
using namespace CreatureObjectNamespace;
|
|
|
|
// ======================================================================
|
|
// CreatureObject: PRIVATE STATIC
|
|
// ======================================================================
|
|
/**
|
|
* Install the mounts code.
|
|
*
|
|
* This function should be called only from CreatureObject::install().
|
|
*/
|
|
|
|
void CreatureObject::installForMounts()
|
|
{
|
|
s_riderSlotId[0] = SlotIdManager::findSlotId(ConstCharCrcLowerString(cs_riderSlotName));
|
|
|
|
char slotName[256];
|
|
|
|
for (int i = 1; i < cs_totalNumberOfRiders; ++i)
|
|
{
|
|
snprintf(slotName, 255, "%s%d", cs_riderSlotName, i);
|
|
s_riderSlotId[i] = SlotIdManager::findSlotId(ConstCharCrcLowerString(slotName));
|
|
}
|
|
}
|
|
|
|
// ======================================================================
|
|
// CreatureObject: PUBLIC
|
|
// ======================================================================
|
|
|
|
/**
|
|
* If this is the driver of a vehicle/mount, transfer the rider
|
|
* position to the mount.
|
|
*
|
|
* Note: this function must be called prior to the rider's
|
|
* m_commandQueue->update() function. This is because the
|
|
* command queue might get a message that has to look at the
|
|
* mounted vehicle's position (e.g. during a dismount), which
|
|
* will not yet be updated until this function is called.
|
|
*/
|
|
|
|
void CreatureObject::transferRiderPositionToMount()
|
|
{
|
|
//-- This function only applies to the driving rider of a mount.
|
|
if (!getState(States::RidingMount))
|
|
return;
|
|
|
|
//-- Ensure we can get to this rider's vehicle/mount.
|
|
CreatureObject *const mountObject = getMountedCreature();
|
|
if (!mountObject)
|
|
{
|
|
LOG("mounts-bug", ("CreatureObject::transferRiderPositionToMount(): server id=[%d],object id=[%s] has state RidingMount but getMountedCreature() returns NULL.", static_cast<int>(GameServer::getInstance().getProcessId()), getNetworkId().getValueString().c_str()));
|
|
emergencyDismountForRider();
|
|
return;
|
|
}
|
|
|
|
if (mountObject->getPrimaryMountingRider() != this)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//-- Set mount to the same cell and o2p transform as the rider.
|
|
// Note: this code assumes there is no parent-child relationship
|
|
// between the rider and the mount on the server.
|
|
CellProperty *const riderCellProperty = getParentCell();
|
|
bool const cellChanged = (mountObject->getParentCell() != riderCellProperty);
|
|
|
|
if (cellChanged)
|
|
mountObject->setParentCell(riderCellProperty);
|
|
|
|
// Only assign new o2p transform to mount if it is different from the rider's; otherwise,
|
|
// identical information gets flooded to all client proxys of the mount due to other code
|
|
// assuming the transform actually changed. Note: we might want to put the change detection
|
|
// logic at a lower level so code like this doesn't have to duplicate this functionality
|
|
// everywhere.
|
|
Transform const &riderTransform_o2p = getTransform_o2p();
|
|
Transform const &mountTransform_o2p = mountObject->getTransform_o2p();
|
|
|
|
bool const transformChanged = (mountTransform_o2p != riderTransform_o2p);
|
|
if (transformChanged)
|
|
mountObject->setTransform_o2p(riderTransform_o2p);
|
|
|
|
if (cellChanged || transformChanged)
|
|
{
|
|
// Update the vehicle's collision property if either the cell or the transform changed.
|
|
// This is effectively warping the vehicle so that the server-side collision doesn't
|
|
// get stuck on something that the rider's client didn't get stuck on. I do not use
|
|
// the warp command on CollisionProperty because it has several other side effects that
|
|
// are heavier than necessary for updating the previous position. If this code ever has
|
|
// problems, consider using the CollisionProperty warp but watch for performance issues.
|
|
CollisionProperty *const mountCollisionProperty = mountObject->getCollisionProperty();
|
|
if (mountCollisionProperty)
|
|
mountCollisionProperty->setLastPos(riderCellProperty, riderTransform_o2p);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Handle alter for mounts functionality, specifically for authoritative
|
|
* CreatureObjects.
|
|
*/
|
|
|
|
void CreatureObject::alterAuthoritativeForMounts()
|
|
{
|
|
DEBUG_FATAL(!isAuthoritative(), ("This should only be called from CreatureObject::alter() when isAuthoratative() is true."));
|
|
|
|
//-- Handle rider or mount incapacitation case.
|
|
Postures::Enumerator const currentPosture = getPosture();
|
|
bool const shouldBeDismounted = (currentPosture != Postures::Upright);
|
|
|
|
if (shouldBeDismounted)
|
|
{
|
|
//-- Check for a rider on a mount.
|
|
if (getState(States::RidingMount))
|
|
{
|
|
DEBUG_LOG(cs_mountInfoChannelName, ("rider id=[%s] is being forced to dismount because rider is in posture [%s]", getNetworkId().getValueString().c_str(), Postures::getPostureName(currentPosture)));
|
|
|
|
// Get the mount, request a dismount.
|
|
CreatureObject *const mountObject = getMountedCreature();
|
|
if (mountObject)
|
|
{
|
|
bool const detachSuccess = mountObject->detachRider(getNetworkId());
|
|
if (!detachSuccess)
|
|
LOG(cs_mountErrorChannelName, ("mount id=[%s] failed to successfully detach the rider during posture-initiated dismount.", mountObject->getNetworkId().getValueString().c_str()));
|
|
else
|
|
{
|
|
DEBUG_LOG(cs_mountInfoChannelName, ("mount id=[%s] successfully detached the rider during posture-initiated dismount.", mountObject->getNetworkId().getValueString().c_str()));
|
|
}
|
|
}
|
|
else
|
|
LOG(cs_mountErrorChannelName, ("unexpected: rider object id=[%s],template=[%s] is reporting state States::RidingMount turned on but getMountedCreature() returned NULL.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
}
|
|
else if (getState(States::MountedCreature))
|
|
{
|
|
DEBUG_LOG(cs_mountInfoChannelName, ("mount id=[%s] is being forced to dismount the rider because the mount is in posture [%s]", getNetworkId().getValueString().c_str(), Postures::getPostureName(currentPosture)));
|
|
|
|
// This creature is the mount. Dismount its rider.
|
|
bool const detachSuccess = detachAllRiders();
|
|
if (!detachSuccess)
|
|
LOG(cs_mountErrorChannelName, ("mount id=[%s] failed to successfully detach the rider during posture-initiated dismount.", getNetworkId().getValueString().c_str()));
|
|
else
|
|
{
|
|
DEBUG_LOG(cs_mountInfoChannelName, ("mount id=[%s] successfully detached the rider during posture-initiated dismount.", getNetworkId().getValueString().c_str()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
static void BroadcastSystemMessageToClusterWithLimiter(char const *message)
|
|
{
|
|
if (ConfigServerGame::getMountsSanityCheckerBroadcastEnabled())
|
|
{
|
|
static int requestCount = 0;
|
|
|
|
++requestCount;
|
|
if ((requestCount > 0) && (requestCount <= ConfigServerGame::getMountsSanityCheckerBroadcastLimit()))
|
|
Chat::broadcastSystemMessageToCluster(Unicode::narrowToWide(message), Unicode::emptyString);
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void CreatureObject::alterAnyForMounts()
|
|
{
|
|
//LogMovement(this);
|
|
|
|
if (ConfigServerGame::getMountsSanityCheckerEnabled())
|
|
{
|
|
// NOTE: it is important to remember that we need to ignore the
|
|
// States::MountedCreature and States::RidingMount states
|
|
// when dealing with proxy objects.
|
|
//
|
|
// Proxy objects for the mount and rider can and usually do
|
|
// get the state changes on a different frame than the
|
|
// container change.
|
|
|
|
//-- Handle sanity checking from a mount's point of view.
|
|
|
|
// Check if the mount has riders.
|
|
|
|
typedef std::vector<CreatureObject const *> Riders;
|
|
Riders riders;
|
|
|
|
getMountingRiders(riders);
|
|
|
|
Riders::const_iterator ii = riders.begin();
|
|
Riders::const_iterator iiEnd = riders.end();
|
|
|
|
for (; ii != iiEnd; ++ii)
|
|
{
|
|
CreatureObject const *const rider = *ii;
|
|
|
|
if (rider)
|
|
{
|
|
bool triggeredWarning = false;
|
|
|
|
// Ensure the rider points back to this mount.
|
|
if (rider->getMountedCreature() != this)
|
|
{
|
|
WARNING(true,
|
|
("mounts sanity check failure: game server id=[%d], mount id=[%s,%s], rider id=[%s,%s], rider's mount address=[%p]: mount has rider but rider says it is mounted by something other than this mount.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
isAuthoritative() ? "authoritative" : "proxy",
|
|
rider->getNetworkId().getValueString().c_str(),
|
|
rider->isAuthoritative() ? "authoritative" : "proxy",
|
|
rider->getMountedCreature()
|
|
));
|
|
triggeredWarning = true;
|
|
}
|
|
|
|
// Ensure this mount has the MountedCreature state (only do check on authoritative mount).
|
|
if (isAuthoritative() && !getState(States::MountedCreature))
|
|
{
|
|
WARNING(true,
|
|
("mounts sanity check failure: game server id=[%d], mount id=[%s,%s], rider id=[%s,%s]: authoritative mount does not have MountedCreature state set but getMountingRider returned a non-NULL value.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
isAuthoritative() ? "authoritative" : "proxy",
|
|
rider->getNetworkId().getValueString().c_str(),
|
|
rider->isAuthoritative() ? "authoritative" : "proxy"
|
|
));
|
|
triggeredWarning = true;
|
|
}
|
|
|
|
// Broadcast system message to the cluster.
|
|
if (triggeredWarning)
|
|
{
|
|
char buffer[1024];
|
|
IGNORE_RETURN(snprintf(buffer, sizeof(buffer) - 1, "Attention player %s: please stop whatever you're doing and contact Todd Fiala at x382 (Austin) to address an issue we need to track down related to mount %s. If this player just logged out, please tell them to contact me.",
|
|
Unicode::wideToNarrow(rider->getObjectName()).c_str(),
|
|
Unicode::wideToNarrow(getObjectName()).c_str()
|
|
));
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
BroadcastSystemMessageToClusterWithLimiter(buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Ensure we don't have the mounted creature state set (only do check on authoritative mount).
|
|
|
|
WARNING(isAuthoritative() && getState(States::MountedCreature),
|
|
("mounts sanity check failure: game server id=[%d], mount id=[%s,%s]: authoritative mount does have MountedCreature state set but getMountingRider() returned a NULL value.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
isAuthoritative() ? "authoritative" : "proxy"
|
|
));
|
|
}
|
|
}
|
|
|
|
//-- Handle sanity checking from a rider's point of view.
|
|
|
|
/*
|
|
// Check if the rider has a mount.
|
|
CreatureObject const *const mount = getMountedCreature();
|
|
if (mount)
|
|
{
|
|
bool triggeredWarning = false;
|
|
|
|
// Ensure the mount has this rider as one of its riders.
|
|
// This needs to change once we support multiple riders per mount.
|
|
|
|
not just the primary!!!
|
|
if (mount->getPrimaryMountingRider() != this)
|
|
{
|
|
WARNING(true,
|
|
("mounts sanity check failure: game server id=[%d], rider id=[%s,%s], mount id=[%s,%s], mount's rider address=[%p]: rider has mount but mount says its rider is something other than this rider.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
isAuthoritative() ? "authoritative" : "proxy",
|
|
mount->getNetworkId().getValueString().c_str(),
|
|
mount->isAuthoritative() ? "authoritative" : "proxy",
|
|
mount->getMountingRider()
|
|
));
|
|
triggeredWarning = true;
|
|
}
|
|
|
|
// Ensure this rider has the RidingMount state (only do check on authoritative rider).
|
|
if (isAuthoritative() && !getState(States::RidingMount))
|
|
{
|
|
WARNING(true,
|
|
("mounts sanity check failure: game server id=[%d], rider id=[%s,%s], mount id=[%s,%s]: authoritative rider does not have RidingMount state set but getMountedCreature() returned a non-NULL value.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
isAuthoritative() ? "authoritative" : "proxy",
|
|
mount->getNetworkId().getValueString().c_str(),
|
|
mount->isAuthoritative() ? "authoritative" : "proxy"
|
|
));
|
|
triggeredWarning = true;
|
|
}
|
|
|
|
// Broadcast system message to the cluster.
|
|
if (triggeredWarning)
|
|
{
|
|
char buffer[1024];
|
|
IGNORE_RETURN(snprintf(buffer, sizeof(buffer) - 1, "Attention player %s: please stop whatever you're doing and contact Todd Fiala at x382 (Austin) to address an issue we need to track down related to mount %s. If this player just logged out, please tell them to contact me.",
|
|
Unicode::wideToNarrow(getObjectName()).c_str(),
|
|
Unicode::wideToNarrow(mount->getObjectName()).c_str()
|
|
));
|
|
buffer[sizeof(buffer) - 1] = '\0';
|
|
BroadcastSystemMessageToClusterWithLimiter(buffer);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Ensure we don't have the RidingMount state set (only do check on authoritative rider).
|
|
WARNING(isAuthoritative() && getState(States::RidingMount),
|
|
("mounts sanity check failure: game server id=[%d], rider id=[%s,%s]: authoritative rider does have RidingMount state set but getMountedCreature() returned a NULL value.",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
isAuthoritative() ? "authoritative" : "proxy"
|
|
));
|
|
}
|
|
*/
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool CreatureObject::onContainerAboutToTransferForMounts(ServerObject const * destination, ServerObject* /* transferer */)
|
|
{
|
|
//-- Do not let a mounted rider get detached from a container.
|
|
bool const isRidingMount = getState(States::RidingMount);
|
|
if (!isRidingMount)
|
|
return true;
|
|
|
|
//-- We only allow container transfers of a rider while the rider is not yet mounted
|
|
// (i.e. the container creature has not yet been set.) We must clear the RidingMount
|
|
// state on a rider prior to intentionally dismounting the rider (see and use detachRider()
|
|
// on the mount object.
|
|
bool const canChangeContainerNow = (getMountedCreature() == NULL);
|
|
if (!canChangeContainerNow)
|
|
{
|
|
LOG("mounts-bug", ("CO::onContainerAboutToTransferForMounts():server id=[%d],rider id=[%s] erroneously tried to transfer the player into object id=[%s].",
|
|
static_cast<int>(GameServer::getInstance().getProcessId()),
|
|
getNetworkId().getValueString().c_str(),
|
|
(destination == NULL) ? "<null (world cell)>" : destination->getNetworkId().getValueString().c_str()));
|
|
}
|
|
return canChangeContainerNow;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Determine if this creature can support becoming a mount.
|
|
*
|
|
* This function only checks the potential for the CreatureObject to
|
|
* become a mount. To check if this creature is a mount, use
|
|
* isMountable().
|
|
*/
|
|
|
|
int CreatureObject::getMountabilityStatus() const
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
{
|
|
LOG(cs_mountInfoChannelName, ("CreatureObject::canBeMadeMountable() called on object id=[%s] when mounts are disabled, ignoring call.", getNetworkId().getValueString().c_str()));
|
|
return static_cast<int>(MountValidScaleRangeTable::MS_speciesUnmountable);
|
|
}
|
|
|
|
//-- Indicate we're mountable if we've already been made mountable.
|
|
if (isMountable())
|
|
return static_cast<int>(MountValidScaleRangeTable::MS_creatureMountable);
|
|
|
|
SharedObjectTemplate const *const sharedObjectTemplate = getSharedTemplate();
|
|
|
|
// Note: multi-seater mount support will need to change saddle capacity.
|
|
int const saddleCapacity = 1;
|
|
float const appearanceScale = getScaleFactor();
|
|
|
|
MountValidScaleRangeTable::MountabilityStatus const result = MountValidScaleRangeTable::doesCreatureSupportScaleAndSaddleCapacity(TemporaryCrcString(sharedObjectTemplate->getAppearanceFilename().c_str(), true), appearanceScale, saddleCapacity);
|
|
if (result != MountValidScaleRangeTable::MS_creatureMountable)
|
|
return static_cast<int>(result);
|
|
|
|
//-- Get the slotted container.
|
|
SlottedContainer const * const slottedContainer = ContainerInterface::getSlottedContainer(*this);
|
|
|
|
int const maxSlots = getSaddleSeatingCapacity(this);
|
|
bool hasRequiredSlot = false;
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
hasRequiredSlot = slottedContainer && slottedContainer->hasSlot(s_riderSlotId[i]);
|
|
|
|
if (hasRequiredSlot)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (hasRequiredSlot ? static_cast<int>(MountValidScaleRangeTable::MS_creatureMountable) : static_cast<int>(MountValidScaleRangeTable::MS_speciesMountableMissingRiderSlot));
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Convert a pet into a mountable-pet.
|
|
*
|
|
* This function will DEBUG_FATAL if canBeMadeMountable() returns false.
|
|
* The caller should have already checked canBeMadeMountable().
|
|
*
|
|
* This function flips the TangibleObject condition flag for C_mount.
|
|
*/
|
|
|
|
void CreatureObject::makePetMountable()
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
{
|
|
LOG(cs_mountInfoChannelName, ("CreatureObject::makePetMountable() called on object id=[%s] when mounts are disabled, ignoring call.", getNetworkId().getValueString().c_str()));
|
|
return;
|
|
}
|
|
|
|
//-- Validate preconditions.
|
|
DEBUG_FATAL(!isAuthoritative(), ("makePetMountable(): called on non-authoritative creature id=[%s],template=[%s].", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
DEBUG_FATAL(getMountabilityStatus() != static_cast<int>(MountValidScaleRangeTable::MS_creatureMountable), ("makePetMountable(): creature id=[%s],template=[%s] this creature cannot be made mountable. Caller should have checked getMountabilityStatus() first.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
DEBUG_FATAL(isMountable(), ("makePetMountable(): creature id=[%s],template=[%s] already is mountable. Caller doesn't need to call makePetMountable().", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
|
|
//-- Get the pet control device network id.
|
|
NetworkId const pcdNetworkId = getPetControlDeviceId();
|
|
if (!pcdNetworkId.isValid())
|
|
{
|
|
DEBUG_FATAL(true, ("makePetMountable(): creature id=[%s],template=[%s] does not have a valid pet control device id on it. Expecting script to place this objvar on the pet.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
return;
|
|
}
|
|
|
|
//-- Set the Condition bit to indicate we're a mount.
|
|
setCondition(getCondition() | static_cast<int>(ServerTangibleObjectTemplate::C_mount));
|
|
|
|
//-- Set the Creature as static so that it doesn't count against the spawn limit.
|
|
setIsStatic(true);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Determine if this creature is a mount.
|
|
*/
|
|
|
|
bool CreatureObject::isMountable() const
|
|
{
|
|
if (ConfigServerGame::getMountsEnabled())
|
|
{
|
|
return (getCondition() & static_cast<int>(ServerTangibleObjectTemplate::C_mount)) != 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool CreatureObject::isMountableAndHasRoomForAnother() const
|
|
{
|
|
bool hasEmptySlot = false;
|
|
|
|
if (ConfigServerGame::getMountsEnabled())
|
|
{
|
|
if (isMountable())
|
|
{
|
|
//-- Get the slotted container.
|
|
SlottedContainer const * const slottedContainer = ContainerInterface::getSlottedContainer(*this);
|
|
|
|
int const maxSlots = getSaddleSeatingCapacity(this);
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
if (slottedContainer && slottedContainer->hasSlot(s_riderSlotId[i]))
|
|
{
|
|
Container::ContainerErrorCode error = Container::CEC_Success;
|
|
|
|
if (slottedContainer->isSlotEmpty(s_riderSlotId[i], error) && error == Container::CEC_Success)
|
|
{
|
|
hasEmptySlot = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hasEmptySlot;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
/**
|
|
* Assuming this CreatureObject is a pet or mount, get the network id
|
|
* of the associated pet control device's id.
|
|
*
|
|
* This function gets the network id from the obj_id pet.controlDevice
|
|
* objvar placed on all pets by the pet script-based system.
|
|
*/
|
|
|
|
NetworkId CreatureObject::getPetControlDeviceId() const
|
|
{
|
|
//-- Retrieve the list of objvars on the creature.
|
|
DynamicVariableList const &variableList = getObjVars();
|
|
|
|
//-- Get pet control device's network id.
|
|
NetworkId petControlDeviceId;
|
|
|
|
if (variableList.getItem(cs_petControlDeviceObjVarName, petControlDeviceId))
|
|
return petControlDeviceId;
|
|
else
|
|
return NetworkId::cms_invalid;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool CreatureObject::detachRider(NetworkId const riderId)
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
return false;
|
|
|
|
//-- Only do this on the authoritative mount.
|
|
if (!isAuthoritative())
|
|
{
|
|
typedef MessageQueueGenericValueType<NetworkId> Message;
|
|
Message * const msg = new Message(riderId);
|
|
sendControllerMessageToAuthServer(CM_detachRiderForMount, msg);
|
|
return true;
|
|
}
|
|
|
|
//-- Validate preconditions.
|
|
DEBUG_FATAL(!isMountable(), ("CreatureObject::detachRider(): called on an object id=[%s],template=[%s] that is not mountable, makes no sense.", getNetworkId().getValueString().c_str()));
|
|
|
|
int const maxSlots = getSaddleSeatingCapacity(this);
|
|
bool forceDetachAll = false; // incase riderId is the first person
|
|
bool detachedRider = false;
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
CreatureObject const * const rider = realGetMountingRider(this, s_riderSlotId[i]);
|
|
|
|
bool const detach = ((rider != 0) && (rider->getNetworkId() == riderId)) || forceDetachAll;
|
|
|
|
if (detach)
|
|
{
|
|
detachedRider = realDetachRider(this, s_riderSlotId[i]);
|
|
if (i == 0)
|
|
{
|
|
forceDetachAll = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (forceDetachAll)
|
|
{
|
|
//-- Turn off MountedCreature state on mount.
|
|
setState(States::MountedCreature, false);
|
|
}
|
|
|
|
//-- Set mount's AlterScheduler schedule phase back to zero so that
|
|
// the mount gets altered along with everything else.
|
|
AlterScheduler::setObjectSchedulePhase(*this, 0);
|
|
|
|
updatePositionOnPlanetServer(true);
|
|
|
|
return detachedRider;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
bool CreatureObject::detachAllRiders()
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
return false;
|
|
|
|
//-- Only do this on the authoritative mount.
|
|
if (!isAuthoritative())
|
|
{
|
|
// add a plural message
|
|
sendControllerMessageToAuthServer(CM_detachAllRidersForMount, NULL);
|
|
return true;
|
|
}
|
|
|
|
//-- Validate preconditions.
|
|
DEBUG_FATAL(!isMountable(), ("CreatureObject::detachRider(): called on an object id=[%s],template=[%s] that is not mountable, makes no sense.", getNetworkId().getValueString().c_str()));
|
|
|
|
int const maxSlots = getSaddleSeatingCapacity(this);
|
|
int numberOfSuccesses = 0;
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
if (realDetachRider(this, s_riderSlotId[i]))
|
|
{
|
|
++numberOfSuccesses;
|
|
}
|
|
}
|
|
|
|
bool const detachedRider = numberOfSuccesses == maxSlots;
|
|
|
|
//-- Turn off MountedCreature state on mount.
|
|
setState(States::MountedCreature, false);
|
|
|
|
//-- Set mount's AlterScheduler schedule phase back to zero so that
|
|
// the mount gets altered along with everything else.
|
|
AlterScheduler::setObjectSchedulePhase(*this, 0);
|
|
|
|
return detachedRider;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
CreatureObject const *CreatureObject::getPrimaryMountingRider() const
|
|
{
|
|
return const_cast<CreatureObject*>(this)->getPrimaryMountingRider();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
CreatureObject * CreatureObject::getPrimaryMountingRider()
|
|
{
|
|
return realGetMountingRider(this, s_riderSlotId[0]);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void CreatureObject::getMountingRiders(std::vector<const CreatureObject *> & riders) const
|
|
{
|
|
int const maxSlots = getSaddleSeatingCapacity(this);
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
CreatureObject const * const rider = realGetMountingRider(this, s_riderSlotId[i]);
|
|
|
|
if (rider != 0)
|
|
{
|
|
riders.push_back(rider);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
void CreatureObject::getMountingRiders(std::vector<CreatureObject *> & riders)
|
|
{
|
|
int const maxSlots = getSaddleSeatingCapacity(this);
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
CreatureObject * const rider = realGetMountingRider(this, s_riderSlotId[i]);
|
|
|
|
if (rider != 0)
|
|
{
|
|
riders.push_back(rider);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Instruct this rider to ride on the specified mount.
|
|
*
|
|
* @param mountObject the CreatureObject that will be ridden by this
|
|
* instance.
|
|
*
|
|
* @return true if this rider was able to mount the creature; false otherwise.
|
|
*/
|
|
|
|
bool CreatureObject::mountCreature(CreatureObject &mountObject)
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
{
|
|
LOG(cs_mountInfoChannelName, ("CreatureObject::mountCreature() called on rider object id=[%s] when mounts are disabled, ignoring call.", getNetworkId().getValueString().c_str()));
|
|
return false;
|
|
}
|
|
|
|
//-- Ensure we (the rider) are not already mounted.
|
|
if (getState(States::RidingMount))
|
|
{
|
|
DEBUG_FATAL(true, ("mountCreature(): rider id=[%s] template=[%s] is already riding a mount.", getNetworkId().getValueString().c_str(), getObjectTemplateName()));
|
|
return false;
|
|
}
|
|
|
|
//-- Ensure the mount is mountable.
|
|
if (!mountObject.isMountable())
|
|
{
|
|
DEBUG_FATAL(true, ("mountCreature(): specified mountObject id=[%s] template=[%s] is not mountable.", mountObject.getNetworkId().getValueString().c_str(), mountObject.getObjectTemplateName()));
|
|
return false;
|
|
}
|
|
|
|
//-- Ensure both the mount and the rider are authoritative on this server.
|
|
// (They should be by design of how this function gets called.)
|
|
if (!isAuthoritative() || !mountObject.isAuthoritative())
|
|
{
|
|
DEBUG_WARNING(true, ("mountCreature(): rider id=[%s] or mount id=[%s] was not authoritative on this server, mounting aborted.", getNetworkId().getValueString().c_str(), mountObject.getNetworkId().getValueString().c_str()));
|
|
return false;
|
|
}
|
|
|
|
//-- Reject if the player is not in the world cell
|
|
if (!isInWorldCell())
|
|
{
|
|
DEBUG_WARNING(true, ("mountCreature(): rider id=[%s] is not in the world cell, mounting aborted.", getNetworkId().getValueString().c_str()));
|
|
return false;
|
|
}
|
|
|
|
//-- Reject if the mount is not in the world cell
|
|
if (!mountObject.isInWorldCell())
|
|
{
|
|
DEBUG_WARNING(true, ("mountCreature(): mount id=[%s] is not in the world cell, mounting aborted.", mountObject.getNetworkId().getValueString().c_str()));
|
|
return false;
|
|
}
|
|
|
|
// ... set the new mount-related states on the rider and mount. This is necessary
|
|
// so that I can efficiently handle CreatureObject::onContainerTransferComplete,
|
|
// which is called before transferItemToSlottedContainer() completes.
|
|
setState(States::RidingMount, true);
|
|
mountObject.setState(States::MountedCreature, true);
|
|
|
|
Container::ContainerErrorCode errorCode = Container::CEC_Success;
|
|
bool transferSuccess = false;
|
|
|
|
// transfer into the first available slot
|
|
|
|
int const maxSlots = getSaddleSeatingCapacity(&mountObject);
|
|
|
|
for (int i = 0; i < maxSlots; ++i)
|
|
{
|
|
if (ContainerInterface::canTransferToSlot(mountObject, *this, s_riderSlotId[i], NULL, errorCode))
|
|
{
|
|
transferSuccess = ContainerInterface::transferItemToSlottedContainer(mountObject, *this, s_riderSlotId[i], NULL, errorCode);
|
|
|
|
if ((transferSuccess) && (errorCode == Container::CEC_Success))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
DEBUG_FATAL(transferSuccess && (errorCode != Container::CEC_Success), ("mountCreature(): transferItemToSlottedContainer() returned success but container error code returned error %d.", static_cast<int>(errorCode)));
|
|
|
|
if (!transferSuccess)
|
|
{
|
|
// Clear these states so that the objects are not in a funky bad state.
|
|
setState(States::RidingMount, false);
|
|
|
|
if (mountObject.getPrimaryMountingRider() == 0)
|
|
{
|
|
// clear state if empty mount
|
|
mountObject.setState(States::MountedCreature, false);
|
|
}
|
|
|
|
return transferSuccess;
|
|
}
|
|
|
|
//-- Take the rider out of CollisionWorld.
|
|
// @todo: add all object non-auto-delta change info to another function
|
|
// that can be called via controller message to proxies, end baselines
|
|
// on proxies and from here.
|
|
if (isInWorld())
|
|
CollisionWorld::removeObject(this);
|
|
|
|
CollisionProperty *const collisionProperty = getCollisionProperty();
|
|
if (collisionProperty)
|
|
collisionProperty->setDisableCollisionWorldAddRemove(true);
|
|
|
|
//-- Set the mount's AlterScheduler phase to 1 so that it gets an alter after the rider.
|
|
// The rider will count on this so that the rider can update the mount's server position
|
|
// prior to the mount getting altered that frame.
|
|
AlterScheduler::setObjectSchedulePhase(mountObject, 1);
|
|
|
|
//-- Tell rider to update movement info so it pulls walk/run speed from the mount.
|
|
updateMovementInfo();
|
|
|
|
//-- Indicate transfer success.
|
|
return transferSuccess;
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
CreatureObject const *CreatureObject::getMountedCreature() const
|
|
{
|
|
return const_cast<CreatureObject*>(this)->getMountedCreature();
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
|
|
CreatureObject *CreatureObject::getMountedCreature()
|
|
{
|
|
//-- Ensure mounts are enabled.
|
|
if (!ConfigServerGame::getMountsEnabled())
|
|
return NULL;
|
|
|
|
//-- Check this creature's container object.
|
|
ServerObject *const container = safe_cast<ServerObject*>(ContainerInterface::getContainedByObject(*this));
|
|
if (!container)
|
|
return NULL;
|
|
|
|
//-- Check if the container is a creature.
|
|
CreatureObject *const creatureContainer = container->asCreatureObject();
|
|
if (!creatureContainer)
|
|
return NULL;
|
|
|
|
//-- Ignore non-mountable creature objects.
|
|
CreatureObject *const mount = creatureContainer->isMountable() ? creatureContainer : NULL;
|
|
return mount;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------
|
|
/**
|
|
* Cleanup the mount-related state on this rider such that they no longer
|
|
* act as if mounted.
|
|
*
|
|
* DO NOT CALL THIS UNLESS YOU KNOW THAT THE INTERNAL MOUNTS STATE IS SOMEHOW
|
|
* OUT OF SYNC.
|
|
*
|
|
* The purpose for this emergency function is to handle the case where internal
|
|
* mount-related state is no longer consistent. This function is intended to be
|
|
* called when a rider appears to be mounted (via the RidingMount state) but
|
|
* is not contained by its mount.
|
|
*/
|
|
|
|
void CreatureObject::emergencyDismountForRider()
|
|
{
|
|
LOG("mounts-bug", ("CreatureObject::emergencyDismountRider() called, server id=[%d], rider id=[%s], rider is [%s]", static_cast<int>(GameServer::getInstance().getProcessId()), getNetworkId().getValueString().c_str(), isAuthoritative() ? "authoritative" : "proxy"));
|
|
|
|
//-- Ensure we have the RidingMount state.
|
|
if (!getState(States::RidingMount))
|
|
{
|
|
WARNING(true, ("CreatureObject::emergencyDismountRider() called when RidingMount state is not set. Ignoring call."));
|
|
return;
|
|
}
|
|
|
|
//-- Pass request along to authoritative server if we're not it.
|
|
if (!isAuthoritative())
|
|
{
|
|
sendControllerMessageToAuthServer(CM_emergencyDismountForRider, NULL);
|
|
return;
|
|
}
|
|
|
|
//-- Try to get the mount.
|
|
CreatureObject *const mount = getMountedCreature();
|
|
if (mount)
|
|
{
|
|
IGNORE_RETURN(mount->detachRider(getNetworkId()));
|
|
return;
|
|
}
|
|
|
|
// NOTE: if we've made it this far, the rider appears to be mounted (by states) but the
|
|
// containment relationship has been broken erroneously. Try to fixup the player from here.
|
|
|
|
//-- Set state to be not riding.
|
|
setState(States::RidingMount, false);
|
|
|
|
//-- Transfer the rider to the world cell.
|
|
Container::ContainerErrorCode errorCode = Container::CEC_Success;
|
|
bool const transferSucceeded = ContainerInterface::transferItemToWorld (*this, this->getTransform_o2w(), NULL, errorCode);
|
|
WARNING(!transferSucceeded || (errorCode != Container::CEC_Success), ("Transfer to world failed, return value=[%s], error code=[%d].", transferSucceeded ? "true" : "false", static_cast<int>(errorCode)));
|
|
}
|
|
|
|
// ======================================================================
|