mirror of
https://github.com/SWG-Source/src.git
synced 2026-07-29 23:15:56 -04:00
8552 lines
278 KiB
C++
Executable File
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 ¬ification, bool const hyperspaceOnCreate) :
|
|
Object (newTemplate, NetworkId::cms_invalid),
|
|
m_oldPosition (),
|
|
m_sharedTemplate (nullptr),
|
|
m_client (0),
|
|
m_observers (),
|
|
m_localFlags (0),
|
|
m_scriptObject (new GameScriptObject),
|
|
m_objVars (),
|
|
m_observersCount (0),
|
|
m_volume (newTemplate->getVolume()),
|
|
m_complexity (newTemplate->getComplexity()),
|
|
m_sceneId (ServerWorld::getSceneId()),
|
|
m_objectName (),
|
|
m_includeInBuildout (false),
|
|
m_nameStringId (),
|
|
m_staticItemName (),
|
|
m_staticItemVersion (0),
|
|
m_conversionId (0),
|
|
m_playerControlled (false),
|
|
m_synchronizedUi (),
|
|
m_pendingSyncUi (0),
|
|
m_persisted (false),
|
|
m_authServerProcessId (),
|
|
m_proxyServerProcessIds (),
|
|
m_transformSequence (),
|
|
m_cacheVersion (0),
|
|
m_loadContents (true),
|
|
m_contentsLoaded (false),
|
|
m_contentsRequested (false),
|
|
m_loadWith (NetworkId::cms_invalid),
|
|
m_cashBalance (0),
|
|
m_bankBalance (0),
|
|
m_defaultAlterTime (AlterResult::cms_keepNoAlter),
|
|
m_broadcastListeners (),
|
|
m_broadcastBroadcasters (),
|
|
m_authClientServerPackage (),
|
|
m_authClientServerPackage_np (),
|
|
m_firstParentAuthClientServerPackage(),
|
|
m_firstParentAuthClientServerPackage_np(),
|
|
m_serverPackage (),
|
|
m_serverPackage_np (),
|
|
m_sharedPackage (),
|
|
m_sharedPackage_np (),
|
|
m_networkUpdateFar (nullptr),
|
|
m_triggerVolumes (),
|
|
m_attributesAttained (),
|
|
m_attributesInterested (),
|
|
m_triggerVolumeInfo (),
|
|
m_messageTos (),
|
|
m_worldSphere (Sphere::unit),
|
|
m_dirtyArchiveCallback (),
|
|
m_unreliableControllerMessageSequenceNumber(0),
|
|
m_gameObjectType (0),
|
|
m_calledTriggerDestroy (false),
|
|
m_calledTriggerRemovingFromWorld(false),
|
|
m_loadCTSBank(false),
|
|
m_loadCTSPackedHouses(false)
|
|
{
|
|
synchronizationOnConstruct();
|
|
|
|
m_observersCount.setSourceObject(this);
|
|
m_authServerProcessId.setSourceObject(this);
|
|
|
|
NOT_NULL(newTemplate);
|
|
|
|
setLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnCreate, hyperspaceOnCreate);
|
|
setLocalFlag(LocalObjectFlags::ServerObject_HyperspaceOnDestruct, false);
|
|
|
|
#ifdef _DEBUG
|
|
setDebugName(getTemplateName());
|
|
#endif
|
|
|
|
updateWorldSphere();
|
|
|
|
const std::string & sharedTemplateName = newTemplate->getSharedTemplate();
|
|
m_sharedTemplate = dynamic_cast<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);
|
|
}
|
|
}
|
|
|
|
// ======================================================================
|
|
|