Files
src-1.2/engine/server/library/serverGame/src/shared/object/ResourceContainerObject.cpp
T

676 lines
22 KiB
C++
Executable File

// ======================================================================
//
// ResourceContainerObject.cpp
// copyright (c) 2001 Sony Online Entertainment
//
// ======================================================================
#include "serverGame/FirstServerGame.h"
#include "serverGame/ResourceContainerObject.h"
#include "UnicodeUtils.h"
#include "serverGame/ContainerInterface.h"
#include "serverGame/ResourceContainerController.h"
#include "serverGame/ResourceTypeObject.h"
#include "serverGame/ServerMessageForwarding.h"
#include "serverGame/ServerResourceClassObject.h"
#include "serverGame/ServerResourceContainerObjectTemplate.h"
#include "serverGame/ServerUniverse.h"
#include "serverGame/ServerWorld.h"
#include "sharedFoundation/ConstCharCrcLowerString.h"
#include "sharedFoundation/DynamicVariableList.h"
#include "sharedGame/SharedObjectAttributes.h"
#include "sharedGame/SharedResourceContainerObjectTemplate.h"
#include "sharedNetworkMessages/GenericValueTypeMessage.h"
#include "sharedObject/ObjectTemplateList.h"
#include "sharedObject/SlottedContainmentProperty.h"
// ======================================================================
const SharedObjectTemplate * ResourceContainerObject::m_defaultSharedTemplate = nullptr;
namespace ResourceContainerObjectNamespace
{
const std::string OBJVAR_RESOURCE_DATA("resourceData");
};
using namespace ResourceContainerObjectNamespace;
// ======================================================================
ResourceContainerObject::ResourceContainerObject(const ServerResourceContainerObjectTemplate* newTemplate) :
TangibleObject (newTemplate),
m_resourceType (NetworkId::cms_invalid),
m_quantity (0),
m_source (NetworkId::cms_invalid),
m_maxQuantity (newTemplate->getMaxResources()),
m_resourceName (),
m_parentName ()
{
addMembersToPackages();
}
//-----------------------------------------------------------------------
ResourceContainerObject::~ResourceContainerObject()
{
}
//-----------------------------------------------------------------------
/**
* Returns a shared template if none was given for this object.
*
* @return the shared template
*/
const SharedObjectTemplate * ResourceContainerObject::getDefaultSharedTemplate(void) const
{
static const ConstCharCrcLowerString templateName("object/resource_container/base/shared_resource_container_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, "ResourceContainerObject::removeDefaultTemplate");
}
return m_defaultSharedTemplate;
} // ResourceContainerObject::getDefaultSharedTemplate
//-----------------------------------------------------------------------
/**
* Cleans up the default shared template.
*/
void ResourceContainerObject::removeDefaultTemplate(void)
{
if (m_defaultSharedTemplate != nullptr)
{
m_defaultSharedTemplate->releaseReference();
m_defaultSharedTemplate = nullptr;
}
} // ResourceContainerObject::removeDefaultTemplate
//-----------------------------------------------------------------------
/**
* Creates a default controller for this object.
*
* @return the object's controller
*/
Controller* ResourceContainerObject::createDefaultController(void)
{
Controller* _controller = new ResourceContainerController(this);
setController(_controller);
return _controller;
} // ResourceContainerObject::createDefaultController
// ----------------------------------------------------------------------
bool ResourceContainerObject::addResource(ResourceTypeObject const * const resourceType, int amount, NetworkId const & sourcePlayer)
{
if (!resourceType)
return false;
return addResource(resourceType->getNetworkId(), amount, sourcePlayer);
}
// ----------------------------------------------------------------------
bool ResourceContainerObject::addResource(const NetworkId &resourceType, int amount, const NetworkId &sourcePlayer)
{
if (amount <= 0)
{
WARNING_STRICT_FATAL(amount<0,("Use removeResource to remove resources from a ResourceContainer.\n"));
return amount == 0;
}
if (isAuthoritative())
{
if (m_quantity.get() + amount > m_maxQuantity.get())
return false; // too much
if (m_quantity.get()==0)
{
// need to set the resource attribute objvar for imported resource type
if (resourceType.getValue() > NetworkId::cms_maxNetworkIdWithoutClusterId)
{
DynamicVariableList const & objvars = getObjVars();
if (!objvars.hasItem(OBJVAR_RESOURCE_DATA) || (DynamicVariable::STRING != objvars.getType(OBJVAR_RESOURCE_DATA)))
{
ResourceTypeObject const * const rto = ServerUniverse::getInstance().getImportedResourceTypeById(resourceType);
if (!rto)
return false;
std::string const resourceData = rto->getResourceTypeDataForExport();
if (resourceData.empty())
return false;
removeObjVarItem(OBJVAR_RESOURCE_DATA);
IGNORE_RETURN(setObjVarItem(OBJVAR_RESOURCE_DATA, resourceData));
}
}
m_resourceType = resourceType; // note that m_quantity should be 0 only if this is a brand new crate
m_source = sourcePlayer;
updateResourceNames();
}
else if (resourceType != m_resourceType.get())
return false; // can't contain two different kinds of resource
m_quantity=m_quantity.get() + amount;
if (sourcePlayer!=NetworkId::cms_invalid)
addResourceSource(sourcePlayer, amount);
setAttributeRevisionDirty();
return true;
}
else
{
WARNING_STRICT_FATAL(true,("Cross-server container transfers are not supported.\n"));
return false;
}
}
// ----------------------------------------------------------------------
bool ResourceContainerObject::removeResource(const NetworkId &resourceType, int amount, std::vector<std::pair<NetworkId, int> > *sources)
{
while (amount > 0)
{
int actualAmountRemoved;
NetworkId source(NetworkId::cms_invalid);
if (!internalRemoveResource(resourceType, amount, source, actualAmountRemoved))
return false;
amount -= actualAmountRemoved;
if (sources)
{
sources->push_back(std::pair<NetworkId, int>(source, actualAmountRemoved));
}
}
return true;
}
/**
* Remove up to a certain amount of resource. The resource removed will
* always be from a single source player. It may be necessary to call this
* function more than once to extract the total amount desired.
*
* @param resourceType The type of resource desired
* @param amount The maximum amount to remove
* @param source (output) Which player the resource came from (can be cms_invalid)
* @param amountRemoved (output) The actual amount removed. Will be <= amount
* @return True if at least 1 unit of the specified resource is removed
*/
bool ResourceContainerObject::internalRemoveResource(const NetworkId &resourceType, int amount, NetworkId &source, int &amountRemoved)
{
if (amount <= 0)
{
WARNING_STRICT_FATAL(amount<0,("Trying to make a deposit with a withdrawal slip. (Called removeResource with a negative amount.)\n"));
return amount == 0;
}
if (isAuthoritative())
{
if (resourceType!=m_resourceType.get())
return false; // wrong kind of resource
amountRemoved = std::min(m_quantity.get(), amount);
// Identify source (if possible)
if (m_source.get() == NetworkId::cms_invalid)
{
// multi-source container. Complicated
source = NetworkId::cms_invalid;
DynamicVariableList::NestedList varList(getObjVars(),"source");
DynamicVariableList::NestedList::const_iterator var(varList.begin());
if (var!=varList.end())
{
int varInt;
if (var.getValue(varInt))
{
amountRemoved = std::min(amountRemoved, varInt);
int remainingAmountFromSource = varInt - amountRemoved;
source = NetworkId(var.getName());
if (remainingAmountFromSource > 0)
{
IGNORE_RETURN(setObjVarItem(var.getNameWithPath(), remainingAmountFromSource));
}
else
removeObjVarItem(var.getNameWithPath());
}
}
}
else
{
// single-source container. Simple
source = m_source.get();
}
// Adjust the quantity remaining
m_quantity=m_quantity.get() - amountRemoved;
if (m_quantity.get() == 0)
IGNORE_RETURN(permanentlyDestroy(DeleteReasons::Consumed)); // containers delete themselves when empty
setAttributeRevisionDirty();
return true;
}
else
{
WARNING_STRICT_FATAL(true,("Cross-server container transfers are not supported.\n"));
return false;
}
}
// ----------------------------------------------------------------------
std::string ResourceContainerObject::getResourceCtsData() const
{
// resource came from another galaxy, so all the data is stored in the objvar
DynamicVariableList const & objvars = getObjVars();
if (objvars.hasItem(OBJVAR_RESOURCE_DATA) && (DynamicVariable::STRING == objvars.getType(OBJVAR_RESOURCE_DATA)))
{
std::string resourceData;
IGNORE_RETURN(objvars.getItem(OBJVAR_RESOURCE_DATA, resourceData));
return resourceData;
}
// resource came from this cluster, so construct the resource data
else
{
ResourceTypeObject const * const typeObj = getResourceType();
if (typeObj)
return typeObj->getResourceTypeDataForExport();
return std::string();
}
}
// ----------------------------------------------------------------------
void ResourceContainerObject::setResourceCtsData(int quantity, const std::string & resourceData)
{
if (!isAuthoritative())
return;
if (m_resourceType.get().isValid())
return;
if (m_quantity.get() > 0)
return;
if (quantity <= 0)
return;
if (resourceData.empty())
return;
removeObjVarItem(OBJVAR_RESOURCE_DATA);
NetworkId const importedResourceType = ResourceTypeObject::addImportedResourceType(resourceData);
if (importedResourceType.isValid())
{
if (importedResourceType.getValue() > NetworkId::cms_maxNetworkIdWithoutClusterId)
{
// need to tell all proxy game servers, if any, to create the imported
// resource type, if it hasn't already been created on that game server
ProxyList const &proxyList = getExposedProxyList();
if (!proxyList.empty())
{
ServerMessageForwarding::begin(std::vector<uint32>(proxyList.begin(), proxyList.end()));
GenericValueTypeMessage<std::pair<NetworkId, std::string> > const addImportedResourceTypeMsg("AddImportedResourceType", std::make_pair(importedResourceType, resourceData));
ServerMessageForwarding::send(addImportedResourceTypeMsg);
ServerMessageForwarding::end();
}
}
IGNORE_RETURN(!addResource(importedResourceType, quantity, NetworkId::cms_invalid));
}
if (!getObjVars().hasItem(OBJVAR_RESOURCE_DATA))
{
if (!m_resourceType.get().isValid() || (m_resourceType.get().getValue() > NetworkId::cms_maxNetworkIdWithoutClusterId))
IGNORE_RETURN(setObjVarItem(OBJVAR_RESOURCE_DATA, resourceData));
}
}
// ----------------------------------------------------------------------
std::string ResourceContainerObject::debugPrint() const
{
char buffer[20];
IGNORE_RETURN(snprintf(buffer, 20, "%i",m_quantity.get()));
ResourceTypeObject const * const r=ServerUniverse::getInstance().getResourceTypeById(m_resourceType.get());
if (r)
return (std::string("Resource Container with ")+buffer+" units of "+r->getResourceName());
else
return ("Invalid resource container");
}
// ----------------------------------------------------------------------
/**
* Transfer resources from one container to another.
* @return true if all the resource was tranferred, false if no resources
* or less than the requested amount was transferred.
* @todo Inter-server authority issues
*/
bool ResourceContainerObject::transferTo(ResourceContainerObject &destination, int amount)
{
if (isAuthoritative())
{
if ((m_resourceType.get() != destination.m_resourceType.get() && (destination.m_resourceType.get() != NetworkId::cms_invalid))
|| amount > m_quantity.get())
return false;
NetworkId sourcePlayer(NetworkId::cms_invalid);
int amountRemoved = 0;
while (amount > 0)
{
if (!internalRemoveResource(m_resourceType.get(), amount, sourcePlayer, amountRemoved))
return false;
if (!destination.addResource(m_resourceType.get(), amountRemoved, sourcePlayer))
{
// failed to add resource to destination, put it back into the source
IGNORE_RETURN(addResource(m_resourceType.get(), amountRemoved, sourcePlayer));
return false;
}
amount -= amountRemoved;
}
return true;
}
else
{
WARNING_STRICT_FATAL(true,("Cross-server container transfers are not supported.\n"));
return false;
}
}
// ----------------------------------------------------------------------
/**
* Split this container into two containers
* @param amount The amount to put in the new ResourceContainer
* @param destContainer The container into which the new ResourceContainer should be placed, or cms_Invalid if no container
* @param arrangementId -1 If destContainer is not slotted, otherwise the ID of the arrangement to use
* @param newLocation The coordinates at which to place the new ResourceContainer. (Ignored if it is going into a non-positional container.)
* @param actor The player making the split, or nullptr
*/
bool ResourceContainerObject::splitContainer(int amount, const CachedNetworkId &destContainer, int arrangementId, const Vector &newLocation, ServerObject *actor)
{
UNREF(actor);
bool result = false;
ResourceContainerObject *newCrate = nullptr;
if (destContainer == CachedNetworkId::cms_cachedInvalid)
{
// create in world
Transform tr;
tr.setPosition_p(newLocation);
newCrate = dynamic_cast<ResourceContainerObject*>(ServerWorld::createNewObject(getObjectTemplateName(), tr, 0, isPersisted()));
}
else if (arrangementId < 0)
{
// create in volume container
ServerObject * destObject = safe_cast<ServerObject *>(destContainer.getObject());
if (destObject == nullptr)
return false;
newCrate = dynamic_cast<ResourceContainerObject*>(ServerWorld::createNewObject(getObjectTemplateName(), *destObject, isPersisted()));
}
else
{
// create in slotted container
ServerObject * destObject = safe_cast<ServerObject *>(destContainer.getObject());
if (destObject == nullptr)
return false;
const SlottedContainmentProperty* containmentProperty = ContainerInterface::getSlottedContainmentProperty(*destObject);
if (containmentProperty == nullptr)
return false;
const SlottedContainmentProperty::SlotArrangement & slots = containmentProperty->getSlotArrangement(arrangementId);
if (slots.empty())
return false;
newCrate = dynamic_cast<ResourceContainerObject*>(ServerWorld::createNewObject(getObjectTemplateName(), *destObject, slots.front(), isPersisted()));
}
if (newCrate)
{
result = transferTo(*newCrate, amount);
if (!result)
IGNORE_RETURN(newCrate->permanentlyDestroy(DeleteReasons::BadContainerTransfer));
}
return result;
}
//----------------------------------------------------------------------
void ResourceContainerObject::updateResourceNames()
{
if (!isAuthoritative())
return;
m_resourceName.set (Unicode::emptyString);
m_parentName.set (std::string ());
m_resourceNameId.set (StringId::cms_invalid);
setAttributeRevisionDirty();
if (!m_resourceType.get ().isValid ())
{
setObjectName (Unicode::emptyString);
return;
}
//-- setup the parent inheritance chain.
//-- the client needs this information
ResourceTypeObject const * const type = ServerUniverse::getInstance().getResourceTypeById(m_resourceType.get());
if (type)
{
m_resourceName = Unicode::narrowToWide(type->getResourceName ());
if ((m_resourceName.get())[0] == '@')
{
m_resourceNameId = StringId(Unicode::wideToNarrow(m_resourceName.get()));
}
ResourceClassObject const & parentClass = type->getParentClass();
m_parentName = parentClass.getResourceClassName ();
ResourceClassObject const * const parentClass2 = parentClass.getParent ();
if (parentClass2)
{
setObjectName(Unicode::emptyString);
setObjectNameStringId (parentClass2->getFriendlyName());
}
else
{
setObjectName(Unicode::emptyString);
setObjectNameStringId (parentClass.getFriendlyName());
}
}
else
DEBUG_WARNING (true, ("Unable to find specified resource type %s", m_resourceType.get ().getValueString ().c_str ()));
}
// ----------------------------------------------------------------------
std::string ResourceContainerObject::getItemLog() const
{
char tmpBuffer[1024];
IGNORE_RETURN(snprintf(tmpBuffer, sizeof(tmpBuffer) - 1, ": %d units of %s", getQuantity(), Unicode::wideToNarrow(m_resourceName.get()).c_str()));
tmpBuffer[sizeof(tmpBuffer)-1] = 0;
return TangibleObject::getItemLog() + tmpBuffer;
}
// ----------------------------------------------------------------------
void ResourceContainerObject::getAttributes(stdvector<std::pair<std::string, Unicode::String> >::fwd &data) const
{
TangibleObject::getAttributes(data);
char valueBuffer[32];
IGNORE_RETURN(snprintf (valueBuffer, sizeof (valueBuffer), "%d/%d", getQuantity (), getMaxQuantity ()));
data.push_back (std::make_pair (SharedObjectAttributes::resource_contents, Unicode::narrowToWide (valueBuffer)));
ResourceTypeObject const * const typeObj = getResourceType();
if (typeObj)
{
data.push_back (std::make_pair (SharedObjectAttributes::resource_name, Unicode::narrowToWide(typeObj->getResourceName ())));
ResourceClassObject const & parent = typeObj->getParentClass ();
data.push_back (std::make_pair (SharedObjectAttributes::resource_class, Unicode::narrowToWide ("@" + parent.getFriendlyName ().getCanonicalRepresentation())));
typeObj->getResourceAttributes(data);
}
}
// ----------------------------------------------------------------------
void ResourceContainerObject::endBaselines()
{
TangibleObject::endBaselines();
if (isAuthoritative())
{
// if resource is an imported resource, register the imported resource type on
// this game server, if it hasn't already been registered on this game server;
// this must be done on each game server as it receives the resource container
DynamicVariableList const & objvars = getObjVars();
if (objvars.hasItem(OBJVAR_RESOURCE_DATA) && (DynamicVariable::STRING == objvars.getType(OBJVAR_RESOURCE_DATA)))
{
bool const alreadyRegistered = (m_resourceType.get().isValid() && (m_resourceType.get().getValue() > NetworkId::cms_maxNetworkIdWithoutClusterId) && ServerUniverse::getInstance().getImportedResourceTypeById(m_resourceType.get()));
if (!alreadyRegistered)
{
std::string resourceData;
IGNORE_RETURN(objvars.getItem(OBJVAR_RESOURCE_DATA, resourceData));
NetworkId const importedResourceType = ResourceTypeObject::addImportedResourceType(resourceData);
if (importedResourceType.isValid())
{
if (importedResourceType != m_resourceType.get())
m_resourceType = importedResourceType;
if (importedResourceType.getValue() <= NetworkId::cms_maxNetworkIdWithoutClusterId)
removeObjVarItem(OBJVAR_RESOURCE_DATA);
}
}
}
updateResourceNames();
}
else
{
// if resource is an imported resource, register the imported resource type on
// this game server, if it hasn't already been registered on this game server;
// this must be done on each game server as it receives the resource container
if (m_resourceType.get().isValid() && (m_resourceType.get().getValue() > NetworkId::cms_maxNetworkIdWithoutClusterId) && !ServerUniverse::getInstance().getImportedResourceTypeById(m_resourceType.get()))
{
DynamicVariableList const & objvars = getObjVars();
if (objvars.hasItem(OBJVAR_RESOURCE_DATA) && (DynamicVariable::STRING == objvars.getType(OBJVAR_RESOURCE_DATA)))
{
std::string resourceData;
IGNORE_RETURN(objvars.getItem(OBJVAR_RESOURCE_DATA, resourceData));
IGNORE_RETURN(ResourceTypeObject::addImportedResourceType(resourceData));
}
}
}
}
// ----------------------------------------------------------------------
void ResourceContainerObject::addResourceSource(const NetworkId &source, int amount)
{
if (source == m_source.get())
return; // single-source container adding more resource from the same source -- no change needed
if (m_source.get() != NetworkId::cms_invalid)
{
// converting a single-source container into a multi-source container. All resources except the ones from the new source get credited to the first source
const std::string & varname = std::string("source.") + m_source.get().getValueString();
IGNORE_RETURN(setObjVarItem(varname, m_quantity.get() - amount));
m_source = NetworkId::cms_invalid;
}
const std::string & varname = std::string("source.") + source.getValueString ();
int intvar = 0;
if (getObjVars().getItem(varname,intvar))
{
IGNORE_RETURN(setObjVarItem(varname, intvar+amount));
}
else
{
removeObjVarItem(varname);
IGNORE_RETURN(setObjVarItem(varname, amount));
}
}
// ----------------------------------------------------------------------
bool ResourceContainerObject::removeResourceWithoutExperience(const NetworkId &resourceType, int amount)
{
while (amount > 0)
{
int amountRemoved = 0;
NetworkId source;
if (!internalRemoveResource(resourceType, amount, source, amountRemoved))
return false;
amount -= amountRemoved;
}
return true;
}
// ----------------------------------------------------------------------
ResourceTypeObject const * const ResourceContainerObject::getResourceType() const
{
return ServerUniverse::getInstance().getResourceTypeById(m_resourceType.get());
}
// ----------------------------------------------------------------------
NetworkId ResourceContainerObject::getResourceTypeId() const
{
return m_resourceType.get();
}
// ----------------------------------------------------------------------
/**
* For debugging only, convert this container to the recycled equivalent.
* This is not intended for uses other than debugging, because the container
* might not be the right kind of container to hold the recycled resource
*/
bool ResourceContainerObject::debugRecycle()
{
if (!isAuthoritative())
return false;
ResourceTypeObject const * const resourceType=getResourceType();
if (!resourceType)
return false;
if (resourceType->isRecycled())
return true; // already recycled
ResourceTypeObject const * const recycledType=resourceType->getRecycledVersion();
if (!recycledType)
return false; // no recycled version of this resource
DEBUG_FATAL(recycledType == resourceType,("Programmer bug: resourceType->getRecycledVersion on %s returned the same type.",resourceType->getResourceName().c_str()));
m_resourceType = recycledType->getNetworkId();
updateResourceNames();
return true;
}
// ======================================================================