// ====================================================================== // // 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 // ====================================================================== 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 Riders; Riders riders; if (mount != 0) { mount->getMountingRiders(riders); if (!riders.empty()) { { int const process = static_cast(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(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(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(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(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(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 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(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(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(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(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(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(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(GameServer::getInstance().getProcessId()), getNetworkId().getValueString().c_str(), (destination == NULL) ? "" : 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(MountValidScaleRangeTable::MS_speciesUnmountable); } //-- Indicate we're mountable if we've already been made mountable. if (isMountable()) return static_cast(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(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(MountValidScaleRangeTable::MS_creatureMountable) : static_cast(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(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(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(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 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(this)->getPrimaryMountingRider(); } // ---------------------------------------------------------------------- CreatureObject * CreatureObject::getPrimaryMountingRider() { return realGetMountingRider(this, s_riderSlotId[0]); } // ---------------------------------------------------------------------- void CreatureObject::getMountingRiders(std::vector & 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 & 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(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(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(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(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(errorCode))); } // ======================================================================