// ====================================================================== // //ServerObject.cpp // //Copyright 2000-2003 Sony Online Entertainment // // ====================================================================== #include "serverGame/FirstServerGame.h" #include "serverGame/ServerObject.h" #include "UnicodeUtils.h" #include "serverGame/AuthTransferTracker.h" #include "serverGame/BuildingObject.h" #include "serverGame/CellObject.h" #include "serverGame/Chat.h" #include "serverGame/Client.h" #include "serverGame/CommoditiesMarket.h" #include "serverGame/ConfigServerGame.h" #include "serverGame/ConnectionServerConnection.h" #include "serverGame/ConsoleManager.h" #include "serverGame/ContainerInterface.h" #include "serverGame/ContainmentMessageManager.h" #include "serverGame/CreatureObject.h" #include "serverGame/DistributionListStack.h" #include "serverGame/GameServer.h" #include "serverGame/InstantDeleteList.h" #include "serverGame/IntangibleVolumeContainer.h" #include "serverGame/LineOfSightCache.h" #include "serverGame/LogoutTracker.h" #include "serverGame/MessageToQueue.h" #include "serverGame/NetworkTriggerVolume.h" #include "serverGame/NewbieTutorial.h" #include "serverGame/ObjectIdManager.h" #include "serverGame/ObjectTracker.h" #include "serverGame/ObserveTracker.h" #include "serverGame/PatrolPathNodeProperty.h" #include "serverGame/PatrolPathRootProperty.h" #include "serverGame/PlayerCreatureController.h" #include "serverGame/PlayerQuestObject.h" #include "serverGame/PlayerObject.h" #include "serverGame/PlayerSanityChecker.h" #include "serverGame/PlayerShipController.h" #include "serverGame/PositionUpdateTracker.h" #include "serverGame/ResourceContainerObject.h" #include "serverGame/ServerClearCollidableFloraNotification.h" #include "serverGame/ServerController.h" #include "serverGame/ServerCreatureObjectTemplate.h" #include "serverGame/ServerMessageForwarding.h" #include "serverGame/ServerNoBuildNotification.h" #include "serverGame/ServerObjectTemplate.h" #include "serverGame/ServerObjectTerrainModificationNotification.h" #include "serverGame/ServerResourceContainerObjectTemplate.h" #include "serverGame/ServerSecureTrade.h" #include "serverGame/ServerSynchronizedUi.h" #include "serverGame/ServerUIManager.h" #include "serverGame/ServerUniverse.h" #include "serverGame/ServerWorld.h" #include "serverGame/SpaceVisibilityManager.h" #include "serverGame/SceneGlobalData.h" #include "serverGame/TangibleObject.h" #include "serverGame/TangibleVolumeContainer.h" #include "serverGame/TeleportFixupHandler.h" #include "serverGame/TriggerVolume.h" #include "serverGame/VeteranRewardManager.h" #include "serverNetworkMessages/AccountFeatureIdRequest.h" #include "serverNetworkMessages/AdjustAccountFeatureIdResponse.h" #include "serverNetworkMessages/FlagObjectForDeleteMessage.h" #include "serverNetworkMessages/GameConnectionServerMessages.h" #include "serverNetworkMessages/LoadContainedObjectMessage.h" #include "serverNetworkMessages/LoadContentsMessage.h" #include "serverNetworkMessages/MessageQueueTeleportObject.h" #include "serverNetworkMessages/MessageToAckMessage.h" #include "serverNetworkMessages/MessageToMessage.h" #include "serverNetworkMessages/MessageToPayload.h" #include "serverNetworkMessages/UnloadObjectMessage.h" #include "serverPathfinding/ServerPathfindingConstants.h" #include "serverScript/GameScriptObject.h" #include "serverScript/ScriptParameters.h" #include "serverUtility/ChatLogManager.h" #include "serverUtility/ServerClock.h" #include "Session/CommonAPI/CommonAPI.h" #include "sharedCollision/BaseExtent.h" #include "sharedCollision/CollisionProperty.h" #include "sharedCollision/CollisionWorld.h" #include "sharedDebug/Profiler.h" #include "sharedFoundation/CalendarTime.h" #include "sharedFoundation/ConstCharCrcLowerString.h" #include "sharedFoundation/Clock.h" #include "sharedFoundation/ConstCharCrcString.h" #include "sharedFoundation/DynamicVariableLocationData.h" #include "sharedFoundation/FormattedString.h" #include "sharedFoundation/GameControllerMessage.h" #include "sharedFoundation/Os.h" #include "sharedGame/LfgCharacterData.h" #include "sharedGame/PlatformFeatureBits.h" #include "sharedGame/ProsePackage.h" #include "sharedGame/QuestManager.h" #include "sharedGame/SharedObjectTemplate.h" #include "sharedGame/SharedStringIds.h" #include "sharedGame/SocialsManager.h" #include "sharedGame/SpatialChatManager.h" #include "sharedLog/Log.h" #include "sharedMathArchive/TransformArchive.h" #include "sharedMessageDispatch/Message.h" #include "sharedMessageDispatch/Transceiver.h" #include "sharedNetworkMessages/AIDebuggingMessages.h" #include "sharedNetworkMessages/ChatAvatarId.h" #include "sharedNetworkMessages/CommandChannelMessages.h" #include "sharedNetworkMessages/DeltasMessage.h" #include "sharedNetworkMessages/GenericValueTypeMessage.h" #include "sharedNetworkMessages/LogoutMessage.h" #include "sharedNetworkMessages/MessageQueueCombatSpam.h" #include "sharedNetworkMessages/MessageQueueDataTransform.h" #include "sharedNetworkMessages/MessageQueueDataTransformWithParent.h" #include "sharedNetworkMessages/MessageQueueGeneric.h" #include "sharedNetworkMessages/MessageQueueGenericValueType.h" #include "sharedNetworkMessages/MessageQueueNetworkIdAndTransform.h" #include "sharedNetworkMessages/MessageQueueSocial.h" #include "sharedNetworkMessages/MessageQueueSpatialChat.h" #include "sharedNetworkMessages/ObjectChannelMessages.h" #include "sharedObject/AlterResult.h" #include "sharedObject/AlterScheduler.h" #include "sharedObject/Appearance.h" #include "sharedObject/ArrangementDescriptor.h" #include "sharedObject/CellProperty.h" #include "sharedObject/ContainedByProperty.h" #include "sharedObject/Container.h" #include "sharedObject/CustomizationData.h" #include "sharedObject/CustomizationDataProperty.h" #include "sharedObject/LayerProperty.h" #include "sharedObject/NetworkIdManager.h" #include "sharedObject/ObjectTemplateList.h" #include "sharedObject/PortalProperty.h" #include "sharedObject/RangedIntCustomizationVariable.h" #include "sharedObject/SlotDescriptor.h" #include "sharedObject/SlotDescriptorList.h" #include "sharedObject/SlotIdManager.h" #include "sharedObject/SlottedContainer.h" #include "sharedObject/SlottedContainmentProperty.h" #include "sharedObject/VolumeContainmentProperty.h" #include "sharedTerrain/TerrainObject.h" #include "sharedUtility/DataTable.h" #include "sharedUtility/DataTableManager.h" #include "swgServerNetworkMessages/MessageQueueScriptTransferMoney.h" #include // ====================================================================== namespace DeleteReasons { const std::string & describeDeleteReason(const int reason) { switch(reason) { case NotDeleted: { static const std::string r = "NotDeleted"; return r; } break; case Decay: { static const std::string r = "Decay"; return r; } break; case God: { static const std::string r = "God"; return r; } break; case Player: { static const std::string r = "Player"; return r; } break; case Script: { static const std::string r = "Script"; return r; } break; case CharacterDeleted: { static const std::string r = "CharacterDeleted"; return r; } break; case ContainerDeleted: { static const std::string r = "ContainerDeleted"; return r; } break; case Publish: { static const std::string r = "Publish"; return r; } break; case BadContainerTransfer: { static const std::string r = "BadContainerTransfer"; return r; } break; case Consumed: { static const std::string r = "Consumed"; return r; } break; case SetupFailed: { static const std::string r = "SetupFailed"; return r; } break; case Replaced: { static const std::string r = "Replaced"; return r; } break; } static const std::string unknown = "UNKNOWN"; return unknown; } } using namespace DeleteReasons; // ====================================================================== namespace ServerObjectNamespace { const std::string OBJVAR_STRUCTURE_PLAYER_PACK("structure.player.pack"); const std::string OBJVAR_OPEN_BANK_TERMINAL_ID("open_bank_location"); unsigned long gs_objectCount = 0; const char * const portalPropertyCrcObjectVariableName = "portalProperty.crc"; // ---------------------------------------------------------------------- const std::string objvar_item_attribute_keys = "item_attrib_keys"; const std::string objvar_item_attribute_values = "item_attrib_values"; // ---------------------------------------------------------------------- const uint32 s_clearLocalFlagsOnConcludeMask = ~( (1u<, MessageToId> s_currentMessageToBeingHandled = std::make_pair(std::make_pair(0, 0), MessageToId::cms_invalid); // ---------------------------------------------------------------------- // list of object types that supports no trade "shared" behavior // (no trade item of those types can be picked up by another character // on the same account as the character who currently owns the item) std::set s_noTradeSharedTemplateItemType; std::set s_noTradeSharedStaticItemType; // ---------------------------------------------------------------------- void sendDeleteToDatabase(const ServerObject& obj, int reason) { bool const immediate = InstantDeleteList::isOnList(*obj.getObjectTemplate()); bool const cascadeDeleteReason = !obj.getLoadContents() && reason == DeleteReasons::House; // Anything that is deleted by house deletion and is demanded-loaded (i.e. cells) should have the delete reason of its contents set to DeleteReasons::House also, instead of DeleteReasons::ContainerDeleted. This is so the /restoreHouse command can identify these items FlagObjectForDeleteMessage const dm(obj.getNetworkId(), reason, immediate, !obj.getLoadContents(), cascadeDeleteReason); GameServer::getInstance().sendToDatabaseServer(dm); } //---------------------------------------------------------------------- void flagChildObjectsForDeletion(ServerObject &obj, int reason) { Container * const container = ContainerInterface::getContainer(obj); if (container) { for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator) { ServerObject * const containedObject = safe_cast((*containerIterator).getObject()); if (containedObject && containedObject->isPersisted()) { sendDeleteToDatabase(*containedObject, reason); LOG("CustomerService",("Deletion:%s deleted from game. Reason: %s", ServerObject::getLogDescription(containedObject).c_str(), describeDeleteReason(reason).c_str())); flagChildObjectsForDeletion(*containedObject, reason); } } } } //---------------------------------------------------------------------- void endBaselinesInitializeScript(ServerObject& obj, bool doChildCheck) { if (doChildCheck) { ServerObject * parent = safe_cast(ContainerInterface::getContainedByObject(obj)); if (parent && !parent->isInitialized()) return; } obj.getScriptObject()->initScriptInstances(); obj.getScriptObject()->setOwnerIsInitialized(); Container* container = ContainerInterface::getContainer(obj); if (container) { std::vector initList; for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator) { initList.push_back(*containerIterator); } for(std::vector::iterator i = initList.begin(); i != initList.end(); ++i) { ServerObject * containedObject = safe_cast((*i).getObject()); if (!containedObject) { WARNING_STRICT_FATAL(true, ("Cannot initialize object %s because it did not resolve to an object from an id", (*i).getValueString().c_str())); } else endBaselinesInitializeScript(*containedObject, false); } } } //---------------------------------------------------------------------- const std::string OBJVAR_BANK_NAME_ID = "banking_bankid"; //---------------------------------------------------------------------- bool checkBankTransfer(ServerObject* dest, const ServerObject& item, const ServerObject* transferer) { if (!dest || !transferer) return true; const CreatureObject * const creature = transferer->asCreatureObject(); if (creature && dest == creature->getBankContainer()) { float distance = 0.0f; //Banks are carried along with the player, so their distance //is always going to be 0. This block changes the parentInWorld //to be the bank terminal that was opened rather than the bank //itself. const ServerObject * const bank = creature->getBankContainer(); if (bank) { NetworkId bankTerminalId; if (bank->areContentsLoaded() && creature->getObjVars().getItem(OBJVAR_OPEN_BANK_TERMINAL_ID,bankTerminalId)) { const TangibleObject * const bankTerminal = safe_cast(NetworkIdManager::getObjectById(bankTerminalId)); if (bankTerminal) { Unicode::String bankName; if (bankTerminal->getObjVars().getItem(OBJVAR_BANK_NAME_ID, bankName)) { if (!creature->isBankMember(bankName)) { return false; } } distance = creature->getPosition_w().magnitudeBetween(bankTerminal->getPosition_w()); distance -= dest->getRadius(); distance -= bankTerminal->getRadius(); // Check range on the server with a 1m fudge factor in it. if (distance > 6.0f + 1.0f) //todo MLS make that range of 6.0 configurable { static const StringId message("system_msg", "out_of_range"); Chat::sendSystemMessage(*creature, message, Unicode::emptyString); return false; } } else { return false; } } else { return false; } } } return true; } //---------------------------------------------------------------------- void getPlayerControlledContents(ServerObject &obj, std::vector &results) { if (obj.isPlayerControlled()) results.push_back(CachedNetworkId(obj)); Container *container = ContainerInterface::getContainer(obj); if (container) { for (ContainerIterator i = container->begin(); i != container->end(); ++i) { ServerObject *containedObject = safe_cast((*i).getObject()); if (containedObject) getPlayerControlledContents(*containedObject, results); } } } // ---------------------------------------------------------------------- void getContainedClients(ServerObject &obj, std::vector > &containedClients) { // find all clients for primary controlled objects in the containment hierarchy under obj (including obj) Client * const client = obj.getClient(); if (client && client->getCharacterObjectId() == obj.getNetworkId()) containedClients.push_back(Watcher(client)); Container * const container = ContainerInterface::getContainer(obj); if (container) { for (ContainerIterator i = container->begin(); i != container->end(); ++i) { ServerObject * const containedObject = safe_cast((*i).getObject()); if (containedObject) getContainedClients(*containedObject, containedClients); } } } // ---------------------------------------------------------------------- unsigned int getPackedObjVarField(std::string const &buf, unsigned int offset, std::string &result) { std::vector s; while (offset < buf.size()) { if (buf[offset] == '|') break; s.push_back(buf[offset++]); } s.push_back('\0'); result = &(s[0]); return offset+1; } // ---------------------------------------------------------------------- unsigned int getPackedObjVarField(std::string const &buf, unsigned int offset, int &result) { std::string s; offset = getPackedObjVarField(buf, offset, s); result = atoi(s.c_str()); return offset; } // ---------------------------------------------------------------------- void appendPackedObjVarField(std::vector &buf, char const *s) { while (*s) { if ((*s) != '|') buf.push_back(*s); ++s; } buf.push_back('|'); } // ---------------------------------------------------------------------- void appendPackedObjVarField(std::vector &buf, int val) { char tempbuf[32]; sprintf(tempbuf, "%d", val); appendPackedObjVarField(buf, tempbuf); } // ---------------------------------------------------------------------- void remove() { } } using namespace ServerObjectNamespace; // ====================================================================== const SharedObjectTemplate * ServerObject::m_defaultSharedTemplate = nullptr; float ServerObject::ms_buildingUpdateRadiusMultiplier = 0; // ====================================================================== class ServerObject::PobFixupNotification : public ObjectNotification { public: PobFixupNotification(); ~PobFixupNotification(); virtual void pobFixupComplete(Object &object) const; private: PobFixupNotification(const PobFixupNotification & ); PobFixupNotification& operator= (const PobFixupNotification&); }; //------------------------------------------------------------------------------------------ ServerObject::PobFixupNotification::PobFixupNotification() : ObjectNotification() { } //------------------------------------------------------------------------------------------ ServerObject::PobFixupNotification::~PobFixupNotification() { } //------------------------------------------------------------------------------------------ void ServerObject::PobFixupNotification::pobFixupComplete(Object &object) const { static_cast(&object)->onPobFixupComplete(); // object.removeNotification(*this); } //------------------------------------------------------------------------------------------ static ServerObject::PobFixupNotification pobFixupNotification; //------------------------------------------------------------------------------------------ class ServerObject::MoveNotification : public ObjectNotification { public: MoveNotification(); ~MoveNotification(); virtual void addToWorld(Object &object) const; virtual void cellChanged(Object &object, bool dueToParentChange) const; virtual bool positionChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const; virtual void rotationChanged(Object &object, bool dueToParentChange) const; virtual bool positionAndRotationChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const; virtual void visibilityDataChanged(Object &object) const; private: void checkForGoldObject(const Object &object) const; private: MoveNotification(const MoveNotification &); MoveNotification & operator= (const MoveNotification &); }; static ServerObject::MoveNotification moveNotification; //------------------------------------------------------------------------------------------ ServerObject::MoveNotification::MoveNotification() : ObjectNotification() { } //------------------------------------------------------------------------------------------ ServerObject::MoveNotification::~MoveNotification() { } //------------------------------------------------------------------------------------------ void ServerObject::MoveNotification::addToWorld(Object &object) const { static_cast(object).updateWorldSphere(); } // ---------------------------------------------------------------------- void ServerObject::MoveNotification::checkForGoldObject(const Object &object) const { if (ConfigServerGame::getMaxGoldNetworkId()!=NetworkId::cms_invalid && object.getNetworkId() < ConfigServerGame::getMaxGoldNetworkId()) { DEBUG_FATAL(ConfigServerGame::getFatalOnMovingGoldObject(),("Attempted to move object %s, which is a gold object that should not be movable.", object.getNetworkId().getValueString().c_str())); DEBUG_WARNING(true,("Attempted to move object %s, which is a gold object that should not be movable.", object.getNetworkId().getValueString().c_str())); } } //------------------------------------------------------------------------------------------ void ServerObject::MoveNotification::cellChanged(Object &object, bool dueToParentChange) const { checkForGoldObject(object); static_cast(object).updateWorldSphere(); if (!dueToParentChange) { DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing cells")); static_cast(object).setTransformChanged(true); } } //------------------------------------------------------------------------------------------ bool ServerObject::MoveNotification::positionChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const { checkForGoldObject(object); UNREF(oldPosition); static_cast(object).updateWorldSphere(); if (!dueToParentChange) { DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing position")); static_cast(object).setTransformChanged(true); } return true; } //------------------------------------------------------------------------------------------ void ServerObject::MoveNotification::rotationChanged(Object &object, bool dueToParentChange) const { checkForGoldObject(object); static_cast(object).updateWorldSphere(); if (!dueToParentChange) { DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing rotation")); static_cast(object).setTransformChanged(true); } } //------------------------------------------------------------------------------------------ bool ServerObject::MoveNotification::positionAndRotationChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const { checkForGoldObject(object); UNREF(oldPosition); static_cast(object).updateWorldSphere(); if (!dueToParentChange) { DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing position and rotation")); static_cast(object).setTransformChanged(true); } return true; } //------------------------------------------------------------------------------------------ void ServerObject::MoveNotification::visibilityDataChanged(Object &object) const { static_cast(object).updateWorldSphere(); static_cast(object).addObjectToConcludeList(); } // ====================================================================== void ServerObject::AuthProcessIdCallback::modified(ServerObject &target, uint32 oldValue, uint32, bool) const { AuthTransferTracker::sendConfirmAuthTransfer(target.getNetworkId(), oldValue); } void ServerObject::ObserversCountCallback::modified(ServerObject &target, int oldValue, int, bool) const { target.observersCountChanged(); } // ====================================================================== ServerObject::ServerObject(const ServerObjectTemplate* newTemplate, const ObjectNotification ¬ification, bool const hyperspaceOnCreate) : Object (newTemplate, NetworkId::cms_invalid), m_oldPosition (), m_sharedTemplate (nullptr), m_client (0), m_observers (), m_localFlags (0), m_scriptObject (new GameScriptObject), m_objVars (), m_observersCount (0), m_volume (newTemplate->getVolume()), m_complexity (newTemplate->getComplexity()), m_sceneId (ServerWorld::getSceneId()), m_objectName (), m_includeInBuildout (false), m_nameStringId (), m_staticItemName (), m_staticItemVersion (0), m_conversionId (0), m_playerControlled (false), m_synchronizedUi (), m_pendingSyncUi (0), m_persisted (false), m_authServerProcessId (), m_proxyServerProcessIds (), m_transformSequence (), m_cacheVersion (0), m_loadContents (true), m_contentsLoaded (false), m_contentsRequested (false), m_loadWith (NetworkId::cms_invalid), m_cashBalance (0), m_bankBalance (0), m_defaultAlterTime (AlterResult::cms_keepNoAlter), m_broadcastListeners (), m_broadcastBroadcasters (), m_authClientServerPackage (), m_authClientServerPackage_np (), m_firstParentAuthClientServerPackage(), m_firstParentAuthClientServerPackage_np(), m_serverPackage (), m_serverPackage_np (), m_sharedPackage (), m_sharedPackage_np (), m_networkUpdateFar (nullptr), m_triggerVolumes (), m_attributesAttained (), m_attributesInterested (), m_triggerVolumeInfo (), m_messageTos (), m_worldSphere (Sphere::unit), m_dirtyArchiveCallback (), m_unreliableControllerMessageSequenceNumber(0), m_gameObjectType (0), m_calledTriggerDestroy (false), m_calledTriggerRemovingFromWorld(false), m_loadCTSBank(false), m_loadCTSPackedHouses(false) { synchronizationOnConstruct(); m_observersCount.setSourceObject(this); m_authServerProcessId.setSourceObject(this); NOT_NULL(newTemplate); setLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnCreate, hyperspaceOnCreate); setLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnDestruct, false); #ifdef _DEBUG setDebugName(getTemplateName()); #endif updateWorldSphere(); const std::string & sharedTemplateName = newTemplate->getSharedTemplate(); m_sharedTemplate = dynamic_cast(ObjectTemplateList::fetch(sharedTemplateName)); if (m_sharedTemplate == nullptr) { WARNING_STRICT_FATAL(!sharedTemplateName.empty(), ("Template %s has an invalid shared template %s. We will use the default shared template for now.", newTemplate->getName(), sharedTemplateName.c_str())); } if (getSharedTemplate() != nullptr) { m_nameStringId = getSharedTemplate()->getObjectName(); m_descriptionStringId = getSharedTemplate()->getDetailedDescription(); } m_scriptObject->setOwner(this); ContainedByProperty *containedBy = new ContainedByProperty(*this, nullptr); addProperty(*containedBy); //-- create the SlottedContainment property SlottedContainmentProperty *slottedProperty = new SlottedContainmentProperty(*this); addProperty(*slottedProperty); //-- get ArrangementDescriptor if specified if (getSharedTemplate() != nullptr) { const ArrangementDescriptor *const arrangementDescriptor = getSharedTemplate()->getArrangementDescriptor(); if (arrangementDescriptor) { const int arrangementCount = arrangementDescriptor->getArrangementCount(); for (int i = 0; i < arrangementCount; ++i) { slottedProperty->addArrangement(arrangementDescriptor->getArrangement(i)); } } } //set up containers on this object if it has any if (getSharedTemplate() != nullptr) { SharedObjectTemplate::ContainerType const containerType = getSharedTemplate()->getContainerType(); switch (containerType) { //buildings (portal properties get added below case SharedObjectTemplate::CT_none: break; case SharedObjectTemplate::CT_slotted: case SharedObjectTemplate::CT_ridable: { const SlotDescriptor *const slotDescriptor = getSharedTemplate()->getSlotDescriptor(); if (slotDescriptor) { //-- create a SlottedContainer with the given slots SlottedContainer* slotContainer = new SlottedContainer(*this, slotDescriptor->getSlots()); addProperty(*slotContainer); slotContainer = 0; } else { DEBUG_WARNING(true, ("DATA ERROR: specified slotted container type but could not get slot descriptor")); } break; } case SharedObjectTemplate::CT_volume: { VolumeContainer * volumeContainer = 0; int maxVolume = getSharedTemplate()->getContainerVolumeLimit(); if (maxVolume <= 0) { maxVolume = VolumeContainer::VL_NoVolumeLimit; } volumeContainer = new TangibleVolumeContainer(*this, maxVolume); addProperty(*volumeContainer); volumeContainer = 0; break; } case SharedObjectTemplate::CT_volumeIntangible: { int maxVolume = getSharedTemplate()->getContainerVolumeLimit(); if (maxVolume <= 0) { maxVolume = VolumeContainer::VL_NoVolumeLimit; } VolumeContainer * volumeContainer = new IntangibleVolumeContainer(*this, maxVolume); addProperty(*volumeContainer); volumeContainer = 0; break; } case SharedObjectTemplate::CT_volumeGeneric: { int maxVolume = getSharedTemplate()->getContainerVolumeLimit(); if (maxVolume <= 0) { maxVolume = VolumeContainer::VL_NoVolumeLimit; } VolumeContainer * volumeContainer = new VolumeContainer(*this, maxVolume); addProperty(*volumeContainer); volumeContainer = 0; break; } default: DEBUG_WARNING(true, ("Invalid container type specified.")); break; } setLocalFlag(LocalObjectFlags::ServerObject_SendToClient, getSharedTemplate()->getSendToClient()); m_gameObjectType = static_cast(getSharedTemplate()->getGameObjectType()); } //If the object template specifies a volume, give it a volumeContainment int vol = newTemplate->getVolume(); VolumeContainmentProperty* volumeProperty = new VolumeContainmentProperty(*this, (vol < 1 ? 1 : vol) ); addProperty(*volumeProperty); volumeProperty = 0; //lint !e423 // creation of memory leak // no, managed by property list // add the portal property if one is requested if (getSharedTemplate() != nullptr) { const std::string &portalLayoutFileName = getSharedTemplate()->getPortalLayoutFilename(); if (!portalLayoutFileName.empty()) { PortalProperty *portalProperty = new PortalProperty(*this, portalLayoutFileName.c_str()); addProperty(*portalProperty); } } m_triggerVolumeInfo.setOnChanged(this, &ServerObject::updateTriggerVolumes); addMembersToPackages(); addNotification(notification); addNotification(moveNotification); gs_objectCount++; ObjectTracker::addObject(); //-- add the no build notification { if (getSharedTemplate () && getSharedTemplate ()->getNoBuildRadius () > 0.f) addNotification (ServerNoBuildNotification::getInstance ()); } //-- add the clear flora notification { if (getSharedTemplate () && getSharedTemplate ()->getClearFloraRadius () > 0.f) addNotification (ServerClearCollidableFloraNotification::getInstance ()); } } //----------------------------------------------------------------------- ServerObject::~ServerObject() { //-- This must be the first line in the destructor to invalidate any watchers watching this object nullWatchers(); ObserveTracker::onObjectDestroyed(*this, getLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnDestruct)); PositionUpdateTracker::serverObjDestroyed(*this); synchronizationOnDestruct(); if (getLayer()) ObjectTracker::removeRunTimeRule(); if (getClient()) getClient()->removeControlledObject(*this); WARNING_STRICT_FATAL(getClient(),("Deleting server object %s which had a client.",getNetworkId().getValueString().c_str())); { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::~ServerObject removeFromWorld"); //remove from world if (isInWorld()) removeFromWorld(); } WARNING_STRICT_FATAL(!m_triggerVolumes.empty(), ("Deleting object %s that still has trigger volumes attached", getNetworkId().getValueString().c_str())); { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::~ServerObject destroyTriggerVolumes"); // make absolutely sure our trigger volumes are removed from the world destroyTriggerVolumes(); } if (m_sharedTemplate != nullptr) { m_sharedTemplate->releaseReference(); m_sharedTemplate = nullptr; } if (getClient()) { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::~ServerObject dropClient"); //Object has a client!! WARNING(true, ("Destroying object %s which isAuth: %d", getNetworkId().getValueString().c_str(), isAuthoritative())); GameServer::getInstance().dropClient(getNetworkId()); } { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::~ServerObject removeObjectFromGame"); // clean ourselves off any of ServerWorlds lists. ServerWorld::removeObjectFromGame(*this); } delete m_pendingSyncUi; m_pendingSyncUi = 0; delete m_synchronizedUi; m_synchronizedUi = 0; { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::~ServerObject delete script object"); delete m_scriptObject; } m_scriptObject = nullptr; gs_objectCount--; { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::~ServerObject ObjectTracker::removeObject"); ObjectTracker::removeObject(); } } // ---------------------------------------------------------------------- void ServerObject::install() { // read in list of object types that supports no trade "shared" behavior // (no trade item of those types can be picked up by another character // on the same account as the character who currently owns the item) char const * const noTradeSharedDataTableName = "datatables/no_trade/no_trade_shared.iff"; DataTable * table = DataTableManager::getTable(noTradeSharedDataTableName, true); if (table) { int const columnObjectTemplate = table->findColumnNumber("Object Template"); if (columnObjectTemplate >= 0) { std::string objectTemplate; for (int i = 0, numRows = table->getNumRows(); i < numRows; ++i) { objectTemplate = table->getStringValue(columnObjectTemplate, i); if (objectTemplate.empty()) continue; if (objectTemplate.find(".iff") != std::string::npos) s_noTradeSharedTemplateItemType.insert(CrcLowerString::calculateCrc(objectTemplate.c_str())); else s_noTradeSharedStaticItemType.insert(objectTemplate); } } DataTableManager::close(noTradeSharedDataTableName); } ExitChain::add(ServerObjectNamespace::remove, "ServerObjectNamespace::remove"); } //----------------------------------------------------------------------- ServerObject * ServerObject::getServerObject(NetworkId const & networkId) { return asServerObject(NetworkIdManager::getObjectById(networkId)); } //----------------------------------------------------------------------- ServerObject * ServerObject::asServerObject(Object * const object) { return (object != nullptr) ? object->asServerObject() : nullptr; } //----------------------------------------------------------------------- ServerObject const * ServerObject::asServerObject(Object const * const object) { return (object != nullptr) ? object->asServerObject() : nullptr; } //----------------------------------------------------------------------- ServerObject * ServerObject::asServerObject() { return this; } //----------------------------------------------------------------------- ServerObject const * ServerObject::asServerObject() const { return this; } //----------------------------------------------------------------------- CreatureObject * ServerObject::asCreatureObject() { return 0; } //----------------------------------------------------------------------- CreatureObject const * ServerObject::asCreatureObject() const { return 0; } //----------------------------------------------------------------------- BuildingObject * ServerObject::asBuildingObject() { return 0; } //----------------------------------------------------------------------- BuildingObject const * ServerObject::asBuildingObject() const { return 0; } //----------------------------------------------------------------------- StaticObject * ServerObject::asStaticObject() { return 0; } //----------------------------------------------------------------------- StaticObject const * ServerObject::asStaticObject() const { return 0; } //----------------------------------------------------------------------- TangibleObject * ServerObject::asTangibleObject() { return 0; } //----------------------------------------------------------------------- TangibleObject const * ServerObject::asTangibleObject() const { return 0; } //----------------------------------------------------------------------- IntangibleObject *ServerObject::asIntangibleObject() { return 0; } //----------------------------------------------------------------------- IntangibleObject const *ServerObject::asIntangibleObject() const { return 0; } //----------------------------------------------------------------------- MissionObject * ServerObject::asMissionObject() { return 0; } //----------------------------------------------------------------------- MissionObject const * ServerObject::asMissionObject() const { return 0; } //----------------------------------------------------------------------- CellObject * ServerObject::asCellObject() { return 0; } //----------------------------------------------------------------------- CellObject const * ServerObject::asCellObject() const { return 0; } //----------------------------------------------------------------------- GroupObject * ServerObject::asGroupObject() { return 0; } //----------------------------------------------------------------------- GroupObject const * ServerObject::asGroupObject() const { return 0; } //----------------------------------------------------------------------- ShipObject * ServerObject::asShipObject() { return 0; } //----------------------------------------------------------------------- ShipObject const * ServerObject::asShipObject() const { return 0; } //----------------------------------------------------------------------- PlayerObject *ServerObject::asPlayerObject() { return 0; } //----------------------------------------------------------------------- PlayerObject const * ServerObject::asPlayerObject() const { return 0; } //----------------------------------------------------------------------- WeaponObject *ServerObject::asWeaponObject() { return 0; } //----------------------------------------------------------------------- WeaponObject const * ServerObject::asWeaponObject() const { return 0; } //----------------------------------------------------------------------- ManufactureSchematicObject *ServerObject::asManufactureSchematicObject() { return 0; } //----------------------------------------------------------------------- ManufactureSchematicObject const * ServerObject::asManufactureSchematicObject() const { return 0; } //----------------------------------------------------------------------- PlayerQuestObject * ServerObject::asPlayerQuestObject() { return 0; } //----------------------------------------------------------------------- PlayerQuestObject const * ServerObject::asPlayerQuestObject() const { return 0; } //----------------------------------------------------------------------- /** * Returns a shared template if none was given for this object. * * @return the shared template */ const SharedObjectTemplate * ServerObject::getDefaultSharedTemplate(void) const { static const ConstCharCrcLowerString templateName("object/object/base/shared_object_default.iff"); if (m_defaultSharedTemplate == nullptr) { m_defaultSharedTemplate = safe_cast( ObjectTemplateList::fetch(templateName)); WARNING_STRICT_FATAL(m_defaultSharedTemplate == nullptr, ("Cannot create default shared object template %s", templateName.getString())); if (m_defaultSharedTemplate != nullptr) ExitChain::add (removeDefaultTemplate, "ServerObject::removeDefaultTemplate"); } return m_defaultSharedTemplate; } // ServerObject::getDefaultSharedTemplate //----------------------------------------------------------------------- const unsigned long ServerObject::getObjectCount() { return gs_objectCount; } //----------------------------------------------------------------------- /** * Cleans up the default shared template. */ void ServerObject::removeDefaultTemplate(void) { if (m_defaultSharedTemplate != nullptr) { m_defaultSharedTemplate->releaseReference(); m_defaultSharedTemplate = nullptr; } } // ServerObject::removeDefaultTemplate // ---------------------------------------------------------------------- void ServerObject::addObjectToConcludeList() { if (!isInitialized()) return; if (!isGoingToConclude()) { setGoingToConclude(true); ServerWorld::addObjectToConcludeList(this); } } // ---------------------------------------------------------------------- /** * Called during the alter phase of the game. * * @param time time since last frame * * @return true or false */ float ServerObject::alter(float time) { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::alter"); //Check initialization WARNING_STRICT_FATAL(!isInitialized(), ("Alter called on unitialized object %s.", getNetworkId().getValueString().c_str())); if (!isInitialized()) return 1.0f; // Alter the base class. float result = Object::alter(time); if (isPlacing()) { TerrainObject* terrain = TerrainObject::getInstance(); if (terrain && getSharedTemplate() && getSharedTemplate()->getSnapToTerrain() && isInWorldCell() && isInWorld()) { terrain->placeObject(*this, false, true); } setPlacing(false); } if (result == AlterResult::cms_kill) { if (isAuthoritative()) { //If we are being deleted this frame, make sure to update our deltas, position, etc. sendDeltasForSelfAndContents(); } if (isUnloading()) { Object * const containerObject = ContainerInterface::getContainedByObject(*this); //If we're contained, remove ourself from the container before destruction if (containerObject) { Container * const container = ContainerInterface::getContainer(*containerObject); NOT_NULL(container); container->internalItemRemoved(*this); DEBUG_REPORT_LOG(true, ("Removing %s from container %s because it is being unloaded\n", getNetworkId().getValueString().c_str(), containerObject->getNetworkId().getValueString().c_str())); } } } return result; } // ---------------------------------------------------------------------- bool ServerObject::canDropInWorld() const { return false; } // ---------------------------------------------------------------------- bool ServerObject::canManipulateObject(ServerObject const &target, bool movingObject, bool checkPermissions, bool checkPermissionsOnParent, float maxDistance, Container::ContainerErrorCode&, bool skipNoTradeCheck, bool * allowedByGodMode) const { //In general objects cannot manipulate other objects, unless they are creatures. This may not be true of all //objects, however, which is why the function is implemented this way (ie a player object that derives from ServerObject). UNREF(target); UNREF(maxDistance); UNREF(checkPermissions); UNREF(checkPermissionsOnParent); UNREF(movingObject); UNREF(skipNoTradeCheck); UNREF(allowedByGodMode); return false; } //----------------------------------------------------------------------- bool ServerObject::canTrade() const { if( isInBazaarOrVendor() ) { return false; } return !markedNoTrade(); } //----------------------------------------------------------------------- bool ServerObject::canTradeRecursive(bool testPlayers) const { if( testPlayers || !isPlayerControlled() ) { if( !canTrade() ) { return false; } else { // check the contained items const Container * const container = ContainerInterface::getContainer(*this); if( container ) { for( ContainerConstIterator i = container->begin(); i != container->end(); ++i ) { const ServerObject * const content = safe_cast((*i).getObject()); if( content ) { if( !content->canTradeRecursive(testPlayers) ) return false; } } } } } return true; } //----------------------------------------------------------------------- bool ServerObject::markedNoTrade() const { return ( getObjVars().hasItem("noTrade") || getObjVars().hasItem("notrade") ); } //----------------------------------------------------------------------- bool ServerObject::markedNoTradeRecursive(bool testPlayers, bool testOnlyContainedItems) const { if( testPlayers || !isPlayerControlled() ) { if( !testOnlyContainedItems && markedNoTrade() ) { return true; } else { // check the contained items const Container * const container = ContainerInterface::getContainer(*this); if( container ) { for( ContainerConstIterator i = container->begin(); i != container->end(); ++i ) { const ServerObject * const content = safe_cast((*i).getObject()); if( content ) { if( content->markedNoTradeRecursive(testPlayers) ) return true; } } } } } return false; } //----------------------------------------------------------------------- bool ServerObject::markedNoTradeShared(bool includeCheckForNoTrade) const { if (includeCheckForNoTrade) { if (!markedNoTrade()) return false; } if (getObjVars().hasItem("noTradeShared")) return true; // see if the item type is no trade "shared" if (m_staticItemName.get().empty()) return (s_noTradeSharedTemplateItemType.count(getTemplateCrc()) > 0); return (s_noTradeSharedStaticItemType.count(m_staticItemName.get()) > 0); } //----------------------------------------------------------------------- bool ServerObject::isInSecureTrade() const { const ServerObject *parent = safe_cast(ContainerInterface::getFirstParentInWorld(*this)); if( parent ) { const CreatureObject *creatureObjParent = parent->asCreatureObject(); if( creatureObjParent ) { const CreatureController *cc = creatureObjParent->getCreatureController(); if( cc ) { const ServerSecureTrade *trade = cc->getSecureTrade(); if( trade && trade->hasItemInSecureTrade(*creatureObjParent, *this) ) { return true; } } } } return false; } // ---------------------------------------------------------------------- /** * Returns whether an object is bio-linked * * @return true if a the object is bio-linked */ bool ServerObject::isBioLinked() const { // By default, objects are not bio-linked return false; } // ---------------------------------------------------------------------- /** * Returns whether an object, or any contained object, is biolinked * * @return true if a biolinked object is found */ bool ServerObject::isBioLinkedRecursive() const { if( isBioLinked() ) { return true; } else { // If we are a container, see if any of the contained objects are bio-linked const Container * const container = ContainerInterface::getContainer(*this); if( container ) { for( ContainerConstIterator i = container->begin(); i != container->end(); ++i ) { const ServerObject * const content = safe_cast((*i).getObject()); if( content ) { if( content->isBioLinkedRecursive() ) { return true; } } } } } return false; } //----------------------------------------------------------------------- bool ServerObject::isWaypoint() const { return getObjectTemplate()->derivesFrom(SERVER_PATHFINDING_WAYPOINT_TEMPLATE_BASE); } //----------------------------------------------------------------------- bool ServerObject::isInBazaarOrVendor() const { bool inBazaarOrVendor = false; const ServerObject *parent = safe_cast(ContainerInterface::getFirstParentInWorld(*this)); const ServerObject *grandParent = nullptr; if( parent ) { grandParent = safe_cast(ContainerInterface::getFirstParentInWorld(*parent)); } if( parent && (parent->isVendor() || parent->isBazaarTerminal() ) ) { inBazaarOrVendor = true; } else if( grandParent && (grandParent->isVendor() || grandParent->isBazaarTerminal()) ) { inBazaarOrVendor = true; } return inBazaarOrVendor; } void ServerObject::removeAllAuctions() { if ( isVendor() ) { ServerObject *inv = getBazaarContainer(); NetworkId playerOwnerId; getObjVars().getItem("vendor_owner", playerOwnerId); if( inv && playerOwnerId.getValue()>0 ) { // if this was an uninitialized vendor, remove objvar from owner ServerObject *owner = dynamic_cast(NetworkIdManager::getObjectById(playerOwnerId)); if (owner) { NetworkId notInitalizedVendor; if (owner->getObjVars().getItem("vendor_not_initialized", notInitalizedVendor)) { if (notInitalizedVendor == Object::getNetworkId()) { owner->removeObjVarItem("vendor_not_initialized"); } } } CommoditiesMarket::destroyVendorMarket(playerOwnerId, *inv); } } } // ---------------------------------------------------------------------- bool ServerObject::checkLOSTo(ServerObject const &target) const { return LineOfSightCache::checkLOS(*this, target); } //----------------------------------------------------------------------- bool ServerObject::checkLOSTo(Location const &target) const { return LineOfSightCache::checkLOS(*this, target); } //----------------------------------------------------------------------- void ServerObject::conclude() { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::conclude"); // everything has updated, check trigger volume if (getTransformChanged() && isInWorld()) { Vector const &newPosition = getTransform_o2w().getPosition_p(); // move the object and trigger volumes, detect volume enter/leave ServerWorld::moveObject(*this, m_oldPosition, newPosition); updatePositionOnPlanetServer(); m_oldPosition = newPosition; } m_localFlags &= s_clearLocalFlagsOnConcludeMask; { PROFILER_AUTO_BLOCK_DEFINE("Object::conclude"); Object::conclude(); } } //----------------------------------------------------------------------- /** * Creates a default controller for this object. * * @return the object's controller * @todo This function should be removed. Use the controller factory instead. */ Controller* ServerObject::createDefaultController(void) { Controller* controller = new ServerController(this); setController(controller); return controller; } // Object::createDefaultController //----------------------------------------------------------------------- /** Called by the container system when this object's contained By value is changed */ void ServerObject::containedByModified(NetworkId const &oldValue, NetworkId const &newValue, bool isLocal) { UNREF(isLocal); if (oldValue == newValue) return; ServerObject * const oldContainer = safe_cast(NetworkIdManager::getObjectById(oldValue)); ServerObject * const newContainer = safe_cast(NetworkIdManager::getObjectById(newValue)); // TODO: reenable these fatals after container proxying changes are done //FATAL(oldValue.isValid() && !oldContainer, ("ServerObject::containedByModified: obj %s, could not resolve old container %s.", getDebugInformation().c_str(), oldValue.getValueString().c_str())); //FATAL(newValue.isValid() && !newContainer, ("ServerObject::containedByModified: obj %s, could not resolve new container %s.", getDebugInformation().c_str(), newValue.getValueString().c_str())); if (isAuthoritative()) { FATAL((newContainer && !newContainer->isAuthoritative()), ("Invalid container transfer with auth object to/from non-auth containers.")); } else if (isInitialized()) { FATAL((oldContainer && oldContainer->isAuthoritative()) || (newContainer && newContainer->isAuthoritative()), ("Invalid container transfer with non-auth object to/from auth containers.")); // For proxy objects and mounts, it is essential for this function // to setup new container state before calling // isContentItemExposedWith. Failure to do this causes proxy // server objects of a rider to handle dismounting improperly // because wasInWorld will be set to false erroneously. // Also, it is essential to call this function prior to calling // containerHandleUpdateClients() below since that function // assumes the objects are in the new containment state. containerDepersistContents(oldContainer, newContainer); Container const * const newContainerProperty = newContainer ? ContainerInterface::getContainer(*newContainer) : 0; bool const wasInWorld = isInWorld(); bool const nowInWorld = newValue == NetworkId::cms_invalid || (newContainerProperty && newContainer->isInWorld() && newContainerProperty->isContentItemExposedWith(*this)); // DEBUG_REPORT_LOG(true, ("Updating %s with old container %s and new container %s\n", // getNetworkId().getValueString().c_str(), // oldValue.getValueString().c_str(), // newValue.getValueString().c_str())); containerHandleUpdateClients(oldContainer, newContainer, (wasInWorld && !nowInWorld), (!wasInWorld && nowInWorld)); } scheduleForAlter(); } //----------------------------------------------------------------------- void ServerObject::arrangementModified(int oldValue, int newValue, bool isLocal) { if (!isLocal && !isAuthoritative() && isInitialized()) { ServerObject * const parent = safe_cast(ContainerInterface::getContainedByObject(*this)); if (parent) { SlottedContainer * const slotContainer = ContainerInterface::getSlottedContainer(*parent); if (slotContainer) { SlottedContainmentProperty* const slotted = ContainerInterface::getSlottedContainmentProperty(*this); DEBUG_FATAL(!slotted, ("Error unpacking non slotted objects into a slotted container")); slotContainer->updateArrangement(*this, oldValue, slotted->getCurrentArrangement()); } } ObserveTracker::onObjectArrangementChanged(*this); } } //----------------------------------------------------------------------- void ServerObject::destroyTriggerVolumes() { // get rid of any trigger volumes for (TriggerVolumeMap::iterator i = m_triggerVolumes.begin(); i != m_triggerVolumes.end(); ++i) { ServerWorld::removeObjectTriggerVolume((*i).second); delete (*i).second; } m_triggerVolumes.clear(); m_networkUpdateFar = 0; } //----------------------------------------------------------------------- void ServerObject::updateTriggerVolumes() { ServerObject *topMost = safe_cast(ContainerInterface::getFirstParentInWorld(*this)); if (!topMost || topMost != this || !topMost->isInWorld()) { destroyTriggerVolumes(); return; } // keep track of what trigger volumes should exist so we can destroy the rest afterwards std::vector newVolumes; // create any new volumes { for (std::vector::const_iterator i = m_triggerVolumeInfo.begin(); i != m_triggerVolumeInfo.end(); ++i) { const TriggerVolumeMap::iterator j = m_triggerVolumes.find((*i).name); if (j != m_triggerVolumes.end()) { TriggerVolume *t = (*j).second; // if we already have one that matches, leave it alone if ( t->getPromiscuous() == (*i).isPromiscuous && t->getRadius() == (*i).radius) { newVolumes.push_back(t); continue; } // does not match, so destroy it and fall through to creating the new one ServerWorld::removeObjectTriggerVolume(t); delete t; m_triggerVolumes.erase(j); } TriggerVolume * t = new TriggerVolume(*this, (*i).radius, (*i).name, (*i).isPromiscuous); addTriggerVolume(t); newVolumes.push_back(t); } } // remove any volumes which no longer exist { TriggerVolumeMap::iterator i = m_triggerVolumes.begin(); while (i != m_triggerVolumes.end()) { TriggerVolume *t = (*i).second; if ( t != m_networkUpdateFar && std::find(newVolumes.begin(), newVolumes.end(), t) == newVolumes.end()) { ServerWorld::removeObjectTriggerVolume(t); delete t; m_triggerVolumes.erase(i++); } else ++i; } } } //----------------------------------------------------------------------- void ServerObject::addTriggerVolume(TriggerVolume * t) { if (!t) { WARNING_STRICT_FATAL(true, ("Cannot add nullptr volume")); return; } m_triggerVolumes.insert(std::make_pair(t->getName(), t)); ServerWorld::addObjectTriggerVolume(t); } //----------------------------------------------------------------------- void ServerObject::createTriggerVolume(const real radius, const std::string & name, const bool isPromiscuous) { removeTriggerVolume(name); TriggerVolumeInfo triggerVolumeInfo; triggerVolumeInfo.name = name; triggerVolumeInfo.radius = radius; triggerVolumeInfo.isPromiscuous = isPromiscuous; m_triggerVolumeInfo.push_back(triggerVolumeInfo); } //----------------------------------------------------------------------- void ServerObject::serverObjectEndBaselines(bool fromDatabase) { setInEndBaselines(true); // handle trying to create a player in an incorrect scene, or without a PlayerObject bool needSceneFix = false; if (isPlayerControlled()) { bool doUnload = false; if (!PlayerCreatureController::getPlayerObject(asCreatureObject())) { WARNING(true, ("TRACE_LOGIN: Loading a player controlled creature object (%s) without a player object.", getNetworkId().getValueString().c_str())); doUnload = true; } else if (getSceneId() != ServerWorld::getSceneId()) { Client* p_client = GameServer::getInstance().getClient( getNetworkId() ); if ( (p_client) && ( p_client->getSendToStarport() ) ) { needSceneFix = true; GameServer::getInstance().requestSceneWarp(CachedNetworkId(*this),"tatooine",Vector(3528,5,-4804), NetworkId::cms_invalid,Vector(3528,5,-4804)); } else if (isAuthoritative() && ContainerInterface::getContainedByObject(*this)) { needSceneFix = true; } else { WARNING(true, ("Unloading object id=[%s] on game server id=[%d] due to incorrect scene during baselines: server scene=[%s], object scene=[%s], fromDatabase=[%s], authoritative=[%s])", getNetworkId().getValueString().c_str(), static_cast(GameServer::getInstance().getProcessId()), ServerWorld::getSceneId().c_str(), getSceneId().c_str(), fromDatabase ? "true" : "false", isAuthoritative() ? "true" : "false" )); //-- As a precautionary measure, force CentralServer to clear out its player id/scene id map since a bug in the updating of Central's map probably is the origin of this routing error. GenericValueTypeMessage > > const setSceneMsg("SetSceneForPlayer", std::make_pair(getNetworkId(), std::make_pair(std::string(""), false))); GameServer::getInstance().sendToCentralServer(setSceneMsg); doUnload = true; } } if (doUnload) { if (getClient()) GameServer::getInstance().dropClient(getNetworkId()); unload(); delete this; return; } } //This is the public interface to end baselines. ::endBaselines is a protected virtual function now // so that we can enforce order. endBaselines(); if (needSceneFix) { WARNING(true, ("Fixing scene id for contained object %s due to incorrect scene id during baselines", getNetworkId().getValueString().c_str())); setSceneIdOnThisAndContents(ServerWorld::getSceneId()); } if (fromDatabase) onLoadedFromDatabase(); updateWorldSphere(); if (getScriptObject() != nullptr) { endBaselinesInitializeScript(*this, true); // If the object was created authoritative, trigger if needed if (isAuthoritative()) triggerMadeAuthoritative(); } if (isPlayerControlled()) PlayerSanityChecker::add(getNetworkId()); //-- Validate the object transform. checkAndLogInvalidTransform(); //-- Schedule for alter just in case something needs an alter. scheduleForAlter(); } //----------------------------------------------------------------------- void ServerObject::onLoadedFromDatabase() { } //----------------------------------------------------------------------- void ServerObject::endBaselines() { // This should always go first: it ensures there are no deltas // accumulated for this object at the time we start processing // our baselined data. Only changes after this point will // generate deltas. clearDeltas(); //Handle updating container info bool addToWorldFlag = handleContentsSetup(); setInitialized(true); ServerController * c = safe_cast(getController()); c->endBaselines(); setTransformChanged(false); // check for parent getting uncached if (isAuthoritative() && getCacheVersion() != 0) { ServerObject *parent = safe_cast(ContainerInterface::getContainedByObject(*this)); if (parent && parent->getCacheVersion() == 0) setCacheVersion(0); } // handle version numbering for contents on portal properties PortalProperty *portalProperty = getPortalProperty(); if (portalProperty) { int crcValue=0; getObjVars().getItem(portalPropertyCrcObjectVariableName,crcValue); std::vector unfixables; if (portalProperty->serverEndBaselines(crcValue, unfixables, isAuthoritative())) { FATAL(ConfigServerGame::getFatalOnGoldPobChange() && getCacheVersion() != 0, ("Object %s is a client cached object and the POB changed from [%d] to [%d]", getNetworkId().getValueString().c_str(), crcValue, portalProperty->getCrc())); DEBUG_WARNING(getCacheVersion() != 0, ("Object %s is a client cached object and the POB changed from [%d] to [%d]", getNetworkId().getValueString().c_str(), crcValue, portalProperty->getCrc())); if (isAuthoritative()) setCacheVersion(0); } if (isAuthoritative()) setObjVarItem(portalPropertyCrcObjectVariableName, portalProperty->getCrc()); if (!unfixables.empty()) { WARNING(true, ("Object %s contained objects that could not be placed after pob fixup", getNetworkId().getValueString().c_str())); } } if (addToWorldFlag) addToWorld(); if (isAuthoritative()) { // don't allow login if the character is in the middle of CTS or has been successfully CTS(ed) Client * const client = GameServer::getInstance().getClient(getNetworkId()); if (client && !getClient() && (client->getCharacterObjectId() == getNetworkId())) { bool kickRequested = false; DynamicVariableList const & objVars = getObjVars(); int timeOut; if (objVars.getItem("disableLoginCtsInProgress", timeOut)) { if (timeOut > static_cast(ServerClock::getInstance().getGameTimeSeconds())) { MessageToQueue::getInstance().sendMessageToC(getNetworkId(), "C++kickPlayerCtsCompletedOrInProgress", "", 0, false); kickRequested = true; } else { removeObjVarItem("disableLoginCtsInProgress"); } } int transferredTime; if (!kickRequested && objVars.getItem("disableLoginCtsCompleted", transferredTime) && !client->isUsingAdminLogin()) { MessageToQueue::getInstance().sendMessageToC(getNetworkId(), "C++kickPlayerCtsCompletedOrInProgress", "", 0, false); kickRequested = true; } } // attach a pending client to this object if needed checkAndAddClientControl(client); // Make sure the PlanetServer knows about the new object if it needs to updatePositionOnPlanetServer(true); // for initialized, unpacked vendor, sync the vendor location with the commodities server if (isVendor()) CommoditiesMarket::updateVendorLocation(*this); // if this is a reward item that supports trade in, attach the trade in script to it to provide the trade in functionality VeteranRewardManager::attachTradeInScriptToItem(*this); // sanity check - fix up any player character negative cash/bank balance if (client) { if (m_cashBalance.get() < 0) m_cashBalance = 0; if (m_bankBalance.get() < 0) m_bankBalance = 0; } } // If our immediate container is initialized, we were just added to the container so update the ObserveTracker ServerObject *immediateContainer = safe_cast(ContainerInterface::getContainedByObject(*this)); if (immediateContainer && immediateContainer->isInitialized()) { // handle the case of a player who logged out in a buildout cell // and that buildout id got changed to some other object, like a rock if (isPlayerControlled() && isAuthoritative() && (immediateContainer->getNetworkId().getValue() < static_cast(0))) { if (immediateContainer->asCellObject() != nullptr || ContainerInterface::getCell(*immediateContainer) != nullptr) ObserveTracker::onObjectContainerChanged(*this); else { WARNING(true, ("ServerObject::endBaselines player (%s) is trying to be put into non-cell object (%s). We will put the player in the world instead.", PlayerObject::getAccountDescription(this).c_str(), immediateContainer->getDebugInformation().c_str())); ContainedByProperty * const cby = ContainerInterface::getContainedByProperty(*this); if (cby) { cby->setContainedBy(NetworkId::cms_invalid, true); if (!ServerWorld::isSpaceScene()) { // if the player was in a building, try and put them at the building's ejection point DynamicVariableLocationData ejectionPoint; if (getObjVars().getItem("building_ejection_point", ejectionPoint) && (ServerWorld::getSceneId() == ejectionPoint.scene) && (!ejectionPoint.cell.isValid())) { removeObjVarItem("building_ejection_point"); setPosition_p(ejectionPoint.pos); } } addToWorld(); } } } else { //@todo: we may want to do something here if this call fails ObserveTracker::onObjectContainerChanged(*this); } } if (isAuthoritative() && (!isPlayerControlled() || getClient()) && getObjVars().hasItem("teleportFixup")) TeleportFixupHandler::add(*this); setInEndBaselines(false); } // ---------------------------------------------------------------------- void ServerObject::getAuthClients(std::set &authClients) const { Client const * const client = getClient(); if (client) IGNORE_RETURN(authClients.insert(client)); } // ---------------------------------------------------------------------- bool ServerObject::handlePlayerInInteriorSetup(ContainedByProperty *containedBy) { // If we are player-controlled, assume that it may have been a while since we logged in. // If we were in a POB, it might have changed. So, ignore the persisted cell number and look // for the best cell. If anything goes wrong looking for the cell, warp to a good location. bool retval = false; Object * const originalCell = containedBy->getContainedBy(); if (originalCell) { bool fixOk = false; CellProperty * const originalCellProp = originalCell->getCellProperty(); ServerObject * const pob = safe_cast(ContainerInterface::getContainedByObject(*originalCell)); BuildingObject * const building = pob ? pob->asBuildingObject() : 0; PortalProperty * const portal = pob ? pob->getPortalProperty() : 0; // Don't ever put the character into a packed house if ((building) && (!building->isInWorld())) { DEBUG_REPORT_LOG(true,("Ejecting player %s from building %s, because the building is packed\n",getNetworkId().getValueString().c_str(), building->getNetworkId().getValueString().c_str())); LOG("CustomerService",("Login: Ejecting player %s from building %s, because the building is packed",getNetworkId().getValueString().c_str(), building->getNetworkId().getValueString().c_str())); // settting fixOk to false will cause the character to get moved // into the world at the building ejection point (see below) fixOk = false; } else if (portal) { originalCellProp->depersistContents(*this); if (pob->isInitialized()) { if (isAuthoritative()) { Transform const saveTransform(getTransform_o2p()); Container::ContainerErrorCode tmp = Container::CEC_Success; originalCellProp->remove(*this, tmp); containedBy->setContainedBy(NetworkId::cms_invalid); fixOk = portal->fixupObject(*this, saveTransform); containerHandleUpdateProxies(nullptr, safe_cast(ContainerInterface::getContainedByObject(*this))); if (building) building->gainedPlayer(*this); } else { fixOk = true; DEBUG_REPORT_LOG(true,("Skipping POB fixup on non-authoritative player %s\n",getNetworkId().getValueString().c_str())); retval = true; } } else { if (isAuthoritative()) { Container::ContainerErrorCode tmp = Container::CEC_Success; portal->queueObjectForFixup(*this); originalCellProp->remove(*this, tmp); containedBy->setContainedBy(NetworkId::cms_invalid); setNeedsPobFixup(true); addNotification(pobFixupNotification); } else DEBUG_REPORT_LOG(true,("Skipping POB fixup on non-authoritative player %s\n",getNetworkId().getValueString().c_str())); fixOk = true; } } if (!fixOk) { // warp to safe location DEBUG_WARNING(true,("Player-controlled object %s was warped outside of its container because a good cell could not be found.",getNetworkId().getValueString().c_str())); containedBy->setContainedBy(NetworkId::cms_invalid); // attempt to put the character at the ejection point of the building if (!ServerWorld::isSpaceScene()) { DynamicVariableLocationData ejectionPoint; if (getObjVars().getItem("building_ejection_point", ejectionPoint) && (ServerWorld::getSceneId() == ejectionPoint.scene) && (!ejectionPoint.cell.isValid())) { removeObjVarItem("building_ejection_point"); setPosition_p(ejectionPoint.pos); } } retval = true; } } else { // the container (most likely a building) we were in doesn't exist any more DEBUG_REPORT_LOG(true,("Ejecting player %s from building %s, because the building does not exist anymore\n",getNetworkId().getValueString().c_str(), containedBy->getContainedByNetworkId().getValueString().c_str())); LOG("CustomerService",("Login: Ejecting player %s from building %s, because the building does not exist anymore",getNetworkId().getValueString().c_str(), containedBy->getContainedByNetworkId().getValueString().c_str())); containedBy->setContainedBy(NetworkId::cms_invalid); // attempt to put the character at the ejection point of the building if (!ServerWorld::isSpaceScene()) { DynamicVariableLocationData ejectionPoint; if (getObjVars().getItem("building_ejection_point", ejectionPoint) && (ServerWorld::getSceneId() == ejectionPoint.scene) && (!ejectionPoint.cell.isValid())) { removeObjVarItem("building_ejection_point"); setPosition_p(ejectionPoint.pos); } else // warp the player to the Mos Eisley Starport { DEBUG_REPORT_LOG(true, ("Tried to load creature %s into invalid container. Moving to Mos Eisley Starport.\n", getNetworkId().getValueString().c_str())); LOG("CustomerService",("Login: Tried to load creature %s into invalid container. Moving to Mos Eisley Starport.\n", getNetworkId().getValueString().c_str())); GameServer::getInstance().requestSceneWarp(CachedNetworkId(*this),"tatooine",Vector(3528,5,-4804),NetworkId::cms_invalid,Vector(3528,5,-4804)); } } retval = true; } return retval; } //----------------------------------------------------------------------- /** * Handle setting up the contents of any containedby objects while de-persisting this object */ bool ServerObject::handleContentsSetup() { ContainedByProperty * const containedBy = ContainerInterface::getContainedByProperty(*this); if (!containedBy || containedBy->getContainedByNetworkId() == NetworkId::cms_invalid) return true; ServerObject * const containingObject = safe_cast(containedBy->getContainedBy()); DEBUG_REPORT_LOG(!containingObject, ("Could not depersist object %s into non-existant object %s.\n", getNetworkId().getValueString().c_str(), containedBy->getContainedByNetworkId().getValueString().c_str())); // handle special case of player controlled objects depersisting into interiors if ( isPlayerControlled() && ( (containingObject && containingObject->getCellProperty()) || (containedBy->getContainedByNetworkId() != NetworkId::cms_invalid && !containingObject))) return handlePlayerInInteriorSetup(containedBy); if (containingObject) { Container * const container = ContainerInterface::getContainer(*containingObject); SlottedContainer * const slotContainer = dynamic_cast(container); if (slotContainer) { SlottedContainmentProperty * const slottedProperty = ContainerInterface::getSlottedContainmentProperty(*this); if (slottedProperty) slotContainer->depersistSlotContents(*this, slottedProperty->getCurrentArrangement()); else WARNING_STRICT_FATAL(true, ("Tried to depersist an object with no arrangement into a slotted container")); } else if (container) { container->depersistContents(*this); if (containingObject->isInitialized() && containingObject->isInWorld() && container->isContentItemExposedWith(*this)) addToWorld(); } synchronizationOnLoadedInContainer(); return false; } LOG("CustomerService", ("BadContainerDepersist: Tried to depersist non-player object %s into container %s which could not be found.", getDebugInformation().c_str(), containedBy->getContainedByNetworkId().getValueString().c_str())); unload(); return false; } //----------------------------------------------------------------------- unsigned long ServerObject::getAndIncrementMoveSequenceNumber() { unsigned long newValue = m_transformSequence.get(); newValue++; m_transformSequence = newValue; return newValue; } //----------------------------------------------------------------------- const bool ServerObject::getPositionChanged() const { return (getTransform_o2w().getPosition_p() != m_oldPosition); } //----------------------------------------------------------------------- const int ServerObject::getCacheVersion() const { return m_cacheVersion.get(); } //----------------------------------------------------------------------- const char * ServerObject::getSharedTemplateName() const { if (getSharedTemplate() == nullptr) return nullptr; return getSharedTemplate()->ObjectTemplate::getName(); } //----------------------------------------------------------------------- const char * ServerObject::getClientSharedTemplateName () const { return getSharedTemplateName(); } //----------------------------------------------------------------------- const Unicode::String ServerObject::getObjectName(void) const { // try the player defined name first const Unicode::String & name = m_objectName.get(); if (!name.empty ()) return name; // try the stringId name const StringId & nameId = m_nameStringId.get(); if (!nameId.isInvalid ()) return Unicode::narrowToWide(nameId.getDebugString ()); // use the template name return Unicode::narrowToWide(getTemplateName()); } //----------------------------------------------------------------------- const Sphere & ServerObject::getSphereExtent() const { return m_worldSphere; } //----------------------------------------------------------------------- const Sphere ServerObject::getLocalSphere() const { Sphere sphere (Sphere::unit); const CollisionProperty * collision = getCollisionProperty(); if (collision) { sphere = collision->getBoundingSphere_l(); } else { const Appearance * appearance = getAppearance(); if (appearance) { sphere = appearance->getSphere(); } } if (sphere.getRadius() == 0.0f) { sphere.setRadius(1.0f); } return sphere; } //----------------------------------------------------------------------- void ServerObject::updateWorldSphere() { Sphere sphere (getLocalSphere()); m_worldSphere.setCenter (rotateTranslate_o2w(sphere.getCenter())); m_worldSphere.setRadius(sphere.getRadius()); } //----------------------------------------------------------------------- /** * Gets the approximate height of the object. * * @return the object's height */ float ServerObject::getHeight() const { return getSphereExtent().getRadius() * 2.0f * getScale().y; } // ServerObject::getHeight //----------------------------------------------------------------------- /** * Gets the approximate radius of the object. * * @return the object's radius */ float ServerObject::getRadius() const { return getSphereExtent().getRadius() * std::max(getScale().x, getScale().z); } // ServerObject::getHeight //----------------------------------------------------------------------- /** * Returns the volume of this object (and it's contents, if it's a container) * * @return the object's volume */ int ServerObject::getVolume(void) const { const VolumeContainmentProperty* volumeProperty = ContainerInterface::getVolumeContainmentProperty(*this); return volumeProperty ? volumeProperty->getVolume() : 1; } //----------------------------------------------------------------------- const TriggerVolume * ServerObject::getTriggerVolume(const std::string & triggerVolumeName) const { const TriggerVolume * result = 0; const TriggerVolumeMap::const_iterator i = m_triggerVolumes.find(triggerVolumeName); if(i != m_triggerVolumes.end()) { result = (*i).second; } return result; } //----------------------------------------------------------------------- TriggerVolume * ServerObject::getTriggerVolume(const std::string & triggerVolumeName) { TriggerVolume * result = 0; const TriggerVolumeMap::iterator i = m_triggerVolumes.find(triggerVolumeName); if (i != m_triggerVolumes.end()) { result = (*i).second; } return result; } //---------------------------------------------------------------------- void ServerObject::seeCombatSpam (const MessageQueueCombatSpam & spamMsg) { if (!isPlayerControlled()) return; if (!isAuthoritative ()) { sendControllerMessageToAuthServer(CM_combatSpam, new MessageQueueCombatSpam(spamMsg)); return; } if (getClient ()) { bool shouldSend = (spamMsg.m_attacker == getNetworkId()); if (!shouldSend) { ServerObject * sourceObject = safe_cast(NetworkIdManager::getObjectById(spamMsg.m_attacker)); if ( sourceObject && ObserveTracker::isObserving(*getClient(), *sourceObject) && !PlayerObject::isIgnoring(getNetworkId(), spamMsg.m_attacker)) { shouldSend = true; } } if (shouldSend) { MessageQueueCombatSpam * const msg = new MessageQueueCombatSpam (spamMsg); Controller *const controller = NON_NULL (getController()); controller->appendMessage(CM_combatSpam, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_CLIENT); } } } //---------------------------------------------------------------------- void ServerObject::seeSocial (const MessageQueueSocial & socialMsg) { Controller * const controller = NON_NULL (getController()); if (!isAuthoritative ()) { MessageQueueSocial * const msg = new MessageQueueSocial (socialMsg); controller->appendMessage(CM_socialReceive, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); return; } if (getClient ()) { ServerObject *sourceObject = safe_cast(NetworkIdManager::getObjectById(socialMsg.getSourceId())); if ( sourceObject && ( sourceObject->getCacheVersion() > 0 || ( ObserveTracker::isObserving(*getClient(), *sourceObject) && !PlayerObject::isIgnoring(getNetworkId(), socialMsg.getSourceId())))) { MessageQueueSocial * const msg = new MessageQueueSocial (socialMsg); controller->appendMessage(CM_socialReceive, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_CLIENT); } } } //----------------------------------------------------------------------- bool ServerObject::serverObjectInitializeFirstTimeObject(ServerObject *cell, Transform const &transform) { //This is the public interface for initializeFirstTimeObject. ::initializeFirstTimeObject has been made //protected virtual so that we could enforce order. initializeFirstTimeObject(); // attach template scripts to this object NOT_NULL(getScriptObject()); getScriptObject()->initScriptInstances(); ServerObjectTemplate const * const newTemplate = safe_cast(getObjectTemplate()); NOT_NULL(newTemplate); int count = newTemplate->getScriptsCount(); for (int i = 0; i < count; ++i) getScriptObject()->attachScript(newTemplate->getScripts(i), true); if (cell) { Container::ContainerErrorCode tmp = Container::CEC_Success; if (!ContainerInterface::transferItemToCell(*cell, *this, transform, nullptr, tmp)) { DEBUG_WARNING(true, ("ServerWorld::createNewObjectIntermediate tried to create a new object in a cell, but it failed.")); return false; } } else if (transform != Transform::identity) setTransform_o2p(transform); getScriptObject()->setOwnerIsInitialized(); addObjectToConcludeList(); // validate the object transform checkAndLogInvalidTransform(); //-- Initialize the controller { ServerController * const controller = safe_cast(getController()); controller->endBaselines(); } return true; } //----------------------------------------------------------------------- void ServerObject::initializeFirstTimeObject() { DEBUG_FATAL(!isAuthoritative(), ("Calling initialize on non authoritative object %s", getNetworkId().getValueString().c_str())); setInitialized(true); setPlacing(true); const ServerObjectTemplate* newTemplate = safe_cast(getObjectTemplate()); NOT_NULL(newTemplate); // set up objvars - we know we are authoritative so we can get by with this newTemplate->getObjvars(m_objVars); // record the crc of the pob used to create this object PortalProperty *portalProperty = getPortalProperty(); if (portalProperty) m_objVars.setItem(portalPropertyCrcObjectVariableName, portalProperty->getCrc()); // add template equipment to this object int i; int count = newTemplate->getContentsCount(); for (i = 0; i < count; ++i) { //Don't move this declaration of contents out of the loop or you will leak a ref. ServerObjectTemplate::Contents contents; newTemplate->getContents(contents, i); if (contents.content == nullptr) { DEBUG_WARNING(true, ("No template for contents item %d", i)); continue; } if (contents.equipObject) { // find out where the contents go const SlotId & slotId = SlotIdManager::findSlotId(CrcLowerString(contents.slotName.c_str())); if (slotId != SlotId::invalid) { //We'll persist the contents automatically if the parent object is persisted. ServerObject * newObject = ServerWorld::createNewObject(*(safe_cast(contents.content)), *this,slotId, false); if (newObject == nullptr) { DEBUG_WARNING(true, ("Can't create object from template %s",contents.content->getName())); } } else { DEBUG_WARNING(true, ("Slot %s is not a valid slot name", contents.slotName.c_str())); } } else { // get the object (which we assume is a volume container) in the // desired slot SlottedContainer * const container = ContainerInterface::getSlottedContainer(*this); if (container != nullptr) { Container::ContainerErrorCode tmp = Container::CEC_Success; CachedNetworkId equippedObjectId = container->getObjectInSlot(SlotIdManager::findSlotId(CrcLowerString(contents.slotName.c_str())), tmp); if (equippedObjectId != NetworkId::cms_invalid) { // put the contents in the volume container ServerObject * equippedObject = safe_cast(equippedObjectId.getObject()); if (equippedObject != nullptr) { //We'll persist the contents automatically if the parent object is persisted. ServerObject * newObject = ServerWorld::createNewObject(*(safe_cast(contents.content)), *equippedObject,false); if (newObject == nullptr) { DEBUG_WARNING(true, ("Can't create object from template %s", contents.content->getName())); } } else { DEBUG_WARNING(true, ("Object in slot %s not loaded",contents.slotName.c_str())); } } else { DEBUG_WARNING(true, ("No object in slot %s", contents.slotName.c_str())); } } else DEBUG_WARNING(true, ("No slotted container for object")); } } { const PropertyList::iterator iEnd = m_propertyList->end(); for (PropertyList::iterator i = m_propertyList->begin(); i != iEnd; ++i) (*i)->initializeFirstTimeObject(); } SharedObjectTemplate::GameObjectType got = static_cast (getGameObjectType()); if (got == SharedObjectTemplate::GOT_terminal_bazaar) { setLoadContents(false); } //-- Assume this object wants to be altered. scheduleForAlter(); } //----------------------------------------------------------------------- void ServerObject::kill() { Object::kill(); } // ---------------------------------------------------------------------- /** * Mark all the children of this object as persisted. */ void ServerObject::markChildrenPersisted() { if (!safe_cast(getObjectTemplate())->getPersistContents()) return; Container *container = ContainerInterface::getContainer(*this); if (container) { for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator) { ServerObject *containedObject = safe_cast((*containerIterator).getObject()); if (containedObject) { containedObject->m_persisted=true; containedObject->markChildrenPersisted(); } } } } //----------------------------------------------------------------------- void ServerObject::addToWorld() { if (getKill()) return; if (isInWorld()) { DEBUG_WARNING(true, ("Calling add to world on object already in it %s", getNetworkId().getValueString().c_str())); return; } updateWorldSphere(); Object::addToWorld(); updateTriggerVolumes(); if (getScriptObject() != nullptr) getScriptObject()->setOwnerIsLoaded(); onAddedToWorld(); } //----------------------------------------------------------------------- void ServerObject::onAddedToWorld() { m_oldPosition = getTransform_o2w().getPosition_p(); // sanity check whether we should really be in the world or not ContainedByProperty const * const containedByProperty = getContainedByProperty(); if (containedByProperty && containedByProperty->getContainedByNetworkId().isValid()) { Object const * const containedByObject = containedByProperty->getContainedBy(); if (containedByObject) { WARNING_DEBUG_FATAL( !containedByObject->isInWorld(), ( "Tried to add object %s to the world with container %s which is not in the world.", getDebugInformation().c_str(), containedByObject->getDebugInformation().c_str())); Container const * const containerProperty = ContainerInterface::getContainer(*containedByObject); if (containerProperty) { WARNING_DEBUG_FATAL( !containerProperty->isContentItemExposedWith(*this), ( "Tried to add object %s to the world with container %s which does not expose it.", getDebugInformation().c_str(), containedByObject->getDebugInformation().c_str())); } else { WARNING_DEBUG_FATAL( true, ( "Tried to add object %s to the world with container %s with no container property.", getDebugInformation().c_str(), containedByObject->getDebugInformation().c_str())); } } else { WARNING_DEBUG_FATAL( true, ( "Tried to add object %s to the world with unresolvable container %s", getDebugInformation().c_str(), containedByProperty->getContainedByNetworkId().getValueString().c_str())); } } // Add networkUpdate volume if (isInWorldCell()) onAddedToWorldCell(); if (isAuthoritative()) { //Tell scripts we are added to world ScriptParams params; IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_ADDED_TO_WORLD, params)); //remove the vendor reinitialization flag if it exists removeObjVarItem(CommoditiesMarket::OBJVAR_VENDOR_REINITIALIZING); } } //----------------------------------------------------------------------- bool ServerObject::checkAndAddClientControl(Client* client) { if (isPlayerControlled()) { CreatureObject* creature = asCreatureObject(); if (client && creature && isInitialized() && isAuthoritative() && !isNeedingPobFixup()) { client->onLoadPlayer(*creature); return true; } } return false; } //----------------------------------------------------------------------- void ServerObject::onPobFixupComplete() { setNeedsPobFixup(false); checkAndAddClientControl(GameServer::getInstance().getClient(getNetworkId())); BuildingObject * const building = static_cast(ContainerInterface::getTopmostContainer(*this))->asBuildingObject(); if (building) building->gainedPlayer(*this); } //----------------------------------------------------------------------- void ServerObject::onClientReady(Client *c) { NOT_NULL(c); CmdSceneReady const readyMessage; c->send(readyMessage, true); ScriptParams params; IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_LOGIN, params)); int galacticReserve = 0; if (!getObjVars().getItem("galactic_reserve", galacticReserve)) { galacticReserve = 0; } LOG("CustomerService", ("Money:%s logged in with %d money, %d galactic reserve", PlayerObject::getAccountDescription(getNetworkId()).c_str(), getTotalMoney(), galacticReserve)); if (getClient() && getClient()->isFreeTrialAccount()) { bool addToConcludeList = false; if ((m_cashBalance.get() + m_bankBalance.get()) > ConfigServerGame::getMaxFreeTrialMoney()) { int amountOver = (m_cashBalance.get() + m_bankBalance.get()) - ConfigServerGame::getMaxFreeTrialMoney(); if (amountOver <= m_bankBalance.get()) { m_bankBalance = m_bankBalance.get() - amountOver; LOG("CustomerService", ("Money:%s is a free trial user and logged in with too much. Deducting %d from bank.", PlayerObject::getAccountDescription(getNetworkId()).c_str(), amountOver)); } else { m_cashBalance = m_cashBalance.get() - (amountOver - m_bankBalance.get()); LOG("CustomerService", ("Money:%s is a free trial user and logged in with too much. Deducting %d from bank and %d from cash.", PlayerObject::getAccountDescription(getNetworkId()).c_str(), m_bankBalance.get(), amountOver - m_bankBalance.get())); m_bankBalance = 0; } // sanity check - fix up any negative cash/bank balance if (m_cashBalance.get() < 0) m_cashBalance = 0; if (m_bankBalance.get() < 0) m_bankBalance = 0; addToConcludeList = true; } // trial accounts cannot have galactic reserve if (galacticReserve > 0) { LOG("CustomerService", ("Money:%s is a free trial user and logged in with %d galactic reserve. Setting galactic reserve to 0.", PlayerObject::getAccountDescription(getNetworkId()).c_str(), galacticReserve)); removeObjVarItem("galactic_reserve"); PlayerObject * const po = PlayerCreatureController::getPlayerObject(asCreatureObject()); if (po) { po->depersistGalacticReserveDeposit(); } addToConcludeList = true; } if (addToConcludeList) addObjectToConcludeList(); } } // ---------------------------------------------------------------------- void ServerObject::onClientAboutToLoad() { } // ---------------------------------------------------------------------- void ServerObject::onLoadingScreenComplete() { } // ---------------------------------------------------------------------- bool ServerObject::onContainerAboutToLoseItem(ServerObject * destination, ServerObject& item, ServerObject* transferer) { if (transferer && !checkBankTransfer(this, item, transferer)) return false; Object *parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) return parent->asServerObject()->onContainerChildAboutToLoseItem(destination, item, transferer); return true; } //----------------------------------------------------------------------- bool ServerObject::onContainerAboutToTransfer(ServerObject * destination, ServerObject* transferer) { UNREF(destination); UNREF(transferer); return true; } //----------------------------------------------------------------------- int ServerObject::onContainerAboutToGainItem(ServerObject& item, ServerObject* transferer) { if (transferer && !checkBankTransfer(this, item, transferer)) return Container::CEC_BlockedByDestinationContainer; Object *parent = ContainerInterface::getContainedByObject(*this); //disallow putting crafting stations in other crafting stations if (transferer && parent && parent->asServerObject()) { SharedObjectTemplate::GameObjectType parentGot = static_cast (parent->asServerObject()->getGameObjectType()); SharedObjectTemplate::GameObjectType itemGot = static_cast (item.getGameObjectType()); if (parentGot == SharedObjectTemplate::GOT_misc_crafting_station && itemGot == parentGot) { return Container::CEC_BlockedByDestinationContainer; } } if (parent && parent->asServerObject()) return parent->asServerObject()->onContainerChildAboutToGainItem(item, this, transferer) ? Container::CEC_Success : Container::CEC_BlockedByDestinationContainer; return Container::CEC_Success; } //----------------------------------------------------------------------- void ServerObject::onContainerLostItem(ServerObject * destination, ServerObject& item, ServerObject* transferer) { Object *parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) parent->asServerObject()->onContainerChildLostItem(destination, item, this, transferer); } //----------------------------------------------------------------------- void ServerObject::onContainerTransfer(ServerObject * destination, ServerObject* transferer) { UNREF(destination); UNREF(transferer); } //----------------------------------------------------------------------- void ServerObject::onContainerGainItem(ServerObject& item, ServerObject* source, ServerObject* transferer) { Object *parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) parent->asServerObject()->onContainerChildGainItem(item, source, transferer); combineResourceContainers(item); } //----------------------------------------------------------------------- void ServerObject::onContainerTransferComplete(ServerObject *oldContainer, ServerObject *newContainer) { if (!isAuthoritative()) { WARNING_STRICT_FATAL(true, ("onContainerTransferComplete called on non auth object %s", getNetworkId().getValueString().c_str())); return; } if (newContainer) { //If this object is not persisted, but it placed into a container that is // Then persist the object if (!isPersisted() && newContainer->isPersisted() && safe_cast(newContainer->getObjectTemplate())->getPersistContents()) persist(); } DEBUG_FATAL(getCellProperty(), ("Cell object container transfer complete")); setTransformChanged(true); if ( (oldContainer && !oldContainer->asCellObject()) || (newContainer && !newContainer->asCellObject())) PositionUpdateTracker::flushPositionUpdate(*this); containerHandleUpdateProxies(oldContainer, newContainer); Container const * const newContainerProperty = newContainer ? ContainerInterface::getContainer(*newContainer) : 0; bool const wasInWorld = isInWorld(); bool const nowInWorld = !newContainer || (newContainerProperty && newContainer->isInWorld() && newContainerProperty->isContentItemExposedWith(*this)); containerHandleUpdateClients(oldContainer, newContainer, (wasInWorld && !nowInWorld), (!wasInWorld && nowInWorld)); ObserveTracker::onObjectArrangementChanged(*this); scheduleForAlter(); updatePositionOnPlanetServer(true); } //----------------------------------------------------------------------- void ServerObject::containerDepersistContents(ServerObject * oldParent, ServerObject * newParent) { Container * const oldContainer = oldParent ? ContainerInterface::getContainer(*oldParent) : nullptr; Container * const newContainer = newParent ? ContainerInterface::getContainer(*newParent) : nullptr; if (oldContainer) oldContainer->internalItemRemoved(*this); if (newContainer) { SlottedContainer * const slotContainer = dynamic_cast(newContainer); if (slotContainer) { SlottedContainmentProperty* const slotted = ContainerInterface::getSlottedContainmentProperty(*this); DEBUG_FATAL(!slotted, ("Error unpacking non slotted objects into a slotted container")); slotContainer->depersistSlotContents(*this, -1); slotContainer->updateArrangement(*this, -1, slotted->getCurrentArrangement()); } else { newContainer->depersistContents(*this); } } } //----------------------------------------------------------------------- void ServerObject::containerHandleUpdateClients(ServerObject* oldContainer, ServerObject* newContainer, bool remFromWorld, bool addWorld) { if (remFromWorld) removeFromWorld(); // If we're moving in or out of the world cell, update as appropriate (adds or removes farUpdateVolume) if (oldContainer && !newContainer && !addWorld) onAddedToWorldCell(); else if (newContainer && !oldContainer && !remFromWorld) onRemovedFromWorldCell(); if (addWorld) addToWorld(); ObserveTracker::onObjectContainerChanged(*this); synchronizationOnContainerChange(oldContainer, newContainer); } //----------------------------------------------------------------------- void ServerObject::createFarNetworkUpdateVolume(float const overrideVal) { if (isBeingDestroyed() || isUnloading()) { DEBUG_REPORT_LOG(true, ("Not creating volume for object that is being destroyed\n")); return; } if (m_networkUpdateFar) { WARNING_STRICT_FATAL(true, ("Object %s already has a far update volume", getNetworkId().getValueString().c_str())); return; } // There's no reason for a contained item to have a far network update volume // if this object is contained by something else. Object const *const containedByObject = ContainerInterface::getContainedByObject(*this); if (containedByObject) { DEBUG_REPORT_LOG(true, ("SO::createFarNetworkUpdateVolume(): server id=[%lu],object id=[%s],container id=[%s]: skipping creation of far network update volume because item is contained.\n", GameServer::getInstance().getProcessId(), getNetworkId().getValueString().c_str(), containedByObject->getNetworkId().getValueString().c_str())); return; } if (getCacheVersion() == 0) { const ServerObjectTemplate * myTemplate = safe_cast(getObjectTemplate()); UNREF(myTemplate); float const radius_far = overrideVal != 0.0f ? overrideVal : getFarNetworkUpdateRadius(); if (radius_far > 0) { if (ServerWorld::isSpaceScene()) { DEBUG_REPORT_LOG(ConfigServerGame::getLogObservers(), ("creating far update volume (space version) for %s (template=%s, radius=%g)\n", getNetworkId().getValueString().c_str(), myTemplate->getName(), radius_far)); // don't make link-dead creatures visible to other people at the top level in space if (getSendToClient() && (getClient() || !asCreatureObject())) SpaceVisibilityManager::addObject (*this, static_cast(radius_far)); } else { DEBUG_REPORT_LOG(ConfigServerGame::getLogObservers(), ("creating far update volume for %s (template=%s, radius=%g)\n", getNetworkId().getValueString().c_str(), myTemplate->getName(), radius_far)); TriggerVolume * t = new NetworkTriggerVolume(*this, radius_far); addTriggerVolume(t); m_networkUpdateFar = t; } } } } //----------------------------------------------------------------------- void ServerObject::destroyFarNetworkUpdateVolume() { if (!m_networkUpdateFar) return; DEBUG_REPORT_LOG(ConfigServerGame::getLogObservers(), ("Removing far update volume for %s\n", getNetworkId().getValueString().c_str())); ServerWorld::removeObjectTriggerVolume(m_networkUpdateFar); TriggerVolumeMap::iterator i = m_triggerVolumes.find(NetworkTriggerVolumeNamespace::NetworkTriggerVolumeName); if (i == m_triggerVolumes.end() || i->second != m_networkUpdateFar) { WARNING_STRICT_FATAL(true, ("Called destroyFarNetworkUpdateVolume but could not find it in m_triggerVolumes")); } else m_triggerVolumes.erase(i); delete m_networkUpdateFar; m_networkUpdateFar = 0; } //----------------------------------------------------------------------- bool ServerObject::isNetworkUpdateFarTriggerVolume(TriggerVolume const &triggerVolume) const { return triggerVolume.isNetworkTriggerVolume(); } // ---------------------------------------------------------------------- TriggerVolume *ServerObject::getNetworkTriggerVolume() { return m_networkUpdateFar; } // ---------------------------------------------------------------------- float ServerObject::getFarNetworkUpdateRadius() const { if (ServerWorld::getSceneId() == NewbieTutorial::getSceneId()) return 0.f; // Overide update radius for all scenes for all objects gloablly with config entry int const overrideRadius = ConfigServerGame::getOverrideUpdateRadius(); // Overide the update radius for a specific scene if the above global overide is not set // If Scene overide is disabled in ConfigServerGame::getEnableSceneGlobalData() it will return zero float radius = overrideRadius > 0 ? static_cast(overrideRadius) : SceneGlobalData::getUpdateRadius(ServerWorld::getSceneId()); // Set the radius to the far update range of from the object template if it hasn't already been overiden radius = radius > 0 ? radius : safe_cast(getObjectTemplate())->getUpdateRanges(ServerObjectTemplate::UR_far); if (!ServerWorld::isSpaceScene()) { if (ms_buildingUpdateRadiusMultiplier == 0) setBuildingUpdateRadiusMultiplier(ConfigServerGame::getBuildingUpdateRadiusMultiplier()); // Potentially allow larger objects to come into range sooner if the cfg setting to do so is set if (radius >= 127.0f && radius < 512.0f && !asCreatureObject()) radius *= ms_buildingUpdateRadiusMultiplier; } return radius; } // ---------------------------------------------------------------------- void ServerObject::onTriggerEnter(ServerObject & object, TriggerVolume & triggerVolume) { } //----------------------------------------------------------------------- // object is leaving this' volume void ServerObject::onTriggerExit(ServerObject & object, TriggerVolume & triggerVolume) { } // ---------------------------------------------------------------------- // this object got added to a trigger volume void ServerObject::onAddedToTriggerVolume(TriggerVolume & triggerVolume) { // only CreatureObject should ever be added to a TriggerVolume WARNING(true, ("ServerObject (%s) being added to TriggerVolume (%s) owned by (%s)", getDebugInformation().c_str(), triggerVolume.getName().c_str(), triggerVolume.getOwner().getDebugInformation().c_str())); } // ---------------------------------------------------------------------- // this object got removed from a trigger volume void ServerObject::onRemovedFromTriggerVolume(TriggerVolume & triggerVolume) { // NOTE: be careful about referencing the TriggerVolume because this // function can be called from the TriggerVolume's dtor // only CreatureObject should ever be added to a TriggerVolume WARNING(true, ("ServerObject (%s) being removed from TriggerVolume (%s) owned by (%s)", getDebugInformation().c_str(), triggerVolume.getName().c_str(), triggerVolume.getOwner().getDebugInformation().c_str())); } // ---------------------------------------------------------------------- /** * Send an object to the database process to be persisted, if it isn't already. * After this function is called, changes to this object will be sent to the database. */ bool ServerObject::persist() { // Do not persist buildout objects (buildout objects have negative networkIds) if (getNetworkId() < NetworkId::cms_invalid) return false; if (isAuthoritative()) { ContainedByProperty *containedBy = getContainedByProperty(); bool const contained = containedBy && containedBy->getContainedByNetworkId().isValid(); if (ServerWorld::isSpaceScene()) { // in space, non-player objects are only allowed to persist at the origin if (!contained && !isPlayerControlled() && getPosition_p() != Vector::zero) { WARNING_STRICT_FATAL(true, ("Tried to persist non-player object %s in a space scene away from the origin (not persisting).", getDebugInformation().c_str())); return false; } } else { WARNING_STRICT_FATAL((getPosition_w() == Vector::zero) && !contained && (getSceneId() != "universe"), ("Persisting object %s (%s) at (0,0,0)",getNetworkId().getValueString().c_str(),getTemplateName())); } if (!m_persisted.get()) { #ifdef _DEBUG ServerObject *parent = static_cast(ContainerInterface::getContainedByObject(*this)); if (parent && !parent->isPersisted()) { WARNING(true, ("Persisting %s into %s, which is not persisted!", getNetworkId().getValueString().c_str(), parent->getNetworkId().getValueString().c_str())); } #endif m_persisted = true; markChildrenPersisted(); sendCreateAndBaselinesToDatabaseServer(); PositionUpdateTracker::sendPositionUpdate(*this); } return true; } else { sendControllerMessageToAuthServer(CM_persistObject, 0); } return false; } // ---------------------------------------------------------------------- void ServerObject::removeTriggerVolume(const std::string & name) { for (unsigned int i = 0; i < m_triggerVolumeInfo.size(); ++i) { if (m_triggerVolumeInfo[i].name == name) { m_triggerVolumeInfo.erase(i); break; } } } //----------------------------------------------------------------------- void ServerObject::sendControllerMessageToAuthServer(enum GameControllerMessage cm, MessageQueue::Data * msg, float value) { DEBUG_WARNING(isInEndBaselines(), ("sendControllerMessageToAuthServer while ending baselines: message id=(%d) from object id=[%s], template=[%s], on server id=[%d]", static_cast(cm), getNetworkId().getValueString().c_str(), getObjectTemplateName(), static_cast(GameServer::getInstance().getProcessId()))); //DEBUG_WARNING(!isInitialized(), ("sendControllerMessageToAuthServer while uninitialized: message id=(%d) from object id=[%s], template=[%s], on server id=[%d]", static_cast(cm), getNetworkId().getValueString().c_str(), getObjectTemplateName(), static_cast(GameServer::getInstance().getProcessId()))); Controller * controller = getController(); if(controller) { controller->appendMessage(cm, value, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } else { DEBUG_WARNING(true, ("No controller for sendControllerMessageToAuthServer")); delete msg; } } //----------------------------------------------------------------------- bool ServerObject::getSendToClient() const { return getCacheVersion() == 0 && getLocalFlag(LocalObjectFlags::ServerObject_SendToClient); } //----------------------------------------------------------------------- void ServerObject::sendToClientsInUpdateRange(const GameNetworkMessage & message, bool reliable, bool includeSelf) const { std::set const &clients = getObservers(); std::set::const_iterator i; std::map > &tmpDistributionList = DistributionListStack::alloc(); Client const * const myClient = getClient(); float minDistanceSquared = 0.0f; if (!reliable) { if (m_unreliableControllerMessageSequenceNumber == 0) { // Send reliable reliable = true; ++m_unreliableControllerMessageSequenceNumber; } else { // Send un-reliable based on distance switch (m_unreliableControllerMessageSequenceNumber) { // The server is guaranteed to send one reliable transform update per 2 seconds (8:1 ratio for unreliable/reliable) case 1: { minDistanceSquared = ConfigServerGame::getUpdateRange7PacketsSquared(); } break; // 7 unreliable per 2 seconds case 2: { minDistanceSquared = ConfigServerGame::getUpdateRange3PacketsSquared(); } break; // 3 unreliable per 2 seconds case 3: { minDistanceSquared = ConfigServerGame::getUpdateRange5PacketsSquared(); } break; // 5 unreliable per 2 seconds case 4: { minDistanceSquared = ConfigServerGame::getUpdateRange1PacketsSquared(); } break; // 1 unreliable per 2 seconds case 5: { minDistanceSquared = ConfigServerGame::getUpdateRange4PacketsSquared(); } break; // 4 unreliable per 2 seconds case 6: { minDistanceSquared = ConfigServerGame::getUpdateRange2PacketsSquared(); } break; // 2 unreliable per 2 seconds case 7: { minDistanceSquared = ConfigServerGame::getUpdateRange6PacketsSquared(); } break; // 6 unreliable per 2 seconds default: { DEBUG_WARNING(true, ("All cases should be handled for the sequence numbers: %d", m_unreliableControllerMessageSequenceNumber)); } } if (m_unreliableControllerMessageSequenceNumber >= 7) { m_unreliableControllerMessageSequenceNumber = 0; } else { ++m_unreliableControllerMessageSequenceNumber; } } } // need to do special handling for CM_combatSpam controller // message to determine if the client should get the message bool isCombatSpamMessage = false; NetworkId combatSpamAttacker, combatSpamDefender; Vector combatSpamAttackerPosition_w, combatSpamDefenderPosition_w; if (!clients.empty()) { static unsigned long int const controllerMessageCrc = MessageDispatch::MessageBase::makeMessageTypeFromString("ObjControllerMessage"); if (controllerMessageCrc == message.getType()) { ObjControllerMessage const & ocm = static_cast(message); if (ocm.getMessage() == CM_combatSpam) { MessageQueueCombatSpam const * mqcs = static_cast(ocm.getData()); if (mqcs) { isCombatSpamMessage = true; combatSpamAttacker = mqcs->m_attacker; combatSpamAttackerPosition_w = mqcs->m_attackerPosition_w; combatSpamDefender = mqcs->m_defender; combatSpamDefenderPosition_w = mqcs->m_defenderPosition_w; } } } } for (i = clients.begin(); i != clients.end(); ++i) { // build connection list Client * const c = (*i); if (isCombatSpamMessage && !c->shouldReceiveCombatSpam(combatSpamAttacker, combatSpamAttackerPosition_w, combatSpamDefender, combatSpamDefenderPosition_w)) continue; if (!reliable) { // if it's time to try to send to this client // because it is close enough, add it to the // target list, otherwise continue looking // through clients. ServerObject * const obj = c->getCharacterObject(); if (obj != nullptr) { if ( (ConfigServerGame::getSkipUnreliableTransformsForOtherCells() && obj->getAttachedTo() != getAttachedTo()) || (obj->getPosition_w().magnitudeBetweenSquared(getPosition_w()) > minDistanceSquared) || (!CellProperty::areAdjacent(getParentCell(), obj->getParentCell()))) { // Throw out this un-reliable packet due to distance continue; } } } if(includeSelf || ( (!includeSelf) && c != myClient) ) { ConnectionServerConnection * const n = c->getConnection(); if (n) tmpDistributionList[n].push_back(c->getCharacterObjectId()); } } for (std::map >::const_iterator connIter = tmpDistributionList.begin(); connIter != tmpDistributionList.end(); ++connIter) { GameClientMessage const msg((*connIter).second, reliable, message); (*connIter).first->send(msg, reliable); } DistributionListStack::release(); } //----------------------------------------------------------------------- void ServerObject::sendToSpecifiedClients(const GameNetworkMessage & message, bool reliable, const std::vector & clients) { for (std::vector::const_iterator i = clients.begin(); i != clients.end(); ++i) { ServerObject * o = safe_cast(NetworkIdManager::getObjectById(*i)); if (o != nullptr && o->getClient() != nullptr) o->getClient()->send(message, reliable); } } //----------------------------------------------------------------------- void ServerObject::appendMessage(int const message, float const value, uint32 const flags) { Controller * const co = getController(); if (co) co->appendMessage(message, value, flags); else DEBUG_REPORT_LOG(true, ("ServerObject::appendMessage: %s called without a controller.\n", getDebugInformation().c_str())); } //----------------------------------------------------------------------- void ServerObject::appendMessage(int const message, float const value, MessageQueue::Data * const data, uint32 const flags) { Controller * const co = getController(); if (co) co->appendMessage(message, 0.0f, data, flags); else DEBUG_REPORT_LOG(true, ("ServerObject::appendMessage: %s called without a controller.\n", getDebugInformation().c_str())); } //----------------------------------------------------------------------- void ServerObject::forwardServerObjectSpecificBaselines() const { } //----------------------------------------------------------------------- void ServerObject::sendObjectSpecificBaselinesToClient(Client const &) const { } //----------------------------------------------------------------------- BaselinesMessage * ServerObject::createSharedBaselinesMessage() const { return new BaselinesMessage(getNetworkId(), getObjectType(), m_sharedPackage, BaselinesMessage::BASELINES_SHARED); } //----------------------------------------------------------------------- BaselinesMessage * ServerObject::createSharedNpBaselinesMessage() const { return new BaselinesMessage(getNetworkId(), getObjectType(), m_sharedPackage_np, BaselinesMessage::BASELINES_SHARED_NP); } //----------------------------------------------------------------------- void ServerObject::setAuthority() { // We make sure to send out deltas before setting authority, so that we don't push out deltas at the // end of the frame which were intended to be nonauthoritative. if (isInitialized()) sendDeltasForSelfAndContents(); DEBUG_REPORT_LOG(ConfigServerGame::getLogObservers(), ("Called setAuthority on %s\n", getNetworkId().getValueString().c_str())); Container * const container = ContainerInterface::getContainer(*this); if (container) { std::vector idList; idList.reserve(container->getNumberOfItems()); for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator) idList.push_back(*containerIterator); for (std::vector::iterator i = idList.begin(); i != idList.end(); ++i) { ServerObject * const containedObject = safe_cast(i->getObject()); if (containedObject) containedObject->setAuthority(); } } safe_cast(getController())->setAuthoritative(true); if (getScriptObject()) getScriptObject()->setOwnerIsAuthoritative(true, GameServer::getInstance().getProcessId()); setAuthServerProcessId(GameServer::getInstance().getProcessId()); if (isInitialized()) updatePositionOnPlanetServer(true); if (isAuthoritative() && (!isPlayerControlled() || getClient()) && getObjVars().hasItem("teleportFixup")) TeleportFixupHandler::add(*this); // Update loadWith so we don't depend on a stale value for objects that // drop authority and regain it later without unloading. setLoadWith(ContainerInterface::getLoadWithContainerId(*this)); virtualOnSetAuthority(); scheduleForAlter(); if (!m_messageTos.empty()) MessageToQueue::getInstance().addToScheduler(*this, m_messageTos.begin()->second.getCallTime()); if (isPlayerControlled()) { // Make sure Central knows the right sceneId for the player GenericValueTypeMessage > > const msg( "SetSceneForPlayer", std::make_pair( getNetworkId(), std::make_pair(ServerWorld::getSceneId(), false))); GameServer::getInstance().sendToCentralServer(msg); std::map const & connectedCharacterLfgData = ServerUniverse::getConnectedCharacterLfgData(); std::map::const_iterator iterFind = connectedCharacterLfgData.find(getNetworkId()); if ((iterFind != connectedCharacterLfgData.end()) && (iterFind->second.locationPlanet != ServerWorld::getSceneId())) ServerUniverse::setConnectedCharacterPlanetData(getNetworkId(), ServerWorld::getSceneId()); } // When a top level object or an object whose contiaining object is // authoritative gets to this point, all its contents have been // made authoritative, so it is now safe to trigger appropriate objects // to notify them of gaining authority. ServerObject * const containingObject = safe_cast(ContainerInterface::getContainedByObject(*this)); if (!containingObject || containingObject->isAuthoritative()) triggerMadeAuthoritative(); } //----------------------------------------------------------------------- void ServerObject::virtualOnSetClient() { if (getObjVars().hasItem("teleportFixup")) TeleportFixupHandler::add(*this); if (m_pendingSyncUi) { for (std::vector::const_iterator i = m_pendingSyncUi->begin(); i != m_pendingSyncUi->end(); ++i) { ServerObject *uiObject = safe_cast(NetworkIdManager::getObjectById(*i)); if (uiObject && uiObject->getSynchronizedUi()) uiObject->addSynchronizedUiClient(*this); } delete m_pendingSyncUi; m_pendingSyncUi = 0; } } // ---------------------------------------------------------------------- void ServerObject::setClient(Client &newClient) { m_client = &newClient; virtualOnSetClient(); safe_cast(getController())->onClientReady(); } // ---------------------------------------------------------------------- void ServerObject::clearClient() { if (m_client) { safe_cast(getController())->onClientLost(); m_client = 0; } } //----------------------------------------------------------------------- void ServerObject::setCacheVersion(const int c) { if (isAuthoritative()) { bool const becomingUncached = c == 0 && m_cacheVersion.get() != 0; if (becomingUncached) ObserveTracker::onObjectDestroyed(*this, false); m_cacheVersion.set(c); if (c == 0) { Container* container = ContainerInterface::getContainer(*this); if (container) { for (ContainerIterator i = container->begin(); i != container->end(); ++i) { ServerObject *containedObject = safe_cast((*i).getObject()); if (containedObject && containedObject->getCacheVersion() != 0) containedObject->setCacheVersion(0); } } if (becomingUncached && isInWorld() && !ContainerInterface::getContainedByObject(*this)) createFarNetworkUpdateVolume(); } } else { sendControllerMessageToAuthServer(CM_setCacheVersion, new MessageQueueGenericValueType(c)); } } // ---------------------------------------------------------------------- void ServerObject::setDefaultAlterTime(float time) { // if the server is running in build mode, always alter, so that designers' changes // will be made even if there are no players around if (ConfigServerGame::getBuildoutAreaEditingEnabled() && time > AlterResult::cms_alterQuickly) time = AlterResult::cms_alterQuickly; m_defaultAlterTime = time; } // ---------------------------------------------------------------------- // CellProperty::addObject_w calls around this function (it calls Object::setParentCell directly) // to ensure that server-side objects send cellChanged notifications correctly. void ServerObject::setParentCell(CellProperty * newCell) { // Uninitialized or proxy objects can set the parent cell directly if (!isInitialized() || !isAuthoritative()) { Object::setParentCell(newCell); return; } // Everything else has to go through ContainerInterface Transform oldTransform = getTransform_o2p(); bool result = false; CellProperty * oldCell = getParentCell(); if(oldCell == nullptr) oldCell = CellProperty::getWorldCellProperty(); if(newCell == nullptr) newCell = CellProperty::getWorldCellProperty(); // ---------- if(oldCell == newCell) { // Cells are the same - nothing to do } if(newCell == CellProperty::getWorldCellProperty()) { // Object was in a cell and is moving to the world, transfer from cell container to world Object * oldCellObject = &oldCell->getOwner(); Transform oldCellTransform = oldCellObject->getTransform_o2w(); Transform newTransform; newTransform.multiply(oldCellTransform,oldTransform); Container::ContainerErrorCode tmp = Container::CEC_Success; result = ContainerInterface::transferItemToWorld(*this, newTransform, nullptr, tmp); DEBUG_REPORT_LOG(!result, ("Object %s (id=%s) transfer to world was denied via ContainerInterface::transferItemToWorld()\n", getObjectTemplateName(), getNetworkId().getValueString().c_str())); } else { // Object is moving to a different cell Object * newCellObject = &newCell->getOwner(); ServerObject * newCellServerObject = safe_cast(newCellObject); Container::ContainerErrorCode tmp = Container::CEC_Success; result = ContainerInterface::transferItemToCell(*newCellServerObject, *this, nullptr, tmp); DEBUG_REPORT_LOG(!result, ("Object %s (id=%s) transfer to cell (id=%s) was denied via ContainerInterface::transferItemToCell()\n", getObjectTemplateName(), getNetworkId().getValueString().c_str(), newCellObject->getNetworkId().getValueString().c_str())); } // ---------- if(!result) { // A failed transfer may have mucked up the transform, so restore it now. setTransform_o2p(oldTransform); } } //----------------------------------------------------------------------- NetworkId const &ServerObject::getOwnerId() const { return NetworkId::cms_invalid; } //----------------------------------------------------------------------- bool ServerObject::isOwner(NetworkId const &, Client const *client) const { return client && client->isGod(); } //----------------------------------------------------------------------- void ServerObject::setOwnerId(NetworkId const &) { } //----------------------------------------------------------------------- /** * Rename an object. * @param newName The new name for the object * You cannot rename a player-controlled object using this function. * @see PlayerCreationManagerServer::renamePlayer() */ void ServerObject::setObjectName(const Unicode::String &newName) { if(isAuthoritative()) { if (isPlayerControlled()) { DEBUG_WARNING(true, ("You cannot set the name of a player controlled object directly. Name:<%s>", Unicode::wideToNarrow(newName).c_str())); return; } std::string UTF8String = Unicode::wideToUTF8(newName); if (UTF8String.length() > 127) { DEBUG_WARNING(true, ("Tried to set object %s's name to something too long (truncating). [%s]", getNetworkId().getValueString().c_str(), UTF8String.c_str())); m_objectName = Unicode::utf8ToWide(UTF8String.substr(0, 127)); } else m_objectName = newName; if (newName[0] == '@') { StringId id(Unicode::wideToNarrow(newName)); setObjectNameStringId(id); setObjectName(Unicode::emptyString); } } else { sendControllerMessageToAuthServer(CM_setObjectName, new MessageQueueGenericValueType(newName)); } } //----------------------------------------------------------------- void ServerObject::setObjectNameStringId (const StringId & id) { if(isAuthoritative()) { m_nameStringId = id; } else { sendControllerMessageToAuthServer(CM_setObjectNameStringId, new MessageQueueGenericValueType(id)); } } //---------------------------------------------------------------------- void ServerObject::setSceneIdOnThisAndContents (const std::string & sceneId) { setSceneId(sceneId); Container *container = ContainerInterface::getContainer(*this); if (container) { for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator) { ServerObject *containedObject = safe_cast((*containerIterator).getObject()); if (containedObject) { containedObject->setSceneIdOnThisAndContents(sceneId); } } } } //---------------------------------------------------------------------- void ServerObject::speakText(NetworkId const &target, uint32 chatType, uint32 mood, uint32 flags, Unicode::String const &speech, int language, Unicode::String const &oob) { if (speech.empty()) { return; } if (chatType > 16384 || mood > 16384 || flags > 16384) { WARNING (true, ("ChatType, Mood, or Flags are out of 16 bit range. %d, %d, %d", chatType, mood, flags)); return; } const bool isTypePrivate = SpatialChatManager::isPrivate (chatType); if (isTypePrivate) flags |= MessageQueueSpatialChat::F_isPrivate; const uint16 volume = SpatialChatManager::getVolume (chatType); MessageQueueSpatialChat const chatMessage( getNetworkId(), target, speech, volume, static_cast(chatType), static_cast(mood), flags, language, oob); speakText(chatMessage); } //----------------------------------------------------------------------- void ServerObject::speakText(MessageQueueSpatialChat const &spatialChat) { if (!isAuthoritative ()) { Controller * const controller = getController(); NOT_NULL (controller); MessageQueueSpatialChat * msg = new MessageQueueSpatialChat (spatialChat); controller->appendMessage(CM_spatialChatSend, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); return; } if (m_scriptObject) { ScriptParams params; params.addParam(spatialChat.getText ()); if (m_scriptObject->trigAllScripts(Scripting::TRIG_SPEAKING, params) != SCRIPT_CONTINUE) { LOG("ScriptInvestigation", ("Chat could not speak for %s because scripts said not to", getNetworkId().getValueString().c_str())); return; } } const float distance = static_cast(spatialChat.getVolume ()); const uint32 flags = spatialChat.getFlags(); const bool isPrivate = (flags & MessageQueueSpatialChat::F_isPrivate) != 0; const bool isTargetOnly = (flags & MessageQueueSpatialChat::F_targetOnly) != 0; const bool isTargetGroupOnly = (flags & MessageQueueSpatialChat::F_targetGroupOnly) != 0; const bool isTargetAndSourceGroup = (flags & MessageQueueSpatialChat::F_targetAndSourceGroup) != 0; const NetworkId & chatTargetId = spatialChat.getTargetId (); const NetworkId & chatSourceId = spatialChat.getSourceId (); typedef std::vector ServerObjectVector; ServerObjectVector results; ServerWorld::getSpatialChatListeners(*this, distance, results); const ServerObject * const chatTarget = safe_cast(NetworkIdManager::getObjectById (chatTargetId)); const CreatureObject * const chatTargetCreature = chatTarget ? chatTarget->asCreatureObject () : 0; int const chatMessageIndex = ChatLogManager::getNextMessageIndex(); const ServerObject * const chatSource = safe_cast(NetworkIdManager::getObjectById (chatSourceId)); const CreatureObject * const chatSourceCreature = chatSource ? chatSource->asCreatureObject () : 0; for(ServerObjectVector::iterator i = results.begin(); i != results.end(); ++i) { ServerObject * const target = NON_NULL (*i); const NetworkId & targetId = target->getNetworkId (); //-- see if the target is valid for the flags if ((spatialChat.getFlags() & MessageQueueSpatialChat::F_skipTarget) != 0 && target->getNetworkId() == spatialChat.getTargetId()) continue; if ((spatialChat.getFlags() & MessageQueueSpatialChat::F_skipSource) != 0 && target->getNetworkId() == spatialChat.getSourceId()) continue; if (isTargetAndSourceGroup) { const CreatureObject * const targetCreature = target->asCreatureObject (); if (!targetCreature) { continue; } const GroupObject * const chatSourceGroup = chatSourceCreature ? chatSourceCreature->getGroup () : 0; const GroupObject * const chatTargetGroup = chatTargetCreature ? chatTargetCreature->getGroup () : 0; const GroupObject * const targetGroup = targetCreature ? targetCreature->getGroup () : 0; //continue if targetCreature is not in a group with the chat source or chat target if (targetId != chatTargetId && targetId != chatSourceId) { if (!targetGroup || (targetGroup != chatSourceGroup && targetGroup != chatTargetGroup)) { continue; } } } else if (isTargetGroupOnly) { if (!chatTargetCreature) continue; if (targetId != chatTargetId) { const CreatureObject * const targetCreature = target->asCreatureObject (); if (!targetCreature || !targetCreature->getGroup() || targetCreature->getGroup () != chatTargetCreature->getGroup ()) continue; } } else if (isTargetOnly) { if (targetId != chatTargetId) continue; } if (isPrivate) { if (targetId == chatTargetId || getNetworkId () == chatTargetId || getNetworkId () == chatSourceId) { //-- don't send text to ourselves if we are an NPC if (isPlayerControlled () || chatSourceId != getNetworkId ()) { target->hearText(*this, spatialChat, chatMessageIndex); } } else { const MessageQueueSpatialChat noTextMsg (spatialChat.getSourceId (), spatialChat.getTargetId (), Unicode::emptyString, spatialChat.getVolume (), spatialChat.getChatType (), spatialChat.getMoodType (), spatialChat.getFlags (), spatialChat.getLanguage (), Unicode::emptyString); target->hearText(*this, noTextMsg, chatMessageIndex); } } else { target->hearText(*this, spatialChat, chatMessageIndex); } } } //----------------------------------------------------------------------- void ServerObject::hearText(ServerObject const &source, MessageQueueSpatialChat const &spatialChat, int chatMessageIndex) { Controller * const controller = NON_NULL (getController()); if (!isAuthoritative()) { MessageQueueSpatialChat * const msg = new MessageQueueSpatialChat (spatialChat); controller->appendMessage(CM_spatialChatReceive, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); return; } if (m_scriptObject) { ScriptParams params; params.addParam(spatialChat.getSourceId()); params.addParam(spatialChat.getText()); if (!m_scriptObject->trigAllScripts(Scripting::TRIG_HEAR_SPEECH_STRING, params)) return; } if (getClient ()) { if ( source.getCacheVersion() > 0 || ( ObserveTracker::isObserving(*getClient(), source) && !PlayerObject::isIgnoring(getNetworkId(), spatialChat.getSourceId()))) { // Log the chat a player hears CreatureObject * const creatureObject = asCreatureObject(); if (creatureObject != nullptr) { PlayerObject * const playerObject = PlayerCreatureController::getPlayerObject(creatureObject); if (playerObject != nullptr) { ChatAvatarId playerChatAvatarId(Chat::constructChatAvatarId(source)); Unicode::String playerName(Unicode::narrowToWide(playerChatAvatarId.getFullName())); std::string chatTypeName; if (spatialChat.getChatType() == 0) { chatTypeName = "spatial"; } else { if (!SpatialChatManager::getChatNameByType(spatialChat.getChatType(), chatTypeName)) { chatTypeName = "error"; } } ChatLogManager::logChat(playerName, Unicode::emptyString, spatialChat.getText(), Unicode::narrowToWide(chatTypeName), chatMessageIndex, Os::getRealSystemTime()); playerObject->logChat(chatMessageIndex); } } MessageQueueSpatialChat * const msg = new MessageQueueSpatialChat (spatialChat); controller->appendMessage(CM_spatialChatReceive, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_CLIENT); } } } //---------------------------------------------------------------------- void ServerObject::performSocial (const NetworkId & target, uint32 socialType, bool animationOk, bool textOk) { const MessageQueueSocial socialMsg (CachedNetworkId (*this), CachedNetworkId (target), socialType, animationOk, textOk); performSocial (socialMsg); } //---------------------------------------------------------------------- void ServerObject::performSocial (const MessageQueueSocial & socialMsg) { // a permanently squelched player cannot do any emote PlayerObject * p = PlayerCreatureController::getPlayerObject(asCreatureObject()); if (p && (p->getSecondsUntilUnsquelched() < 0)) { return; } if (!isAuthoritative ()) { Controller *const controller = NON_NULL (getController()); MessageQueueSocial * const msg = new MessageQueueSocial (socialMsg); controller->appendMessage(CM_socialSend, 0, msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); return; } // todo: this needs to have a better value sometime static const float distance = 50.0f; const Vector & pos_w = getPosition_w (); typedef std::vector ServerObjectVector; ServerObjectVector results; ServerWorld::findObjectsInRange (pos_w, distance, results); bool triggerOk = false; std::string socialTypeName; triggerOk = SocialsManager::getSocialNameByType (socialMsg.getSocialType(), socialTypeName); Unicode::String unicodeSocialTypeName(Unicode::narrowToWide(socialTypeName)); // allow scripts to prevent the player from performing the emote ScriptParams params; if (getScriptObject() != nullptr) { params.addParam(unicodeSocialTypeName); if (getScriptObject()->trigAllScripts(Scripting::TRIG_PERFORM_EMOTE, params) != SCRIPT_CONTINUE) return; params.clear(); } if (triggerOk) { params.addParam(getNetworkId()); params.addParam(unicodeSocialTypeName); } static const float trigger_range = 4.0f; for (ServerObjectVector::const_iterator it = results.begin(); it != results.end(); ++it) { ServerObject * const target = *it; NOT_NULL (target); if (triggerOk && this != target) { const float target_trigger_range_sqr = sqr (trigger_range + target->getSphereExtent ().getRadius ()); const float distance_sqr = pos_w.magnitudeBetweenSquared (target->getPosition_w ()); if (distance_sqr < target_trigger_range_sqr) IGNORE_RETURN(target->getScriptObject()->trigAllScripts(Scripting::TRIG_SAW_EMOTE, params)); } (*it)->seeSocial (socialMsg); } } //---------------------------------------------------------------------- void ServerObject::performCombatSpam (const MessageQueueCombatSpam & spamMsg, bool sendToSelf, bool sendToTarget, bool sendToBystanders) { if (!isAuthoritative ()) { WARNING_STRICT_FATAL (true, ("Do not call performCombatSpam on non-authoritative objects")); return; } if (sendToSelf && !sendToBystanders) seeCombatSpam (spamMsg); if (sendToTarget && !sendToBystanders) { ServerObject * const target = safe_cast(NetworkIdManager::getObjectById(spamMsg.m_defender)); if (target) { if (target->getNetworkId() != spamMsg.m_attacker) { target->seeCombatSpam (spamMsg); } } else { WARNING_STRICT_FATAL (!sendToSelf && !sendToBystanders, ("nullptr target_obj in commandFuncCombatSpam, when sendToTarget was set true")); } } if (sendToBystanders) { Controller *const controller = NON_NULL (getController()); controller->appendMessage (CM_combatSpam, 0, new MessageQueueCombatSpam(spamMsg), GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_ALL_CLIENT); } } // --------------------------------------------------------------------- void ServerObject::teleportObject(Vector const &position_w, NetworkId const &targetContainer, std::string const &targetCellName, Vector const &position_p, std::string const &scriptCallback, bool forceLoadScreen) { Vector destPosition_w(position_w); // clamp destination if in space if ( ServerWorld::isSpaceScene() && ( std::abs(position_w.x) > 8000.f || std::abs(position_w.y) > 8000.f || std::abs(position_w.z) > 8000.f)) { WARNING(true, ("Tried to warp object %s outside of space zone boundaries (%g,%g,%g)", getDebugInformation().c_str(), position_w.x, position_w.y, position_w.z)); destPosition_w.set( clamp(-8000.f, position_w.x, 8000.f), clamp(-8000.f, position_w.y, 8000.f), clamp(-8000.f, position_w.z, 8000.f)); } if (!isAuthoritative()) { sendControllerMessageToAuthServer(CM_teleportObject, new MessageQueueTeleportObject(destPosition_w, targetContainer, targetCellName, position_p, scriptCallback)); return; } // make a copy of the destination position, as it might change if we are // warping to a cloning facility Vector finalPosition_p(position_p); bool doWarp = forceLoadScreen; bool ignoreContainer = true; Client * const client = m_client; std::vector > containedClients; getContainedClients(*this, containedClients); ServerObject *targetContainerObject = 0; if (targetContainer != NetworkId::cms_invalid) { ignoreContainer = false; targetContainerObject = safe_cast(NetworkIdManager::getObjectById(targetContainer)); if (targetContainerObject && !targetCellName.empty()) { PortalProperty *portalProp = targetContainerObject->getPortalProperty(); targetContainerObject = 0; // clear in case we have a building that doesn't have the cell if (portalProp != nullptr) { CellProperty *cellProp = portalProp->getCell(targetCellName.c_str()); if (cellProp != nullptr) targetContainerObject = safe_cast(&(cellProp->getOwner())); } if (!targetContainerObject) { WARNING(true, ("Tried to teleport to nonexistant cell '%s' of building %s.", targetCellName.c_str(), targetContainer.getValueString().c_str())); ignoreContainer = true; } } } Vector const oldPosition_w(NON_NULL(ContainerInterface::getTopmostContainer(*this))->getPosition_w()); if ( destPosition_w.magnitudeBetweenSquared(oldPosition_w) >= sqr(ConfigServerGame::getMaxTeleportDistanceWithoutSceneWarp()) || ( targetContainer != NetworkId::cms_invalid && ( !targetContainerObject || ( getClient() && !ObserveTracker::isObserving(*getClient(), *targetContainerObject))))) doWarp = true; if (doWarp && !containedClients.empty()) { for (std::vector >::const_iterator i = containedClients.begin(); i != containedClients.end(); ++i) { Client * const containedClient = (*i).getPointer(); if (containedClient) { ServerObject * const characterObject = containedClient->getCharacterObject(); if (characterObject) characterObject->onClientAboutToLoad(); containedClient->clearControlledObjects(); } } } bool moveNeeded = true; if (!ignoreContainer && targetContainer != NetworkId::cms_invalid) { if (!doWarp && targetContainerObject && targetContainerObject->isAuthoritative()) { // allow the destination container to change the warp position float yaw = getObjectFrameK_p().theta(); const ServerObject * targetObject = safe_cast( ContainerInterface::getTopmostContainer(*targetContainerObject)); if (targetObject && targetObject->isAuthoritative()) targetObject->changeTeleportDestination(finalPosition_p, yaw); // teleporting to a container on this server if (targetContainerObject->asCellObject()) { Transform worldToCell; worldToCell.invert(targetContainerObject->getTransform_o2w()); Transform objectToCell; objectToCell.multiply(worldToCell, getTransform_o2w()); objectToCell.setPosition_p(finalPosition_p); yaw -= getObjectFrameK_p().theta(); if (yaw != 0.0f) { objectToCell.yaw_l(yaw); objectToCell.reorthonormalize(); } safe_cast(getController())->teleport(objectToCell, targetContainerObject); } else { if (isPlayerControlled()) { WARNING(true, ("Tried to use ServerObject::teleportObject to teleport a player controlled object into a non-cell container!")); return; } Container::ContainerErrorCode err = Container::CEC_Success; ContainerInterface::transferItemToGeneralContainer(*targetContainerObject, *this, 0, err, false); } moveNeeded = false; } else { // container is on a different server - flag to move us into the container as soon as we change authority setInteriorTeleportDestination(targetContainer, targetCellName, position_p); } } //-- setup the script callback if (!scriptCallback.empty()) setTeleportScriptCallback(scriptCallback); if (moveNeeded) { // Moves the object. If the new area is on a different server, the PlanetServer will change our authority. // What we really want to check here is whether we want to use the ServerController's teleport // if a player has logged out, but we still have them loaded in game, their client will be nullptr, // but we still want to use the ServerController's teleport method // so, we check against the player creature controller and player ship controller as well ServerController * controller = safe_cast(getController()); if (controller && (client || dynamic_cast(controller) || dynamic_cast(controller))) { Transform newTransform(getTransform_o2w()); newTransform.setPosition_p(destPosition_w); controller->teleport(newTransform, 0); } else { if (ContainerInterface::getContainedByObject(*this)) { Transform newTransform(getTransform_o2w()); newTransform.setPosition_p(destPosition_w); Container::ContainerErrorCode err = Container::CEC_Success; ContainerInterface::transferItemToWorld(*this, newTransform, 0, err); } else setPosition_p(destPosition_w); } } if (doWarp && !containedClients.empty()) { for (std::vector >::const_iterator i = containedClients.begin(); i != containedClients.end(); ++i) { Client * const containedClient = (*i).getPointer(); if (containedClient) { ServerObject * const so = NON_NULL(safe_cast(NetworkIdManager::getObjectById(containedClient->getCharacterObjectId()))); CreatureObject * const primaryControlledCreature = NON_NULL(so->asCreatureObject()); containedClient->assumeControl(*primaryControlledCreature); } } } CollisionWorld::objectWarped(this); if (isAuthoritative() && (!isPlayerControlled() || getClient()) && getObjVars().hasItem("teleportFixup")) TeleportFixupHandler::add(*this); } // ---------------------------------------------------------------------- void ServerObject::changeTeleportDestination(Vector &, float &) const { } // ---------------------------------------------------------------------- void ServerObject::updatePositionOnPlanetServer(bool forceUpdate) { PROFILER_AUTO_BLOCK_DEFINE("updatePositionOnPlanetServer"); // In space, only send the forced updates if (isAuthoritative() && isInitialized() && (forceUpdate || !ServerWorld::isSpaceScene())) updatePlanetServerInternal(forceUpdate); } //---------------------------------------------------------------------- void ServerObject::updatePlanetServerInternal(bool) const { } //---------------------------------------------------------------------- /** * Attempts to create a synchronized ui object. If the synchronized ui object * does not exist, its creation is attempted via createSynchronizedUi. The default implementation * of createSynchronizedUi returns nullptr, and renders this method a no-op. */ void ServerObject::addSynchronizedUi(const std::vector & clients) { if (!m_synchronizedUi.getPointer()) m_synchronizedUi = createSynchronizedUi (); if (m_synchronizedUi.getPointer()) { for (std::vector::const_iterator i = clients.begin(); i != clients.end(); ++i) { ServerObject * client = safe_cast( NetworkIdManager::getObjectById(*i)); if (client != nullptr) { if (client->getClient() != nullptr) m_synchronizedUi->addClientObject (*client); else { client->addPendingSynchronizedUi(*this); } } else { WARNING(true, ("Object %s tried to add non-existant client %s to its sync ui", getNetworkId().getValueString().c_str(), i->getValueString().c_str())); } } scheduleForAlter(); } } //---------------------------------------------------------------------- /** * Attempts to add a client to the synchronized ui object. If the synchronized ui object * does not exist, its creation is attempted via createSynchronizedUi. The default implementation * of createSynchronizedUi returns nullptr, and renders this method a no-op. * */ void ServerObject::addSynchronizedUiClient(ServerObject & client) { if (isAuthoritative()) { if (!m_synchronizedUi.getPointer()) m_synchronizedUi = createSynchronizedUi (); if (!m_synchronizedUi.getPointer()) return; m_synchronizedUi->addClientObject (client); scheduleForAlter(); } else { WARNING_STRICT_FATAL(true, ("Non-authoritative object %s is being asked to create a synchronizedUi for client %s", getNetworkId().getValueString().c_str(), client.getNetworkId().getValueString().c_str())); } } //---------------------------------------------------------------------- void ServerObject::removeSynchronizedUiClient(const NetworkId & clientId) { if (!m_synchronizedUi.getPointer()) return; WARNING(!isAuthoritative(), ("ServerObject::removeSynchronizedUiClient being called on non-authoritative object %s for client %s", getNetworkId().getValueString().c_str(), clientId.getValueString().c_str())); m_synchronizedUi->removeClientObject (clientId); if (m_synchronizedUi->getNumClients () == 0) { delete m_synchronizedUi.getPointer(); } scheduleForAlter(); } //---------------------------------------------------------------------- /** * Subclasses implement this if they have an appropriate synchronized ui object. * Base class implementation returns nullptr. */ ServerSynchronizedUi * ServerObject::createSynchronizedUi () { return 0; } //---------------------------------------------------------------------- /** * Adds a object to the list of objects that have a synchronized ui and are * waiting for this object to have a client attached. * * @param uiObject the object with the synchronized ui */ void ServerObject::addPendingSynchronizedUi(const ServerObject & uiObject) { if (getClient() != nullptr) { WARNING(true, ("ServerObject::addPendingSynchronizedUi called on object %s that already has a client", getNetworkId().getValueString().c_str())); return; } if (m_pendingSyncUi == nullptr) m_pendingSyncUi = new std::vector; m_pendingSyncUi->push_back(uiObject.getNetworkId()); } //---------------------------------------------------------------------- void ServerObject::setPlayerControlled(bool newValue) { if(isAuthoritative()) { m_playerControlled=newValue; if (newValue) setDefaultAlterTime(0); else setDefaultAlterTime(AlterResult::cms_keepNoAlter); } else { sendControllerMessageToAuthServer(CM_setPlayerControlled, new MessageQueueGenericValueType(newValue)); } } // ---------------------------------------------------------------------- void ServerObject::internalAdjustBankBalance(int const amount, bool makeRoom) { if ((amount > 0) && ((amount + m_bankBalance.get()) > ConfigServerGame::getMaxMoney()) && makeRoom && isAuthoritative()) { PlayerObject * const po = PlayerCreatureController::getPlayerObject(asCreatureObject()); if (po && po->isAuthoritative()) { int attempt = 0; do { int amountToMove = amount + m_bankBalance.get() - ConfigServerGame::getMaxMoney(); if (amountToMove > m_bankBalance.get()) { amountToMove = m_bankBalance.get(); } if (amountToMove <= 0) break; if ((amountToMove + m_cashBalance.get()) > ConfigServerGame::getMaxMoney()) { // not enough room in cash, see if there's still room in galactic reserve int8 const galacticReserve = po->getGalacticReserveDeposit(); if (static_cast(galacticReserve) < ConfigServerGame::getMaxGalacticReserveDepositBillion()) { // transfer 1 billion into galactic reserve, and try again, if necessary po->depositToGalacticReserve(true); // failed to transfer to galactic reserve, don't try again if (galacticReserve == po->getGalacticReserveDeposit()) { break; } } else { // no more room in galactic reserve, move as much as possible into cash amountToMove = ConfigServerGame::getMaxMoney() - m_cashBalance.get(); if (amountToMove > m_bankBalance.get()) { amountToMove = m_bankBalance.get(); } if (amountToMove > 0) { withdrawCashFromBank(amountToMove); } break; } } else { if (amountToMove > 0) { withdrawCashFromBank(amountToMove); } break; } ++attempt; } while (((amount + m_bankBalance.get()) > ConfigServerGame::getMaxMoney()) && (attempt < 2)); } } m_bankBalance = m_bankBalance.get() + amount; if (m_bankBalance.get() > ConfigServerGame::getMaxMoney()) { LOG("CustomerService",("SuspectedCheaterChannel: %s has %i credits in the bank.", PlayerObject::getAccountDescription(this).c_str(), m_bankBalance.get())); m_bankBalance = ConfigServerGame::getMaxMoney(); } if (getClient() && getClient()->isFreeTrialAccount() && (m_cashBalance.get() + m_bankBalance.get()) > ConfigServerGame::getMaxFreeTrialMoney()) { LOG("CustomerService",("FreeTrial: %s has exceeded max free trial balance of %d from a bank transfer amount of %d. Money lost: %d.",PlayerObject::getAccountDescription(this).c_str(), ConfigServerGame::getMaxFreeTrialMoney(), amount, m_cashBalance.get() + m_bankBalance.get() - ConfigServerGame::getMaxFreeTrialMoney())); m_bankBalance = ConfigServerGame::getMaxFreeTrialMoney() - m_cashBalance.get(); ProsePackage prosePackage; prosePackage.stringId = StringId("ui_trade", "err_max_free_trial_money"); prosePackage.digitInteger = ConfigServerGame::getMaxFreeTrialMoney(); Chat::sendSystemMessage(*this, prosePackage); } // sanity check - fix up any player character negative cash/bank balance if ((m_cashBalance.get() < 0) && PlayerCreatureController::getPlayerObject(asCreatureObject())) m_cashBalance = 0; if ((m_bankBalance.get() < 0) && PlayerCreatureController::getPlayerObject(asCreatureObject())) m_bankBalance = 0; addObjectToConcludeList(); } // ---------------------------------------------------------------------- void ServerObject::internalAdjustCashBalance(int const amount, bool makeRoom) { if ((amount > 0) && ((amount + m_cashBalance.get()) > ConfigServerGame::getMaxMoney()) && makeRoom && isAuthoritative()) { PlayerObject * const po = PlayerCreatureController::getPlayerObject(asCreatureObject()); if (po && po->isAuthoritative()) { int attempt = 0; do { int amountToMove = amount + m_cashBalance.get() - ConfigServerGame::getMaxMoney(); if (amountToMove > m_cashBalance.get()) { amountToMove = m_cashBalance.get(); } if (amountToMove <= 0) break; if ((amountToMove + m_bankBalance.get()) > ConfigServerGame::getMaxMoney()) { // not enough room in bank, see if there's still room in galactic reserve int8 const galacticReserve = po->getGalacticReserveDeposit(); if (static_cast(galacticReserve) < ConfigServerGame::getMaxGalacticReserveDepositBillion()) { // transfer 1 billion into galactic reserve, and try again, if necessary po->depositToGalacticReserve(true); // failed to transfer to galactic reserve, don't try again if (galacticReserve == po->getGalacticReserveDeposit()) { break; } } else { // no more room in galactic reserve, move as much as possible into bank amountToMove = ConfigServerGame::getMaxMoney() - m_bankBalance.get(); if (amountToMove > m_cashBalance.get()) { amountToMove = m_cashBalance.get(); } if (amountToMove > 0) { depositCashToBank(amountToMove); } break; } } else { if (amountToMove > 0) { depositCashToBank(amountToMove); } break; } ++attempt; } while (((amount + m_cashBalance.get()) > ConfigServerGame::getMaxMoney()) && (attempt < 2)); } } m_cashBalance = m_cashBalance.get() + amount; if (m_cashBalance.get() > ConfigServerGame::getMaxMoney()) { LOG("CustomerService",("SuspectedCheaterChannel: %s has %i credits cash.",PlayerObject::getAccountDescription(this).c_str(), m_cashBalance.get())); m_cashBalance = ConfigServerGame::getMaxMoney(); } if (getClient() && getClient()->isFreeTrialAccount() && (m_cashBalance.get() + m_bankBalance.get()) > ConfigServerGame::getMaxFreeTrialMoney()) { LOG("CustomerService",("FreeTrial: %s has exceeded max free trial balance of %d from a cash transfer amount of %d. Money lost: %d.",PlayerObject::getAccountDescription(this).c_str(), ConfigServerGame::getMaxFreeTrialMoney(), amount, m_cashBalance.get() + m_bankBalance.get() - ConfigServerGame::getMaxFreeTrialMoney())); m_cashBalance = ConfigServerGame::getMaxFreeTrialMoney() - m_bankBalance.get(); ProsePackage prosePackage; prosePackage.stringId = StringId("ui_trade", "err_max_free_trial_money"); prosePackage.digitInteger = ConfigServerGame::getMaxFreeTrialMoney(); Chat::sendSystemMessage(*this, prosePackage); } // sanity check - fix up any player character negative cash/bank balance if ((m_cashBalance.get() < 0) && PlayerCreatureController::getPlayerObject(asCreatureObject())) m_cashBalance = 0; if ((m_bankBalance.get() < 0) && PlayerCreatureController::getPlayerObject(asCreatureObject())) m_bankBalance = 0; addObjectToConcludeList(); } // ---------------------------------------------------------------------- bool ServerObject::transferCashTo(const NetworkId &target, int amount, const NetworkId &transactionId) { if (amount <= 0) { DEBUG_REPORT_LOG(true, ("Attempting to transfer 0 or negative money on object %s, amount %d\n", getNetworkId().getValueString().c_str(), amount)); return false; } if (isAuthoritative()) { if (amount > ConfigServerGame::getMaxMoneyTransfer()) { DEBUG_REPORT_LOG(true,("Tried to transfer too much cash. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.getValueString().c_str(),amount)); return false; } int sourceBalance=getCashBalance(); if (sourceBalance < amount) { DEBUG_REPORT_LOG(true,("Tried to transfer too much cash. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.getValueString().c_str(),amount)); return false; } internalAdjustCashBalance(-amount, true); // send persistent message to add money to target. char buffer[100]; snprintf(buffer,100,"%i",amount); MessageToQueue::getInstance().sendMessageToC(target, "C++FinishCashTransfer", buffer, 0, true); LOG("CustomerService", ("Money: cash transfer from %s to %s, amount %i, total(from) %i", PlayerObject::getAccountDescription(getNetworkId()).c_str(),PlayerObject::getAccountDescription(target).c_str(),amount, getTotalMoney())); return true; } else { //TODO: handle multi-server banking DEBUG_FATAL(true,("Banking across game servers is not yet implemented.\n")); return false; } } // ---------------------------------------------------------------------- bool ServerObject::transferBankCreditsTo(const NetworkId &target, int amount, const NetworkId &transactionId) { if (amount <= 0) { DEBUG_REPORT_LOG(true, ("Attempting to transfer 0 or negative money on object %s, amount %d\n", getNetworkId().getValueString().c_str(), amount)); return false; } if (isAuthoritative()) { if (amount > ConfigServerGame::getMaxMoneyTransfer()) { DEBUG_REPORT_LOG(true,("Tried to transfer too many bank credits. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.getValueString().c_str(),amount)); return false; } int sourceBalance=getBankBalance(); if (sourceBalance < amount) { DEBUG_REPORT_LOG(true,("Tried to transfer too many bank credits. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.getValueString().c_str(),amount)); return false; } internalAdjustBankBalance(-amount, true); // send persistent message to add money to target. char buffer[100]; snprintf(buffer,100,"%i",amount); MessageToQueue::getInstance().sendMessageToC(target, "C++FinishBankTransfer", buffer, 0, true); LOG("CustomerService", ("Money: bank transfer from %s to %s, amount %i, total(from) %i", PlayerObject::getAccountDescription(getNetworkId()).c_str(),PlayerObject::getAccountDescription(target).c_str(),amount, getTotalMoney())); return true; } else { //TODO: handle multi-server banking DEBUG_FATAL(true,("Banking across game servers is not yet implemented.\n")); return false; } } // ---------------------------------------------------------------------- bool ServerObject::transferBankCreditsTo(const std::string &target, int amount, const NetworkId &transactionId) { if (amount <= 0) { DEBUG_REPORT_LOG(true, ("Attempting to transfer 0 or negative money on object %s, amount %d\n", getNetworkId().getValueString().c_str(), amount)); return false; } if (isAuthoritative()) { // allow transfers to/from the commodities escrow account or galactic reserve to bypass // the max transfer limit because commodities item can be sold/bought for more than the // transfer limit and the galactic reserve transfers 1 billion credits at a time if ((target != CommoditiesMarket::getCommoditiesNamedEscrowAccount()) && (target != std::string("galactic_reserve"))) { if (amount > ConfigServerGame::getMaxMoneyTransfer()) { DEBUG_REPORT_LOG(true,("Tried to transfer too many bank credits. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.c_str(),amount)); return false; } } int sourceBalance=getBankBalance(); if (sourceBalance < amount) { DEBUG_REPORT_LOG(true,("Tried to transfer too many bank credits. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.c_str(),amount)); return false; } internalAdjustBankBalance(-amount, true); LOG("CustomerService", ("Money: bank transfer from %s to named account %s, amount %i, total %i", PlayerObject::getAccountDescription(getNetworkId()).c_str(),target.c_str(), amount, getTotalMoney())); return true; } else { //TODO: handle multi-server banking DEBUG_FATAL(true,("Banking across game servers is not yet implemented.\n")); return false; } } // ---------------------------------------------------------------------- bool ServerObject::transferBankCreditsFrom(const std::string &target, int amount, const NetworkId &transactionId) { if (amount <= 0) { DEBUG_REPORT_LOG(true, ("Attempting to transfer 0 or negative money on object %s, amount %d\n", getNetworkId().getValueString().c_str(), amount)); return false; } if (isAuthoritative()) { // allow transfers to/from the commodities escrow account or galactic reserve to bypass // the max transfer limit because commodities item can be sold/bought for more than the // transfer limit and the galactic reserve transfers 1 billion credits at a time if ((target != CommoditiesMarket::getCommoditiesNamedEscrowAccount()) && (target != std::string("galactic_reserve"))) { if (amount > ConfigServerGame::getMaxMoneyTransfer()) { DEBUG_REPORT_LOG(true,("Tried to transfer too many bank credits. Source object %s, target %s, amount %i", getNetworkId().getValueString().c_str(),target.c_str(),amount)); return false; } } internalAdjustBankBalance(amount, true); LOG("CustomerService", ("Money: bank transfer from named account %s to %s, amount %i, total %i", target.c_str(),PlayerObject::getAccountDescription(getNetworkId()).c_str(), amount, getTotalMoney())); return true; } else { //TODO: handle multi-server banking DEBUG_FATAL(true,("Banking across game servers is not yet implemented.\n")); return false; } } // ---------------------------------------------------------------------- bool ServerObject::withdrawCashFromBank(int amount, const NetworkId &transactionId) { if (amount <= 0) { DEBUG_REPORT_LOG(true, ("Attempting to transfer 0 or negative money on object %s, amount %d\n", getNetworkId().getValueString().c_str(), amount)); return false; } if (isAuthoritative()) { // adjust amount so it won't overflow the cash limit if ((getCashBalance() + amount) > ConfigServerGame::getMaxMoney()) { amount = ConfigServerGame::getMaxMoney() - getCashBalance(); } if (amount > 0) { int bankBalance=getBankBalance(); if (bankBalance < amount) { DEBUG_REPORT_LOG(true,("Object %s tried to bounce a check. Bank balance %i, requested %i.\n",getNetworkId().getValueString().c_str(),bankBalance,amount)); return false; } internalAdjustBankBalance(-amount, false); internalAdjustCashBalance(amount, false); LOG("CustomerService", ("Money: cash withdraw from bank by %s, amount %i, total %i", PlayerObject::getAccountDescription(getNetworkId()).c_str(), amount, getTotalMoney())); } return true; } else { //TODO: handle multi-server banking DEBUG_FATAL(true,("Banking across game servers is not yet implemented.\n")); return false; } } // ---------------------------------------------------------------------- bool ServerObject::depositCashToBank(int amount, const NetworkId &transactionId) { if (amount <= 0) { DEBUG_REPORT_LOG(true, ("Attempting to transfer 0 or negative money on object %s, amount %d\n", getNetworkId().getValueString().c_str(), amount)); return false; } if (isAuthoritative()) { // adjust amount so it won't overflow the bank limit if ((getBankBalance() + amount) > ConfigServerGame::getMaxMoney()) { amount = ConfigServerGame::getMaxMoney() - getBankBalance(); } if (amount > 0) { int cashBalance=getCashBalance(); if (cashBalance < amount) { DEBUG_REPORT_LOG(true,("Object %s tried to deposit %i credits, but only has %i credits\n",getNetworkId().getValueString().c_str(),amount,cashBalance)); return false; } internalAdjustCashBalance(-amount, false); internalAdjustBankBalance(amount, false); LOG("CustomerService", ("Money: cash deposit to bank by %s, amount %i, total %i", PlayerObject::getAccountDescription(getNetworkId()).c_str(), amount, getTotalMoney())); } return true; } else { //TODO: handle multi-server banking DEBUG_FATAL(true,("Banking across game servers is not yet implemented.\n")); return false; } } // ---------------------------------------------------------------------- char const *ServerObject::getTemplateName() const { return getObjectTemplate()->getName(); } // ---------------------------------------------------------------------- bool ServerObject::getHyperspaceOnCreate () const { return getLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnCreate); } // ---------------------------------------------------------------------- void ServerObject::setHyperspaceOnCreate (bool const hyperspaceOnCreate) { setLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnCreate, hyperspaceOnCreate); } // ---------------------------------------------------------------------- bool ServerObject::getHyperspaceOnDestroy () const { return getLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnDestruct); } // ---------------------------------------------------------------------- void ServerObject::setHyperspaceOnDestroy (bool const hyperspaceOnDestroy) { setLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnDestruct, hyperspaceOnDestroy); } // ---------------------------------------------------------------------- std::string const ServerObject::getPackedObjVars(std::string const &prefix) const { std::vector buf; for (DynamicVariableList::MapType::const_iterator i = getObjVars().begin(); i != getObjVars().end(); ++i) { if (!(*i).first.compare(0, prefix.size(), prefix)) { appendPackedObjVarField(buf, (*i).first.c_str()); appendPackedObjVarField(buf, (*i).second.getType()); appendPackedObjVarField(buf, Unicode::wideToNarrow((*i).second.getPackedValueString()).c_str()); } } appendPackedObjVarField(buf, "$"); return std::string(buf.begin(), buf.end()); } // ---------------------------------------------------------------------- bool ServerObject::setPackedObjVars(std::string const &packedVarString) { unsigned int offset = 0; while (offset < packedVarString.size()) { std::string name; offset = getPackedObjVarField(packedVarString, offset, name); if (name == "$") return true; int typeId; offset = getPackedObjVarField(packedVarString, offset, typeId); std::string valueString; offset = getPackedObjVarField(packedVarString, offset, valueString); DynamicVariable value; value.load(0, typeId, Unicode::narrowToWide(valueString)); setObjVarItem(name, value); } return false; } // ---------------------------------------------------------------------- #ifdef _WIN32 #define atoll(a) _atoi64(a) #endif // ---------------------------------------------------------------------- std::string ServerObject::debugGetMessageToList() const { unsigned long const now = ServerClock::getInstance().getGameTimeSeconds(); std::string result; time_t const timeNow = ::time(nullptr); for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator i=m_messageTos.begin(); i!=m_messageTos.end(); ++i) { char temp[256]; if (i->second.getCallTime() > now) { snprintf(temp,sizeof(temp)-1,"%s: %s [call time = %lu (%s) (%s) (%s), persisted = %s, recurring = %i]\n",i->second.getMessageId().getValueString().c_str(), i->second.getMethod().c_str(), i->second.getCallTime(), CalendarTime::convertSecondsToDHMS(static_cast(i->second.getCallTime() - now)).c_str(), CalendarTime::convertEpochToTimeStringGMT(i->second.getCallTime() - now + timeNow).c_str(), CalendarTime::convertEpochToTimeStringLocal(i->second.getCallTime() - now + timeNow).c_str(), (i->second.getPersisted() ? "true" : "false"), i->second.getRecurringTime()); } else { snprintf(temp,sizeof(temp)-1,"%s: %s [call time = %lu, persisted = %s, recurring = %i]\n",i->second.getMessageId().getValueString().c_str(), i->second.getMethod().c_str(), i->second.getCallTime(), (i->second.getPersisted() ? "true" : "false"), i->second.getRecurringTime()); } temp[sizeof(temp)-1]='\0'; result += temp; } return result; } // ---------------------------------------------------------------------- /** * Go through the list of messageTos and handle any whose time has been * reached. * @param effectiveMessageToTime The current Server Clock, for the purpose of MessageTos * @return The delivery time of the next message, or 0 if there are no more messages */ unsigned long ServerObject::processQueuedMessageTos(unsigned long effectiveMessageToTime) { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::processQueuedMessageTos"); if (isAuthoritative()) { int handledMessageCount = 0; unsigned long startTime = Clock::timeMs(); while (!m_messageTos.empty() && m_messageTos.begin()->second.getCallTime() <= effectiveMessageToTime) { ++handledMessageCount; if ((ConfigServerGame::getMaxMessageTosPerObjectPerFrame() !=0 ) && (handledMessageCount > ConfigServerGame::getMaxMessageTosPerObjectPerFrame())) { std::string nextTenMessages; { int count = 1; for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator iter = m_messageTos.begin(); ((iter != m_messageTos.end()) && (count <= 10)); ++iter, ++count) { nextTenMessages += iter->second.getMethod(); nextTenMessages += ", "; } } WARNING(true,("MessageTo Backlog: object %s had more than %i messages to be handled in one frame. Remaining %d messages will be delayed until next frame. Next ten messages are (%s).", getNetworkId().getValueString().c_str(), ConfigServerGame::getMaxMessageTosPerObjectPerFrame(), m_messageTos.size(), nextTenMessages.c_str())); MessageToQueue::getInstance().incrementBackloggedObjectCount(); break; } if ((ConfigServerGame::getMaxMessageToTimePerObjectPerFrame() != 0) && (static_cast(Clock::timeMs() - startTime) > ConfigServerGame::getMaxMessageToTimePerObjectPerFrame())) { WARNING(true,("MessageTo Backlog: object %s spent more than %i ms processing messages. Remaining %d messages will be delayed until next frame.", getNetworkId().getValueString().c_str(), ConfigServerGame::getMaxMessageToTimePerObjectPerFrame(), m_messageTos.size())); MessageToQueue::getInstance().incrementBackloggedObjectCount(); break; } Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator message = m_messageTos.begin(); // if the message is going to be recurring, create a // new messageTo to reschedule the recurring message MessageToPayload * copyOfRecurringMessage = nullptr; if (message->second.getRecurringTime() > 0) { copyOfRecurringMessage = new MessageToPayload( getNetworkId(), message->second.getMessageId(), message->second.getMethod(), message->second.getPackedDataVector(), ServerClock::getInstance().getGameTimeSeconds() + static_cast(message->second.getRecurringTime()), false, message->second.getDeliveryType(), NetworkId::cms_invalid, "", message->second.getRecurringTime()); } // set sentinel to prevent this message from being removed // from m_messageTos while it is being handled s_currentMessageToBeingHandled = message->first; handleMessageTo(message->second); // remove message that just got handled IGNORE_RETURN(m_messageTos.erase(s_currentMessageToBeingHandled)); s_currentMessageToBeingHandled = std::make_pair(std::make_pair(0, 0), MessageToId::cms_invalid); // Reschedule the recurring message, if necessary if (copyOfRecurringMessage) { deliverMessageTo(*copyOfRecurringMessage); delete copyOfRecurringMessage; } } // Make sure object will handle messages again when the next message is due if (!m_messageTos.empty()) return m_messageTos.begin()->second.getCallTime(); else return 0; } else return 0; } // ---------------------------------------------------------------------- void ServerObject::handleMessageTo(MessageToPayload const & message) { if (message.getDeliveryType() == MessageToPayload::DT_c) { handleCMessageTo(message); } else if (message.getDeliveryType() == MessageToPayload::DT_java) { GameScriptObject *scriptObj=getScriptObject(); if (scriptObj) { if (!scriptObj->handleMessage(message.getMethod(), message.getPackedDataVector())) { WARNING_STRICT_FATAL(true, ("ServerObject::handleMessageTo, message %s to object %s - script function was not called!", message.getMethod().c_str(), message.getNetworkId().getValueString().c_str())); } } else { WARNING_STRICT_FATAL(true, ("ServerObject::handleMessageTo, message %s to object %s - object does not have a ScriptObject attached!", message.getMethod().c_str(), message.getNetworkId().getValueString().c_str())); } } else WARNING_STRICT_FATAL(true,("Called ServerObject::handleMessageTo with a message that had an unrecognized delivery type. Message is %s, delivery type is %i", message.getMethod().c_str(), message.getDeliveryType())); if (message.getPersisted()) { MessageToAckMessage const reply(message.getMessageId()); GameServer::getInstance().sendToDatabaseServer(reply); } } // ---------------------------------------------------------------------- void ServerObject::handleCMessageTo(const MessageToPayload &message) { std::vector const &packedData = message.getPackedDataVector(); if (message.getMethod() == "C++ForceAlter") { scheduleForAlter(); } else if (message.getMethod() == "C++RenameCharacter") { //TODO: resolve Unicode issues with messageto's m_objectName=Unicode::narrowToWide(std::string(packedData.begin(), packedData.end())); // rename has been completed, remove the objvar so the system won't think there's a pending rename request Unicode::String previousRenameName; if (getObjVars().getItem("renameCharacterRequest.requestNewName", previousRenameName) && (previousRenameName == m_objectName.get())) removeObjVarItem("renameCharacterRequest.requestNewName"); // if character is connected, update the client (character selection) with the new name Client * const client = getClient(); if (client) { GenericValueTypeMessage, Unicode::String> > const characterNameChanged("CharacterNameChanged", std::make_pair(std::make_pair(GameServer::getInstance().getClusterName(), getNetworkId()), m_objectName.get())); client->send(characterNameChanged, true); } } else if (message.getMethod() == "C++FinishBankTransfer") { std::string packedDataString(packedData.begin(), packedData.end()); int m_amount=strtoul(packedDataString.c_str(), 0, 10); DEBUG_FATAL(m_amount < 0,("C++FinishBankTransaction had a negative amount. This message should always have a positive amount.\n")); internalAdjustBankBalance(m_amount, true); } // transfers money from a named account to the bank else if( message.getMethod() == "C++FromNamedAccountToBank") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; //DEBUG_WARNING(true, ("Processing C++FromNamedAccountToBank. Params = '%s' MessageId=%Ld", params, message.getMessageId().getValue() )); char *splitPos = strstr(params, " "); if( splitPos) { *splitPos = 0; std::string const named_account(params); int const amount = atoi(splitPos+1); DEBUG_FATAL(amount < 0,("C++FromNamedAccountToBank had a negative amount. This message should always have a positive amount.\n")); transferBankCreditsFrom(named_account, amount); } delete [] params; } else if (message.getMethod() == "C++FinishCashTransfer") { std::string packedDataString(packedData.begin(), packedData.end()); int m_amount=strtoul(packedDataString.c_str(), 0, 10); DEBUG_FATAL(m_amount < 0,("C++FinishCashTransaction had a negative amount. This message should always have a positive amount.\n")); internalAdjustCashBalance(m_amount, true); } else if (message.getMethod() == "C++ModifyCash") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; char *splitPos = strstr(params, " "); if (splitPos) { *splitPos = 0; NetworkId recipient(atoll(params)); int amount = atoi(splitPos+1); transferCashTo(recipient, amount); } delete [] params; } else if (message.getMethod() == "C++ModifyBank") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; char *splitPos = strstr(params, " "); if (splitPos) { *splitPos = 0; NetworkId recipient(atoll(params)); int amount = atoi(splitPos+1); transferBankCreditsTo(recipient, amount); } delete [] params; } else if (message.getMethod() == "C++DestroySelf") { permanentlyDestroy(DeleteReasons::Script); } else if (message.getMethod() == "C++VendorStatusChange") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; int status = atoi(params); CommoditiesMarket::vendorStatusChange(getNetworkId(), status); delete [] params; } else if (message.getMethod() == "C++Unstick") { CreatureObject* creatureObject = asCreatureObject(); if (creatureObject && creatureObject->isInWorld()) { PlayerObject * p = PlayerCreatureController::getPlayerObject(creatureObject); if (p && p->getIsUnsticking()) { //Receive positional information with this unstick message. If you change //the format of this positional information, you must also change it in the sender in CommandCppFuncs.cpp Vector position = getPosition_p(); char buf[255]; snprintf(buf, sizeof(buf), "%f:%f", position.x, position.z); buf[sizeof(buf) - 1] = '\0'; char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; if (strcmp(buf, params) == 0) { //handle unstick static MessageDispatch::Emitter e; Chat::sendSystemMessageSimple(*this, SharedStringIds::unstick_request_complete, nullptr); RequestUnstick r; r.setClientId(getNetworkId()); e.emitMessage(r); p->setIsUnsticking(false); } delete [] params; } } } else if (message.getMethod() == "C++WaitForPatrolPreload") { WARNING(true, ("Non-creature object %s received a C++WaitForPatrolPreload message", getNetworkId().getValueString().c_str())); MessageToQueue::cancelRecurringMessageTo(getNetworkId(), "C++WaitForPatrolPreload"); } else if (message.getMethod() == "CancelRecurringMessageTo") { std::string const & methodName = message.getDataAsString(); for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator i=m_messageTos.begin(); i!=m_messageTos.end(); ++i) { if ((i->second.getRecurringTime() != 0) && (i->second.getMethod() == methodName) && (i->first != s_currentMessageToBeingHandled)) { IGNORE_RETURN(m_messageTos.erase(i)); break; } } } else if (message.getMethod() == "C++updateObserversCount") { std::string const & buffer = message.getDataAsString(); if (!buffer.empty()) { int count = atoi(buffer.c_str()); updateObserversCount(count); } } else if (message.getMethod() == "C++addBroadcastListener") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; char *splitPos = strstr(params, " "); if (splitPos) { *splitPos = 0; addBroadcastListener(std::string(params), NetworkId(std::string(splitPos+1))); } delete [] params; } else if (message.getMethod() == "C++removeBroadcastListener") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; char *splitPos = strstr(params, " "); if (splitPos) { *splitPos = 0; removeBroadcastListener(std::string(params), NetworkId(std::string(splitPos+1))); } delete [] params; } else if (message.getMethod() == "C++removeBroadcastListenerAllMessage") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; removeBroadcastListener(NetworkId(std::string(params))); delete [] params; } else if (message.getMethod() == "C++listenToBroadcastMessage") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; char *splitPos = strstr(params, " "); if (splitPos) { *splitPos = 0; listenToBroadcastMessage(std::string(params), NetworkId(std::string(splitPos+1))); } delete [] params; } else if (message.getMethod() == "C++stopListeningToBroadcastMessage") { char *params = new char[packedData.size()+1]; memcpy(params, &(*packedData.begin()), packedData.size()); params[packedData.size()] = 0; char *splitPos = strstr(params, " "); if (splitPos) { *splitPos = 0; stopListeningToBroadcastMessage(std::string(params), NetworkId(std::string(splitPos+1))); } delete [] params; } else if (message.getMethod() == "C++stopListeningToAllBroadcastMessage") { stopListeningToAllBroadcastMessages(); } else if (message.getMethod() == "C++AdjustAccountFeatureIdResponse") { if (!packedData.empty()) { Archive::ByteStream bs; bs.put(reinterpret_cast(&packedData[0]), static_cast(packedData.size())); Archive::ReadIterator ri = bs.begin(); AdjustAccountFeatureIdResponse const msg(ri); if (msg.getTargetPlayer() == getNetworkId()) { if (msg.getResultCode() == RESULT_SUCCESS) { Client * client = getClient(); if (client) { if (msg.getGameCode() == PlatformGameCode::SWG) { // request an updated list of feature id AccountFeatureIdRequest const req(NetworkId::cms_invalid, GameServer::getInstance().getProcessId(), msg.getTargetPlayer(), msg.getTargetStationId(), msg.getGameCode(), AccountFeatureIdRequest::RR_Reload); client->sendToConnectionServer(req); // let player know that the reward was successfully traded in if (VeteranRewardManager::isTradeInRewardFeatureId(msg.getFeatureId()) && !msg.getTargetPlayerDescription().empty() && msg.getTargetItem().isValid() && !msg.getTargetItemDescription().empty()) Chat::sendSystemMessage(*this, StringId("veteran_new", "trade_in_success"), Unicode::emptyString); } else if (msg.getGameCode() == PlatformGameCode::SWGTCG) { // let player know that the card was successfully redeemed Chat::sendSystemMessage(*this, StringId("veteran_new", "redeem_tcg_card_success"), Unicode::emptyString); } } } else if (msg.getGameCode() == PlatformGameCode::SWGTCG) { Client * client = getClient(); if (client) { // let player know that the card failed to redeem Chat::sendSystemMessage(*this, StringId("veteran_new", "redeem_tcg_card_failure"), Unicode::emptyString); } } else if ((msg.getGameCode() == PlatformGameCode::SWG) && VeteranRewardManager::isTradeInRewardFeatureId(msg.getFeatureId()) && !msg.getTargetPlayerDescription().empty() && msg.getTargetItem().isValid() && !msg.getTargetItemDescription().empty()) { Client * client = getClient(); if (client) { // let player know that the reward trade in failed Chat::sendSystemMessage(*this, StringId("veteran_new", "trade_in_failure"), Unicode::emptyString); } } } if (msg.getRequestingPlayer() == getNetworkId()) { if (msg.getResultCode() == RESULT_SUCCESS) { if (msg.getResultCameFromSession()) ConsoleMgr::broadcastString(FormattedString<1024>().sprintf("feature Id (%lu) on account (%lu) has been adjusted from (%d) to (%d); session/Platform reported successful result code (%u, %s:%s)", msg.getFeatureId(), msg.getTargetStationId(), msg.getOldValue(), msg.getNewValue(), msg.getResultCode(), msg.getSessionResultString().c_str(), msg.getSessionResultText().c_str()), getClient()); else ConsoleMgr::broadcastString(FormattedString<1024>().sprintf("feature Id (%lu) on account (%lu) has been adjusted from (%d) to (%d)", msg.getFeatureId(), msg.getTargetStationId(), msg.getOldValue(), msg.getNewValue()), getClient()); } else { if (msg.getResultCameFromSession()) ConsoleMgr::broadcastString(FormattedString<1024>().sprintf("request to adjust feature Id for account (%lu) failed with error code (%u, %s:%s)", msg.getTargetStationId(), msg.getResultCode(), msg.getSessionResultString().c_str(), msg.getSessionResultText().c_str()), getClient()); else ConsoleMgr::broadcastString(FormattedString<1024>().sprintf("request to adjust feature Id for account (%lu) failed", msg.getTargetStationId()), getClient()); } } if (msg.getTargetItem() == getNetworkId()) { if (msg.getGameCode() == PlatformGameCode::SWGTCG) VeteranRewardManager::handleTcgRedemptionResponse(*this, msg); else if ((msg.getGameCode() == PlatformGameCode::SWG) && VeteranRewardManager::isTradeInRewardFeatureId(msg.getFeatureId()) && !msg.getTargetPlayerDescription().empty() && !msg.getTargetItemDescription().empty()) VeteranRewardManager::handleTradeInResponse(*this, msg); } } } else if (message.getMethod() == "C++kickPlayerCtsCompletedOrInProgress") { LOG("CustomerService",("Login: dropping %s because of CTS completed or in progress", PlayerObject::getAccountDescription(this).c_str())); KickPlayer const kickMessage(getNetworkId(), "CTS completed or in progress"); GameServer::getInstance().sendToConnectionServers(kickMessage); GameServer::getInstance().dropClient(getNetworkId(), true); } } // ---------------------------------------------------------------------- bool ServerObject::handleTeleportFixup(bool force) { if (getObjVars().hasItem("teleportFixup")) { LOG("TeleportFixup", ("Teleport fixup called for %s\n", getNetworkId().getValueString().c_str())); if (getObjVars().hasItem("teleportFixup.x")) { // Ignore the container portion of a teleport fixup if we are already contained if (!ContainerInterface::getContainedByObject(*this)) { ServerObject *container = 0; NetworkId destContainer; std::string destCellName; Vector destPosition_p; getObjVars().getItem("teleportFixup.x", destPosition_p.x); getObjVars().getItem("teleportFixup.y", destPosition_p.y); getObjVars().getItem("teleportFixup.z", destPosition_p.z); getObjVars().getItem("teleportFixup.container", destContainer); if (getObjVars().getItem("teleportFixup.cellname", destCellName)) { ServerObject * const destBuilding = safe_cast(NetworkIdManager::getObjectById(destContainer)); if (destBuilding) { PortalProperty * const portalProp = destBuilding->getPortalProperty(); if (portalProp) { CellProperty * const cellProp = portalProp->getCell(destCellName.c_str()); if (cellProp) container = safe_cast(&(cellProp->getOwner())); } } } else container = safe_cast(NetworkIdManager::getObjectById(destContainer)); if (container) { // allow the destination container to change the warp position float yaw = 0.0f; ServerObject * const topmost = safe_cast(ContainerInterface::getTopmostContainer(*container)); if (topmost && topmost->isAuthoritative()) topmost->changeTeleportDestination(destPosition_p, yaw); Transform t; t.setPosition_p(destPosition_p); yaw -= getObjectFrameK_p().theta(); if (yaw != 0.0f) { t.yaw_l(yaw); t.reorthonormalize(); } LOG("TeleportFixup", ("TeleportFixup from (%g,%g,%g) in world to (%g,%g,%g) in %s\n", getPosition_w().x, getPosition_w().y, getPosition_w().z, t.getPosition_p().x, t.getPosition_p().y, t.getPosition_p().z, container->getNetworkId().getValueString().c_str())); safe_cast(getController())->teleport(t, container); } else if (destContainer != NetworkId::cms_invalid && !force) { LOG("TeleportFixup", ("Failing teleport fixup because object %s is in a nullptr dest container and force was passed in\n", getNetworkId().getValueString().c_str())); return false; // going to a container and it wasn't loaded, defer } } else LOG("TeleportFixup", ("Ignoring teleport fixup because object %s is already contained by %s\n", getNetworkId().getValueString().c_str(), ContainerInterface::getContainedByObject(*this)->getNetworkId().getValueString().c_str())); } // Teleporting may cause us to become nonauthoritative here, and if so we should not clear the fixup, so it is // reapplied on the new auth server. if (!isAuthoritative()) return true; std::string scriptCallback; if (getObjVars().getItem("teleportFixup.callback", scriptCallback)) { DEBUG_REPORT_LOG(true, ("Player warp making callback %s\n", scriptCallback.c_str())); MessageToQueue::getInstance().sendMessageToJava(getNetworkId(), scriptCallback, std::vector(), 0, true); } LOG("TeleportFixup", ("Removing teleport fixup objvars for object %s\n", getNetworkId().getValueString().c_str())); removeObjVarItem("teleportFixup"); } return true; } // ---------------------------------------------------------------------- /** * Sets customization data for this object. * * @param customName name of the customization parameter * @param value value to set the customization to */ void ServerObject::customize(const std::string & customName, int value) { if(isAuthoritative()) { CustomizationDataProperty *const cdProperty = safe_cast(getProperty(CustomizationDataProperty::getClassPropertyId())); if (cdProperty != nullptr) { CustomizationData *const customizationData = cdProperty->fetchCustomizationData(); if (customizationData != nullptr) { RangedIntCustomizationVariable * variable = dynamic_cast< RangedIntCustomizationVariable*>(customizationData->findVariable( customName)); if (variable != nullptr) variable->setValue(value); else { DEBUG_WARNING(true, ("ServerObject::customize() tried to access var [%s] as ranged int, but not right type.", customName.c_str())); } //-- release local references customizationData->release(); } else { // this shouldn't happen DEBUG_WARNING(true, ("CustomizationDataProperty returned nullptr CustomizationData on fetch.")); } } else { // this shouldn't happen DEBUG_WARNING(true, ("ServerObject::customize() object does not have customization property")); } } else { sendControllerMessageToAuthServer(CM_customize, new MessageQueueGenericValueType >(std::make_pair(customName, value))); } } // ServerObject::customize // ---------------------------------------------------------------------- bool ServerObject::isInWorldCell() const { return isInWorld() && (ContainerInterface::getTopmostContainer(*this) == this); } // ---------------------------------------------------------------------- bool ServerObject::canDestroy() const { if (isPlayerControlled()) return false; const Container * container = ContainerInterface::getContainer(*this); if (container) { //Check contents to make sure they can be destroyed. ContainerConstIterator i = container->begin(); for (; i != container->end(); ++i) { const ServerObject* child = safe_cast((*i).getObject()); if (child && !child->canDestroy()) { DEBUG_REPORT_LOG(true, ("Tried to delete object %s but could not delete child %s\n", getNetworkId().getValueString().c_str(), child->getNetworkId().getValueString().c_str())); return false; } } } return true; } // ---------------------------------------------------------------------- bool ServerObject::permanentlyDestroy(DeleteReasons::Enumerator reason) { // prevent recursion if (isBeingDestroyed()) return true; if (!isAuthoritative()) { sendControllerMessageToAuthServer(CM_permanentlyDestroy, new MessageQueueGenericValueType(reason)); return false; } if (!canDestroy()) { return false; } // can't destroy an initialized vendor unless the commodities server is up because // there is commodities server data that *MUST* be removed when the vendor is // destroyed, or else the commodities server will think the player has more // vendors than he actually has if ( isVendor() && getObjVars().hasItem("vendor_initialized") && !CommoditiesMarket::isCommoditiesServerAvailable() ) { return false; } BuildingObject* buildingObject = asBuildingObject(); const bool houseDestroyedByScript = (reason == DeleteReasons::Script && buildingObject && buildingObject->isPlayerPlaced()); // tell scripts we want to destroy the object if (getScriptObject() != nullptr && !m_calledTriggerDestroy) { m_calledTriggerDestroy = true; ScriptParams params; if (getScriptObject()->trigAllScripts(Scripting::TRIG_DESTROY, params) == SCRIPT_OVERRIDE) { if(reason != DeleteReasons::God) { m_calledTriggerDestroy = false; return false; } } } if (isAuthoritative() && (getScriptObject() != nullptr) && !m_calledTriggerRemovingFromWorld) { ScriptParams params; m_calledTriggerRemovingFromWorld = true; IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_REMOVING_FROM_WORLD, params)); } setBeingDestroyed(true); if (isInBazaarOrVendor()) { CommoditiesMarket::auctionCancel(getOwnerId(), getNetworkId().getValue()); } // need to remove all the items from the Commodities Market // TODO: how to remove an item that might get deleted from a vendor's inventory without the vendor being deleted? (GM deleting and item for instance) // TODO PROBLEM: contents of vendors aren't guaranteed to be loaded if ( isVendor() ) { ServerObject *inv = getBazaarContainer(); NetworkId playerOwnerId; getObjVars().getItem("vendor_owner", playerOwnerId); if( inv && playerOwnerId.getValue()>0 ) { // if this was an uninitialized vendor, remove objvar from owner ServerObject *owner = dynamic_cast(NetworkIdManager::getObjectById(playerOwnerId)); if (owner) { NetworkId notInitalizedVendor; if (owner->getObjVars().getItem("vendor_not_initialized", notInitalizedVendor)) { if (notInitalizedVendor == Object::getNetworkId()) { owner->removeObjVarItem("vendor_not_initialized"); } } } CommoditiesMarket::destroyVendorMarket(playerOwnerId, *inv); } } if (isPersisted()) { sendDeleteToDatabase(*this, houseDestroyedByScript ? DeleteReasons::House : reason); ServerObject const * const oldContainer = safe_cast(ContainerInterface::getContainedByObject(*this)); LOG("CustomerService",("Deletion: %s in container: %s deleted from game. Reason: %s", ServerObject::getLogDescription(this).c_str(), oldContainer ? ServerObject::getLogDescription(oldContainer).c_str() : "none", describeDeleteReason(static_cast(reason)).c_str())); int childReason = DeleteReasons::ContainerDeleted; if (houseDestroyedByScript) childReason = DeleteReasons::House; flagChildObjectsForDeletion(*this, childReason); } // grab the proxy list prior to removing it from the container, because it's about to lose its proxy list ProxyList const &proxyList = getExposedProxyList(); if (!proxyList.empty()) { ServerMessageForwarding::begin(std::vector(proxyList.begin(), proxyList.end())); UnloadObjectMessage const unloadMessage(getNetworkId()); ServerMessageForwarding::send(unloadMessage); ServerMessageForwarding::end(); } onPermanentlyDestroyed(); unload(); //-- Ensure state is set to initialized (i.e. have received end baselines or // created on this server). This is required so that the AlterScheduler // can accept the Object. If changing initialization state, warn about it here. if (!isInitialized()) { DEBUG_WARNING(true, ("ServerObject::permanentlyDestroy(): object is not initialized, setting to initialized to allow submission to alter scheduler for delete. id=[%s],template=[%s].", getNetworkId().getValueString().c_str(), getObjectTemplateName())); setInitialized(true); } //-- Ensure this object gets altered next frame so that kill() can take effect. AlterScheduler::submitForAlter(*this); return true; } // ---------------------------------------------------------------------- /** * Allows an object to do pre-deletion cleanup. */ void ServerObject::onRemovingFromWorld() { // If we're being removed from the world, we may need to remove ourselves from the parent container list. if (isAuthoritative() && isPlayerControlled() && isUnloading()) { Object * const parent = ContainerInterface::getContainedByObject(*this); if (parent) { Container * const container = parent->getCellProperty(); if (container) { container->internalItemRemoved(*this); } } } if (m_networkUpdateFar) onRemovedFromWorldCell(); destroyTriggerVolumes(); } // ---------------------------------------------------------------------- /* Why would I unload something? * - It might be a player character logging out * - Or the planet server might be telling me to unload an object */ // ---------------------------------------------------------------------- void ServerObject::unload() { scheduleForAlter(); addObjectToConcludeList(); // prevent recursion if (isUnloading()) { kill(); return; } setUnloading(true); DEBUG_REPORT_LOG(ConfigServerGame::getLogObjectLoading(), ("Unloading object (%s : %s)\n", getObjectTemplateName(), getNetworkId().getValueString().c_str())); // If the object has a client, uncontrol it. When the primary controlled // object is unloaded, this prevents us from sending destroys to ourself, if (getClient()) getClient()->removeControlledObject(*this); synchronizationOnUnload(); if (isAuthoritative()) { ScriptParams params; IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_UNLOADED_FROM_MEMORY, params)); } kill(); // if this object is in a slot in a slotted container, immediately remove // it from the slot, because another object may be immediately put into // the slot before this object is eventually destroyed and removed from the container Object * const containedBy = ContainerInterface::getContainedByObject(*this); if (containedBy) { Container * const container = ContainerInterface::getContainer(*containedBy); SlottedContainer * const slotContainer = dynamic_cast(container); if (slotContainer) slotContainer->removeItemFromSlotOnly(*this); } } // ---------------------------------------------------------------------- /** * Unload the object, and tell the database to move it to a specified player */ void ServerObject::moveToPlayerAndUnload(const NetworkId &player) { GenericValueTypeMessage > const moveToPlayerMessage( "MoveToPlayer", std::make_pair(getNetworkId(), player)); GameServer::getInstance().sendToDatabaseServer(moveToPlayerMessage); unload(); } // ---------------------------------------------------------------------- /** * Unload the object, and tell the database to move it to a specified player's bank */ void ServerObject::moveToPlayerBankAndUnload(const NetworkId &player) { GenericValueTypeMessage > const moveToPlayerMessage( "MoveToPlayerBankMessage", std::make_pair(getNetworkId(), player)); GameServer::getInstance().sendToDatabaseServer(moveToPlayerMessage); unload(); } // ---------------------------------------------------------------------- /** * Unload the object, and tell the database to move it to a specified player's bank */ void ServerObject::moveToPlayerDatapadAndUnload(const NetworkId &player, int maxDepth) { GenericValueTypeMessage, int> > const moveToPlayerMessage( "MoveToPlayerDatapadMessage", std::make_pair(std::make_pair(getNetworkId(), player), maxDepth)); GameServer::getInstance().sendToDatabaseServer(moveToPlayerMessage); unload(); } // ---------------------------------------------------------------------- void ServerObject::onPermanentlyDestroyed() { IGNORE_RETURN(ContainerInterface::onObjectDestroy(*this)); } // ------------------------------------------------------------------------------------------ /** * Request to load a specific object contained by this object. * (This object must be a demand-load container, i.e. m_loadContents==false.) */ void ServerObject::loadContainedObjectFromDB(const NetworkId &oid) const { LoadContainedObjectMessage const msg(getNetworkId(), oid); GameServer::getInstance().sendToDatabaseServer(msg); } // ---------------------------------------------------------------------- bool ServerObject::areContentsLoaded() const { return m_contentsLoaded.get(); } // ---------------------------------------------------------------------- void ServerObject::loadAllContentsFromDB() const { if (m_contentsLoaded.get() || m_contentsRequested.get()) { DEBUG_REPORT_LOG(true,("Ignoring LoadAllContentsFromDB for object %s because they are already loaded\n",getNetworkId().getValueString().c_str())); return; } DEBUG_REPORT_LOG(true,("LoadAllContentsFromDB, object %s\n",getNetworkId().getValueString().c_str())); if (m_loadContents.get()) WARNING_STRICT_FATAL(true,("Requested loading contents of container %s, but the container is not a demand-load container.",getNetworkId().getValueString().c_str())); else { m_contentsRequested = true; LoadContentsMessage const msg(getNetworkId()); GameServer::getInstance().sendToDatabaseServer(msg); } } //---------------------------------------------------------------------- void ServerObject::setLoadContents(bool loadContents) { if (isAuthoritative()) { m_loadContents = loadContents; } else { // this function should probably never be called on a proxy object WARNING_STRICT_FATAL(true,("Setting loadContent across game servers is not allowed.")); } } //---------------------------------------------------------------------- void ServerObject::unloadPersistedContents() { if (!areContentsLoaded()) { return; } Container *container = ContainerInterface::getContainer(*this); if (container) { for (ContainerIterator j = container->begin(); j != container->end(); ++j) { ServerObject * obj = safe_cast((*j).getObject()); if (obj && obj->isPersisted()) { obj->unload(); } } m_contentsLoaded = false; m_contentsRequested = false; } } //---------------------------------------------------------------------- const Unicode::String ServerObject::getAssignedObjectFirstName () const { const Unicode::String & name = m_objectName.get (); // split off the last name from toName std::string::size_type spacePos = name.find(' '); if (spacePos != std::string::npos) return name.substr(0, spacePos); return name; } //---------------------------------------------------------------------- /** * This function is used to obtain a client-displayable name regardless of whether * the object has an assigned name or string id. */ const Unicode::String ServerObject::getEncodedObjectName () const { static const Unicode::String at (1, '@'); if (!m_objectName.get ().empty ()) return m_objectName.get (); else return at + Unicode::narrowToWide (m_nameStringId.get().getCanonicalRepresentation ()); } // ---------------------------------------------------------------------- /** * Do a money transfer and send the reply to a script * @param target Target of the money transfer * @param replyTo Object that wants the reply message (this object does not need to be involved in the transaction) * @param successCallback Script method on the object to call on success. * @param failCallback Script method on the object to call on failure. * @param packedDictionary Dictionary to be passed to the script (packed into a string) */ void ServerObject::scriptTransferCashTo(const NetworkId &target, int amount, const NetworkId &replyTo, const std::string &successCallback, const std::string &failCallback, const std::vector &packedDictionary) { if (isAuthoritative()) { bool result = transferCashTo(target, amount); if (result) { if (!successCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,successCallback,packedDictionary,0,true); } else { if (!failCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,failCallback,packedDictionary,0,true); } } else { MessageQueueScriptTransferMoney *msg = new MessageQueueScriptTransferMoney(MessageQueueScriptTransferMoney::TT_cashTransfer, target, "", amount, replyTo, successCallback, failCallback, packedDictionary); Controller *const controller = getController(); NOT_NULL(controller); controller->appendMessage(CM_scriptTransferMoney,0,msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } } // ---------------------------------------------------------------------- /** * Do a money transfer and send the reply to a script * @param target Target of the money transfer * @param replyTo Object that wants the reply message (this object does not need to be involved in the transaction) * @param successCallback Script method on the object to call on success. * @param failCallback Script method on the object to call on failure. * @param packedDictionary Dictionary to be passed to the script (packed into a string) */ void ServerObject::scriptTransferBankCreditsTo(const NetworkId &target, int amount, const NetworkId &replyTo, const std::string &successCallback, const std::string &failCallback, const std::vector &packedDictionary) { if (isAuthoritative()) { bool result = transferBankCreditsTo(target, amount); if (result) { if (!successCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,successCallback,packedDictionary,0,true); } else { if (!failCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,failCallback,packedDictionary,0,true); } } else { MessageQueueScriptTransferMoney *msg = new MessageQueueScriptTransferMoney(MessageQueueScriptTransferMoney::TT_bankTransfer, target, "", amount, replyTo, successCallback, failCallback, packedDictionary); Controller *const controller = getController(); NOT_NULL(controller); controller->appendMessage(CM_scriptTransferMoney,0,msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } } // ---------------------------------------------------------------------- void ServerObject::scriptWithdrawCashFromBank(int amount, const NetworkId &replyTo, const std::string &successCallback, const std::string &failCallback, const std::vector &packedDictionary) { if (isAuthoritative()) { bool result = withdrawCashFromBank(amount); if (result) { if (!successCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,successCallback,packedDictionary,0,true); } else { if (!failCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,failCallback,packedDictionary,0,true); } } else { MessageQueueScriptTransferMoney *msg = new MessageQueueScriptTransferMoney(MessageQueueScriptTransferMoney::TT_bankWithdrawal, NetworkId::cms_invalid, "", amount, replyTo, successCallback, failCallback, packedDictionary); Controller *const controller = getController(); NOT_NULL(controller); controller->appendMessage(CM_scriptTransferMoney,0,msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } } // ---------------------------------------------------------------------- void ServerObject::scriptDepositCashToBank(int amount, const NetworkId &replyTo, const std::string &successCallback, const std::string &failCallback, const std::vector &packedDictionary) { if (isAuthoritative()) { bool result = depositCashToBank(amount); if (result) { if (!successCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,successCallback,packedDictionary,0,true); } else { if (!failCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,failCallback,packedDictionary,0,true); } } else { MessageQueueScriptTransferMoney *msg = new MessageQueueScriptTransferMoney(MessageQueueScriptTransferMoney::TT_bankDeposit, NetworkId::cms_invalid, "", amount, replyTo, successCallback, failCallback, packedDictionary); Controller *const controller = getController(); NOT_NULL(controller); controller->appendMessage(CM_scriptTransferMoney,0,msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } } // ---------------------------------------------------------------------- /** * Do a money transfer and send the reply to a script * @param target Target of the money transfer * @param replyTo Object that wants the reply message (this object does not need to be involved in the transaction) * @param successCallback Script method on the object to call on success. * @param failCallback Script method on the object to call on failure. * @param packedDictionary Dictionary to be passed to the script (packed into a string) */ void ServerObject::scriptTransferBankCreditsTo(const std::string &target, int amount, const NetworkId &replyTo, const std::string &successCallback, const std::string &failCallback, const std::vector &packedDictionary) { if (isAuthoritative()) { bool result = transferBankCreditsTo(target, amount); if (result) { if (!successCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,successCallback,packedDictionary,0,true); } else { if (!failCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,failCallback,packedDictionary,0,true); } } else { MessageQueueScriptTransferMoney *msg = new MessageQueueScriptTransferMoney(MessageQueueScriptTransferMoney::TT_bankTransferToNamedAccount, NetworkId::cms_invalid, target, amount, replyTo, successCallback, failCallback, packedDictionary); Controller *const controller = getController(); NOT_NULL(controller); controller->appendMessage(CM_scriptTransferMoney,0,msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } } // ---------------------------------------------------------------------- void ServerObject::scriptTransferBankCreditsFrom(const std::string &target, int amount, const NetworkId &replyTo, const std::string &successCallback, const std::string &failCallback, const std::vector &packedDictionary) { if (isAuthoritative()) { bool result = transferBankCreditsFrom(target, amount); if (result) { if (!successCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,successCallback,packedDictionary,0,true); } else { if (!failCallback.empty()) MessageToQueue::getInstance().sendMessageToJava(replyTo,failCallback,packedDictionary,0,true); } } else { MessageQueueScriptTransferMoney *msg = new MessageQueueScriptTransferMoney(MessageQueueScriptTransferMoney::TT_bankTransferFromNamedAccount, NetworkId::cms_invalid, target, amount, replyTo, successCallback, failCallback, packedDictionary); Controller *const controller = getController(); NOT_NULL(controller); controller->appendMessage(CM_scriptTransferMoney,0,msg, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_AUTH_SERVER); } } //---------------------------------------------------------------------- void ServerObject::getAttributes(const NetworkId & playerId, AttributeVector &data) const { getAttributes (data); if (!isAuthoritative()) { DEBUG_WARNING (true, ("ServerObject::getAttributes invoked on non-auth object %s", getNetworkId ().getValueString ().c_str ())); return; } GameScriptObject * const gso = const_cast(getScriptObject()); if (gso->hasTrigger (Scripting::TRIG_GET_ATTRIBS)) { const CreatureObject * player = safe_cast(ServerWorld::findObjectByNetworkId(playerId)); int MAX_ATTRIBS = 80; if(player) { if(player->getClient()) { if(player->getClient()->isGod()) { MAX_ATTRIBS = 2000; } } } // allow scripts to add attribute data to the vector static std::vector names (MAX_ATTRIBS, static_cast(0)); static std::vector attribs (MAX_ATTRIBS, static_cast(0)); //-- clear the vectors names.clear (); attribs.clear (); names.resize (MAX_ATTRIBS, static_cast(0)); attribs.resize (MAX_ATTRIBS, static_cast(0)); ScriptParams params; params.addParam (playerId); params.addParam (names); params.addParam (attribs); NOT_NULL (gso); IGNORE_RETURN(gso->trigAllScripts(Scripting::TRIG_GET_ATTRIBS, params)); const std::vector & newNames = params.getStringArrayParam (1); const std::vector & newAttribs = params.getUnicodeArrayParam (2); // copy the returned strings to data for (int i = 0; i < MAX_ATTRIBS; ++i) { const char * const newName = newNames [i]; const Unicode::String * const newAttrib = newAttribs [i]; if (newName && *newName && newAttrib && !newAttrib->empty ()) { data.push_back (std::make_pair (std::string (newName), *newAttrib)); } else break; } // if the requestor is a gm, add objvars and scriptvars // to result data (ignore max attribs as well!) } } // ---------------------------------------------------------------------- void ServerObject::getAttributes(AttributeVector &) const { } // ---------------------------------------------------------------------- /** * Return all the standard attributes for an item, plus special * auction-specific attributes. * * May eventually make this into a virtual function if we add more to it. */ void ServerObject::getAttributesForAuction(AttributeVector &data) const { static const std::string attributeClientSharedTemplateName("ClientSharedTemplateName"); DEBUG_FATAL(strlen(getClientSharedTemplateName()) > 1000,("Client shared template name was more than 1000 characters long, too long to save.")); getAttributes(NetworkId::cms_invalid,data); data.push_back(std::make_pair(attributeClientSharedTemplateName, Unicode::narrowToWide(getClientSharedTemplateName()))); } // ---------------------------------------------------------------------- bool ServerObject::alwaysSendReliableTransform() const { return true; } // ---------------------------------------------------------------------- /** * Called when a request to load a contained object has been completed. * * Two Important Notes: * 1) This is called in response to loadContainedObjectFromDB. It is not * called in response to loadAllContentsFromDB * 2) There is no guarantee the requested object exists. If the object * id was wrong or the object wasn't in the container, it will not * have been loaded. */ void ServerObject::onContainedObjectLoaded(NetworkId const &oid) { CommoditiesMarket::checkPendingLoads(oid); } // ---------------------------------------------------------------------- bool ServerObject::isBazaarTerminal() const { SharedObjectTemplate::GameObjectType got = static_cast (getGameObjectType()); return (got == SharedObjectTemplate::GOT_terminal_bazaar); } // ---------------------------------------------------------------------- bool ServerObject::isVendor() const { const TangibleObject *tangibleObject = asTangibleObject(); if (tangibleObject) { return tangibleObject->hasCondition(ServerTangibleObjectTemplate::C_vendor); } return false; } // ---------------------------------------------------------------------- void ServerObject::makeVendor() { CreatureObject *creatureObject = asCreatureObject(); if (creatureObject) { const char* s_inventoryTemplate = "object/tangible/inventory/vendor_inventory.iff"; std::set inventoryObservers; ServerObject *inventory = creatureObject->getInventory(); if (inventory) { // if there's already a vendor inventory on the creature, // then it means this creature is already a vendor; we // don't want to destroy that vendor inventory and create // another vendor inventory, because the vendor inventory // is already registered with the commodities server and // may have items in it, and deleting the vendor inventory // would cause those items to be orphaned; // doing what we are doing here will cause the existing // vendor inventory to be registered with the commodities // server again, but it won't hurt, and it covers the // recovery case where if for some reason the first // registration failed, we will try the registration again if(strcmp(inventory->getTemplateName(), s_inventoryTemplate) == 0) { LOG("CustomerService", ("vendor: Calling makeVendor() for vendor (%s) who already has a vendor inventory object (%s)", getDebugInformation().c_str(), inventory->getDebugInformation().c_str())); return; } // make a copy of the current inventory observers inventoryObservers = inventory->getObservers(); // destroy the existing inventory LOG("CustomerService", ("vendor: makeVendor() for vendor (%s), destroying existing inventory object (%s)", getDebugInformation().c_str(), inventory->getDebugInformation().c_str())); inventory->permanentlyDestroy(DeleteReasons::Replaced); } const SlotId invenSlot = SlotIdManager::findSlotId(ConstCharCrcLowerString("inventory")); ServerObject *vendorInventory = ServerWorld::createNewObject(std::string(s_inventoryTemplate), *this, invenSlot, true); if (vendorInventory) { LOG("CustomerService", ("vendor: makeVendor() for vendor (%s), created vendor inventory object (%s)", getDebugInformation().c_str(), vendorInventory->getDebugInformation().c_str())); vendorInventory->setLoadContents(false); // make sure that the new inventory object has // the same observers as the old inventory object ObserveTracker::onMakeVendorInventory(*vendorInventory, inventoryObservers); } else { LOG("CustomerService", ("vendor: makeVendor() for vendor (%s), failed to create vendor inventory object", getDebugInformation().c_str())); } } else { setLoadContents(false); } } // ---------------------------------------------------------------------- ServerObject *ServerObject::getBazaarContainer() { CreatureObject *creatureObject = asCreatureObject(); if (creatureObject) { return creatureObject->getInventory(); } else { return this; } } // ---------------------------------------------------------------------- const ServerObject *ServerObject::getBazaarContainer() const { const CreatureObject *creatureObject = asCreatureObject(); if (creatureObject) { return creatureObject->getInventory(); } else { return this; } } // ---------------------------------------------------------------------- /** * Called when a request to load a contained object has been completed. * * Two Important Notes: * 1) This is called in response to loadAllContentsFromDB. It is not * called in response to loadContainedObjectFromDB * 2) There is no guarantee the container now contains any objects. * If there were no contained objects in the database, the container * will be empty. */ void ServerObject::onAllContentsLoaded() { GameServer::removePendingLoadRequest(getNetworkId()); m_contentsLoaded=true; if (getObjVars().hasItem(CommoditiesMarket::OBJVAR_VENDOR_REINITIALIZING)) { //vendor is re-initializing setLoadContents( false ); CommoditiesMarket::reinitializeVendorContents(*this); } else if (getObjVars().hasItem(OBJVAR_STRUCTURE_PLAYER_PACK)) { //Get player initiating load NetworkId playerId; if (getObjVars().getItem(OBJVAR_STRUCTURE_PLAYER_PACK, playerId)) { bool failed = true; if (isAuthoritative()) { ServerObject *player = nullptr; if ((player = getServerObject(playerId)) != nullptr) { //Tell scripts we are loaded ScriptParams params; params.addParam( getNetworkId() ); IGNORE_RETURN(player->getScriptObject()->trigAllScripts(Scripting::TRIG_BUILDING_CONTENTS_LOADED, params)); failed = false; } } if (failed) { // fail pack-up as authority lost on player - notify script MessageToQueue::getInstance().sendMessageToJava(playerId, "handleFailedStructurePackup", std::vector(), 0, false); } } //remove the vendor reinitialization flag if it exists removeObjVarItem(OBJVAR_STRUCTURE_PLAYER_PACK); } else { ServerObject const * const containedBy = safe_cast(ContainerInterface::getContainedByObject(*this)); if (containedBy) { Client * const client = containedBy->getClient(); if (client) { CreatureObject const * const containedByCreature = containedBy->asCreatureObject(); if (containedByCreature && containedByCreature->getBankContainer() == this) { if (containedBy->getLoadCTSBank()) { LOG("CustomerService", ("CharacterTransfer: serverObject has LoadCTSBank flag set, sending ReplyBankCTSLoaded message")); GenericValueTypeMessage setFromCTSLoadBank("ReplyBankCTSLoaded", containedBy->getNetworkId()); client->sendToConnectionServer(setFromCTSLoadBank); } else client->openContainer(*this, 0, std::string()); } } } } } // ---------------------------------------------------------------------- bool ServerObject::immediateLogoutAllowed() { return true; } // ---------------------------------------------------------------------- void ServerObject::disconnect() { if (!isAuthoritative()) sendControllerMessageToAuthServer(CM_disconnect, 0); else if (getClient()) GameServer::getInstance().dropClient(getNetworkId()); } // ---------------------------------------------------------------------- void ServerObject::handleDisconnect(bool immediate) { if (isPlayerControlled()) { // client has disconnected, log pertinent information about the play session CreatureObject * const creatureObject = asCreatureObject(); PlayerObject * playerObject = nullptr; if (creatureObject) { playerObject = PlayerCreatureController::getPlayerObject(creatureObject); if (playerObject && (playerObject->getSessionStartPlayTime() != 0)) { char buffer[128]; std::string logText; // leading delimiter logText += "|"; // character name logText += Unicode::wideToNarrow(getObjectName()); logText += "|"; // character id logText += getNetworkId().getValueString(); logText += "|"; // character birth date // get the base date of Jan 1st, 2001 struct tm baseTimeData; baseTimeData.tm_hour = 0; baseTimeData.tm_isdst = 0; baseTimeData.tm_mday = 1; baseTimeData.tm_min = 0; baseTimeData.tm_mon = 0; baseTimeData.tm_sec = 0; baseTimeData.tm_wday = 0; baseTimeData.tm_yday = 0; baseTimeData.tm_year = 101; time_t baseTime = mktime(&baseTimeData); // the birth date is the number of days since Jan 1st, 2001 baseTime += static_cast(playerObject->getBornDate() * 60 * 60 * 24); struct tm * birthDateTimeData = gmtime(&baseTime); snprintf(buffer, sizeof(buffer)-1, "%d%02d%02d", (birthDateTimeData->tm_year + 1900), (birthDateTimeData->tm_mon + 1), birthDateTimeData->tm_mday); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // station id snprintf(buffer, sizeof(buffer)-1, "%lu", playerObject->getStationId()); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // session play time logText += CalendarTime::convertSecondsToHMS(static_cast(playerObject->getSessionPlayTimeDuration())); logText += "|"; // session active play time logText += CalendarTime::convertSecondsToHMS(static_cast(playerObject->getSessionActivePlayTimeDuration())); logText += "|"; // session activity snprintf(buffer, sizeof(buffer)-1, "%lu", playerObject->getSessionActivity()); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // scene name logText += ServerWorld::getSceneId(); logText += "|"; // container id (0 if in world cell) Object const * const container = ContainerInterface::getContainedByObject(*this); snprintf(buffer, sizeof(buffer)-1, "%s", (container ? container->getNetworkId().getValueString().c_str() : "0")); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // x,y,z in container Vector const posContainer = getPosition_p(); snprintf(buffer, sizeof(buffer)-1, "%d %d %d", static_cast(posContainer.x), static_cast(posContainer.y), static_cast(posContainer.z)); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // x,y,z in world Vector const posWorld = getPosition_w(); snprintf(buffer, sizeof(buffer)-1, "%d %d %d", static_cast(posWorld.x), static_cast(posWorld.y), static_cast(posWorld.z)); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // profession logText += playerObject->getSkillTemplate(); logText += "|"; // level snprintf(buffer, sizeof(buffer)-1, "%d", creatureObject->getLevel()); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // current quest id snprintf(buffer, sizeof(buffer)-1, "%lu", playerObject->getCurrentQuest()); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; logText += "|"; // current quest name if (playerObject->getCurrentQuest() != 0) { logText += QuestManager::getQuestName(playerObject->getCurrentQuest()); } else { logText += "none"; } logText += "|"; // xp bool hasLoggedFirstXp = false; std::map const & xp = playerObject->getExperiencePoints(); for (std::map::const_iterator iter = xp.begin(); iter != xp.end(); ++iter) { if (iter->second == 0) continue; if (hasLoggedFirstXp) logText += ","; logText += iter->first; logText += "="; snprintf(buffer, sizeof(buffer)-1, "%d", iter->second); buffer[sizeof(buffer)-1] = '\0'; logText += buffer; hasLoggedFirstXp = true; } logText += "|"; // log it LOG("CustomerService", ("Logout:%s", logText.c_str())); // clear the session play time info playerObject->setSessionPlayTimeInfo(0, 0, 0); // clear the session activity info playerObject->clearSessionActivity(); } } if (!immediate && !immediateLogoutAllowed()) LogoutTracker::add(getNetworkId()); else { virtualOnLogout(); if (ConfigServerGame::getLogoutTriggerEnabled()) { ScriptParams params; getScriptObject()->trigAllScripts(Scripting::TRIG_LOGOUT, params); } Client * const client = getClient(); if (client) { LogoutMessage const logoutMessage; client->send(logoutMessage, true); } PositionUpdateTracker::flushPositionUpdate(*this); LogoutTracker::addPendingSave(this); // get rid of the proxies clearProxyList(); // if we're inside a container, it's the topmost parent that contains our proxy // list, so the above clearProxyList() call will not get rid of the proxies synchronizationOnUnload(); // don't allow anyone to continue observing this object (and associated PlayerObject) if (playerObject) ObserveTracker::onObjectDestroyed(*playerObject, false); ObserveTracker::onObjectDestroyed(*this, false); // remove from world and/or container as necessary but keep containment information available if (isInWorld()) removeFromWorld(); else WARNING(getTriggerVolumeEntered() && !getTriggerVolumeEntered()->empty(), ("ServerObject::handleDisconnect called for (%s) which is not in the world but is contained in one or more trigger volumes", getDebugInformation().c_str())); // the object should have been removed from all TriggerVolumes // (from removeFromWorld() above) by now, but as a safety measure, // we'll do a final check and remove the object from any // TriggerVolumes that it is still in if (getTriggerVolumeEntered()) { std::set const triggerVolumeEntered = *(getTriggerVolumeEntered()); for (std::set::const_iterator i = triggerVolumeEntered.begin(); i != triggerVolumeEntered.end(); ++i) { WARNING(true, ("Object (%s) added to LogoutTracker but hasn't been removed from TriggerVolume (%s) owned by (%s)", getDebugInformation().c_str(), (*i)->getName().c_str(), (*i)->getOwner().getDebugInformation().c_str())); (*i)->removeObject(*this); } } Object *containerObject = ContainerInterface::getContainedByObject(*this); if (containerObject) { NON_NULL(ContainerInterface::getContainer(*containerObject))->internalItemRemoved(*this); // if the character was in a building, tell the building it has lost // the player so the building demand load/unload system can work correctly Object *buildingObj = ContainerInterface::getTopmostContainer(*containerObject); if (buildingObj && buildingObj->isAuthoritative() && buildingObj->asServerObject() && buildingObj->asServerObject()->asBuildingObject()) buildingObj->asServerObject()->asBuildingObject()->lostPlayer(*this); } // remove all sui pages for character ServerUIManager::onPlayerLogout(getNetworkId()); } } } // ---------------------------------------------------------------------- void ServerObject::setAttributeAttained(int attribute) { BitArray tmp(m_attributesAttained.get()); tmp.setBit(attribute); m_attributesAttained = tmp; } // ---------------------------------------------------------------------- void ServerObject::clearAttributeAttained(int attribute) { BitArray tmp(m_attributesAttained.get()); tmp.clearBit(attribute); m_attributesAttained = tmp; } // ---------------------------------------------------------------------- bool ServerObject::hasAttributeAttained(int attribute) const { return m_attributesAttained.get().testBit(attribute); } // ---------------------------------------------------------------------- void ServerObject::setAttributeInterested(int attribute) { BitArray tmp(m_attributesInterested.get()); tmp.setBit(attribute); m_attributesInterested = tmp; } // ---------------------------------------------------------------------- void ServerObject::clearAttributeInterested(int attribute) { BitArray tmp(m_attributesInterested.get()); tmp.clearBit(attribute); m_attributesInterested = tmp; } // ---------------------------------------------------------------------- bool ServerObject::hasAttributeInterest(int attribute) const { return m_attributesInterested.get().testBit(attribute); } // ---------------------------------------------------------------------- bool ServerObject::isInterested(const ServerObject &rhs) const { return m_attributesInterested.get().matchAnyBit(rhs.m_attributesAttained.get()); } // ---------------------------------------------------------------------- std::string ServerObject::getItemLog() const { return getNetworkId().getValueString() + ' ' + Unicode::wideToNarrow(getObjectName()); } // ---------------------------------------------------------------------- void ServerObject::checkAndLogInvalidTransform() const { Transform const &transform_o2p = getTransform_o2p(); bool const isValid = transform_o2p.validate(true); if (!isValid) { Object const *const container = ContainerInterface::getContainedByObject(*this); Vector const position = transform_o2p.getPosition_p(); Vector const containerPosition = container ? container->getTransform_o2w().getPosition_p() : Vector::zero; WARNING(!isValid, ("transform is invalid for object id=[%s], object template name=[%s], server id=[%d], server scene id=[%s], position=[%.2f,%.2f,%.2f], container id=[%s], container position=[%.2f,%.2f,%.2f]", getNetworkId().getValueString().c_str(), getObjectTemplateName(), static_cast(GameServer::getInstance().getProcessId()), ServerWorld::getSceneId().c_str(), position.x, position.y, position.z, container ? container->getNetworkId().getValueString().c_str() : "", containerPosition.x, containerPosition.y, containerPosition.z )); } } // ---------------------------------------------------------------------- ServerObject * ServerObject::combineResourceContainers(ServerObject &item) { if (isVendor() || isBazaarTerminal() || isInBazaarOrVendor() ) { //Do not combine resources on vendors or bazaars, as they need //to remain separate to sell separately. return &item; } ServerObject * returnedItem = &item; typedef std::vector SimilarContainers; static SimilarContainers similarContainers; if (item.getObjectType() == ServerResourceContainerObjectTemplate::ServerResourceContainerObjectTemplate_tag) { ResourceContainerObject *resourceContainerObject = safe_cast(&item); if (resourceContainerObject != nullptr) { // If this is a container, see if it contains another resource container of the same type Container *container = ContainerInterface::getContainer(*this); if (container != nullptr) { ContainerIterator iterContainer = container->begin(); for (; iterContainer != container->end(); ++iterContainer) { Object *containedObject = NetworkIdManager::getObjectById(*iterContainer); // Make sure the contained item is not the item we are gaining and that // the item is a resource container if ((containedObject != &item) && (containedObject != nullptr) && (containedObject->getObjectType() == ServerResourceContainerObjectTemplate::ServerResourceContainerObjectTemplate_tag)) { ResourceContainerObject *containedResourceContainerObject = safe_cast(containedObject); if ((containedResourceContainerObject != nullptr) && (resourceContainerObject->getResourceType() == containedResourceContainerObject->getResourceType())) { // This is a container of similar type, if it is not full, save it to the list int const quantity = containedResourceContainerObject->getQuantity(); int const maxQuantity = containedResourceContainerObject->getMaxQuantity(); if (quantity != maxQuantity) { similarContainers.push_back(containedResourceContainerObject); } } } } } } // Combine the resources as much as possible bool done = false; int resourcesLeftToAdd = resourceContainerObject->getQuantity(); while (!done) { // Find the most full container SimilarContainers::iterator iterSimilarContainers = similarContainers.begin(); SimilarContainers::iterator fullestContainer = similarContainers.end(); for (; iterSimilarContainers != similarContainers.end(); ++iterSimilarContainers) { if (fullestContainer == similarContainers.end()) { fullestContainer = iterSimilarContainers; } else { int const currentQuantity = (*iterSimilarContainers)->getQuantity(); int const fullestQuantity = (*fullestContainer)->getQuantity(); if (currentQuantity > fullestQuantity) { fullestContainer = iterSimilarContainers; } } } // Add as much as we can to the fullest container if (fullestContainer != similarContainers.end()) { int const fullestQuantity = (*fullestContainer)->getQuantity(); int const fullestMaxQuantity = (*fullestContainer)->getMaxQuantity(); int const amountToAdd = std::min(resourcesLeftToAdd, fullestMaxQuantity - fullestQuantity); // Add the amount to the fullest container ResourceContainerObject *sourceObj = resourceContainerObject; ResourceContainerObject *destObj = *fullestContainer; if (sourceObj && destObj && (amountToAdd > 0)) { sourceObj->transferTo(*destObj, amountToAdd); if (sourceObj->isBeingDestroyed()) returnedItem = destObj; } resourcesLeftToAdd -= amountToAdd; // Remove this container from the list similarContainers.erase(fullestContainer); } else { // There are no more containers to combine to, so we are done done = true; } if (resourcesLeftToAdd <= 0) { // If there is no more resources left to combine, then we are done done = true; } } similarContainers.clear(); } return returnedItem; } // ---------------------------------------------------------------------- /** * Determines if this object is contained by another object. * * @param container the object that might contain this one * @param includeContents if true, return true if one of the container's contents contains us * * @return true if we are contained by the container (or its contents) */ bool ServerObject::isContainedBy(const ServerObject & container, bool includeContents) const { // we actually check what we are contained by instead of checking the // container's contents const Object * test = ContainerInterface::getContainedByObject(*this); if (test == &container) { // our container is the desired container return true; } else if (test != nullptr && test != this && includeContents) { // our container isn't the desired container, see if it is contained return safe_cast(test)->isContainedBy(container, true); } return false; } // ---------------------------------------------------------------------- void ServerObject::concludeScriptVars() { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::concludeScriptVars"); GameScriptObject::packAllScriptVarDeltas(); } // ---------------------------------------------------------------------- void ServerObject::scheduleForAlter() { // ServerObjects do not recurse into containers, so are always responsible for scheduling themselves directly. if (isInitialized()) { // DEBUG_REPORT_LOG(true, ("[aitest] Scheduling %s for alter\n", getNetworkId().getValueString().c_str())); AlterScheduler::submitForAlter(*this); } } // ---------------------------------------------------------------------- void ServerObject::onAddedToWorldCell() { createFarNetworkUpdateVolume(); virtualOnAddedToWorldCell(); updatePositionOnPlanetServer(true); } // ---------------------------------------------------------------------- void ServerObject::virtualOnAddedToWorldCell() { } //------------------------------------------------------------------------------------------ void ServerObject::onRemovedFromWorldCell() { virtualOnRemovedFromWorldCell(); } //------------------------------------------------------------------------------------------ void ServerObject::virtualOnRemovedFromWorldCell() { destroyFarNetworkUpdateVolume(); } // ---------------------------------------------------------------------- void ServerObject::virtualOnSetAuthority() { } // ---------------------------------------------------------------------- void ServerObject::virtualOnReleaseAuthority() { } // ---------------------------------------------------------------------- void ServerObject::virtualOnLogout() { } //------------------------------------------------------------------------------------------ bool ServerObject::wantSawAttackTriggers() const { return false; } // ---------------------------------------------------------------------- void ServerObject::setTeleportScriptCallback(std::string const &scriptCallback) { setObjVarItem("teleportFixup.callback", scriptCallback); } // ---------------------------------------------------------------------- void ServerObject::setInteriorTeleportDestination(NetworkId const &destContainerId, std::string const &destCellName, Vector const &destPosition_p) { LOG("TeleportFixup", ("Setting interior telerport destination for %s: into %f %f %f %s %s\n", getNetworkId().getValueString().c_str(), destPosition_p.x, destPosition_p.y, destPosition_p.z, destCellName.c_str(), destContainerId.getValueString().c_str())); removeObjVarItem("teleportFixup"); setObjVarItem("teleportFixup.x", destPosition_p.x); setObjVarItem("teleportFixup.y", destPosition_p.y); setObjVarItem("teleportFixup.z", destPosition_p.z); setObjVarItem("teleportFixup.container", destContainerId); if (!destCellName.empty()) setObjVarItem("teleportFixup.cellname", destCellName); } // ---------------------------------------------------------------------- Vector const &ServerObject::getTriggerPosition() const { return m_oldPosition; } // ---------------------------------------------------------------------- bool ServerObject::getTransformChanged() const { return getLocalFlag(LocalObjectFlags::ServerObject_TransformChanged); } // ---------------------------------------------------------------------- void ServerObject::setTransformChanged(bool c) { setLocalFlag(LocalObjectFlags::ServerObject_TransformChanged, c); if (c) { addObjectToConcludeList(); if (isAuthoritative() && isPersisted()) PositionUpdateTracker::positionChanged(*this); } } // ---------------------------------------------------------------------- bool ServerObject::onContainerChildAboutToLoseItem(ServerObject * destination, ServerObject& item, ServerObject* transferer) { if (transferer && !checkBankTransfer(this, item, transferer)) return false; Object * const parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) return parent->asServerObject()->onContainerChildAboutToLoseItem(destination, item, transferer); return true; } // ---------------------------------------------------------------------- bool ServerObject::onContainerChildAboutToGainItem(ServerObject& item, ServerObject* destination, ServerObject* transferer) { if (transferer && !checkBankTransfer(this, item, transferer)) return false; Object * const parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) return parent->asServerObject()->onContainerChildAboutToGainItem(item, destination, transferer); return true; } // ---------------------------------------------------------------------- void ServerObject::onContainerChildLostItem(ServerObject * destination, ServerObject& item, ServerObject* source, ServerObject* transferer) { Object * const parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) parent->asServerObject()->onContainerChildLostItem(destination, item, source, transferer); } // ---------------------------------------------------------------------- void ServerObject::onContainerChildGainItem(ServerObject& item, ServerObject* source, ServerObject* transferer) { Object * const parent = ContainerInterface::getContainedByObject(*this); if (parent && parent->asServerObject()) parent->asServerObject()->onContainerChildGainItem(item, source, transferer); } // ---------------------------------------------------------------------- void ServerObject::setLoadCTSBank(bool isForCTS) { m_loadCTSBank = isForCTS; } // ---------------------------------------------------------------------- void ServerObject::setLoadCTSPackedHouses(bool isForCTS) { m_loadCTSPackedHouses = isForCTS; } // ---------------------------------------------------------------------- bool ServerObject::getLoadCTSBank() const { return m_loadCTSBank; } // ---------------------------------------------------------------------- bool ServerObject::getLoadCTSPackedHouses() const { return m_loadCTSPackedHouses; } // ---------------------------------------------------------------------- void ServerObject::packedHouseLoaded() { // see if we have the objvar if (getLoadCTSPackedHouses()) { if (getObjVars().hasItem("cts.packed_house_count")) { int count = 0; if (getObjVars().getItem("cts.packed_house_count", count)) { --count; if (count <= 0) { LOG("CustomerService", ("CharacterTransfer: all houses loaded, responding to connection server.")); removeObjVarItem("cts.packed_house_count"); // tell the connection server. Client * client = getClient(); if (client && client->getConnection()) { GenericValueTypeMessage loadedMsg("PackedHousesLoaded", getNetworkId()); client->getConnection()->send(loadedMsg, true); } } else { LOG("CustomerService", ("CharacterTransfer: packed house is loaded, but we expect %d more.", count)); setObjVarItem("cts.packed_house_count", count); } } } } } // ---------------------------------------------------------------------- void ServerObject::setLoadWith(const NetworkId& id) { m_loadWith = id; } //----------------------------------------------------------------------- std::string ServerObject::getLogDescription(const NetworkId & id) { std::string const result = getLogDescription(safe_cast(NetworkIdManager::getObjectById(id))); if (result.empty()) return id.getValueString(); return result; } //----------------------------------------------------------------------- std::string ServerObject::getLogDescription(const ServerObject * source) { std::string desc = ""; if(source) { desc += "NonPlayerObject "; desc += Unicode::wideToNarrow(source->getObjectName()); desc += "("; desc += source->getNetworkId().getValueString(); desc += ") "; desc += source->getObjectTemplateName(); } return desc; } //---------------------------------------------------------------------- void ServerObject::retrieveStoredObjectAttributes (AttributeVector & av) const { typedef stdvector::fwd UnicodeStringVector; static UnicodeStringVector kv; static UnicodeStringVector vv; kv.clear (); vv.clear (); getObjVars ().getItem (objvar_item_attribute_keys, kv); getObjVars ().getItem (objvar_item_attribute_values, vv); if (kv.size () == vv.size ()) { const size_t size = vv.size (); av.reserve (av.size () + kv.size ()); for (size_t i = 0; i < size; ++i) { const std::string & key = Unicode::wideToNarrow (kv [i]); const Unicode::String & value = vv [i]; av.push_back (std::make_pair (key, value)); } } } //---------------------------------------------------------------------- void ServerObject::storeObjectAttributes (const ServerObject & prototype) { typedef stdvector::fwd UnicodeStringVector; static UnicodeStringVector kv; static UnicodeStringVector vv; static AttributeVector av; av.clear (); kv.clear (); vv.clear (); prototype.getAttributes (NetworkId::cms_invalid, av); kv.reserve (av.size()); vv.reserve (av.size()); for (AttributeVector::const_iterator it = av.begin (); it != av.end (); ++it) { const std::string & key = (*it).first; const Unicode::String & value = (*it).second; kv.push_back (Unicode::narrowToWide (key)); vv.push_back (value); } setObjVarItem (objvar_item_attribute_keys, kv); setObjVarItem (objvar_item_attribute_values, vv); } //---------------------------------------------------------------------- void ServerObject::clearStoredObjectAttributes () { m_objVars.removeItemByName (objvar_item_attribute_keys); m_objVars.removeItemByName (objvar_item_attribute_values); } //---------------------------------------------------------------------- void ServerObject::replaceStoredObjectAttribute(Unicode::String const & attribute, Unicode::String const & value) { typedef stdvector::fwd UnicodeStringVector; static UnicodeStringVector kv; static UnicodeStringVector vv; kv.clear (); vv.clear (); getObjVars ().getItem (objvar_item_attribute_keys, kv); getObjVars ().getItem (objvar_item_attribute_values, vv); if (kv.size () == vv.size ()) { const size_t size = vv.size (); for (size_t i = 0; i < size; ++i) { if (kv [i] == attribute) { vv [i] = value; setObjVarItem (objvar_item_attribute_values, vv); break; } } } } // ---------------------------------------------------------------------- void ServerObject::setInitialized(bool initialized) { setLocalFlag(LocalObjectFlags::ServerObject_Initialized, initialized); } // ---------------------------------------------------------------------- void ServerObject::setBeingDestroyed(bool beingDestroyed) { setLocalFlag(LocalObjectFlags::ServerObject_BeingDestroyed, beingDestroyed); // set corresponding flag in script if (beingDestroyed && isAuthoritative() && getScriptObject()) getScriptObject()->setOwnerDestroyed(); // stop listening to broadcast messages if (beingDestroyed && isAuthoritative()) stopListeningToAllBroadcastMessages(); } // ---------------------------------------------------------------------- void ServerObject::setPlacing(bool placing) { setLocalFlag(LocalObjectFlags::ServerObject_Placing, placing); } // ---------------------------------------------------------------------- void ServerObject::setUnloading(bool unloading) { setLocalFlag(LocalObjectFlags::ServerObject_Unloading, unloading); } // ---------------------------------------------------------------------- void ServerObject::setGoingToConclude(bool goingToConclude) { setLocalFlag(LocalObjectFlags::ServerObject_GoingToConclude, goingToConclude); } // ---------------------------------------------------------------------- void ServerObject::setInEndBaselines(bool inEndBaselines) { setLocalFlag(LocalObjectFlags::ServerObject_InEndBaselines, inEndBaselines); } // ---------------------------------------------------------------------- void ServerObject::setNeedsPobFixup(bool needsFixup) { setLocalFlag(LocalObjectFlags::ServerObject_NeedsPobFixup, needsFixup); } // ---------------------------------------------------------------------- bool ServerObject::isInitialized() const { return getLocalFlag(LocalObjectFlags::ServerObject_Initialized); } // ---------------------------------------------------------------------- bool ServerObject::isPlacing() const { return getLocalFlag(LocalObjectFlags::ServerObject_Placing); } // ---------------------------------------------------------------------- bool ServerObject::isUnloading() const { return getLocalFlag(LocalObjectFlags::ServerObject_Unloading); } // ---------------------------------------------------------------------- bool ServerObject::isBeingDestroyed() const { return getLocalFlag(LocalObjectFlags::ServerObject_BeingDestroyed); } // ---------------------------------------------------------------------- bool ServerObject::isGoingToConclude() const { return getLocalFlag(LocalObjectFlags::ServerObject_GoingToConclude); } // ---------------------------------------------------------------------- bool ServerObject::isInEndBaselines() const { return getLocalFlag(LocalObjectFlags::ServerObject_InEndBaselines); } // ---------------------------------------------------------------------- bool ServerObject::isNeedingPobFixup() const { return getLocalFlag(LocalObjectFlags::ServerObject_NeedsPobFixup); } // ---------------------------------------------------------------------- float ServerObject::getLocationReservationRadius() const { return getSharedTemplate() ? getSharedTemplate()->getLocationReservationRadius() : 0.f; } // ---------------------------------------------------------------------- void ServerObject::sendDirtyObjectMenuNotification() { if (getLocalFlag(LocalObjectFlags::ServerObject_DirtyObjectMenuSent)) return; setLocalFlag(LocalObjectFlags::ServerObject_DirtyObjectMenuSent, true); Controller * const controller = getController(); if (controller) controller->appendMessage(CM_objectMenuDirty, 0, nullptr, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_ALL_CLIENT); } // ---------------------------------------------------------------------- void ServerObject::sendDirtyAttributesNotification() { if (getLocalFlag(LocalObjectFlags::ServerObject_DirtyAttributesSent)) return; setLocalFlag(LocalObjectFlags::ServerObject_DirtyAttributesSent, true); Controller * const controller = getController(); if (controller) controller->appendMessage(CM_attributesDirty, 0, nullptr, GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_ALL_CLIENT); } //------------------------------------------------------------------------------------------ void ServerObject::setBuildingUpdateRadiusMultiplier(float m) { if (m <= 0) { ms_buildingUpdateRadiusMultiplier = 1.0f; DEBUG_WARNING(true, ("Building Update Radius Multiplier was set to a value (%f) <= 0", m)); } else ms_buildingUpdateRadiusMultiplier = m; } // ---------------------------------------------------------------------- void ServerObject::triggerMadeAuthoritative() { std::vector trigObjs; getPlayerControlledContents(*this, trigObjs); for (std::vector::const_iterator i = trigObjs.begin(); i != trigObjs.end(); ++i) { ServerObject *trigObj = safe_cast((*i).getObject()); if (trigObj && trigObj->isAuthoritative()) { ScriptParams params; IGNORE_RETURN(trigObj->getScriptObject()->trigAllScripts(Scripting::TRIG_MADE_AUTHORITATIVE, params)); } } } //------------------------------------------------------------------------------------------ void ServerObject::setLayer(TerrainGenerator::Layer* layer) { LayerProperty * layerProperty = nullptr; Property * property = getProperty(LayerProperty::getClassPropertyId()); if (property != nullptr) layerProperty = safe_cast(property); else layerProperty = new LayerProperty(*this); layerProperty->setLayer(layer); if (property == nullptr) { addProperty(*layerProperty, true); ObjectTracker::addRunTimeRule(); addNotification (ServerObjectTerrainModificationNotification::getInstance ()); } } //------------------------------------------------------------------------------------------ TerrainGenerator::Layer* ServerObject::getLayer() const { const Property * property = getProperty(LayerProperty::getClassPropertyId()); if (property != nullptr) { const LayerProperty * layerProperty = safe_cast(property); return layerProperty->getLayer(); } return nullptr; } //------------------------------------------------------------------------------------------ void ServerObject::getByteStreamFromAutoVariable(const std::string & name, Archive::ByteStream & target) const { if(name == "complexity") { m_complexity.pack(target); } else if(name == "bankBalance") { m_bankBalance.pack(target); } } //------------------------------------------------------------------------------------------ void ServerObject::setAutoVariableFromByteStream(const std::string & name, const Archive::ByteStream & source) { Archive::ReadIterator ri(source); if(name == "complexity") { m_complexity.unpackDelta(ri); } else if(name == "bankBalance") { m_bankBalance.unpackDelta(ri); } addObjectToConcludeList(); } // ---------------------------------------------------------------------- /** * Deliver a MessageTo to this object. If the object is not * authoritative, send the message to the authoritative server. * If the MessageTo's time has expired, handle it. If not, enqueue it * for later. */ void ServerObject::deliverMessageTo(MessageToPayload & message) { PROFILER_AUTO_BLOCK_DEFINE("ServerObject::deliverMessageTo"); if (message.getRecurringTime() != 0) { // Recurring messages can have only one instance each. Ignore // this message if there is already a recurring one with the same method name. for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator i=m_messageTos.begin(); i!=m_messageTos.end(); ++i) { if ((i->second.getRecurringTime() != 0) && (i->second.getMethod() == message.getMethod())) return; } } if (isAuthoritative()) { if (message.getCallTime() <= MessageToQueue::getInstance().getEffectiveMessageToTime()) { // Message is already due, handle it right away handleMessageTo(message); } else { if (isPersisted() && message.getGuaranteed() && !message.getPersisted()) { // Message is guaranteed. Since it's being delayed, make sure the DB saves it // in case of a server crash message.setPersisted(true); MessageToMessage const msg(message, GameServer::getInstance().getProcessId()); GameServer::getInstance().sendToDatabaseServer(msg); } if (m_messageTos.empty() || message.getCallTime() < m_messageTos.begin()->second.getCallTime()) MessageToQueue::getInstance().addToScheduler(*this, message.getCallTime()); pushMessageTo(message); } } else { message.addBounceServer(GameServer::getInstance().getProcessId()); ServerMessageForwarding::begin(getAuthServerProcessId()); ServerMessageForwarding::send(MessageToMessage(message, GameServer::getInstance().getProcessId())); ServerMessageForwarding::end(); } } // ---------------------------------------------------------------------- /** * Add a MessageTo to the sorted vector of pending messages * * Does a binary search to find the appropriate insertion point. */ void ServerObject::pushMessageTo(MessageToPayload const & newMessage) { m_messageTos.set(std::make_pair(std::make_pair(newMessage.getCallTime(), newMessage.getCounterMessageToPayload()), newMessage.getMessageId()), newMessage); } // ---------------------------------------------------------------------- int ServerObject::cancelMessageTo(std::string const & messageName) { int removeCount; if (isAuthoritative()) { removeCount = 0; for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator i=m_messageTos.begin(); i!=m_messageTos.end();) { if ((i->second.getMethod() == messageName) && (i->first != s_currentMessageToBeingHandled)) { i = m_messageTos.erase(i); ++removeCount; } else ++i; } } else { sendControllerMessageToAuthServer( CM_cancelMessageTo, new MessageQueueGenericValueType(messageName)); // -1 means request has been forward to authoritative server for processing removeCount = -1; } return removeCount; } // ---------------------------------------------------------------------- int ServerObject::cancelMessageToByMessageId(NetworkId const & messageId) { int removeCount; if (isAuthoritative()) { removeCount = 0; for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator i=m_messageTos.begin(); i!=m_messageTos.end();) { if ((i->second.getMessageId() == messageId) && (i->first != s_currentMessageToBeingHandled)) { i = m_messageTos.erase(i); ++removeCount; break; } else ++i; } } else { sendControllerMessageToAuthServer( CM_cancelMessageTo, new MessageQueueGenericValueType(messageId.getValueString())); // -1 means request has been forward to authoritative server for processing removeCount = -1; } return removeCount; } // ---------------------------------------------------------------------- // returns the number of seconds until the messageTo fires, which *CAN* be 0 // returns -1 if object doesn't have the messageTo int ServerObject::timeUntilMessageTo(std::string const & messageName) const { for (Archive::AutoDeltaMap, MessageToId>, MessageToPayload>::const_iterator i=m_messageTos.begin(); i!=m_messageTos.end(); ++i) { if (i->second.getMethod() == messageName) { if ((i->first != s_currentMessageToBeingHandled) || (i->second.getRecurringTime() > 0)) { unsigned long const now = ServerClock::getInstance().getGameTimeSeconds(); unsigned long const callTime = i->second.getCallTime(); if (callTime > now) return static_cast(callTime - now); return 0; } } } return -1; // doesn't have the messageTo } // ---------------------------------------------------------------------- /** * Sets the root node for a patrol path node. * * @param root the root node * * NOTE: Does not work multiserver! */ void ServerObject::setPatrolPathRoot(const ServerObject & root) { if (!isAuthoritative()) { WARNING(true, ("ServerObject::setPatrolPathRoot called on non-authoritative object %s for %s", getNetworkId().getValueString().c_str(), root.getNetworkId().getValueString().c_str())); return; } // make sure we aren't already a root node Property * p = getProperty(PatrolPathRootProperty::getClassPropertyId()); if (p != nullptr) { if (root.getNetworkId() != getNetworkId()) { WARNING(true, ("ServerObject::setPatrolPathRoot trying to set root node %s on node %s that is already a root", root.getNetworkId().getValueString().c_str(), getNetworkId().getValueString().c_str())); } return; } // see if we are going to be the root node if (root.getNetworkId() == getNetworkId()) { p = new PatrolPathRootProperty(*this); addProperty(*p, true); } else { // add the root node to our node list p = getProperty(PatrolPathNodeProperty::getClassPropertyId()); if (p == nullptr) { p = new PatrolPathNodeProperty(*this); addProperty(*p, true); } else if (safe_cast(p)->hasRoot(root)) return; safe_cast(p)->addRoot(root); if (!root.isPatrolPathRoot()) { // the root has not been set up, do it now WARNING(true, ("ServerObject::setPatrolPathRoot for node %s: root node %s was not initialized, doing it now (THIS SHOULD NEVER HAPPEN)", getNetworkId().getValueString().c_str(), root.getNetworkId().getValueString().c_str())); const_cast(root).setPatrolPathRoot(root); } } // we need to add any players in range to our path observer count TriggerVolume * triggerVolume = getNetworkTriggerVolume(); if (triggerVolume != nullptr) { TriggerVolume::ContentsSet const & contents = triggerVolume->getContents(); for (TriggerVolume::ContentsSet::const_iterator i = contents.begin(); i != contents.end(); ++i) { if (*i != nullptr) { Client * client = (*i)->getClient(); if (client != nullptr) { if (!ObserveTracker::isObserving(*client, *this)) ObserveTracker::onClientEnteredNetworkTriggerVolume(*client, *triggerVolume); else { // note: we call addPatrolPathObserver() on the root instead of us so // that we don't update the count of any paths we already belong to const_cast(root).addPatrolPathObserver(); } } } } } } // ServerObject::setPatrolPathRoot // ---------------------------------------------------------------------- /** * Returns the root node for a patrol path node. * * @return the root node, or nullptr if this isn't a patrol path node */ const std::set & ServerObject::getPatrolPathRoots() const { static const std::set noRoots; const Property * p = getProperty(PatrolPathNodeProperty::getClassPropertyId()); if (p != nullptr) { return safe_cast(p)->getRoots(); } return noRoots; } // ServerObject::getPatrolPathRoot // ---------------------------------------------------------------------- /** * Adds an object that is moving along a patrol path where this object is the * root node. If our path goes from being unobserved to observed, all the objects * using this path will be scheduled for alter. * * @param ai the object that's on our path * * NOTE: Does not work multiserver! */ void ServerObject::addPatrolPathingObject(const ServerObject & ai) { if (!isAuthoritative()) { WARNING(true, ("ServerObject::addPatrolPathingObject called on non-authoritative object %s for %s", getNetworkId().getValueString().c_str(), ai.getNetworkId().getValueString().c_str())); return; } Property * p = getProperty(PatrolPathRootProperty::getClassPropertyId()); if (p != nullptr) { safe_cast(p)->addPatrollingObject(ai); DEBUG_REPORT_LOG(true, ("[patrolpath] add patrol ai %s to root %s\n", ai.getNetworkId().getValueString().c_str(), getNetworkId().getValueString().c_str())); } else { // if we are not a root, try to add the ai to our root (but only do so if we have // one root) DEBUG_WARNING(true, ("ServerObject::addPatrolPathingObject trying to add an ai %s to a non-root path node %s, going to use our root", ai.getNetworkId().getValueString().c_str(), getNetworkId().getValueString().c_str())); const std::set & roots = getPatrolPathRoots(); if (roots.size() == 1) { ServerObject * root = safe_cast(roots.begin()->getObject()); if (root != nullptr) root->addPatrolPathingObject(ai); } else { WARNING(true, ("ServerObject::addPatrolPathingObject trying to add an ai %s to a non-root path node %s which has muliple roots.", ai.getNetworkId().getValueString().c_str(), getNetworkId().getValueString().c_str())); } } } // ServerObject::addPatrolPathingObject // ---------------------------------------------------------------------- /** * Removes an object that is moving along our patrol path. * * @param ai the object to remove * * NOTE: Does not work multiserver! */ void ServerObject::removePatrolPathingObject(const ServerObject & ai) { if (!isAuthoritative()) { WARNING(true, ("ServerObject::removePatrolPathingObject called on non-authoritative object %s for %s", getNetworkId().getValueString().c_str(), ai.getNetworkId().getValueString().c_str())); return; } Property * p = getProperty(PatrolPathRootProperty::getClassPropertyId()); if (p != nullptr) { safe_cast(p)->removePatrollingObject(ai); DEBUG_REPORT_LOG(true, ("[patrolpath] remove patrol ai %s from root %s\n", ai.getNetworkId().getValueString().c_str(), getNetworkId().getValueString().c_str())); } else { // if we are not a root, try to remove the ai to our root (but only do so if we have // one root) const std::set & roots = getPatrolPathRoots(); if (roots.size() == 1) { ServerObject * root = safe_cast(roots.begin()->getObject()); if (root != nullptr) root->removePatrolPathingObject(ai); } else { WARNING(true, ("ServerObject::removePatrolPathingObject trying to remove an ai %s from a non-root path node %s which has muliple roots.", ai.getNetworkId().getValueString().c_str(), getNetworkId().getValueString().c_str())); } } } // ServerObject::removePatrolPathingObject // ---------------------------------------------------------------------- /** * Increments the observer count for this patrol path. If this is the first observer, * schedules any patrolling objects for alter. * * NOTE: Does not work multiserver! */ void ServerObject::addPatrolPathObserver() { if (!isAuthoritative()) { WARNING(true, ("ServerObject::addPatrolPathObserver called on non-authoritative object %s", getNetworkId().getValueString().c_str())); return; } // if we are a root node, increment our observer count Property * p = getProperty(PatrolPathRootProperty::getClassPropertyId()); if (p != nullptr) { PatrolPathRootProperty * pprp = safe_cast(p); pprp->incrementObserverCount(); DEBUG_REPORT_LOG(true, ("[patrolpath] add observer to %s, count = %d\n", getNetworkId().getValueString().c_str(), pprp->getObserverCount())); if (pprp->getObserverCount() == 1) { // we need to wake up hibernating ai const std::set > & ai = pprp->getPatrollingObjects(); for (std::set >::const_iterator i = ai.begin(); i != ai.end(); ++i) { if (*i != nullptr) { const ServerObject * ai = *i; if (ai->getController()->asCreatureController() != nullptr) { const CreatureController * controller = ai->getController()->asCreatureController(); if (controller->getHibernate()) { DEBUG_REPORT_LOG(true, ("[patrolpath] path root %s waking up ai %s", getNetworkId().getValueString().c_str(), ai->getNetworkId().getValueString().c_str())); const_cast(ai)->scheduleForAlter(); } } } } } } else { // increment the count of the root nodes of any paths we belong to const std::set & roots = getPatrolPathRoots(); for (std::set::const_iterator i = roots.begin(); i != roots.end(); ++i) { ServerObject * root = safe_cast(i->getObject()); if (root != nullptr) { root->addPatrolPathObserver(); } } } } // ServerObject::addPatrolPathObserver // ---------------------------------------------------------------------- /** * Decrements the observer count for this patrol path. * * NOTE: Does not work multiserver! */ void ServerObject::removePatrolPathObserver() { if (!isAuthoritative()) { WARNING(true, ("ServerObject::removePatrolPathObserver called on non-authoritative object %s", getNetworkId().getValueString().c_str())); return; } // if we are a root node, decrement our observer count Property * p = getProperty(PatrolPathRootProperty::getClassPropertyId()); if (p != nullptr) { PatrolPathRootProperty * pprp = safe_cast(p); pprp->decrementObserverCount(); DEBUG_REPORT_LOG(true, ("[patrolpath] remove observer from %s, count = %d\n", getNetworkId().getValueString().c_str(), pprp->getObserverCount())); if (pprp->getObserverCount() < 0) { WARNING(true, ("ServerObject::removePatrolPathObserver %s has negative patrol path observer count of %d", getNetworkId().getValueString().c_str(), pprp->getObserverCount())); } } else { // decrement the count of the root nodes of any paths we belong to const std::set & roots = getPatrolPathRoots(); for (std::set::const_iterator i = roots.begin(); i != roots.end(); ++i) { ServerObject * root = safe_cast(i->getObject()); if (root != nullptr) { root->removePatrolPathObserver(); } } } } // ServerObject::removePatrolPathObserver // ---------------------------------------------------------------------- /** * Returns the number of observers of this patrol path. If this node connects to multiple * roots, we will return the total for all paths it connects to. * * @return the observer count */ int ServerObject::getPatrolPathObservers() const { int observers = 0; const Property * p = getProperty(PatrolPathRootProperty::getClassPropertyId()); if (p != nullptr) { observers = safe_cast(p)->getObserverCount(); } else { const std::set & roots = getPatrolPathRoots(); for (std::set::const_iterator i = roots.begin(); i != roots.end(); ++i) { const ServerObject * root = safe_cast(i->getObject()); if (root != nullptr && root->isPatrolPathRoot()) { observers += root->getPatrolPathObservers(); } } } return observers; } // ServerObject::getPatrolPathObservers // ---------------------------------------------------------------------- bool ServerObject::isPatrolPathNode() const { return (getProperty(PatrolPathNodeProperty::getClassPropertyId()) != nullptr) || isPatrolPathRoot(); } // ---------------------------------------------------------------------- bool ServerObject::isPatrolPathRoot() const { return getProperty(PatrolPathRootProperty::getClassPropertyId()) != nullptr; } // ---------------------------------------------------------------------- bool ServerObject::hasAttributeCaching() const { // rls - if attribute caching breaks for an object, you can disable // caching by returning false here. return true; } // ---------------------------------------------------------------------- int ServerObject::getAttributeRevision() const { return m_objVars.getRevision(); } // ---------------------------------------------------------------------- void ServerObject::setAttributeRevisionDirty() { m_objVars.updateRevision(); } // ---------------------------------------------------------------------- bool ServerObject::getIncludeInBuildout() const { return m_includeInBuildout.get(); } // ---------------------------------------------------------------------- void ServerObject::setIncludeInBuildout( bool value ) { m_includeInBuildout = value; } // ---------------------------------------------------------------------- void ServerObject::setDescriptionStringId(const StringId & id) { m_descriptionStringId.set(id); } // ---------------------------------------------------------------------- StringId const & ServerObject::getDescriptionStringId() { return m_descriptionStringId.get(); } // ---------------------------------------------------------------------- void ServerObject::addBroadcastListener(std::string const & messageName, NetworkId const & listener) { if (!isAuthoritative()) return; m_broadcastListeners.insert(std::make_pair(messageName, listener)); } // ---------------------------------------------------------------------- void ServerObject::removeBroadcastListener(std::string const & messageName, NetworkId const & listener) { if (!isAuthoritative()) return; IGNORE_RETURN(m_broadcastListeners.erase(std::make_pair(messageName, listener))); } // ---------------------------------------------------------------------- void ServerObject::removeBroadcastListener(NetworkId const & listener) { if (!isAuthoritative()) return; for (BroadcastMap::const_iterator iter = m_broadcastListeners.begin(); iter != m_broadcastListeners.end();) { if (iter->second == listener) { // AutoDeltaSet's erase() will increment iter to point to the next item m_broadcastListeners.erase(iter); } else { ++iter; } } } // ---------------------------------------------------------------------- void ServerObject::addBroadcastBroadcaster(std::string const & messageName, NetworkId const & broadcaster) { if (!isAuthoritative()) return; m_broadcastBroadcasters.insert(std::make_pair(messageName, broadcaster)); } // ---------------------------------------------------------------------- void ServerObject::removeBroadcastBroadcaster(std::string const & messageName, NetworkId const & broadcaster) { if (!isAuthoritative()) return; IGNORE_RETURN(m_broadcastBroadcasters.erase(std::make_pair(messageName, broadcaster))); } // ---------------------------------------------------------------------- void ServerObject::removeBroadcastAllBroadcasters() { if (!isAuthoritative()) return; m_broadcastBroadcasters.clear(); } // ---------------------------------------------------------------------- void ServerObject::listenToBroadcastMessage(std::string const & messageName, NetworkId const & broadcaster) { if (messageName.empty() || !broadcaster.isValid()) return; if (!isAuthoritative()) { char buffer[128]; snprintf(buffer, sizeof(buffer)-1, "%s %s", messageName.c_str(), broadcaster.getValueString().c_str()); buffer[sizeof(buffer)-1] = '\0'; MessageToQueue::getInstance().sendMessageToC(getNetworkId(), "C++listenToBroadcastMessage", buffer, 0, false); } else { // need to send a message to the broadcaster if the broadcaster is not on this game server ServerObject * objBroadcaster = ServerWorld::findObjectByNetworkId(broadcaster); if (objBroadcaster && objBroadcaster->isAuthoritative()) { // tell broadcaster that I want to listen objBroadcaster->addBroadcastListener(messageName, getNetworkId()); } else { char buffer[128]; snprintf(buffer, sizeof(buffer)-1, "%s %s", messageName.c_str(), getNetworkId().getValueString().c_str()); buffer[sizeof(buffer)-1] = '\0'; MessageToQueue::getInstance().sendMessageToC(broadcaster, "C++addBroadcastListener", buffer, 0, false); } // this object is now listening to the broadcaster // @TODO: technically, we should have the remote broadcaster // send a message back confirming that this object has // been added to the broadcaster's listener list before // we mark this object as listening to the broadcaster addBroadcastBroadcaster(messageName, broadcaster); } } // ---------------------------------------------------------------------- void ServerObject::stopListeningToBroadcastMessage(std::string const & messageName, NetworkId const & broadcaster) { if (messageName.empty() || !broadcaster.isValid()) return; if (!isAuthoritative()) { char buffer[128]; snprintf(buffer, sizeof(buffer)-1, "%s %s", messageName.c_str(), broadcaster.getValueString().c_str()); buffer[sizeof(buffer)-1] = '\0'; MessageToQueue::getInstance().sendMessageToC(getNetworkId(), "C++stopListeningToBroadcastMessage", buffer, 0, false); } else { // need to send a message to the broadcaster if the broadcaster is not on this game server ServerObject * objBroadcaster = ServerWorld::findObjectByNetworkId(broadcaster); if (objBroadcaster && objBroadcaster->isAuthoritative()) { // tell broadcaster that I no longer want to listen objBroadcaster->removeBroadcastListener(messageName, getNetworkId()); } else { char buffer[128]; snprintf(buffer, sizeof(buffer)-1, "%s %s", messageName.c_str(), getNetworkId().getValueString().c_str()); buffer[sizeof(buffer)-1] = '\0'; MessageToQueue::getInstance().sendMessageToC(broadcaster, "C++removeBroadcastListener", buffer, 0, false); } // this object is no longer listening to the broadcaster // @TODO: technically, we should have the remote broadcaster // send a message back confirming that this object has // been removed from the broadcaster's listener list before // we mark this object as no longer listening to the broadcaster removeBroadcastBroadcaster(messageName, broadcaster); } } // ---------------------------------------------------------------------- void ServerObject::stopListeningToAllBroadcastMessages() { if (!isAuthoritative()) { MessageToQueue::getInstance().sendMessageToC(getNetworkId(), "C++stopListeningToAllBroadcastMessage", "", 0, false); } else { // build list of all broadcasters this object is currently listening to std::unordered_set broadcasters; for (BroadcastMap::const_iterator iter = m_broadcastBroadcasters.begin(); iter != m_broadcastBroadcasters.end(); ++iter) IGNORE_RETURN(broadcasters.insert(iter->second)); for (std::unordered_set::const_iterator iter2 = broadcasters.begin(); iter2 != broadcasters.end(); ++iter2) { // need to send a message to the broadcaster if the broadcaster is not on this game server ServerObject * objBroadcaster = ServerWorld::findObjectByNetworkId(*iter2); if (objBroadcaster && objBroadcaster->isAuthoritative()) { // tell broadcaster that I no longer want to listen objBroadcaster->removeBroadcastListener(getNetworkId()); } else { char buffer[128]; snprintf(buffer, sizeof(buffer)-1, "%s", getNetworkId().getValueString().c_str()); buffer[sizeof(buffer)-1] = '\0'; MessageToQueue::getInstance().sendMessageToC(*iter2, "C++removeBroadcastListenerAllMessage", buffer, 0, false); } } // this object is no longer listening to any broadcasters // @TODO: technically, we should have the remote broadcaster // send a message back confirming that this object has // been removed from the broadcaster's listener list before // we mark this object as no longer listening to the broadcaster removeBroadcastAllBroadcasters(); } } // ---------------------------------------------------------------------- void ServerObject::addObserver(Client * client) { std::pair::const_iterator, bool> result = m_observers.insert(client); if (result.second) { if (isAuthoritative()) { m_observersCount = m_observersCount.get() + 1; } else { sendControllerMessageToAuthServer(CM_updateObserversCount, new MessageQueueGenericValueType(1)); } } } // ---------------------------------------------------------------------- void ServerObject::removeObserver(Client * client) { int erased = m_observers.erase(client); if (erased > 0) { if (isAuthoritative()) { m_observersCount = m_observersCount.get() - erased; if (m_observersCount.get() < 0) { WARNING(true, ("ServerObject::removeObserver for object %s has observer count of %d", getNetworkId().getValueString().c_str(), m_observersCount.get())); m_observersCount = 0; } } else { sendControllerMessageToAuthServer(CM_updateObserversCount, new MessageQueueGenericValueType(-erased)); } } } // ---------------------------------------------------------------------- void ServerObject::clearObservers() { int erased = m_observers.size(); m_observers.clear(); if (erased > 0) { if (isAuthoritative()) { m_observersCount = m_observersCount.get() - erased; if (m_observersCount.get() < 0) { WARNING(true, ("ServerObject::clearObservers for object %s has observer count of %d", getNetworkId().getValueString().c_str(), m_observersCount.get())); m_observersCount = 0; } } else { // if we aren't being deleted, send a controller message, otherwise send a messageTo if (!getKill()) sendControllerMessageToAuthServer(CM_updateObserversCount, new MessageQueueGenericValueType(-erased)); else { char buffer[32]; _itoa(-erased, buffer, 10); MessageToQueue::getInstance().sendMessageToC(getNetworkId(), "C++updateObserversCount", buffer, 0, false); } } } } // ---------------------------------------------------------------------- void ServerObject::clearObserversCount() { if (isAuthoritative()) { m_observersCount = 0; } } // ---------------------------------------------------------------------- void ServerObject::updateObserversCount(int delta) { if (isAuthoritative()) { m_observersCount = m_observersCount.get() + delta; if (m_observersCount.get() < 0) { WARNING(m_observersCount.get() < 0, ("ServerObject::updateObserversCount for object %s has observer count of %d", getNetworkId().getValueString().c_str(), m_observersCount.get())); m_observersCount = 0; } } } // ---------------------------------------------------------------------- /** * Do nothing; this function is base virtual function so that creatures can go * in and out of hibernation when observed. */ void ServerObject::observersCountChanged() { } // ====================================================================== namespace Archive { void put(Archive::ByteStream &target, TriggerVolumeInfo const &source) { put(target, source.name); put(target, source.radius); put(target, source.isPromiscuous); } void get(Archive::ReadIterator &source, TriggerVolumeInfo &target) { get(source, target.name); get(source, target.radius); get(source, target.isPromiscuous); } } // ======================================================================