Files
src-1.2/engine/server/library/serverGame/src/shared/object/CreatureObject_Mounts.cpp
T
2014-01-15 03:59:18 -07:00

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)));
}
// ======================================================================