Files
src/engine/server/library/serverGame/src/shared/object/ServerObject.cpp
T

8552 lines
278 KiB
C++
Executable File

// ======================================================================
//
//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 <algorithm>
// ======================================================================
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<<LocalObjectFlags::ServerObject_DirtyObjectMenuSent)
| (1u<<LocalObjectFlags::ServerObject_DirtyAttributesSent)
| (1u<<LocalObjectFlags::ServerObject_GoingToConclude));
// ----------------------------------------------------------------------
// sentinel to keep the messageTo current being
// handled from getting removed from m_messageTos
std::pair<std::pair<unsigned long, uint64>, 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<uint32> s_noTradeSharedTemplateItemType;
std::set<std::string> 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<ServerObject *>((*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<ServerObject*>(ContainerInterface::getContainedByObject(obj));
if (parent && !parent->isInitialized())
return;
}
obj.getScriptObject()->initScriptInstances();
obj.getScriptObject()->setOwnerIsInitialized();
Container* container = ContainerInterface::getContainer(obj);
if (container)
{
std::vector<CachedNetworkId> initList;
for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator)
{
initList.push_back(*containerIterator);
}
for(std::vector<CachedNetworkId>::iterator i = initList.begin(); i != initList.end(); ++i)
{
ServerObject * containedObject = safe_cast<ServerObject*>((*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<TangibleObject *>(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<CachedNetworkId> &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<ServerObject *>((*i).getObject());
if (containedObject)
getPlayerControlledContents(*containedObject, results);
}
}
}
// ----------------------------------------------------------------------
void getContainedClients(ServerObject &obj, std::vector<Watcher<Client> > &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>(client));
Container * const container = ContainerInterface::getContainer(obj);
if (container)
{
for (ContainerIterator i = container->begin(); i != container->end(); ++i)
{
ServerObject * const containedObject = safe_cast<ServerObject *>((*i).getObject());
if (containedObject)
getContainedClients(*containedObject, containedClients);
}
}
}
// ----------------------------------------------------------------------
unsigned int getPackedObjVarField(std::string const &buf, unsigned int offset, std::string &result)
{
std::vector<char> 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<char> &buf, char const *s)
{
while (*s)
{
if ((*s) != '|')
buf.push_back(*s);
++s;
}
buf.push_back('|');
}
// ----------------------------------------------------------------------
void appendPackedObjVarField(std::vector<char> &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<ServerObject*>(&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<ServerObject &>(object).updateWorldSphere();
}
// ----------------------------------------------------------------------
void ServerObject::MoveNotification::checkForGoldObject(const Object &object) const
{
if (ConfigServerGame::getMaxGoldNetworkId()!=NetworkId::cms_invalid && object.getNetworkId() < ConfigServerGame::getMaxGoldNetworkId())
{
FATAL(ConfigServerGame::getFatalOnMovingGoldObject(),("Attempted to move object %s, which is a gold object that should not be movable.", object.getNetworkId().getValueString().c_str()));
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<ServerObject &>(object).updateWorldSphere();
if (!dueToParentChange)
{
DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing cells"));
static_cast<ServerObject &>(object).setTransformChanged(true);
}
}
//------------------------------------------------------------------------------------------
bool ServerObject::MoveNotification::positionChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const
{
checkForGoldObject(object);
UNREF(oldPosition);
static_cast<ServerObject &>(object).updateWorldSphere();
if (!dueToParentChange)
{
DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing position"));
static_cast<ServerObject &>(object).setTransformChanged(true);
}
return true;
}
//------------------------------------------------------------------------------------------
void ServerObject::MoveNotification::rotationChanged(Object &object, bool dueToParentChange) const
{
checkForGoldObject(object);
static_cast<ServerObject &>(object).updateWorldSphere();
if (!dueToParentChange)
{
DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing rotation"));
static_cast<ServerObject &>(object).setTransformChanged(true);
}
}
//------------------------------------------------------------------------------------------
bool ServerObject::MoveNotification::positionAndRotationChanged(Object &object, bool dueToParentChange, const Vector &oldPosition) const
{
checkForGoldObject(object);
UNREF(oldPosition);
static_cast<ServerObject &>(object).updateWorldSphere();
if (!dueToParentChange)
{
DEBUG_FATAL(object.getCellProperty() && !object.getKill(), ("Cell object changing position and rotation"));
static_cast<ServerObject &>(object).setTransformChanged(true);
}
return true;
}
//------------------------------------------------------------------------------------------
void ServerObject::MoveNotification::visibilityDataChanged(Object &object) const
{
static_cast<ServerObject &>(object).updateWorldSphere();
static_cast<ServerObject &>(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 &notification, 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<const SharedObjectTemplate *>(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<int>(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<const SharedObjectTemplate *>(
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<const ServerObject *>((*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<const ServerObject *>((*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<const ServerObject*>(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<const ServerObject *>((*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<const ServerObject*>(ContainerInterface::getFirstParentInWorld(*this));
const ServerObject *grandParent = nullptr;
if( parent )
{
grandParent = safe_cast<const ServerObject*>(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<ServerObject*>(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<ServerObject *>(NetworkIdManager::getObjectById(oldValue));
ServerObject * const newContainer = safe_cast<ServerObject *>(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<ServerObject*>(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<ServerObject *>(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<TriggerVolume*> newVolumes;
// create any new volumes
{
for (std::vector<TriggerVolumeInfo>::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<int>(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<std::pair<NetworkId, std::pair<std::string, bool> > > 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<ServerController*>(getController());
c->endBaselines();
setTransformChanged(false);
// check for parent getting uncached
if (isAuthoritative() && getCacheVersion() != 0)
{
ServerObject *parent = safe_cast<ServerObject *>(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<Object*> 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<int>(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<ServerObject *>(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<NetworkId::NetworkIdType>(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<Client const *> &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<ServerObject *>(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<ServerObject*>(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<ServerObject *>(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<SlottedContainer *>(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<ServerObject *>(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<ServerObject *>(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<ServerObjectTemplate const *>(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))
{
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<ServerController *>(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<const ServerObjectTemplate*>(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<const ServerObjectTemplate*>(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<ServerObject *>(equippedObjectId.getObject());
if (equippedObject != nullptr)
{
//We'll persist the contents automatically if the parent object is persisted.
ServerObject * newObject = ServerWorld::createNewObject(*(safe_cast<const ServerObjectTemplate*>(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<SharedObjectTemplate::GameObjectType> (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<const ServerObjectTemplate *>(getObjectTemplate())->getPersistContents())
return;
Container *container = ContainerInterface::getContainer(*this);
if (container)
{
for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator)
{
ServerObject *containedObject = safe_cast<ServerObject *>((*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<ServerObject*>(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<SharedObjectTemplate::GameObjectType> (parent->asServerObject()->getGameObjectType());
SharedObjectTemplate::GameObjectType itemGot = static_cast<SharedObjectTemplate::GameObjectType> (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<ServerObjectTemplate const *>(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<SlottedContainer*>(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<const ServerObjectTemplate *>(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<int>(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<float>(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<ServerObjectTemplate const *>(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<ServerObject*>(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)
{
#ifdef _DEBUG
WARNING(isInEndBaselines(), ("sendControllerMessageToAuthServer while ending baselines: message id=(%d) from object id=[%s], template=[%s], on server id=[%d]", static_cast<int>(cm), getNetworkId().getValueString().c_str(), getObjectTemplateName(), static_cast<int>(GameServer::getInstance().getProcessId())));
WARNING(!isInitialized(), ("sendControllerMessageToAuthServer while uninitialized: message id=(%d) from object id=[%s], template=[%s], on server id=[%d]", static_cast<int>(cm), getNetworkId().getValueString().c_str(), getObjectTemplateName(), static_cast<int>(GameServer::getInstance().getProcessId())));
#endif
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<Client *> const &clients = getObservers();
std::set<Client *>::const_iterator i;
std::map<ConnectionServerConnection *, std::vector<NetworkId> > &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<ObjControllerMessage const &>(message);
if (ocm.getMessage() == CM_combatSpam)
{
MessageQueueCombatSpam const * mqcs = static_cast<MessageQueueCombatSpam const *>(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<ConnectionServerConnection *, std::vector<NetworkId> >::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<NetworkId> & clients)
{
for (std::vector<NetworkId>::const_iterator i = clients.begin(); i != clients.end(); ++i)
{
ServerObject * o = safe_cast<ServerObject *>(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<CachedNetworkId> idList;
idList.reserve(container->getNumberOfItems());
for (ContainerIterator containerIterator = container->begin(); containerIterator != container->end(); ++containerIterator)
idList.push_back(*containerIterator);
for (std::vector<CachedNetworkId>::iterator i = idList.begin(); i != idList.end(); ++i)
{
ServerObject * const containedObject = safe_cast<ServerObject *>(i->getObject());
if (containedObject)
containedObject->setAuthority();
}
}
safe_cast<ServerController*>(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<std::pair<NetworkId, std::pair<std::string, bool> > > const msg(
"SetSceneForPlayer",
std::make_pair(
getNetworkId(),
std::make_pair(ServerWorld::getSceneId(), false)));
GameServer::getInstance().sendToCentralServer(msg);
std::map<NetworkId, LfgCharacterData> const & connectedCharacterLfgData = ServerUniverse::getConnectedCharacterLfgData();
std::map<NetworkId, LfgCharacterData>::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<ServerObject *>(ContainerInterface::getContainedByObject(*this));
if (!containingObject || containingObject->isAuthoritative())
triggerMadeAuthoritative();
}
//-----------------------------------------------------------------------
void ServerObject::virtualOnSetClient()
{
if (getObjVars().hasItem("teleportFixup"))
TeleportFixupHandler::add(*this);
if (m_pendingSyncUi)
{
for (std::vector<NetworkId>::const_iterator i = m_pendingSyncUi->begin(); i != m_pendingSyncUi->end(); ++i)
{
ServerObject *uiObject = safe_cast<ServerObject *>(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<ServerController *>(getController())->onClientReady();
}
// ----------------------------------------------------------------------
void ServerObject::clearClient()
{
if (m_client)
{
safe_cast<ServerController *>(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<ServerObject*>((*i).getObject());
if (containedObject && containedObject->getCacheVersion() != 0)
containedObject->setCacheVersion(0);
}
}
if (becomingUncached && isInWorld() && !ContainerInterface::getContainedByObject(*this))
createFarNetworkUpdateVolume();
}
}
else
{
sendControllerMessageToAuthServer(CM_setCacheVersion, new MessageQueueGenericValueType<int>(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<ServerObject*>(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<Unicode::String>(newName));
}
}
//-----------------------------------------------------------------
void ServerObject::setObjectNameStringId (const StringId & id)
{
if(isAuthoritative())
{
m_nameStringId = id;
}
else
{
sendControllerMessageToAuthServer(CM_setObjectNameStringId, new MessageQueueGenericValueType<StringId>(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<ServerObject *>((*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<uint16>(chatType),
static_cast<uint16>(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<float>(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<ServerObject *> ServerObjectVector;
ServerObjectVector results;
ServerWorld::getSpatialChatListeners(*this, distance, results);
const ServerObject * const chatTarget = safe_cast<const ServerObject *>(NetworkIdManager::getObjectById (chatTargetId));
const CreatureObject * const chatTargetCreature = chatTarget ? chatTarget->asCreatureObject () : 0;
int const chatMessageIndex = ChatLogManager::getNextMessageIndex();
const ServerObject * const chatSource = safe_cast<const ServerObject *>(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<ServerObject *> 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<ServerObject *>(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<Watcher<Client> > containedClients;
getContainedClients(*this, containedClients);
ServerObject *targetContainerObject = 0;
if (targetContainer != NetworkId::cms_invalid)
{
ignoreContainer = false;
targetContainerObject = safe_cast<ServerObject *>(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<ServerObject *>(&(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<Watcher<Client> >::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<const ServerObject *>(
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<ServerController *>(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<ServerController *>(getController());
if (controller && (client || dynamic_cast<PlayerCreatureController *>(controller) || dynamic_cast<PlayerShipController *>(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<Watcher<Client> >::const_iterator i = containedClients.begin(); i != containedClients.end(); ++i)
{
Client * const containedClient = (*i).getPointer();
if (containedClient)
{
ServerObject * const so = NON_NULL(safe_cast<ServerObject *>(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<NetworkId> & clients)
{
if (!m_synchronizedUi.getPointer())
m_synchronizedUi = createSynchronizedUi ();
if (m_synchronizedUi.getPointer())
{
for (std::vector<NetworkId>::const_iterator i = clients.begin(); i != clients.end(); ++i)
{
ServerObject * client = safe_cast<ServerObject *>(
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<NetworkId>;
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<bool>(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<int>(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<int>(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<char> 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<std::pair<std::pair<unsigned long, uint64>, 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<unsigned int>(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<std::pair<std::pair<unsigned long, uint64>, 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<int>(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<std::pair<std::pair<unsigned long, uint64>, 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<unsigned long>(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<int8> 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<std::pair<std::pair<std::string, NetworkId>, 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<std::pair<std::pair<unsigned long, uint64>, 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<const void *>(&packedData[0]), static_cast<unsigned int>(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<ServerObject *>(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<ServerObject*>(&(cellProp->getOwner()));
}
}
}
else
container = safe_cast<ServerObject *>(NetworkIdManager::getObjectById(destContainer));
if (container)
{
// allow the destination container to change the warp position
float yaw = 0.0f;
ServerObject * const topmost = safe_cast<ServerObject *>(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<ServerController*>(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<int8>(), 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<CustomizationDataProperty*>(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::pair<std::string, int> >(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<const ServerObject *>((*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<int>(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<ServerObject*>(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<ServerObject const *>(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<int>(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<uint32>(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<SlottedContainer *>(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<std::pair<NetworkId, NetworkId> > 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<std::pair<NetworkId, NetworkId> > 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<std::pair<std::pair<NetworkId, NetworkId>, 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<ServerObject *>((*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<int8> &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<int8> &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<int8> &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<int8> &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<int8> &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<int8> &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<GameScriptObject *>(getScriptObject());
if (gso->hasTrigger (Scripting::TRIG_GET_ATTRIBS))
{
const CreatureObject * player = safe_cast<const CreatureObject *>(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<const char *> names (MAX_ATTRIBS, static_cast<const char *>(0));
static std::vector<const Unicode::String *> attribs (MAX_ATTRIBS, static_cast<const Unicode::String *>(0));
//-- clear the vectors
names.clear ();
attribs.clear ();
names.resize (MAX_ATTRIBS, static_cast<const char *>(0));
attribs.resize (MAX_ATTRIBS, static_cast<const Unicode::String *>(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<const char *> & newNames = params.getStringArrayParam (1);
const std::vector<const Unicode::String *> & 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<SharedObjectTemplate::GameObjectType> (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<Client *> 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<int8>(), 0, false);
}
}
//remove the vendor reinitialization flag if it exists
removeObjVarItem(OBJVAR_STRUCTURE_PLAYER_PACK);
}
else
{
ServerObject const * const containedBy = safe_cast<ServerObject const *>(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<NetworkId> 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<time_t>(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<unsigned int>(playerObject->getSessionPlayTimeDuration()));
logText += "|";
// session active play time
logText += CalendarTime::convertSecondsToHMS(static_cast<unsigned int>(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<int>(posContainer.x), static_cast<int>(posContainer.y), static_cast<int>(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<int>(posWorld.x), static_cast<int>(posWorld.y), static_cast<int>(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<std::string, int> const & xp = playerObject->getExperiencePoints();
for (std::map<std::string, int>::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<TriggerVolume *> const triggerVolumeEntered = *(getTriggerVolumeEntered());
for (std::set<TriggerVolume *>::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<int>(GameServer::getInstance().getProcessId()),
ServerWorld::getSceneId().c_str(),
position.x,
position.y,
position.z,
container ? container->getNetworkId().getValueString().c_str() : "<not contained>",
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<ResourceContainerObject *> SimilarContainers;
static SimilarContainers similarContainers;
if (item.getObjectType() == ServerResourceContainerObjectTemplate::ServerResourceContainerObjectTemplate_tag)
{
ResourceContainerObject *resourceContainerObject = safe_cast<ResourceContainerObject *>(&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<ResourceContainerObject *>(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<const ServerObject *>(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 <NetworkId> 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<ServerObject *>(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<Unicode::String>::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<Unicode::String>::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<Unicode::String>::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<CachedNetworkId> trigObjs;
getPlayerControlledContents(*this, trigObjs);
for (std::vector<CachedNetworkId>::const_iterator i = trigObjs.begin(); i != trigObjs.end(); ++i)
{
ServerObject *trigObj = safe_cast<ServerObject *>((*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<LayerProperty *>(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<const LayerProperty *>(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<std::pair<std::pair<unsigned long, uint64>, 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<std::pair<std::pair<unsigned long, uint64>, 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<std::string>(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<std::pair<std::pair<unsigned long, uint64>, 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<std::string>(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<std::pair<std::pair<unsigned long, uint64>, 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<int>(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<PatrolPathNodeProperty *>(p)->hasRoot(root))
return;
safe_cast<PatrolPathNodeProperty *>(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<ServerObject &>(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<ServerObject &>(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<CachedNetworkId> & ServerObject::getPatrolPathRoots() const
{
static const std::set<CachedNetworkId> noRoots;
const Property * p = getProperty(PatrolPathNodeProperty::getClassPropertyId());
if (p != nullptr)
{
return safe_cast<const PatrolPathNodeProperty *>(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<PatrolPathRootProperty *>(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<CachedNetworkId> & roots = getPatrolPathRoots();
if (roots.size() == 1)
{
ServerObject * root = safe_cast<ServerObject *>(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<PatrolPathRootProperty *>(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<CachedNetworkId> & roots = getPatrolPathRoots();
if (roots.size() == 1)
{
ServerObject * root = safe_cast<ServerObject *>(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<PatrolPathRootProperty *>(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<ConstWatcher<ServerObject> > & ai = pprp->getPatrollingObjects();
for (std::set<ConstWatcher<ServerObject> >::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<ServerObject *>(ai)->scheduleForAlter();
}
}
}
}
}
}
else
{
// increment the count of the root nodes of any paths we belong to
const std::set<CachedNetworkId> & roots = getPatrolPathRoots();
for (std::set<CachedNetworkId>::const_iterator i = roots.begin(); i != roots.end(); ++i)
{
ServerObject * root = safe_cast<ServerObject *>(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<PatrolPathRootProperty *>(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<CachedNetworkId> & roots = getPatrolPathRoots();
for (std::set<CachedNetworkId>::const_iterator i = roots.begin(); i != roots.end(); ++i)
{
ServerObject * root = safe_cast<ServerObject *>(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<const PatrolPathRootProperty *>(p)->getObserverCount();
}
else
{
const std::set<CachedNetworkId> & roots = getPatrolPathRoots();
for (std::set<CachedNetworkId>::const_iterator i = roots.begin(); i != roots.end(); ++i)
{
const ServerObject * root = safe_cast<const ServerObject *>(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<NetworkId> broadcasters;
for (BroadcastMap::const_iterator iter = m_broadcastBroadcasters.begin(); iter != m_broadcastBroadcasters.end(); ++iter)
IGNORE_RETURN(broadcasters.insert(iter->second));
for (std::unordered_set<NetworkId>::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<std::set<Client *>::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<int>(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<int>(-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<int>(-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);
}
}
// ======================================================================