// ======================================================================
//
// TangibleObject.cpp
// Copyright 2001, 2002 Sony Online Entertainment Inc.
// All Rights Reserved.
//
// ======================================================================

#include "serverGame/FirstServerGame.h"
#include "serverGame/TangibleObject.h"

#include "SwgGameServer/CombatEngine.h"
#include "SwgGameServer/ConfigCombatEngine.h"
#include "serverGame/AiCombatPulseQueue.h"
#include "serverGame/AiCreatureController.h"
#include "serverGame/Chat.h"
#include "serverGame/CityInfo.h"
#include "serverGame/CityInterface.h"
#include "serverGame/Client.h"
#include "serverGame/CombatTracker.h"
#include "serverGame/CommandQueue.h"
#include "serverGame/ConfigServerGame.h"
#include "serverGame/ContainerInterface.h"
#include "serverGame/CreatureObject.h"
#include "serverGame/DraftSchematicObject.h"
#include "serverGame/GameServer.h"
#include "serverGame/GroupObject.h"
#include "serverGame/ManufactureSchematicObject.h"
#include "serverGame/MessageToQueue.h"
#include "serverGame/NameManager.h"
#include "serverGame/NetworkTriggerVolume.h"
#include "serverGame/NpcConversation.h"
#include "serverGame/ObjectTracker.h"
#include "serverGame/ObserveTracker.h"
#include "serverGame/PlanetObject.h"
#include "serverGame/PlayerCreatureController.h"
#include "serverGame/PlayerObject.h"
#include "serverGame/Pvp.h"
#include "serverGame/RegionMaster.h"
#include "serverGame/RegionPvp.h"
#include "serverGame/ResourceContainerObject.h"
#include "serverGame/ResourceTypeObject.h"
#include "serverGame/ServerCollisionProperty.h"
#include "serverGame/ServerLotManagerNotification.h"
#include "serverGame/ServerMessageForwarding.h"
#include "serverGame/ServerResourceClassObject.h"
#include "serverGame/ServerSynchronizedUi.h"
#include "serverGame/ServerTangibleObjectTemplate.h"
#include "serverGame/ServerUniverse.h"
#include "serverGame/ServerWorld.h"
#include "serverGame/ShipComponentDataManager.h"
#include "serverGame/ShipObject.h"
#include "serverGame/TangibleController.h"
#include "serverNetworkMessages/MessageQueueCommandQueueEnqueueFwd.h"
#include "serverNetworkMessages/MessageQueueCommandQueueForceExecuteCommandFwd.h"
#include "serverNetworkMessages/MessageToPayload.h"
#include "serverNetworkMessages/UpdateObjectOnPlanetMessage.h"
#include "serverPathfinding/ServerPathfindingNotification.h"
#include "serverScript/GameScriptObject.h"
#include "serverScript/ScriptDictionary.h"
#include "serverScript/ScriptFunctionTable.h"
#include "serverScript/ScriptParameters.h"
#include "serverUtility/ServerClock.h"
#include "sharedDebug/Profiler.h"
#include "sharedFile/FileNameUtils.h"
#include "sharedFoundation/ConstCharCrcLowerString.h"
#include "sharedFoundation/ConstCharCrcString.h"
#include "sharedFoundation/DynamicVariable.h"
#include "sharedFoundation/DynamicVariableList.h"
#include "sharedFoundation/GameControllerMessage.h"
#include "sharedFoundation/Os.h"
#include "sharedGame/Command.h"
#include "sharedGame/CraftingData.h"
#include "sharedGame/GameObjectTypes.h"
#include "sharedGame/LfgCharacterData.h"
#include "sharedGame/PvpData.h"
#include "sharedGame/SharedObjectAttributes.h"
#include "sharedGame/SharedObjectTemplate.h"
#include "sharedGame/SharedStringIds.h"
#include "sharedGame/SharedTangibleObjectTemplate.h"
#include "sharedGame/ShipComponentData.h"
#include "sharedLog/Log.h"
#include "sharedMath/PackedArgb.h"
#include "sharedNetworkMessages/GenericValueTypeMessage.h"
#include "sharedNetworkMessages/IncubatorCommitMessage.h"
#include "sharedNetworkMessages/MessageQueueCommandQueueRemove.h"
#include "sharedNetworkMessages/MessageQueueCraftIngredients.h"
#include "sharedNetworkMessages/MessageQueueGenericValueType.h"
#include "sharedNetworkMessages/MessageQueueNetworkId.h"
#include "sharedNetworkMessages/MessageQueueOpponentInfo.h"
#include "sharedNetworkMessages/MessageQueueShowFlyText.h"
#include "sharedNetworkMessages/MessageQueueShowCombatText.h"
#include "sharedNetworkMessages/MessageQueueStartNpcConversation.h"
#include "sharedNetworkMessages/MessageQueueStopNpcConversation.h"
#include "sharedNetworkMessages/ObjectChannelMessages.h"
#include "sharedNetworkMessages/UpdatePvpStatusMessage.h"
#include "sharedObject/AlterResult.h"
#include "sharedObject/AppearanceTemplateList.h"
#include "sharedObject/ContainedByProperty.h"
#include "sharedObject/Container.h"
#include "sharedObject/CustomizationData.h"
#include "sharedObject/CustomizationDataProperty.h"
#include "sharedObject/NetworkIdManager.h"
#include "sharedObject/ObjectTemplateList.h"
#include "sharedObject/SlotIdManager.h"
#include "sharedObject/SlottedContainer.h"
#include "sharedObject/SlottedContainmentProperty.h"
#include "sharedObject/StructureFootprint.h"
#include "sharedObject/VolumeContainer.h"
#include "sharedUtility/DataTable.h"
#include "sharedUtility/DataTableManager.h"

#ifdef _DEBUG
#include "serverGame/ConsoleManager.h"
#endif

#include <cstdio>
#include <algorithm>
#include <limits>

#pragma warning(disable:4355)


namespace TangibleObjectNamespace
{
	int const s_minCombatDuration = 4;
	bool isEmpty(const ServerObject& obj)
	{
		const Container* container = obj.getContainerProperty();
		if (container)
		{
			return (container->getNumberOfItems() == 0);
		}
		return true;
	}

	std::string const s_attributeBonusNames[] =
	{
		"health",
		"health_regen",
		"action",
		"action_regen",
		"mind",
		"mind_regen"
	};

	const char * const s_accessListScriptVar = "tangible_object.accessList";
	const char * const s_guildAccessListScriptVar = "tangible_object.guildAccessList";

	void getCategorizedSkillModBonuses(TangibleObject const & to, std::string const & objvarName, std::map<std::string, int> & skillModBonuses);
	int getCategorizedSkillModBonus(TangibleObject const & to, std::string const & objvarName, std::string const & skillMod);
}

//----------------------------------------------------------------------

void TangibleObjectNamespace::getCategorizedSkillModBonuses(TangibleObject const & to, std::string const & objvarName,  std::map<std::string, int> & skillModBonuses)
{
	DynamicVariableList::NestedList children(to.getObjVars(), objvarName);
	for (DynamicVariableList::NestedList::const_iterator iterChildren = children.begin(); iterChildren != children.end(); ++iterChildren)
	{
		DynamicVariable::DynamicVariableType const type = iterChildren.getType();
		if (type == DynamicVariable::INT)
		{
			int bonus = 0;
			iterChildren.getValue(bonus);

			std::string const childName = iterChildren.getName();
			std::map<std::string, int>::iterator iterFind = skillModBonuses.find(childName);
			if (iterFind != skillModBonuses.end())
				iterFind->second += bonus;
			else
				IGNORE_RETURN(skillModBonuses.insert(std::make_pair(childName, bonus)));
		}
		else if (type == DynamicVariable::LIST)
		{
			std::string const childFullName = objvarName + '.' + iterChildren.getName();
			getCategorizedSkillModBonuses(to, childFullName, skillModBonuses);
		}
	}
}

//----------------------------------------------------------------------

int TangibleObjectNamespace::getCategorizedSkillModBonus(TangibleObject const & to, std::string const & objvarName, std::string const & skillMod)
{
	int totalBonus = 0;
	DynamicVariableList::NestedList children(to.getObjVars(), objvarName);
	for (DynamicVariableList::NestedList::const_iterator iterChildren = children.begin(); iterChildren != children.end(); ++iterChildren)
	{
		DynamicVariable::DynamicVariableType const type = iterChildren.getType();
		if (type == DynamicVariable::INT)
		{
			if (iterChildren.getName() == skillMod)
			{
				int bonus = 0;
				iterChildren.getValue(bonus);

				totalBonus += bonus;
			}
		}
		else if (type == DynamicVariable::LIST)
		{
			std::string const childFullName = objvarName + '.' + iterChildren.getName();
			totalBonus += getCategorizedSkillModBonus(to, childFullName, skillMod);
		}
	}

	return totalBonus;
}

//----------------------------------------------------------------------

using namespace TangibleObjectNamespace;

//=======================================================================
// class TangibleObject::CraftingToolSyncUi

class TangibleObject::CraftingToolSyncUi : public ServerSynchronizedUi
{
public:
	CraftingToolSyncUi(TangibleObject & owner) :
		ServerSynchronizedUi(owner),
		m_prototype(),
		m_manfSchematic(),
		m_manfSchematicObserver()
	{
		addToUiPackage(m_prototype);
		addToUiPackage(m_manfSchematic);
	}

	virtual ~CraftingToolSyncUi()
	{
		if (!isOwnerChangingAuthority())
		{
			if (m_manfSchematic.get() != CachedNetworkId::cms_cachedInvalid)
			{
				safe_cast<TangibleObject *>(getOwner())->clearCraftingManufactureSchematic();
			}
			if (m_prototype.get() != CachedNetworkId::cms_cachedInvalid)
			{
				safe_cast<TangibleObject *>(getOwner())->clearCraftingPrototype();
			}
		}
	}

	const CachedNetworkId & getPrototype(void) const
	{
		return m_prototype.get();
	}

	void setPrototype(const CachedNetworkId & prototype)
	{
		m_prototype = prototype;
	}

	const CachedNetworkId & getManfSchematic(void) const
	{
		return m_manfSchematic.get();
	}

	void setManfSchematic(const CachedNetworkId & manfSchematic, const CachedNetworkId & observer, bool deleteSchematic)
	{
		m_manfSchematic = manfSchematic;
		m_manfSchematicObserver = observer;
	}

private:

	CraftingToolSyncUi(const CraftingToolSyncUi & src);
	CraftingToolSyncUi & operator =(const CraftingToolSyncUi & src);

private:

	Archive::AutoDeltaVariable<CachedNetworkId> m_prototype;
	Archive::AutoDeltaVariable<CachedNetworkId> m_manfSchematic;

	CachedNetworkId m_manfSchematicObserver;
};

//=======================================================================

static const ConstCharCrcLowerString COMMAND_GROUP_COMBAT("combat");

// armor objvars - also defined in armor.scriptlib
static const std::string OBJVAR_ARMOR_BASE =                          "armor";
static const std::string OBJVAR_ARMOR_ENCUMBRANCE = "armorencumbrance";
static const std::string OBJVAR_ENCUMBRANCE_SPLIT = "encumbranceSplit";
static const std::string OBJVAR_ARMOR_LEVEL       = "armorLevel";

// objvar to flag the object as a shield
static const std::string OBJVAR_ARMOR_SHIELD =                        "armor.isShield";

// armor datatable strings
static const std::string DATATABLE_ARMOR               = "datatables/crafting/armor.iff";
static const std::string DATATABLE_FINAL_ROW           = "final";
static const std::string DATATABLE_TYPE_COL            = "type";
static const std::string DATATABLE_MIN_ENCUMBRANCE_COL = "min_encumbrance";
static const std::string DATATABLE_MAX_ENCUMBRANCE_COL = "max_encumbrance";

// bio-link objvars
static const std::string OBJVAR_BIO_LINK      = "biolink";
static const std::string OBJVAR_BIO_LINK_ID   = "biolink.id";
// This id is used for a bio-linked item that is linked to no one.
// It is safe as long as no player has id 1 (which should be a good assumption).
// Note this value is reflected in library.utils.scriptlib.
static const NetworkId PENDING_BIO_LINK_NETWORK_ID(static_cast<NetworkId::NetworkIdType>(1));

// template for crafting components container
const static ConstCharCrcLowerString CRAFTING_COMPONENTS_CONTAINER =  "object/tangible/crafting/crafting_components_container.iff";

// slots for a crafting tool
const static ConstCharCrcLowerString INGREDIENT_HOPPER_SLOT_NAME =    "ingredient_hopper";
const static ConstCharCrcLowerString MANF_OUTPUT_SLOT_NAME =          "anythingnomod2";
const static ConstCharCrcLowerString TEMP_PROTOTYPE_SLOT_NAME =       "anythingnomod1";
const static ConstCharCrcLowerString TEMP_SCHEMATIC_SLOT_NAME =       "test_manf_schematic";

// objvars for crafting
const static std::string OBJVAR_CRAFTING_TOOL =                       "crafting.tool";
const static std::string OBJVAR_CRAFTING_STATION =                    "crafting.station";
const static std::string OBJVAR_CRAFTING_CRAFTER =                    "crafting.crafter";
const static std::string OBJVAR_CRAFTING_COMPONENTS =                 "crafting.components";
const static std::string OBJVAR_CRAFTING_SCHEMATIC =                  "crafting.source_schematic";
const static std::string OBJVAR_CRAFTING_TYPE =                       "crafting.type";
const static std::string OBJVAR_CRAFTING_REPAIR_TYPE =                "crafting.repair_type";
const static std::string OBJVAR_CRAFTING_PROTOTYPE_TIME =             "crafting.prototypeTime";
const static std::string OBJVAR_CRAFTING_PROTOTYPE_START_TIME =       "crafting.prototypeStartTime";
const static std::string OBJVAR_CRAFTING_PROTOTYPE_OBJECT =           "crafting.prototypeObject";
const static std::string OBJVAR_CRAFTING_PROTOTYPE_CRAFTER =          "crafting.prototypeCrafter";
const static std::string OBJVAR_CREATOR_ID =                          "crafting.creator.id";
const static std::string OBJVAR_CREATOR_XP =                          "crafting.creator.xp";
const static std::string OBJVAR_CREATOR_XP_TYPE =                     "crafting.creator.xpType";
const static std::string OBJVAR_REPAIR_TOOL =                         "crafting.repair";
const static std::string OBJVAR_CRAFTING_STATIONMOD  =                "crafting.stationMod";
const static std::string OBJVAR_SKILLMOD_BONUS =                      "skillmod.bonus";
const static std::string OBJVAR_CATEGORIZED_SKILLMOD_BONUS =          "categorizedSkillmod.bonus";
const static std::string OBJVAR_SKILLMOD_SOCKETS =                    "skillmod.sockets";
const static std::string OBJVAR_ATTRIBUTE_BONUS =                     "attribute.bonus";

const std::string OBJVAR_COMPONENT_DATA("crafting_components");

// battlefield objvars
const static std::string OBJVAR_BATTLEFIELD_PARTICIPANTS =            "battleDudes";

// no trade
static const std::string NOMOVE_SCRIPT  = "item.special.nomove";

static const std::string OBJVAR_DECLINE_DUEL  = "decline_duel";

const SharedObjectTemplate * TangibleObject::m_defaultSharedTemplate = nullptr;


//-----------------------------------------------------------------------
TangibleObject::TangibleObject(const ServerTangibleObjectTemplate* newTemplate) :
	ServerObject(newTemplate),
	m_combatData(nullptr),
	m_pvpType(),
	m_pvpMercenaryType(PvpType_Neutral),
	m_pvpFutureType(-1),
	m_pvpFaction(),
	m_pvpMercenaryFaction(PvpData::getNeutralFactionId()),
	m_pvpRegionCrc(),
	m_pvpEnemies(),
	m_damageTaken(0),
	m_maxHitPoints(newTemplate->getMaxHitPoints()),
	m_ownerId(NetworkId(static_cast<NetworkId::NetworkIdType>(0))),
	m_customAppearance(),
	m_appearanceData(),
	m_locationTargets(),
	m_components(),
	m_visible(true),
	m_hideFromClient(false),
	m_count(newTemplate->getCount()),
	m_condition(newTemplate->getCondition()),
	m_creatorId(NetworkId::cms_invalid),
	m_sourceDraftSchematic(0),
	m_hateList(),
	m_hateOverTime(),
	m_inCombat(false),
	m_combatStartTime(0),
	m_attackableOverride(false),
	m_passiveReveal(),
	m_passiveRevealPlayerCharacter(),
	m_mapColorOverride(0),
	m_accessList(),
	m_guildAccessList(),
	m_effectsMap(),
	m_npcConversation(nullptr),
	m_conversations()
{
	WARNING_STRICT_FATAL(!getSharedTemplate(), ("Tried to create a TANGIBLE %s object without a shared template!\n", newTemplate->DataResource::getName()));

	m_hateList.addServerNpAutoDeltaVariables(m_serverPackage_np);
	m_hateList.setOwner(this);

	m_maxHitPoints.setSourceObject(this);
	m_ownerId.setSourceObject(this);
	m_customAppearance.setSourceObject(this);
	m_appearanceData.setSourceObject(this);
	m_pvpType.setSourceObject(this);
	m_pvpMercenaryType.setSourceObject(this);
	m_pvpFaction.setSourceObject(this);
	m_pvpMercenaryFaction.setSourceObject(this);
	m_pvpRegionCrc.setSourceObject(this);
	m_pvpEnemies.setSourceObject(this);
	m_visible.setSourceObject(this);
	m_hideFromClient.setSourceObject(this);
	m_condition.setSourceObject(this);
	m_inCombat.setSourceObject(this);
	m_attackableOverride.setSourceObject(this);
	m_passiveRevealPlayerCharacter.setSourceObject(this);

	// set to not disabled, and set invulnerable and wantSawAttack from template
	int condition = m_condition.get();
	condition &= ~(  ServerTangibleObjectTemplate::C_disabled
	               | ServerTangibleObjectTemplate::C_invulnerable
	               | ServerTangibleObjectTemplate::C_wantSawAttackTrigger);
	if (newTemplate->getInvulnerable())
		condition |= ServerTangibleObjectTemplate::C_invulnerable;
	if (newTemplate->getWantSawAttackTriggers())
		condition |= ServerTangibleObjectTemplate::C_wantSawAttackTrigger;
	m_condition = condition;

	// attach trigger volumes to this object
	size_t i;
	size_t count = newTemplate->getTriggerVolumesCount();
	for (i = 0; i < count; ++i)
	{
		const TriggerVolumeData & trigger = newTemplate->getTriggerVolumes(static_cast<int>(i));
		createTriggerVolume(trigger.getRadius(), trigger.getName(), true); //@todo : add "promiscuous param to template entry
	}

	//-- initialize CustomizationDataProperty if any customization variables are declared
	//   for the SharedTangibleObjectTemplate.
	const SharedTangibleObjectTemplate *const sharedObjectTemplate = safe_cast<const SharedTangibleObjectTemplate*>(getSharedTemplate());
	if (sharedObjectTemplate)
	{
		//-- create CustomizationDataProperty if appropriate
		sharedObjectTemplate->createCustomizationDataPropertyAsNeeded(*this);

		//-- if a CustomizationDataProperty was created, attach a modification listener.
		// check if the property exists
		CustomizationDataProperty *cdProperty = safe_cast<CustomizationDataProperty*>(getProperty(CustomizationDataProperty::getClassPropertyId()));

		if (cdProperty)
		{
			// retrieve the CustomizationData instance associated with the property
			CustomizationData *const customizationData = cdProperty->fetchCustomizationData();
			NOT_NULL(customizationData);

			// attach modification callback
			customizationData->registerModificationListener(customizationDataModificationCallback, this);

			// release local reference
			customizationData->release();
		}

		if(ConfigServerGame::getCreateAppearances())
		{
			const std::string &appearanceString = sharedObjectTemplate->getAppearanceFilename();
			if (appearanceString.size() != 0 && strstr (appearanceString.c_str (), ".apt") != 0)
			{
				Appearance * newAppearance = AppearanceTemplateList::createAppearance(appearanceString.c_str());

				if(newAppearance != nullptr)
				{
					setAppearance(newAppearance);
				} else {
					DEBUG_WARNING(true, ("Missing appearance for TangibleObject::TangibleObject %s", appearanceString.c_str()));
				}
			}
		}
	}

	// If the object is a path node, attach the pathfinding notification

	if (isWaypoint())
	{
		addNotification( ServerPathfindingNotification::getInstance() );
	}

	// attach the collision property

	ServerCollisionProperty* collision = new ServerCollisionProperty(*this,getSharedTemplate());

	addProperty(*collision);

	addMembersToPackages();

	ObjectTracker::addTangible();

	if(getGameObjectType() == SharedObjectTemplate::GOT_loadbeacon)
	{
		ServerWorld::addLoadBeacon(this);
	}

	//-- add the lot manager notification
	{
		SharedTangibleObjectTemplate const * const sharedTangibleObjectTemplate = dynamic_cast<SharedTangibleObjectTemplate const *> (getSharedTemplate ());
		if (sharedTangibleObjectTemplate)
		{
			StructureFootprint const * const structureFootprint = sharedTangibleObjectTemplate->getStructureFootprint ();
			if (structureFootprint)
				addNotification (ServerLotManagerNotification::getInstance ());
		}
		else
			DEBUG_WARNING (true, ("TangibleObject::TangibleObject - [%s] does not have a shared tangible object template", getObjectTemplate ()->getName ()));
	}
}

//-----------------------------------------------------------------------

TangibleObject::~TangibleObject()
{
	//-- This must be the first line in the destructor to invalidate any watchers watching this object
	nullWatchers();

	if (getSynchronizedUi() != nullptr)
	{
		CraftingToolSyncUi * sync = dynamic_cast<CraftingToolSyncUi *>(
			getSynchronizedUi());
		if (sync != nullptr)
		{
			if (isAuthoritative() && isCraftingTool())
			{
				clearCraftingPrototype();
				clearCraftingManufactureSchematic();
			}
			else
			{
				if (!isAuthoritative())
				{
					WARNING(true, ("Tangible object %s is not authoritative "
						"but has a SynchronizedUi",
						getNetworkId().getValueString().c_str()));
				}
				if (!isCraftingTool())
				{
					WARNING(true, ("Tangible object %s is not a crafting tool "
						"but has a CraftingToolSyncUi",
						getNetworkId().getValueString().c_str()));
				}
				sync->setPrototype(CachedNetworkId::cms_cachedInvalid);
				sync->setManfSchematic(CachedNetworkId::cms_cachedInvalid,
					CachedNetworkId::cms_cachedInvalid, true);
			}
		}
	}

	if(getGameObjectType() == SharedObjectTemplate::GOT_loadbeacon)
	{
		ServerWorld::removeLoadBeacon(this);
	}

	CombatTracker::removeDefender(this);

	if (m_combatData != nullptr)
	{
		if (isAuthoritative())
		{
			if (!getCombatData()->defenseData.damage.empty())
			{
				WARNING(true, ("\tobject still has a non-empty damage list"));

				// look up the call stack information
				const int callStackOffset = 4;
				const int maxStackDepth   = 64;
				const int callStackSize = callStackOffset + maxStackDepth;
				uint32 callStack[callStackOffset + maxStackDepth];

				// look up the caller's file and line
				if (callStack[callStackOffset])
				{
					char lib[4 * 1024] = { '\0' };
					char file[4 * 1024] = { '\0' };
					int line = 0;
					REPORT_LOG(true, ("\tCall stack:"));
					for (int i = callStackOffset; i < callStackSize; ++i)
					{
						if (callStack[i])
						{
								REPORT_LOG(true, ("\t%s(%d) : caller %d\n", file, line, i-callStackOffset));
						}
					}
				}
			}
		}

		delete m_combatData;
		m_combatData = nullptr;

		if (!isPlayerControlled())
			ObjectTracker::removeCombatAI();
	}

	if (m_npcConversation != nullptr)
	{
		delete m_npcConversation;
		m_npcConversation = nullptr;
	}

	ObjectTracker::removeTangible();
}	// TangibleObject::~TangibleObject

//-----------------------------------------------------------------------

/**
 * Initializes a newly created object.
 */
void TangibleObject::initializeFirstTimeObject()
{
	DEBUG_FATAL(!isAuthoritative(), ("Trying to initialize non auth object %s", getNetworkId().getValueString().c_str()));

	// set up armor from the template
	const ServerTangibleObjectTemplate * newTemplate = safe_cast<
		const ServerTangibleObjectTemplate *>(getObjectTemplate());
	if (newTemplate != nullptr)
	{
		setInvulnerable(newTemplate->getInvulnerable());

		if (isAuthoritative())
		{
			initializeVisibility();

			const ServerArmorTemplate * armorTemplate = newTemplate->getArmor();
			if (armorTemplate != nullptr)
			{
				int const rating = static_cast<int>(armorTemplate->getRating());
				if (   rating < static_cast<int>(ServerArmorTemplate::AR_armorNone)
				    || rating > static_cast<int>(ServerArmorTemplate::AR_armorHeavy))
				{
					WARNING(true, ("TangibleObject::initializeFirstTimeObject trying "
						"to add armor data from template %s with an invalid rating "
						"of %d", armorTemplate->getName(), rating));
				}
				else
				{
					// armor integrity replaces hitpoints
					m_maxHitPoints = armorTemplate->getIntegrity();
				}
			}
		}
	}

	// attach the combat skeleton script to this object
	ServerTangibleObjectTemplate::CombatSkeleton skeleton = static_cast<ServerTangibleObjectTemplate::CombatSkeleton>(getCombatSkeleton());
	const ConfigCombatEngine::SkeletonAttackMod & skeletonMod =
		ConfigCombatEngine::getSkeletonAttackMod(skeleton);
	if (!skeletonMod.script.empty() && skeletonMod.script[0] != '\0')
	{
		NOT_NULL(getScriptObject());
		getScriptObject()->attachScript(skeletonMod.script, true);
	}

	handleContainerSetup();

	ServerObject::initializeFirstTimeObject();

	setDefaultAlterTime(AlterResult::cms_keepNoAlter);

	calcPvpableState();
	if (!isNonPvpObject())
		MessageToQueue::sendRecurringMessageToC(getNetworkId(), "C++CheckPvpRegion", std::string(), static_cast<int>(getPvpRegionCheckTime()));
}

//-----------------------------------------------------------------------

/**
 * Initializes a proxy object.
 */
void TangibleObject::endBaselines()
{
	ServerObject::endBaselines();
	handleContainerSetup();

	// set the hate list owner to ensure the player object is set correctly
	m_hateList.setOwner(this);

	if (isAuthoritative())
	{
		// fix the load_with values if they don't match
		if ( getLoadWith() != ContainerInterface::getLoadWithContainerId(*this))
		{
			setLoadWith(ContainerInterface::getLoadWithContainerId(*this));
		}

		// test to flag the object as disabled
		if (getDamageTaken() >= getMaxHitPoints())
			setDisabled(true);

		// pvp needs to be notified after an object is both initialized and made authoritative.
		Pvp::handleAuthorityAcquire(*this);

		// this code is for legacy objects already in the database
		// Don't consider virtual overrides for invulnerability in this case
		if (getCacheVersion() > 0 && !TangibleObject::isInvulnerable())
			setInvulnerable(true);

		if (getObjVars().hasItem(OBJVAR_CRAFTING_SCHEMATIC))
			setCondition(C_crafted);
		else
			clearCondition(C_crafted);

		removeObjVarItem("debuggingLogCommandEnqueue");

		// destroy duped TCG Set5 AT-AT Head ITV
		if ((getStaticItemName() == "item_tcg_loot_reward_series5_atat_head_itv") && !getObjVars().hasItem("rewardGrantInfo"))
		{
			MessageToQueue::getInstance().sendMessageToC(getNetworkId(),
				"C++DestroyDupedTcg5AtAtHeadItv",
				"",
				5,
				false);
		}
	}

	calcPvpableState();

	// check for existence of objvar to enable/disable m_logCommandEnqueue
	CommandQueue * const queue = getCommandQueue();
	if (queue != nullptr)
	{
		queue->setLogCommandEnqueue(getObjVars().hasItem("debuggingLogCommandEnqueue"));
	}

	// if this is an incubator, and it contains imported resource from another
	// galaxy, 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 incubator
	static const uint32 crcIncubator = CrcLowerString::calculateCrc("object/tangible/crafting/station/incubator_station.iff");
	if (getTemplateCrc() == crcIncubator)
	{
		DynamicVariableList const & objvars = getObjVars();
		if (objvars.hasItem("power.resourceData") && (DynamicVariable::STRING == objvars.getType("power.resourceData")))
		{
			std::string resourceData;
			IGNORE_RETURN(objvars.getItem("power.resourceData", resourceData));
			IGNORE_RETURN(ResourceTypeObject::addImportedResourceType(resourceData));
		}
	}
}	// TangibleObject::endBaselines

//-----------------------------------------------------------------------

void TangibleObject::onLoadedFromDatabase()
{
	ServerObject::onLoadedFromDatabase();

	// if we are a crafting tool that is counting down a prototype,
	// reactivate the counter
	if (isCraftingTool() && isAuthoritative())
	{
		float prototypeTime = 0;
		getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_TIME, prototypeTime);
		if (prototypeTime > 0)
		{
			int timeLeft = getCraftingToolPrototypeTime();
			if (timeLeft < 1)
				timeLeft = 1;

			NetworkId ownerId;
			NetworkId prototypeId;
			bool result = getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_OBJECT,
				prototypeId) &&	getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_CRAFTER,
				ownerId);
			if (result)
			{
				ScriptParams params;
				params.addParam(ownerId, "crafter");
				params.addParam(prototypeId, "prototype");
				ScriptDictionaryPtr dictionary;
				getScriptObject()->makeScriptDictionary(params, dictionary);
				if (dictionary.get() != nullptr)
				{
					dictionary->serialize();
					MessageToQueue::getInstance().sendMessageToJava(getNetworkId(),
						"prototypeDone", dictionary->getSerializedData(),
						timeLeft, false);
					MessageToQueue::getInstance().sendMessageToC(getNetworkId(),
						"C++updateCounter", "", 1, false);
				}
			}
		}
	}

	setDefaultAlterTime(AlterResult::cms_keepNoAlter);

	calcPvpableState();
	if (!isNonPvpObject())
		MessageToQueue::sendRecurringMessageToC(getNetworkId(), "C++CheckPvpRegion", std::string(), static_cast<int>(getPvpRegionCheckTime()));

	if(isLocked())
	{
		readInUserAccessListObjVars();
		readInGuildAccessListObjVars();
	}

	// Invalid Socket check.
	bool checkForInvalidSockets = (GameObjectTypes::isTypeOf(getGameObjectType(), SharedObjectTemplate::GOT_clothing) || 
		                           GameObjectTypes::isTypeOf(getGameObjectType(), SharedObjectTemplate::GOT_armor) ) && isCrafted();
	if(checkForInvalidSockets)
	{
		std::vector< std::pair< std::string, int > > skillMods;
		getSkillModBonuses(skillMods, false);

		if(skillMods.size() > 3) // 3 is the max legit amount for any 1 socket item
		{
			// Check all our stats, make sure they aren't over the limit for 1 socket, cross reference names with the reverse engineering datatable.
			DataTable * modTable = DataTableManager::getTable("datatables/crafting/reverse_engineering_mods.iff", true);
			if(!modTable)
				return;
			int invalidSkillCounter = 0;
			// Check each skill mod against the datatable. If we don't have that skill mod in the table, then it could be a legitimate item from the older system.
			for(unsigned int i = 0; i < skillMods.size(); ++i)
			{
				std::string skillModName = skillMods[i].first;
				int row = modTable->searchColumnString(0, skillModName);

				if(row < 0) // wasn't in our table, could be legit.
					continue;

				++invalidSkillCounter;
			}

			if(invalidSkillCounter <= 3) // Valid item.
				return;

			// Player Name
			std::string playerName = NameManager::getInstance().getPlayerFullName(getOwnerId());
			Unicode::String objName = getObjectName();
			std::string narrowObjName;
			if(!objName.empty())
				narrowObjName = Unicode::wideToNarrow(objName);
			// Possible invalid item.
			LOG("CustomerService", ("extraSocketExploit:Reason: Invalid # of Skill mods. Item Name: [%s] OID:[%s] Shared Template Name: [%s]", 
				narrowObjName.empty() ? "" : narrowObjName.c_str(), getNetworkId().getValueString().c_str(), getSharedTemplateName() ? getSharedTemplateName() : ""));
			LOG("CustomerService", ("extraSocketExploit:Owner OID: [%s] Owner Station ID: [%u] Owner Name: [%s]", 
				getOwnerId().getValueString().c_str(), NameManager::getInstance().getPlayerStationId(getOwnerId()), playerName.empty() ? "" : playerName.c_str()));
			LOG("CustomerService", ("extraSocketExploit:Skill Mods are:"));

			for(unsigned int i = 0; i < skillMods.size(); ++i)
			{
				int row = modTable->searchColumnString(0, skillMods[i].first);
				int currentValue = skillMods[i].second;
				int maxValue = 0;

				if(row < 0)
				{
					LOG("CustomerService", ("extraSocketExploit:Skill Mod: [%s] Value: [%d]", skillMods[i].first.c_str(), skillMods[i].second));
					continue;
				}

				int ratioValue = modTable->getIntValue(1, row);

				if(ratioValue != 0)
					maxValue = 35 / ratioValue;

				if(currentValue > maxValue)
					LOG("CustomerService", ("extraSocketExploit:(Invalid)Skill Mod: [%s] Value: [%d] Max Legal Value [%d]", skillMods[i].first.c_str(), skillMods[i].second, maxValue));
				else if(currentValue > 35)
					LOG("CustomerService", ("extraSocketExploit:(Invalid)Skill Mod: [%s] Value: [%d] Max Legal Value [35]", skillMods[i].first.c_str(), skillMods[i].second));
				else
					LOG("CustomerService", ("extraSocketExploit:Skill Mod: [%s] Value: [%d]", skillMods[i].first.c_str(), skillMods[i].second));
			}
		}
		else
		{
			// Check all our stats, make sure they aren't over the limit for 1 socket.
			DataTable * modTable = DataTableManager::getTable("datatables/crafting/reverse_engineering_mods.iff", true);
			if(!modTable)
				return;

			for(unsigned int i = 0; i < skillMods.size(); ++i)
			{
				std::string skillModName = skillMods[i].first;
				int currentValue = skillMods[i].second;
				int maxValue = 0;
				bool logItem = false;

				if(currentValue > 35)
					logItem = true;
				else
				{
					int row = modTable->searchColumnString(0, skillModName);

					if(row < 0)
						return;

					int ratioValue = modTable->getIntValue(1, row);

					if(ratioValue != 0)
						maxValue = 35 / ratioValue;

					if(currentValue > maxValue)
						logItem = true;
				}

				if(logItem)
				{
					// Player Name
					std::string playerName = NameManager::getInstance().getPlayerFullName(getOwnerId());
					Unicode::String objName = getObjectName();
					std::string narrowObjName;
					if(!objName.empty())
						narrowObjName = Unicode::wideToNarrow(objName);
					// Possible invalid item.
					LOG("CustomerService", ("extraSocketExploit:Reason: Invalid Skill mod value. Item Name: [%s] OID:[%s] Shared Template Name: [%s]", 
						narrowObjName.empty() ? "" : narrowObjName.c_str(), getNetworkId().getValueString().c_str(), getSharedTemplateName() ? getSharedTemplateName() : ""));
					LOG("CustomerService", ("extraSocketExploit:Owner OID: [%s] Owner Station ID: [%u] Owner Name: [%s]", 
						getOwnerId().getValueString().c_str(), NameManager::getInstance().getPlayerStationId(getOwnerId()), playerName.empty() ? "" : playerName.c_str()));
					LOG("CustomerService", ("extraSocketExploit:Skill Mods are:"));

					for(unsigned int j = 0; j < skillMods.size(); ++j)
					{

						int row = modTable->searchColumnString(0, skillMods[j].first);
						currentValue = skillMods[j].second;

						if(row < 0)
						{
							LOG("CustomerService", ("extraSocketExploit:Skill Mod: [%s] Value: [%d]", skillMods[j].first.c_str(), skillMods[j].second));
							continue;
						}

						int ratioValue = modTable->getIntValue(1, row);

						if(ratioValue != 0)
							maxValue = 35 / ratioValue;

						if(currentValue > maxValue)
							LOG("CustomerService", ("extraSocketExploit:(Invalid)Skill Mod: [%s] Value: [%d] Max Legal Value [%d]", skillMods[j].first.c_str(), skillMods[j].second, maxValue));
						else if(currentValue > 35)
							LOG("CustomerService", ("extraSocketExploit:(Invalid)Skill Mod: [%s] Value: [%d] Max Legal Value [35]", skillMods[j].first.c_str(), skillMods[j].second));
						else
							LOG("CustomerService", ("extraSocketExploit:Skill Mod: [%s] Value: [%d]", skillMods[j].first.c_str(), skillMods[j].second));
					}

					return;
				}
			}
		}
	}

	// start the decay process going
}

//-----------------------------------------------------------------------

void TangibleObject::virtualOnSetAuthority()
{
	ServerObject::virtualOnSetAuthority();
}

//-----------------------------------------------------------------------

TangibleObject * TangibleObject::getTangibleObject(NetworkId const & networkId)
{
	return asTangibleObject(NetworkIdManager::getObjectById(networkId));
}

//-----------------------------------------------------------------------

TangibleObject * TangibleObject::asTangibleObject(Object * object)
{
	ServerObject * serverObject = (object != nullptr) ? object->asServerObject() : nullptr;
	return (serverObject != nullptr) ? serverObject->asTangibleObject() : nullptr;
}

//-----------------------------------------------------------------------

TangibleObject const * TangibleObject::asTangibleObject(Object const * object)
{
	ServerObject const * serverObject = (object != nullptr) ? object->asServerObject() : nullptr;
	return (serverObject != nullptr) ? serverObject->asTangibleObject() : nullptr;
}

//-----------------------------------------------------------------------

TangibleObject * TangibleObject::asTangibleObject()
{
	return this;
}

// ----------------------------------------------------------------------

TangibleObject const * TangibleObject::asTangibleObject() const
{
	return this;
}

// ----------------------------------------------------------------------

/**
 * Returns a shared template if none was given for this object.
 *
 * @return the shared template
 */
const SharedObjectTemplate * TangibleObject::getDefaultSharedTemplate(void) const
{
static const ConstCharCrcLowerString templateName("object/tangible/base/shared_tangible_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, "TangibleObject::removeDefaultTemplate");
	}
	return m_defaultSharedTemplate;
}	// TangibleObject::getDefaultSharedTemplate

//-----------------------------------------------------------------------

/**
 * Cleans up the default shared template.
 */
void TangibleObject::removeDefaultTemplate(void)
{
	if (m_defaultSharedTemplate != nullptr)
	{
		m_defaultSharedTemplate->releaseReference();
		m_defaultSharedTemplate = nullptr;
	}
}	// TangibleObject::removeDefaultTemplate

//-----------------------------------------------------------------------

/**
 * Returns the name of the shared template that will be sent to the client when
 * this object is created. If this object has a custom appearance, that template
 * name will be returned, otherwise the default name will be returned.
 *
 *
 */
const char * TangibleObject::getClientSharedTemplateName(void) const
{
	if (!m_customAppearance.get().empty())
		return m_customAppearance.get().c_str();
	return ServerObject::getClientSharedTemplateName();
}	// TangibleObject::getClientSharedTemplateName

//-----------------------------------------------------------------------

void TangibleObject::addLocationTarget(const LocationData & location)
{
	if(isAuthoritative())
	{
		std::vector<LocationData>::const_iterator i;
		for(i = m_locationTargets.begin(); i != m_locationTargets.end(); ++i)
		{
			if((*i).name == location.name)
			{
				m_locationTargets.set(static_cast<size_t>(std::distance(m_locationTargets.begin(), i)), location);
				return;
			}
		}
		m_locationTargets.push_back(location);
		scheduleForAlter();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addLocationTarget, new MessageQueueGenericValueType<LocationData>(location));
	}
}

//-----------------------------------------------------------------------

void TangibleObject::removeLocationTarget(const Unicode::String & locationName)
{
	if(isAuthoritative())
	{
		std::vector<LocationData>::const_iterator i;
		for(i = m_locationTargets.begin(); i != m_locationTargets.end(); ++i)
		{
			if((*i).name == locationName)
			{
				m_locationTargets.erase(static_cast<size_t>(std::distance(m_locationTargets.begin(), i)));
				return;
			}
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_removeLocationTarget, new MessageQueueGenericValueType<Unicode::String>(locationName));
	}
}

//-----------------------------------------------------------------------

void TangibleObject::onArrivedAtLocation(const LocationData & location)
{
	// only invokes a script trigger, which is automagically forwarded to
	// the authoritative server
	if(getScriptObject())
	{
		ScriptParams p;
		p.addParam(location.name);
		IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_ARRIVE_AT_LOCATION, p));
	}
}
// ----------------------------------------------------------------------

/**
 * This function is called after the TangibleObject has received a
 * new custom appearance string.
 *
 * @arg value  the value of the new custom appearance string
 */
void TangibleObject::customAppearanceModified(const std::string& value)
{
	// we don't value to use the value arg since we already have the
	// data stored in m_customAppearance, but just in case the interface
	// changes, we'll use it here.

	if (value.empty())
		return;
}	// TangibleObject::customAppearanceModified

// ----------------------------------------------------------------------

/**
 * This function is called after the TangibleObject has received a
 * new appearance data customization string.
 *
 * @arg value  the value of the new appearance customization string
 */

// ----------------------------------------------------------------------

void TangibleObject::appearanceDataModified(const std::string& value)
{
	// we don't value to use the value arg since we already have the
	// data stored in m_appearanceData, but just in case the interface
	// changes, we'll use it here.  Technically we don't need to keep
	// the m_appearanceData string around on the client.  We can build
	// it at will from the appearance.

	//-- skip no-content customization data
	if (value.empty())
		return;

	// check if the property exists
	CustomizationDataProperty *cdProperty = safe_cast<CustomizationDataProperty*>(getProperty(CustomizationDataProperty::getClassPropertyId()));

	if (!cdProperty)
	{
		// every debug warning I put here goes into never-never land in addr2line.  real handy feature...
		//	DEBUG_WARNING(true, ("non-zero-length appearance string sent to object that doesn't declare any customization variables in its template."));
		// DEBUG_WARNING(true, ("received appear string for non-customizable object."));
		return;
	}

	// retrieve the CustomizationData instance associated with the property
	CustomizationData *const customizationData = cdProperty->fetchCustomizationData();
	NOT_NULL(customizationData);

	//-- initialize CustomizationData variable values from the string
	customizationData->loadLocalDataFromString(value);

	//-- release local reference
	customizationData->release();
}
//-----------------------------------------------------------------------

/**
 * Gets all the equipped items corresponding to a combat skeleton "bone".
 *
 * @param combatBone		the bone we want equipment for
 * @param items				list to be filled in with the items
 */
void TangibleObject::getEquippedItems(uint32 combatBone, std::vector<TangibleObject *> &items) const
{
	SlottedContainer const * const container = ContainerInterface::getSlottedContainer(*this);
	if (container == nullptr)
		return;

	std::vector<Container::ContainedItem> itemIds;
	container->getObjectsForCombatBone(combatBone, itemIds);

	std::vector<Container::ContainedItem>::const_iterator iter;
	for (iter = itemIds.begin(); iter != itemIds.end(); ++iter)
	{
		Object * object = (*iter).getObject();
		if (object != nullptr)
		{
			TangibleObject * item = dynamic_cast<TangibleObject *>(object);
			if (item != nullptr)
				items.push_back(item);
		}
	}
}	// TangibleObject::getEquippedItems

//-----------------------------------------------------------------------

/**
 * Returns a random equipped item corresponding to a combat skeleton "bone".
 *
 * @param combatBone		the bone we want the item for
 *
 * @return the item
 */
TangibleObject * TangibleObject::getRandomEquippedItem(uint32 combatBone) const
{
	std::vector<TangibleObject *> items;
	getEquippedItems(combatBone, items);

	if (items.empty())
	{
		return nullptr;
	}

	int index = Random::random(0, items.size() - 1);
	return items[index];
}	// TangibleObject::getRandomEquippedItem

//-----------------------------------------------------------------------

/**
 * Updates the object for damage taken in battle.
 *
 * @param time		current game time
 *
 * @return base class result
 */
float TangibleObject::alter(real time)
{
	PROFILER_AUTO_BLOCK_DEFINE("TangibleObject::alter");

	if (isAuthoritative())
	{
		// Determine the combat state of the object
		{
			CombatEngine::alter(*this);

			m_hateList.alter();

			if (!m_hateList.isEmpty())
			{
				if (!isPlayerControlled())
				{
					// AI

					// Schedule the AI for an update
					{
						AiCombatPulseQueue::schedule(this, 0, static_cast<unsigned long>(time * 1000.0f));
					}
				}
				else
				{
					// Player

					// Make sure the hate list is purged of invalid hate targets. If a player is no
					// longer hated by a hate target, then remove that target from the player's list.
					// A player must be in their hate target's hate list and vice versa.
					{
						typedef std::vector<NetworkId> PurgeList;
						static PurgeList purgeList;
						purgeList.clear();

						HateList::UnSortedList const & unSortedHateList = getUnSortedHateList();
						HateList::UnSortedList::const_iterator iterUnSortedHateList = unSortedHateList.begin();

						for (; iterUnSortedHateList != unSortedHateList.end(); ++iterUnSortedHateList)
						{
							CachedNetworkId const & id = iterUnSortedHateList->first;

							if (!isHatedBy(id.getObject()))
							{
								purgeList.push_back(id);
							}
						}

						PurgeList::const_iterator iterPurgeList = purgeList.begin();

						for (; iterPurgeList != purgeList.end(); ++iterPurgeList)
						{
							removeHateTarget(*iterPurgeList);
						}
					}
				}
			}

			if (m_hateList.isEmpty())
			{
				if (   !isPlayerControlled()
					|| (getCombatDuration() > s_minCombatDuration))
				{
					setInCombat(false);
				}
			}
		}
	}

	// check for location target arrival
	if(isAuthoritative() && !m_locationTargets.empty())
	{
		Vector const effectiveWorldLocation = findPosition_w();
		for (std::vector<LocationData>::const_iterator i = m_locationTargets.begin(); i != m_locationTargets.end(); ++i)
		{
			if ((*i).scene == ServerWorld::getSceneId())
			{
				Sphere const &s = (*i).location;
				// 3d targets are denoted by y coordinates > 50k, with 100k added to them
				// If it's a 3d target, add 100k to our y coordinate
				// If it's a 2d target, set our y coordinate to the target's
				// 2d comparison, put locations on same plane
				Vector v = effectiveWorldLocation;
				if (s.getCenter().y > 50000.0f)
					v.y += 100000.0f;
				else
					v.y = s.getCenter().y;

				if (s.contains(v))
				{
					// copy the location data so we can remove it from the vector prior to triggerring
					LocationData triggeredLocation(*i);
					removeLocationTarget((*i).name);
					// trigger any scripts
					onArrivedAtLocation(triggeredLocation);
					// we'll catch any additional location triggers going off during the next alter
					break;
				}
			}
		}
	}

	//-- handle modified customization data
	if (isCustomizationDataModified())
	{
		// associated CustomizationData instance is modified.  Pack CustomizationData
		// into m_appearanceData string, which will get sent to others.

		//-- get property
		CustomizationDataProperty *cdProperty = safe_cast<CustomizationDataProperty*>(getProperty(CustomizationDataProperty::getClassPropertyId()));
		if (!cdProperty)
		{
			DEBUG_WARNING(true, ("customization data modified but no customization data!"));
		}
		else
		{
			// retrieve the CustomizationData instance associated with the property
			CustomizationData *const customizationData = cdProperty->fetchCustomizationData();
			NOT_NULL(customizationData);

			//-- initialize CustomizationData variable values from the string
			m_appearanceData.set(customizationData->writeLocalDataToString());

			//-- release reference
			customizationData->release();

		}

		// reset flag
		setCustomizationDataModified(false);
	}

	return ServerObject::alter(time);
}	// TangibleObject::alter

//-----------------------------------------------------------------------

float TangibleObject::getNextAlterTime(float baseAlterTime) const
{
	if (baseAlterTime == AlterResult::cms_kill || baseAlterTime == AlterResult::cms_alterNextFrame)
		return baseAlterTime;

	float result = baseAlterTime;

	if (getSynchronizedUi() && dynamic_cast<const CraftingToolSyncUi *>(getSynchronizedUi()))
		return AlterResult::cms_alterNextFrame;

	if (isAuthoritative())
	{
		if (isInCombat())
			return AlterResult::cms_alterNextFrame;

		if (!m_locationTargets.empty())
			AlterResult::incorporateAlterResult(result, ConfigServerGame::getLocationTargetCheckIntervalSec());
	}

	return result;
}

//-----------------------------------------------------------------------

void TangibleObject::conclude()
{
	PROFILER_AUTO_BLOCK_DEFINE("TangibleObject::conclude");

	// if we are a crafting tool, conclude our prototype and manf schematic
	if (getSynchronizedUi() != nullptr)
	{
		const CraftingToolSyncUi * sync = dynamic_cast<const CraftingToolSyncUi *>(
			getSynchronizedUi());
		if (sync != nullptr)
		{
			Object * object = sync->getPrototype().getObject();
			if (object != nullptr)
				object->conclude();
			object = sync->getManfSchematic().getObject();
			if (object != nullptr)
				object->conclude();
		}
	}

	ServerObject::conclude();
}

//-----------------------------------------------------------------------

float TangibleObject::getPvpRegionCheckTime()
{
	return ConfigServerGame::getTangiblePvpRegionCheckTime();
}

//-----------------------------------------------------------------------

void TangibleObject::setPvpType(Pvp::PvpType pvpType)
{
	FATAL(!isAuthoritative(), ("Inappropriate use of TangibleObject::setPvpType - this should only be called from the Pvp interface."));
	Pvp::PvpType oldType = static_cast<Pvp::PvpType>(m_pvpType.get());
	if (oldType != pvpType)
	{
		m_pvpType.set(static_cast<int>(pvpType));
		m_pvpFutureType.set(-1);
		if (getScriptObject())
		{
			ScriptParams params;
			params.addParam(oldType);
			params.addParam(pvpType);
			IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_PVP_TYPE_CHANGED, params));
		}
	}
}

//-----------------------------------------------------------------------

void TangibleObject::setPvpFutureType(Pvp::PvpType pvpFutureType)
{
	FATAL(!isAuthoritative(), ("Inappropriate use of TangibleObject::setPvpFutureType - this should only be called from the Pvp interface."));
	Pvp::PvpType oldFutureType = static_cast<Pvp::PvpType>(m_pvpFutureType.get());
	if (oldFutureType != pvpFutureType)
	{
		m_pvpFutureType.set(static_cast<int>(pvpFutureType));
	}
}

//-----------------------------------------------------------------------

void TangibleObject::setPvpFaction(Pvp::FactionId factionId)
{
	FATAL(!isAuthoritative(), ("Inappropriate use of TangibleObject::setPvpFaction - this should only be called from the Pvp interface."));
	Pvp::FactionId oldFaction = static_cast<Pvp::FactionId>(m_pvpFaction.get());
	if (oldFaction != factionId)
	{
		m_pvpFaction.set(static_cast<uint32>(factionId));
		if (getScriptObject())
		{
			ScriptParams params;
			params.addParam(static_cast<int>(oldFaction));
			params.addParam(static_cast<int>(factionId));
			IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_PVP_FACTION_CHANGED, params));
		}

		// if player character is changing faction, clear
		// out current accumulated CGW points and rating;
		// this *MUST* be done after m_pvpFaction has
		// been set to the new faction because 
		// modifyCurrentGcwRating() uses the faction information
		CreatureObject * creature = asCreatureObject();
		if (creature)
		{
			PlayerObject * player = PlayerCreatureController::getPlayerObject(creature);
			if (player)
			{
				// decrementing the value by a really large
				// number will cause it to reset to 0
				static int const min = std::numeric_limits<int>::min();

				player->modifyCurrentGcwPoints(min, false);
				player->modifyCurrentGcwRating(min, true);

				// if player is no longer factional, reset current pvp kills
				if (!PvpData::isImperialFactionId(factionId) && !PvpData::isRebelFactionId(factionId))
					player->modifyCurrentPvpKills(min, false);

				// if new faction is not neutral, remove any mercenary status
				if (!PvpData::isNeutralFactionId(factionId))
					Pvp::setNeutralMercenaryFaction(*creature, PvpData::getNeutralFactionId(), PvpType_Neutral);
			}
		}
	}
}

//-----------------------------------------------------------------------

void TangibleObject::setPvpMercenaryFaction(Pvp::FactionId factionId, Pvp::PvpType pvpType)
{
	FATAL(!isAuthoritative(), ("Inappropriate use of TangibleObject::setPvpMercenaryFaction - this should only be called from the Pvp interface."));

	// this only applies to neutral
	if (!PvpData::isNeutralFactionId(m_pvpFaction.get()) && !PvpData::isNeutralFactionId(factionId))
		return;

	// this only applies to player character
	PlayerObject * const po = PlayerCreatureController::getPlayerObject(asCreatureObject());
	if (!po || !po->isAuthoritative())
		return;

	// see if mercenary functionality is allowed
	if (!PvpData::isNeutralFactionId(factionId))
	{
		if (PvpData::isImperialFactionId(factionId))
		{
			if (pvpType == PvpType_Covert)
			{
				if (!ConfigServerGame::getEnableCovertImperialMercenary())
					return;

				po->modifyCollectionSlotValue("covert_imperial_mercenary", 1);
				po->modifyCollectionSlotValue("overt_imperial_mercenary", -1);
				po->modifyCollectionSlotValue("covert_rebel_mercenary", -1);
				po->modifyCollectionSlotValue("overt_rebel_mercenary", -1);

				std::string const & currentTitle = po->getTitle();
				if (po->hasCompletedCollectionSlot("covert_imperial_mercenary"))
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle("covert_imperial_mercenary");
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle("covert_imperial_sympathizer");
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle("covert_imperial_loyalist");
				}
				else
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle(std::string());
				}
			}
			else if (pvpType == PvpType_Declared)
			{
				if (!ConfigServerGame::getEnableOvertImperialMercenary())
					return;

				po->modifyCollectionSlotValue("covert_imperial_mercenary", -1);
				po->modifyCollectionSlotValue("overt_imperial_mercenary", 1);
				po->modifyCollectionSlotValue("covert_rebel_mercenary", -1);
				po->modifyCollectionSlotValue("overt_rebel_mercenary", -1);

				std::string const & currentTitle = po->getTitle();
				if (po->hasCompletedCollectionSlot("overt_imperial_mercenary"))
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle("overt_imperial_mercenary");
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle("overt_imperial_sympathizer");
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle("overt_imperial_loyalist");
				}
				else
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle(std::string());
				}
			}
			else
			{
				return;
			}
		}
		else if (PvpData::isRebelFactionId(factionId))
		{
			if (pvpType == PvpType_Covert)
			{
				if (!ConfigServerGame::getEnableCovertRebelMercenary())
					return;

				po->modifyCollectionSlotValue("covert_imperial_mercenary", -1);
				po->modifyCollectionSlotValue("overt_imperial_mercenary", -1);
				po->modifyCollectionSlotValue("covert_rebel_mercenary", 1);
				po->modifyCollectionSlotValue("overt_rebel_mercenary", -1);

				std::string const & currentTitle = po->getTitle();
				if (po->hasCompletedCollectionSlot("covert_rebel_mercenary"))
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle("covert_rebel_mercenary");
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle("covert_rebel_sympathizer");
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle("covert_rebel_insurgent");
				}
				else
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle(std::string());
				}
			}
			else if (pvpType == PvpType_Declared)
			{
				if (!ConfigServerGame::getEnableOvertRebelMercenary())
					return;

				po->modifyCollectionSlotValue("covert_imperial_mercenary", -1);
				po->modifyCollectionSlotValue("overt_imperial_mercenary", -1);
				po->modifyCollectionSlotValue("covert_rebel_mercenary", -1);
				po->modifyCollectionSlotValue("overt_rebel_mercenary", 1);

				std::string const & currentTitle = po->getTitle();
				if (po->hasCompletedCollectionSlot("overt_rebel_mercenary"))
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle("overt_rebel_mercenary");
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle("overt_rebel_sympathizer");
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle("overt_rebel_insurgent");
				}
				else
				{
					if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer"))
						po->setTitle(std::string());
					else if ((currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
						po->setTitle(std::string());
				}
			}
			else
			{
				return;
			}
		}
		else
		{
			return;
		}
	}
	else
	{
		po->modifyCollectionSlotValue("covert_imperial_mercenary", -1);
		po->modifyCollectionSlotValue("overt_imperial_mercenary", -1);
		po->modifyCollectionSlotValue("covert_rebel_mercenary", -1);
		po->modifyCollectionSlotValue("overt_rebel_mercenary", -1);

		std::string const & currentTitle = po->getTitle();
		if ((currentTitle == "covert_imperial_mercenary") || (currentTitle == "overt_imperial_mercenary") || (currentTitle == "covert_rebel_mercenary") || (currentTitle == "overt_rebel_mercenary") ||
		    (currentTitle == "covert_imperial_sympathizer") || (currentTitle == "overt_imperial_sympathizer") || (currentTitle == "covert_rebel_sympathizer") || (currentTitle == "overt_rebel_sympathizer") ||
		    (currentTitle == "covert_imperial_loyalist") || (currentTitle == "overt_imperial_loyalist") || (currentTitle == "covert_rebel_insurgent") || (currentTitle == "overt_rebel_insurgent"))
		{
			po->setTitle(std::string());
		}
	}

	// flag when the player stopped being a factional helper for the particular faction
	if (m_pvpMercenaryFaction.get() != factionId)
	{
		if (PvpData::isImperialFactionId(m_pvpMercenaryFaction.get()))
		{
			IGNORE_RETURN(setObjVarItem("factionalHelper.timeStopHelpingImperial", static_cast<int>(::time(nullptr))));
			removeObjVarItem("factionalHelper.timeStopHelpingRebel");
		}
		else if (PvpData::isRebelFactionId(m_pvpMercenaryFaction.get()))
		{
			IGNORE_RETURN(setObjVarItem("factionalHelper.timeStopHelpingRebel", static_cast<int>(::time(nullptr))));
			removeObjVarItem("factionalHelper.timeStopHelpingImperial");
		}
	}

	m_pvpMercenaryFaction.set(static_cast<uint32>(factionId));
	m_pvpMercenaryType.set(((PvpData::isNeutralFactionId(factionId)) ? PvpType_Neutral : static_cast<int>(pvpType)));
}

//-----------------------------------------------------------------------

Region const *TangibleObject::getPvpRegion() const
{
	uint32 crc = m_pvpRegionCrc.get();
	if (crc)
		return Region::findRegionByNameCrc(crc);
	return 0;
}

//-----------------------------------------------------------------------

/**
 * Sets the pvp region this object is in.
 *
 * @param pvpRegion		the pvp region
 */
void TangibleObject::setPvpRegion(Region const * pvpRegion)
{
	if (isAuthoritative())
	{
		uint32 crc = pvpRegion ? pvpRegion->getNameCrc() : 0;
		if (m_pvpRegionCrc.get() != crc)
			m_pvpRegionCrc = crc;
	}
	else
	{
		FATAL(true, ("TangibleObject::setPvpRegion : send message to auth object!!!"));
	}
}

//-----------------------------------------------------------------------

/**
 * Creates a default controller for this object.
 *
 * @return the object's controller
 */
Controller* TangibleObject::createDefaultController(void)
{
	Controller* controller = new TangibleController(this);

	setController(controller);
	return controller;
}	// TangibleObject::createDefaultController

//-----------------------------------------------------------------------

void TangibleObject::forwardServerObjectSpecificBaselines() const
{
	ServerObject::forwardServerObjectSpecificBaselines();
}

//-----------------------------------------------------------------------

TangibleObject const &TangibleObject::getPvpViewer(Client const &client) const
{
	TangibleObject const * const clientObj = safe_cast<TangibleObject const *>(client.getCharacterObject());
	FATAL(!clientObj, ("Client trying to get pvp viewer with no character object?"));
	return *clientObj;
}

//-----------------------------------------------------------------------

void TangibleObject::sendObjectSpecificBaselinesToClient(Client const &client) const
{
	ServerObject::sendObjectSpecificBaselinesToClient(client);
	if (!isNonPvpObject())
	{
		uint32 flags, factionId;
		Pvp::getClientVisibleStatus(client, *this, flags, factionId);
		UpdatePvpStatusMessage const statusMessage(getNetworkId(), flags, factionId);
		client.send(statusMessage, true);
		PvpUpdateObserver::updatePvpStatusCache(&client, *this, flags, factionId);
	}
}

//-----------------------------------------------------------------------

/**
 * Flags the object as being cached on the client or not.
 */
void TangibleObject::setCacheVersion(const int cacheVersion)
{
	ServerObject::setCacheVersion(cacheVersion);

	// if we are cached, we need to be made invulnerable
	// @todo: if we are not cached, do we need to change our invulnerability?
	if (cacheVersion > 0)
		setInvulnerable(true);
}	// TangibleObject::setCacheVersion

//-----------------------------------------------------------------------

/**
 * Allows an object to do pre-deletion cleanup.
 */
void TangibleObject::onRemovingFromWorld()
{
	ServerObject::onRemovingFromWorld();

	clearHateList();
}

// ----------------------------------------------------------------------

void TangibleObject::unload()
{
	ServerObject::unload();
}

// ----------------------------------------------------------------------

void TangibleObject::onPermanentlyDestroyed()
{
	ServerObject::onPermanentlyDestroyed();

	if (isAuthoritative())
	{
		clearHateList();

		if (isCraftingTool())
		{
			ServerObject * owner = ServerWorld::findObjectByNetworkId(getOwnerId());
			if (owner != nullptr && owner->asCreatureObject() != nullptr)
			{
				PlayerObject * player = PlayerCreatureController::getPlayerObject(
					owner->asCreatureObject());
				if (player != nullptr && player->isCrafting() && player->getCraftingTool() == getNetworkId())
					player->stopCrafting(false);
			}
		}
	}

	if (isWaypoint())
	{
		ServerPathfindingNotification::destroyWaypoint(this);
	}
}

//-----------------------------------------------------------------------

/**
 * Creates a synchronized ui class in order to get data to the client during a
 * crafting session.
 *
 * @return the newly created class
 */
ServerSynchronizedUi * TangibleObject::createSynchronizedUi ()
{
	return new CraftingToolSyncUi(*this);
}


// ======================================================================
// TangibleObject private static member functions
// ======================================================================

void TangibleObject::customizationDataModificationCallback(const CustomizationData & /* customizationData */, const void *context)
{
	if (!context)
		return;

	//-- turn context into TangibleObject
	TangibleObject *const tangibleObject = const_cast<TangibleObject*>(reinterpret_cast<const TangibleObject*>(context));

	//-- set the tangible object's customization data modification flag now.
	//   The reason we don't handle the customization data change now is that
	//   we want to lump up all the changes that occur during one server
	//   frame and handle the string conversion once.
	tangibleObject->setCustomizationDataModified(true);
}

// ======================================================================
// TangibleObject private member functions
// ======================================================================

void TangibleObject::handleContainerSetup()
{

}

//-----------------------------------------------------------------------

void TangibleObject::setOwnerId(NetworkId const &id)
{
	if(isAuthoritative())
	{
		// if we are changing owners, we are no longer insured
		bool ownerChanged = false;
		if (m_ownerId.get() != id)
		{
			ownerChanged = true;
			setInsured(false);
		}

		m_ownerId = id;
		Container* container = ContainerInterface::getContainer(*this);
		if (container)
		{
			ContainerIterator i = container->begin();
			for (; i != container->end(); ++i)
			{
				ServerObject* content = safe_cast<ServerObject*>((*i).getObject());
				if (content && !dynamic_cast<CreatureObject*>(content))
				{
					content->setOwnerId(id);
				}
			}
		}

		// if we're hidden, who can see us has changed
		if (ownerChanged && isInWorld() && isHidden())
			visibilityDataModified();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setOwnerId, new MessageQueueNetworkId(id));
	}
}
//-----------------------------------------------------------------------

bool TangibleObject::onContainerAboutToTransfer(ServerObject * destination, ServerObject* transferer)
{
	const int got = getGameObjectType ();
	if (got == SharedObjectTemplate::GOT_misc_crafting_station && destination && !destination->asCellObject())
	{
		//Only allow stations to be trasferred if their hoppers are empty
		Container* container = ContainerInterface::getContainer(*this);
		if (container)
		{
			ContainerIterator i = container->begin();
			for (; i != container->end(); ++i)
			{
				ServerObject* content = safe_cast<ServerObject*>((*i).getObject());
				if (!isEmpty(*content))
				{

					if (transferer && transferer->getClient())
						ContainerInterface::sendContainerMessageToClient(*transferer, Container::CEC_HopperNotEmpty);
					return false;
				}
			}
		}
	}
	if (destination != nullptr && getObjVars().hasItem(OBJVAR_BIO_LINK_ID))
	{
		// if this item is bio-linked and a player is trying to equip it, make
		// sure the link id matches the player's id
		if (destination->asCreatureObject() != nullptr)
		{
			// allow holograms to have biolinked items transferred to them
			int hologramVal = 0;
			destination->getObjVars().getItem("hologram_performer", hologramVal);
			
			NetworkId linkId(getBioLink());
			if (linkId != destination->getNetworkId() && hologramVal == 0)
			{
				if (transferer && transferer->getClient())
					ContainerInterface::sendContainerMessageToClient(*transferer, Container::CEC_BioLinkedToOtherPlayer);
				return false;
			}
		}

	    // Check to see if we're trying to appearance equip a biolinked item.
		Object * containedBy = ContainerInterface::getContainedByObject(*destination);
		if(containedBy && containedBy->asServerObject() && containedBy->asServerObject()->asCreatureObject())
		{
			CreatureObject * creatureContainer = containedBy->asServerObject()->asCreatureObject();
			if(creatureContainer && creatureContainer->getAppearanceInventory() && creatureContainer->getAppearanceInventory() == destination)
			{
				// We're trying to appearance equip a biolinked item. Make sure the BioLink ID matches our player ID.
				NetworkId linkId(getBioLink());
				if (linkId != creatureContainer->getNetworkId())
				{
					if (transferer && transferer->getClient())
						ContainerInterface::sendContainerMessageToClient(*transferer, Container::CEC_BioLinkedToOtherPlayer);
					return false;
				}
			}
		}
	}
	return ServerObject::onContainerAboutToTransfer(destination, transferer);
}

//-----------------------------------------------------------------------

bool TangibleObject::onContainerChildAboutToLoseItem(ServerObject *destination, ServerObject &item, ServerObject *transferer)
{
	int const got = getGameObjectType();
	if (transferer && got == SharedObjectTemplate::GOT_misc_crafting_station && !isInWorld())
		return false;

	if (getPortalProperty() && !onPobChildAboutToLoseItem(destination, item, transferer))
		return false;

	return ServerObject::onContainerChildAboutToLoseItem(destination, item, transferer);
}

//-----------------------------------------------------------------------

bool TangibleObject::onContainerChildAboutToGainItem(ServerObject &item, ServerObject *destination, ServerObject *transferer)
{
	int const got = getGameObjectType();
	if (transferer && got == SharedObjectTemplate::GOT_misc_crafting_station && !isInWorld())
		return false;

	if (getPortalProperty() && !onPobChildAboutToGainItem(item, destination, transferer))
		return false;

	return ServerObject::onContainerChildAboutToGainItem(item, destination, transferer);
}

// ----------------------------------------------------------------------

void TangibleObject::onContainerChildLostItem(ServerObject *destination, ServerObject &item, ServerObject *source, ServerObject *transferer)
{
	if (getPortalProperty())
		onPobChildLostItem(destination, item, source, transferer);
	ServerObject::onContainerChildLostItem(destination, item, source, transferer);
}

// ----------------------------------------------------------------------

void TangibleObject::onContainerChildGainItem(ServerObject &item, ServerObject *source, ServerObject *transferer)
{
	if (getPortalProperty())
		onPobChildGainItem(item, source, transferer);
	ServerObject::onContainerChildGainItem(item, source, transferer);
}

// ----------------------------------------------------------------------

int TangibleObject::onContainerAboutToGainItem(ServerObject& item, ServerObject* transferer)
{
	if (transferer && isCraftingTool())
	{
		return Container::CEC_BlockedByDestinationContainer;
	}
	return ServerObject::onContainerAboutToGainItem(item, transferer);
}

//----------------------------------------------------------------------

void TangibleObject::onContainerGainItem(ServerObject& item, ServerObject* source, ServerObject* transferer)
{
	ServerObject::onContainerGainItem(item, source, transferer);
	//do not set owners of creatures
	//and do not set the owner of items going into the bazaar
	if (getOwnerId() != NetworkId::cms_invalid && !item.asCreatureObject() && !isBazaarTerminal())
		item.setOwnerId(getOwnerId());
}

// ----------------------------------------------------------------------

Footprint *TangibleObject::getFootprint()
{
	CollisionProperty *const property = getCollisionProperty();
	if (property)
		return property->getFootprint();
	else
		return nullptr;
}

//-----------------------------------------------------------------------

Footprint const *TangibleObject::getFootprint() const
{
	CollisionProperty const *const property = getCollisionProperty();
	if (property)
		return property->getFootprint();
	else
		return nullptr;
}

// ----------------------------------------------------------------------

/**
 * Sets up the object for being in or out of combat.
 *
 * @param inCombat		flag to put the object in combat-ready state or not
 *
 * @return true if state was set, false if something went wrong
 */
void TangibleObject::setInCombat(bool inCombat)
{
	m_inCombat = inCombat;
}

//-----------------------------------------------------------------------

/**
 * If this object is in combat, adds an aim to it.
 */
void TangibleObject::addAim(void)
{
	if(isAuthoritative())
	{
		if (isInCombat())
		{
			CombatEngineData::AttackData & attackData = NON_NULL(getCombatData())->attackData;

			if (attackData.aims < ConfigCombatEngine::getMaxAims())
			{
				++attackData.aims;
			}
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addAim, 0);
	}
}	// TangibleObject::addAim

//-----------------------------------------------------------------------

/**
 * If this object is in combat, clear's it's aims.
 */
void TangibleObject::clearAims(void)
{
	if(isAuthoritative())
	{
		if (isInCombat())
		{
			CombatEngineData::AttackData & attackData = NON_NULL(getCombatData())->attackData;
			attackData.aims = 0;
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_clearAims, 0);
	}
}	// TangibleObject::clearAims

//-----------------------------------------------------------------------

/**
 * Applies damage to the object. No other function should modify m_damageTaken.
 *
 * @param delta					amount of damage to apply
 * @param ignoreInvulnerable	flag to ignore the invulnerable setting of this object
 * @param source				who is doing the damage
 *
 * @return the amount of damage actually applied
 */
int TangibleObject::alterHitPoints(int delta, bool ignoreInvulnerable,
	const NetworkId & source)
{
	if(isAuthoritative())
	{
#ifdef _DEBUG
		char debugBuffer[1024];
		const char * craftedString = "";
		Client *client = nullptr;
		ServerObject * sourceObject = safe_cast<ServerObject *>(NetworkIdManager::getObjectById(source));
		if (source != NetworkId::cms_invalid && sourceObject != nullptr)
			client = sourceObject->getClient();
#endif

		// Only consider tangible invulnerability rules for this case
		if (isDisabled() || (!ignoreInvulnerable && TangibleObject::isInvulnerable()))
			return 0;

		int totalHitPoints = getMaxHitPoints();
		int totalDamageTaken = m_damageTaken.get() - delta;

#ifdef _DEBUG
		if (ConfigCombatEngine::getDebugDamage())
		{
			snprintf(debugBuffer, sizeof(debugBuffer), "%s object %s (hp=%d) damaged "
				"from %d to %d\n", craftedString, getNetworkId().getValueString().c_str(),
				totalHitPoints,	m_damageTaken.get(), totalDamageTaken);
			DEBUG_REPORT_LOG (true, ("%s", debugBuffer));
			if (source != NetworkId::cms_invalid && client != nullptr)
				ConsoleMgr::broadcastString(debugBuffer, client);
		}
#endif

		if (totalDamageTaken >= totalHitPoints)
		{
			// clip delta and damage taken
			if (isDisabled())
				delta = 0;
			else
				delta += totalDamageTaken - totalHitPoints;
			totalDamageTaken = totalHitPoints;
		}
		else if (totalDamageTaken < 0)
			totalDamageTaken = 0;
		if (totalDamageTaken == totalHitPoints)
			setDisabled(true);

		m_damageTaken = totalDamageTaken;

		if (isDisabled())
		{
#ifdef _DEBUG
			if (ConfigCombatEngine::getDebugDamage())
			{
				snprintf(debugBuffer, sizeof(debugBuffer), "%s object %s has been "
					"disabled\n", craftedString, getNetworkId().getValueString().c_str());
				DEBUG_REPORT_LOG (true, ("%s", debugBuffer));
				if (source != NetworkId::cms_invalid && client != nullptr)
					ConsoleMgr::broadcastString(debugBuffer, client);
			}
#endif
			ScriptParams params;
			params.addParam(source);
			IGNORE_RETURN(getScriptObject()->trigAllScripts(
				Scripting::TRIG_OBJECT_DISABLED, params));
		}
		scheduleForAlter();
		return delta;
	}
	else
	{
		sendControllerMessageToAuthServer(CM_alterHitPoints, new MessageQueueGenericValueType<std::pair<int, NetworkId> >(std::make_pair(delta, source)));
		return 0;
	}
}	// TangibleObject::alterHitpoints

//-----------------------------------------------------------------------

/**
 * Applies damage done during combat to the object.
 *
 * @param damageData		the damage
 */
void TangibleObject::applyDamage(const CombatEngineData::DamageData &damageData)
{
	int totalDamage = 0;

	WARNING_STRICT_FATAL(! isAuthoritative(), ("TangibleObject::applyDamage() invoked on a NON-AUTHORITATIVE object %s:%s.", getObjectTemplateName(), getNetworkId().getValueString().c_str()));

	// it is possible for damageData to go bad if this object is being damaged from
	// combat and it goes out of combat, so we copy the items we need off of it
	const NetworkId attackerId(damageData.attackerId);
	const NetworkId weaponId(damageData.weaponId);

	std::vector<AttribMod::AttribMod>::const_iterator iter;
	for (iter = damageData.damage.begin(); !isDisabled() && iter != damageData.damage.end();
		++iter)
	{
		const AttribMod::AttribMod &mod = *iter;
		if (mod.value == 0)
			continue;

		if (AttribMod::isDirectDamage(mod))
		{
			// apply instantanious damage directly
			totalDamage -= alterHitPoints(mod.value, damageData.ignoreInvulnerable,
				attackerId);
		}
		else
		{
			WARNING(true, ("Trying to apply non-immediate damage to tangible "
				"object %s. This isn't supported, the damage won't be applied",
				getNetworkId().getValueString().c_str()));
		}
	}

	// tell scripts we have taken combat damage
	if (attackerId != NetworkId::cms_invalid && totalDamage > 0)
	{
		NOT_NULL(getScriptObject());
		ScriptParams params;
		params.addParam(attackerId);
		params.addParam(weaponId);
		params.addParam(totalDamage);
		IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_OBJECT_DAMAGED,
			params));
	}
}

// ----------------------------------------------------------------------

/**
 * Sends a message to the Planet Server to update the position of the object.
 */
void TangibleObject::updatePlanetServerInternal(const bool forceUpdate) const
{
	// Don't send updates for contained tangibles without an interest radius, and only send when forced or moving
	if ((forceUpdate || getPositionChanged()) && (!ContainerInterface::getContainedByObject(*this) || getInterestRadius() > 0))
	{
		Object const * const topmostContainer = ContainerInterface::getTopmostContainer(*this);
		FATAL(!topmostContainer, ("Object %s was contained by something, but getTopmostContainer() returned nullptr\n",getNetworkId().getValueString().c_str()));
		Vector const &position = topmostContainer->getPosition_p();

		UpdateObjectOnPlanetMessage const msg(
			getNetworkId(),
			topmostContainer == this ? NetworkId::cms_invalid : topmostContainer->getNetworkId(),
			static_cast<int>(position.x),
			static_cast<int>(position.y),
			static_cast<int>(position.z),
			getInterestRadius(),
			static_cast<int> (getLocationReservationRadius()),
			false,
			false,
			static_cast<int>(getObjectType()),
			0,
			false,
			getTemplateCrc(),
			-1,
			-1);
		GameServer::getInstance().sendToPlanetServer(msg);
	}
}

//----------------------------------------------------------------------

/**
 * Clears our damage list.
 */
void TangibleObject::clearDamageList(void)
{
	if (m_combatData != nullptr)
	{
		m_combatData->defenseData.damage.clear();
	}
}

//----------------------------------------------------------------------

bool TangibleObject::addHate(NetworkId const & target, float const hate)
{
	bool result = false;

	if (isAuthoritative())
	{
		if (m_hateList.addHate(target, hate))
		{
			result = true;

			CombatTracker::addDefender(this);
			setInCombat(true);
			scheduleForAlter();
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addHate, new MessageQueueGenericValueType<std::pair<NetworkId, float> >(std::make_pair(target, hate)));
	}

	return result;
}

//----------------------------------------------------------------------

void TangibleObject::addHateOverTime(NetworkId const & target, float const hate, int const seconds)
{
	if (seconds <= 0)
		return;

	if (isAuthoritative())
	{
		unsigned long const currentGameTime =  ServerClock::getInstance().getGameTimeSeconds();
		unsigned long const endGameTime = currentGameTime + seconds;

		IGNORE_RETURN(addHate(target, hate));

		bool const firstHateOverTime = m_hateOverTime.empty();
		m_hateOverTime.set(target, std::make_pair(hate, std::make_pair(currentGameTime, endGameTime)));

		if (firstHateOverTime)
			MessageToQueue::sendMessageToC(getNetworkId(), "C++ProcessHateOverTime", std::string(), 1, false);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addHateOverTime, new MessageQueueGenericValueType<std::pair<NetworkId, std::pair<int, float> > >(std::make_pair(target, std::make_pair(seconds, hate))));
	}
}

//----------------------------------------------------------------------

bool TangibleObject::setHate(NetworkId const & target, float const hate)
{
	bool result = false;

	if (isAuthoritative())
	{
		if (m_hateList.setHate(target, hate))
		{
			result = true;

			CombatTracker::addDefender(this);
			setInCombat(true);
			scheduleForAlter();
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setHate, new MessageQueueGenericValueType<std::pair<NetworkId, float> >(std::make_pair(target, hate)));
	}

	return result;
}

//----------------------------------------------------------------------

float TangibleObject::getHate(NetworkId const & target) const
{
	return m_hateList.getHate(target);
}

//----------------------------------------------------------------------

float TangibleObject::getMaxHate() const
{
	return m_hateList.getMaxHate();
}

//----------------------------------------------------------------------

CachedNetworkId const & TangibleObject::getHateTarget() const
{
	return m_hateList.getTarget();
}

//----------------------------------------------------------------------

void TangibleObject::removeHateTarget(NetworkId const & target)
{
	if (isAuthoritative())
	{
		m_hateList.removeTarget(target);
		scheduleForAlter();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_removeHateTarget, new MessageQueueGenericValueType<NetworkId>(target));
	}
}

//----------------------------------------------------------------------

void TangibleObject::clearHateList()
{
	if (isAuthoritative())
	{
		m_hateList.clear();
		scheduleForAlter();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_clearHateList, nullptr);
	}
}

//-----------------------------------------------------------------------

HateList::UnSortedList const & TangibleObject::getUnSortedHateList() const
{
	return m_hateList.getUnSortedList();
}

//-----------------------------------------------------------------------

void TangibleObject::getSortedHateList(HateList::SortedList & sortedHateList) const
{
	m_hateList.getSortedList(sortedHateList);
}

//-----------------------------------------------------------------------

void TangibleObject::verifyHateList()
{
	LOGC(ConfigServerGame::isAiLoggingEnabled(), "debug_ai", ("TangibleObject::verifyHateList() owner(%s:%s)", getNetworkId().getValueString().c_str(), Unicode::wideToNarrow(getEncodedObjectName()).c_str()));

	m_hateList.alter();
}

//-----------------------------------------------------------------------

bool TangibleObject::isHatedBy(Object * const object)
{
	bool result = false;
	TangibleObject * const hatedByTangibleObject = TangibleObject::asTangibleObject(object);

	if (hatedByTangibleObject != nullptr)
	{
		HateList::UnSortedList const & hateList = hatedByTangibleObject->getUnSortedHateList();

		if (hateList.find(CachedNetworkId(*this)) != hateList.end())
		{
			result = true;
		}
	}

	return result;
}

//-----------------------------------------------------------------------

bool TangibleObject::isOnHateList(NetworkId const & target) const
{
	return m_hateList.isOnList(target);
}

//-----------------------------------------------------------------------

int TangibleObject::getTimeSinceLastHateListUpdate() const
{
	return m_hateList.getTimeSinceLastUpdate();
}

//-----------------------------------------------------------------------

bool TangibleObject::isHateListEmpty() const
{
	return m_hateList.isEmpty();
}

//-----------------------------------------------------------------------
HateList::RecentList const & TangibleObject::getRecentHateList() const
{
	return m_hateList.getRecentList();
}

//-----------------------------------------------------------------------
void TangibleObject::clearRecentHateList()
{
	m_hateList.clearRecentList();
	scheduleForAlter();
}

//-----------------------------------------------------------------------

void TangibleObject::resetHateTimer()
{
	if (isAuthoritative())
	{
		m_hateList.resetHateTimer();
		scheduleForAlter();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_resetHateTimer, nullptr);
	}
}

//-----------------------------------------------------------------------

void TangibleObject::setHateListAutoExpireTargetEnabled(bool const enabled)
{
	m_hateList.setAutoExpireTargetEnabled(enabled);

	if (!isAuthoritative())
	{
		sendControllerMessageToAuthServer(CM_autoExpireHateListTargetEnabled, new MessageQueueGenericValueType<bool>(enabled));
	}
}

//-----------------------------------------------------------------------

bool TangibleObject::isHateListAutoExpireTargetEnabled() const
{
	return m_hateList.isAutoExpireTargetEnabled();
}

//-----------------------------------------------------------------------

int TangibleObject::getHateListAutoExpireTargetDuration() const
{
	return m_hateList.getAutoExpireTargetDuration();
}

//-----------------------------------------------------------------------
float TangibleObject::getAILeashTime() const
{
	return m_hateList.getAILeashTime();
}


//-----------------------------------------------------------------------
void  TangibleObject::setAILeashTime( float time )
{
	m_hateList.setAILeashTime( time );
}


//-----------------------------------------------------------------------

/**
 * @return true if we are crafted, false if not
 */
bool TangibleObject::isCrafted(void) const
{
	return hasCondition(C_crafted);
}	// TangibleObject::isCrafted

//-----------------------------------------------------------------------

/**
 * Returns the id of the manufacturing schematic that was used to craft this
 * item, or NetworkId::cms_invalid if the item wasn't crafted.
 *
 * @return the schematic id
 */
const NetworkId TangibleObject::getCraftedId(void) const
{
	NetworkId craftedId(NetworkId::cms_invalid);
	getObjVars().getItem(OBJVAR_CRAFTING_SCHEMATIC,craftedId);
	return craftedId;
}	// TangibleObject::getCraftedId

//-----------------------------------------------------------------------

/**
 * Sets the id of the manufacturing schematic used to create this object.
 *
 * @param id		the schematic id
 */
void TangibleObject::setCraftedId(const NetworkId & id)
{
	if (isAuthoritative())
	{
		if (id == NetworkId::cms_invalid)
		{
			IGNORE_RETURN(removeObjVarItem(OBJVAR_CRAFTING_SCHEMATIC));
			m_sourceDraftSchematic = 0;
			clearCondition(C_crafted);
		}
		else
		{
			IGNORE_RETURN(setObjVarItem(OBJVAR_CRAFTING_SCHEMATIC, id));
			setCondition(C_crafted);
			const Object * object = NetworkIdManager::getObjectById(id);
			if (object != nullptr)
			{
				const ManufactureSchematicObject * schematic = dynamic_cast<
					const ManufactureSchematicObject *>(object);
				if (schematic != nullptr)
					m_sourceDraftSchematic = schematic->getDraftSchematic();
			}
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCraftedId,
			new MessageQueueGenericValueType<NetworkId>(id));
	}
}	// TangibleObject::setCraftedId

//-----------------------------------------------------------------------

/**
 * Returns the crafting type of a crafting/repair tool.
 *
 * @return the crafting type
 */
int TangibleObject::getCraftingType(void) const
{
	int type = 0;
	getObjVars().getItem(OBJVAR_CRAFTING_TYPE, type);
	return type;
}	// TangibleObject::getCraftingType

//-----------------------------------------------------------------------

/**
 * Returns the crafted type of a crafted object.
 *
 * @return the crafted type
 */
int TangibleObject::getCraftedType(void) const
{
	int craftedType = 0;
	getObjVars().getItem(OBJVAR_CRAFTING_REPAIR_TYPE,craftedType);
	return craftedType;
}	// TangibleObject::getCraftedType

//-----------------------------------------------------------------------

/**
 * Sets the crafted type of a crafted object.
 *
 * @param type		the crafted type
 */
void TangibleObject::setCraftedType(int type)
{
	if (isAuthoritative())
	{
		IGNORE_RETURN(setObjVarItem(OBJVAR_CRAFTING_REPAIR_TYPE, type));
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCraftedType,
			new MessageQueueGenericValueType<int>(type));
	}
}	// TangibleObject::setCraftedType

//-----------------------------------------------------------------------

/**
 * Returns the id of the object's creator's id.
 *
 * @return the object's creator's id
 */
const NetworkId TangibleObject::getCreatorId(void) const
{
	if (m_creatorId.get() == NetworkId::cms_invalid)
	{
		// @todo remove this code when no object has the objvar
		// OBJVAR_CREATOR_ID
		NetworkId creatorId(NetworkId::cms_invalid);
		if (getObjVars().getItem(OBJVAR_CREATOR_ID, creatorId))
		{
			const_cast<TangibleObject*>(this)->setCreatorId(creatorId);
			return creatorId;
		}
	}
	return m_creatorId.get();
}	// TangibleObject::getCreatorId

//-----------------------------------------------------------------------

/**
 * Sets the creator id of this object.
 *
 * @param creatorId		the creator id
 */
void TangibleObject::setCreatorId(const NetworkId & creatorId)
{
	if (isAuthoritative())
	{
		m_creatorId = creatorId;
		if (getObjVars().hasItem(OBJVAR_CREATOR_ID))
			removeObjVarItem(OBJVAR_CREATOR_ID);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCreatorId,
			new MessageQueueGenericValueType<NetworkId>(creatorId));
	}
}	// TangibleObject::setCreatorId

//-----------------------------------------------------------------------

/**
 * Returns the id of the object's creator's name.
 *
 * @return the object's creator's name
 */
const Unicode::String TangibleObject::getCreatorName(void) const
{
	return Unicode::narrowToWide(NameManager::getInstance().getPlayerName(
		getCreatorId()));
}	// TangibleObject::getCreatorName

//-----------------------------------------------------------------------

/**
 * Returns the xp amount the creator of this object will get when it is "used".
 *
 * @return the xp amount
 */
int TangibleObject::getCreatorXp(void) const
{
	int creatorXp=0;
	getObjVars().getItem(OBJVAR_CREATOR_XP,creatorXp);
	return creatorXp;
}	// TangibleObject::getCreatorXp

//-----------------------------------------------------------------------

/**
 * Sets the xp amount the creator of this object will get when it is "used".
 *
 * @param xp		the xp amount
 */
void TangibleObject::setCreatorXp(int xp)
{
	if (isAuthoritative())
	{
		setObjVarItem(OBJVAR_CREATOR_XP, xp);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCreatorXp, new MessageQueueGenericValueType<int>(xp));
	}
}	// TangibleObject::setCreatorXp

//-----------------------------------------------------------------------

/**
 * Returns the xp type the creator of this object will get when it is "used".
 *
 * @return the xp type
 */
int TangibleObject::getCreatorXpType(void) const
{
	int creatorXpType = -1;
	getObjVars().getItem(OBJVAR_CREATOR_XP_TYPE,creatorXpType);
	return creatorXpType;
}	// TangibleObject::getCreatorXpType

//-----------------------------------------------------------------------

/**
 * Sets the xp type the creator of this object will get when it is "used".
 *
 * @param type		the xp type
 */
void TangibleObject::setCreatorXpType(int type)
{
	if (isAuthoritative())
	{
		setObjVarItem(OBJVAR_CREATOR_XP_TYPE, type);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCreatorXpType, new MessageQueueGenericValueType<int>(type));
	}
}	// TangibleObject::setCreatorXpType

//-----------------------------------------------------------------------

/**
 * Returns if this object is a crafting tool or not.
 *
 * @return true if we are a tool, false if not
 */
bool TangibleObject::isCraftingTool(void) const
{
	return getObjVars().hasItem(OBJVAR_CRAFTING_TOOL);
}	// TangibleObject::isCraftingTool

//-----------------------------------------------------------------------

/**
 * Returns if this object is a crafting station or not.
 *
 * @return true if we are a station, false if not
 */
bool TangibleObject::isCraftingStation(void) const
{
	return getObjVars().hasItem(OBJVAR_CRAFTING_STATION);
}	// TangibleObject::isCraftingStation

//-----------------------------------------------------------------------

/**
 * Returns if this object is a repair tool or not.
 *
 * @return true if we are a repair tool, false if not
 */
bool TangibleObject::isRepairTool(void) const
{
	return getObjVars().hasItem(OBJVAR_REPAIR_TOOL);
}	// TangibleObject::isRepairTool

//-----------------------------------------------------------------------

/**
* If this is a crafting tool, tries to start a session with a player.
*
* @param crafter		the player who wants to craft something
*
* @return true if the session was started, false if not
*/
bool TangibleObject::startCraftingSession(CreatureObject & crafter)
{
	static const SlotId outputSlotId(SlotIdManager::findSlotId(MANF_OUTPUT_SLOT_NAME));

	if(isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		PlayerObject * crafterPlayer = PlayerCreatureController::getPlayerObject(&crafter);
		if (crafterPlayer == nullptr)
			return false;

		// make sure we are a crafting tool
		if (!isCraftingTool())
		{
			DEBUG_WARNING(true, ("Tried to start crafting session on non-tool "
				"object %s", myIdString));
			return false;
		}

		if (isDisabled())
		{
			DEBUG_WARNING(true, ("Tried to start crafting session on disabled "
				"tool %s", myIdString));
			return false;
		}

		// make sure the tool isn't creating a prototype
		float realTimeVar = 0;
		getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_TIME,realTimeVar);
		if (realTimeVar > 0)
		{
			StringId message("system_msg", "crafting_tool_creating_prototype");
			Unicode::String outOfBand;
			Chat::sendSystemMessage(crafter, message, outOfBand);
			return false;
		}

		// make sure the tool isn't already being used
		if (getObjVars().hasItem(OBJVAR_CRAFTING_CRAFTER))
		{
			StringId message("system_msg", "crafting_tool_in_use");
			Unicode::String outOfBand;
			Chat::sendSystemMessage(crafter, message, outOfBand);
			return false;
		}

		// make sure the output slot is empty
		SlottedContainer const * const slotContainer = ContainerInterface::getSlottedContainer(*this);
		if (slotContainer == nullptr)
		{
			DEBUG_WARNING(true, ("Crafting tool %s does not have a container!", myIdString));
			return false;
		}
		Container::ContainerErrorCode tmp = Container::CEC_Success;
		if (slotContainer->getObjectInSlot(outputSlotId, tmp) != Container::ContainedItem::cms_invalid)
		{
			StringId message("system_msg", "crafting_tool_full");
			Unicode::String outOfBand;
			Chat::sendSystemMessage(crafter, message, outOfBand);
			return false;
		}

		// get the schematics list from the player and filter for the type of
		// schematics we can make
		int myType = getCraftingType();

		bool sendComplexities = true;
		if (!crafter.isSchematicFilteringEnabled())
			sendComplexities = false;

		std::vector<int> goodSchematics;
		std::vector<float>  scriptComplexities;
		const std::map<std::pair<uint32, uint32>,int> & schematics = crafter.getDraftSchematics();
		std::map<std::pair<uint32, uint32>,int>::const_iterator iter;
		for (iter = schematics.begin(); iter != schematics.end(); ++iter)
		{
			const DraftSchematicObject * schematic = DraftSchematicObject::getSchematic((*iter).first.first);
			if (schematic != nullptr && ((myType & ServerObjectTemplate::CT_genericItem) != 0 ||
				((schematic->getCategory() & myType) != 0)))
			{
				// make sure the player has all the skill commands needed for the
				// schematic
				int skillCount = schematic->getSkillCommandsCount();
				int i;
				for (i = 0; i < skillCount; ++i)
				{
					if (!crafter.hasCommand(schematic->getSkillCommands(i)))
						break;
				}
				if (i == skillCount)
				{
					goodSchematics.push_back((*iter).first.first);
					if (sendComplexities)
						scriptComplexities.push_back(schematic->getComplexity());
					else
						scriptComplexities.push_back(1.0f);
				}
			}
		}

		crafterPlayer->setCraftingTool(*this);

		if (goodSchematics.size() > 0)
		{
			ScriptParams params;
			params.addParam(crafter.getNetworkId());
			params.addParam(goodSchematics);
			params.addParam(scriptComplexities);
			if (getScriptObject()->trigAllScripts(Scripting::TRIG_REQUEST_DRAFT_SCHEMATICS, params) != SCRIPT_CONTINUE)
			{
				std::vector<uint32> empty;
				crafterPlayer->sendUseableDraftSchematics (empty);
				crafterPlayer->stopCrafting(true);

				return false;
			}
			setObjVarItem(OBJVAR_CRAFTING_CRAFTER, crafter.getNetworkId());
		}
		else
		{
			std::vector<uint32> empty;
			crafterPlayer->sendUseableDraftSchematics (empty);
			crafterPlayer->stopCrafting(true);

			StringId message("system_msg", "no_valid_schematics");
			Unicode::String outOfBand;
			Chat::sendSystemMessage(crafter, message, outOfBand);
			return false;
		}

		// the following section to send the ingredients to the player is only needed for
		// testing crafting via the console
#ifdef _DEBUG

		// send the player info about the ingredients in the tool's hopper;
		// the actual sending of the messages could be spread out over a few
		// frames
//		listIngredientsInHopper(crafter);
#endif
	}
	else
	{
		sendControllerMessageToAuthServer(CM_startCraftingSession, new MessageQueueGenericValueType<NetworkId>(crafter.getNetworkId()));
	}
	return true;
}	// TangibleObject::startCraftingSession

//-----------------------------------------------------------------------

/**
 * If this is a crafting tool, tries to stop a session with a player.
 *
 * @return true if the session was stopped, false if not
 */
bool TangibleObject::stopCraftingSession(void)
{
	if (isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		// make sure we are a crafting tool
		if (!isCraftingTool())
		{
			DEBUG_WARNING(true, ("Tried to stop crafting session on non-tool "
				"object %s", myIdString));
			return false;
		}

		// make sure a prototype object isn't being made (the session will be
		// stopped when the prototype is done)
		float realTimeVar = 0;
		if (getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_TIME, realTimeVar) && realTimeVar > 0)
			return false;

		// make sure the tool isn't already being used
		NetworkId crafterId;
		if (!getObjVars().getItem(OBJVAR_CRAFTING_CRAFTER,crafterId))
		{
			DEBUG_REPORT_LOG(true, ("Crafting tool %s in not in use\n", myIdString));
			return false;
		}

		clearCraftingManufactureSchematic();
		clearCraftingPrototype();
		removeObjVarItem(OBJVAR_CRAFTING_CRAFTER);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_stopCraftingSession, 0);
	}
	return true;
}	// TangibleObject::stopCraftingSession

//-----------------------------------------------------------------------

/**
 * Test to see if an object is in a crafting tool's ingredient hopper.
 *
 * @param ingredientId		object we want to test
 *
 * @return true if the object is in the hopper, false if not
 */
bool TangibleObject::isIngredientInHopper(const NetworkId & ingredientId) const
{
	ServerObject const * const hopper = getIngredientHopper();
	if (hopper == nullptr)
		return false;

	Object const * const ingredient = CachedNetworkId(ingredientId).getObject();
	if (ingredient == nullptr)
		return false;

	return ContainerInterface::isNestedWithin(*ingredient, getNetworkId());
}	// TangibleObject::isIngredientInHopper

//-----------------------------------------------------------------------

/**
 * Returns this crafting station's ingredient hopper.
 *
 * @return the hopper
 */
ServerObject * TangibleObject::getIngredientHopper(void) const
{
static const SlotId hopperSlotId(SlotIdManager::findSlotId(INGREDIENT_HOPPER_SLOT_NAME));

	const std::string myId(getNetworkId().getValueString());
	const char * myIdString = myId.c_str();
	UNREF(myIdString);		// needed for release mode

	// make sure we are a crafting station
	if (!isCraftingStation())
	{
		DEBUG_WARNING(true, ("Tried to get input hopper for non-crafting station "
			"object %s", myIdString));
		return nullptr;
	}

	// get the ingredient hopper
	SlottedContainer const * const container = ContainerInterface::getSlottedContainer(*this);
	if (container == nullptr)
	{
		DEBUG_WARNING(true, ("Crafting tool %s does not have a container!", myIdString));
		return nullptr;
	}
	Container::ContainerErrorCode tmp = Container::CEC_Success;
	Container::ContainedItem hopperId = container->getObjectInSlot(hopperSlotId, tmp);
	if (hopperId == NetworkId::cms_invalid)
	{
		DEBUG_WARNING(true, ("Crafting tool %s does not have a ingredient "
			"hopper!", myIdString));
		return nullptr;
	}
	ServerObject * hopper = safe_cast<ServerObject *>(hopperId.getObject());
	if (hopper == nullptr)
	{
		DEBUG_WARNING(true, ("Can't find object for ingredient hopper id %s",
			hopperId.getValueString().c_str()));
		return nullptr;
	}

	return hopper;
}	// TangibleObject::getIngredientHopper

//----------------------------------------------------------------------

/**
 * Adds an object to this tool's output slot.
 *
 * @param object			the object to add
 * @param transferer        who is transferring the ingredient
 *
 * @return true on success, false on fail
 */
bool TangibleObject::addObjectToOutputSlot(ServerObject & object,
	ServerObject * transferer)
{
static const SlotId outputSlotId(SlotIdManager::findSlotId(MANF_OUTPUT_SLOT_NAME));

	if(isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		// get output slot
		SlottedContainer const * const slotContainer = ContainerInterface::getSlottedContainer(*this);
		if (slotContainer == nullptr)
		{
			DEBUG_WARNING(true, ("Crafting tool %s does not have a container!", myIdString));
			return false;
		}

		// make sure the transferer is in the final stage of crafting
		if (transferer == nullptr)
		{
			DEBUG_WARNING(true, ("addObjectToOutputSlot, nullptr transferer"));
			return false;
		}
		NetworkId crafterVarId(NetworkId::cms_invalid);
		if (!getObjVars().getItem(OBJVAR_CRAFTING_CRAFTER, crafterVarId))
		{
			// @todo: send a message to the player saying this tool is in use
			DEBUG_WARNING(true, ("addObjectToOutputSlot, crafting tool %s has "
				"no crafter", myIdString));
			return false;
		}
		if (crafterVarId != transferer->getNetworkId())
		{
			DEBUG_WARNING(true, ("addObjectToOutputSlot, transferer %s is not the "
				"crafter %s", transferer->getNetworkId().getValueString().c_str(),
				crafterVarId.getValueString().c_str()));
			return false;
		}
		CreatureObject const * const creatureTransferer = transferer->asCreatureObject();
		if (creatureTransferer == nullptr)
		{
			DEBUG_WARNING(true, ("addObjectToOutputSlot, transferer %s is not a "
				"creature", transferer->getNetworkId().getValueString().c_str()));
			return false;
		}
		PlayerObject const * const creaturePlayer = PlayerCreatureController::getPlayerObject(creatureTransferer);
		if (creaturePlayer == nullptr)
			return false;

		if (creaturePlayer->getCraftingStage() != Crafting::CS_finish)
		{
			DEBUG_WARNING(true, ("addObjectToOutputSlot, crafter %s is in the "
				"wrong stage %d", transferer->getNetworkId().getValueString().c_str(),
				static_cast<int>(creaturePlayer->getCraftingStage())));
			return false;
		}

		// if the object is our prototype, clear the prototype
		if (getSynchronizedUi() != nullptr)
		{
			CraftingToolSyncUi * sync = dynamic_cast<CraftingToolSyncUi *>(
				getSynchronizedUi());
			if (sync != nullptr)
			{
				if (sync->getPrototype() == object.getNetworkId())
				{
					sync->setPrototype(CachedNetworkId::cms_cachedInvalid);
				}
			}
		}

		// put the object in the slot
		Container::ContainerErrorCode tmp = Container::CEC_Success;
		if (!ContainerInterface::transferItemToSlottedContainer(*this, object, outputSlotId, nullptr, tmp))
		{
			DEBUG_WARNING(true, ("Failed to transfer object %s to crafting "
				"tool %s", object.getNetworkId().getValueString().c_str(),
				myIdString));
			return false;
		}
	}
	else
	{
		NetworkId transfererId;
		if(transferer)
			transfererId = transferer->getNetworkId();

		sendControllerMessageToAuthServer(CM_addObjectToOutputSlot, new MessageQueueGenericValueType<std::pair<NetworkId, NetworkId> >(std::make_pair(object.getNetworkId(), transfererId)));
	}

	return true;
}	// TangibleObject::addObjectToOutputSlot

//----------------------------------------------------------------------

/**
 * Gets the temporary manufacturing schematic used during a crafting session.
 *
 * @return the schematic
 */
ManufactureSchematicObject * TangibleObject::getCraftingManufactureSchematic(void) const
{
	const std::string myId(getNetworkId().getValueString());
	const char * myIdString = myId.c_str();
	UNREF(myIdString);		// needed for release mode

	// make sure we are a crafting tool
	if (!isCraftingTool())
	{
		DEBUG_WARNING(true, ("Tried to get non-crafting tool "
			"object %s", myIdString));
		return nullptr;
	}

	SlottedContainer const * const container = ContainerInterface::getSlottedContainer(*this);
	if (container)
	{
		Container::ContainerErrorCode error;
		const CachedNetworkId & schematicId =
			container->getObjectInSlot(getCraftingManufactureSchematicSlotId(), error);
		return safe_cast<ManufactureSchematicObject *>(schematicId.getObject());
	}
	else
	{
		DEBUG_WARNING(true, ("ManufactureSchematicObject doesn't have a slotted container."));
		return nullptr;
	}
}	// TangibleObject::getCraftingManufactureSchematic

//----------------------------------------------------------------------

/**
 * Gives the schematic to the player at the end of the session.
 *
 * @return the schematic
 */
ManufactureSchematicObject * TangibleObject::removeCraftingManufactureSchematic(void)
{
	const std::string myId(getNetworkId().getValueString());
	const char * myIdString = myId.c_str();
	UNREF(myIdString);		// needed for release mode

	// make sure we are a crafting tool
	if (!isCraftingTool() || getSynchronizedUi() == nullptr)
	{
		DEBUG_WARNING(true, ("Tried to get non-crafting tool "
			"object %s", myIdString));
		return nullptr;
	}

	CraftingToolSyncUi * sync = dynamic_cast<CraftingToolSyncUi *>(
		getSynchronizedUi());
	if (sync == nullptr)
		return nullptr;

	ManufactureSchematicObject * schematic = safe_cast<ManufactureSchematicObject *>(
		sync->getManfSchematic().getObject());

	sync->setManfSchematic(CachedNetworkId::cms_cachedInvalid,
		CachedNetworkId::cms_cachedInvalid, false);

	return schematic;
}	// TangibleObject::removeCraftingManufactureSchematic

//----------------------------------------------------------------------

/**
 * Stores the temporary manufacturing schematic used during crafting in this
 * crafting tool.
 *
 * @param schematic		the schematic
 */
void TangibleObject::setCraftingManufactureSchematic(ManufactureSchematicObject & schematic)
{
	if(isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		// make sure we are a crafting tool
		if (!isCraftingTool() || getSynchronizedUi() == nullptr)
		{
			DEBUG_WARNING(true, ("Tried to get non-crafting tool "
				"object %s", myIdString));
			return;
		}

		CraftingToolSyncUi * sync = dynamic_cast<CraftingToolSyncUi *>(
			getSynchronizedUi());
		if (sync == nullptr)
			return;

		// see if there is already a schematic object set
		const CachedNetworkId & schematicId = sync->getManfSchematic();
		if (schematicId == schematic.getNetworkId())
			return;

		// remove the old schematic
		ServerObject * object = safe_cast<ServerObject *>(schematicId.getObject());
		if (object == nullptr)
		{
			if (schematicId != NetworkId::cms_invalid)
			{
				DEBUG_WARNING(true, ("Can't find object for schematic id %s",
					schematicId.getValueString().c_str()));
				// go on anyway
			}
		}
		else
		{
			sync->setManfSchematic(CachedNetworkId::cms_cachedInvalid,
				CachedNetworkId::cms_cachedInvalid, true);
			object->permanentlyDestroy(DeleteReasons::Consumed);
		}

		// the schematic isn't in the world or in a container, so we need to
		// tell the client about it
		int stationBonus = 0;
		ServerObject * crafter = nullptr;
		NetworkId crafterId;
		if (getObjVars().getItem(OBJVAR_CRAFTING_CRAFTER,crafterId))
		{
			crafter = safe_cast<ServerObject *>(NetworkIdManager::getObjectById(crafterId));
		}
		if (crafter != nullptr)
		{
			sync->setManfSchematic(CachedNetworkId(schematic), CachedNetworkId(*crafter),
				true);

			// if the crafter is near a crafting station, get any crafting bonus
			// it has
			const PlayerObject * player = PlayerCreatureController::getPlayerObject(
				crafter->asCreatureObject());
			if (player != nullptr && player->getCraftingStation().getObject() != nullptr)
			{
				player->getCraftingStation().getObject()->asServerObject()->
					getObjVars().getItem(OBJVAR_CRAFTING_STATIONMOD, stationBonus);
			}
		}
		else
		{
			WARNING_STRICT_FATAL(true, ("TangibleObject::setCraftingManufactureSchematic "
				"no crafter id on crafting tool %s!", myIdString));
		}

		// set any crafting bonus we give on the schematic
		int myBonus = 0;
		getObjVars().getItem(OBJVAR_CRAFTING_STATIONMOD, myBonus);
		if (myBonus + stationBonus != 0)
		{
			schematic.setObjVarItem(OBJVAR_CRAFTING_STATIONMOD, myBonus + stationBonus);
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCraftingManufactureSchematic, new MessageQueueGenericValueType<NetworkId>(schematic.getNetworkId()));
	}
}	// TangibleObject::setCraftingManufactureSchematic

//----------------------------------------------------------------------

/**
 * Removes the temporary manufacturing schematic from the crafting tool and
 * deletes it.
 */
void TangibleObject::clearCraftingManufactureSchematic(void)
{
	if(isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		// make sure we are a crafting tool
		if (!isCraftingTool())
		{
			DEBUG_WARNING(true, ("Tried to get non-crafting tool "
				"object %s", myIdString));
			return;
		}

		// see if we have a schematic in our schematic slot
		CachedNetworkId schematicId = CachedNetworkId::cms_cachedInvalid;
		SlottedContainer const * const container = ContainerInterface::getSlottedContainer(*this);
		if (container)
		{
			Container::ContainerErrorCode error = Container::CEC_Success;
			schematicId = container->getObjectInSlot(
				getCraftingManufactureSchematicSlotId(), error);
		}
		else
		{
			DEBUG_WARNING(true, ("failed to get slotted container for schematic lookup."));
		}

		// get our ui
		CraftingToolSyncUi * sync = nullptr;
		if (getSynchronizedUi() != nullptr)
			sync = dynamic_cast<CraftingToolSyncUi *>(getSynchronizedUi());

		if (schematicId == CachedNetworkId::cms_cachedInvalid && sync == nullptr)
		{
			// no schematic
			return;
		}

		if (schematicId == CachedNetworkId::cms_invalid)
			schematicId = sync->getManfSchematic();
		else if (sync != nullptr && sync->getManfSchematic() != CachedNetworkId::cms_invalid &&
			schematicId != sync->getManfSchematic())
		{
			WARNING(true, ("TangibleObject::clearCraftingManufactureSchematic "
				"crafting tool %s has schematic %s in its slot but schematic %s "
				"in its ui!!!", getNetworkId().getValueString().c_str(),
				schematicId.getValueString().c_str(),
				sync->getManfSchematic().getValueString().c_str()));
			if (schematicId.getObject() != nullptr)
			{
				schematicId.getObject()->asServerObject()->permanentlyDestroy(
					DeleteReasons::Consumed);
			}
			else
			{
				WARNING_STRICT_FATAL(true, ("TangibleObject::clearCraftingManufactureSchematic "
					"crafting tool %s has slotted schematic %s with no object!",
					getNetworkId().getValueString().c_str(),
					schematicId.getValueString().c_str()));
			}
			schematicId = sync->getManfSchematic();
		}

		// get the schematic
		ManufactureSchematicObject * manfSchematic = nullptr;
		ServerObject * object = safe_cast<ServerObject *>(schematicId.getObject());
		if (object == nullptr && schematicId != CachedNetworkId::cms_invalid)
		{
			WARNING_STRICT_FATAL(true, ("Can't find object for manufactring schematic "
				"id %s", schematicId.getValueString().c_str()));
		}
		else
			manfSchematic = safe_cast<ManufactureSchematicObject *>(object);

		if (manfSchematic != nullptr)
		{
			NetworkId crafterId;
			if (getObjVars().getItem(OBJVAR_CRAFTING_CRAFTER,crafterId))
			{
				// if we are at the assembly stage of crafting, or the draft schematic says
				// not to destroy the ingredients, put the ingredients in the schematic back
				// in the tool
				CreatureObject * const crafter = dynamic_cast<CreatureObject *>(NetworkIdManager::getObjectById(crafterId));
				PlayerObject * const crafterPlayer = PlayerCreatureController::getPlayerObject(crafter);
				if (!manfSchematic->mustDestroyIngredients() || (crafterPlayer != nullptr &&
					crafterPlayer->getCraftingStage() == Crafting::CS_assembly))
				{
					crafterPlayer->setAllowEmptySlot(true);
					int count = manfSchematic->getSlotsCount();
					// note this loop is odd because when we clear a slot it may
					// actually be removed if it was optional
					Crafting::IngredientSlot manfSlot;
					for (int i = 0; i < count;)
					{
						if (manfSchematic->getSlot(i, manfSlot, false))
						{
							if (manfSlot.ingredientType != Crafting::IT_none &&
								!manfSlot.ingredients.empty())
							{
								if (crafterPlayer->emptySlot(i, NetworkId::cms_invalid))
								{
									if (count != manfSchematic->getSlotsCount())
									{
										count = manfSchematic->getSlotsCount();
										continue;
									}
								}
							}
						}
						++i;
					}
					crafterPlayer->setAllowEmptySlot(false);
				}
				else
					manfSchematic->destroyAllIngredients();
			}
		}

		if (sync != nullptr)
			sync->setManfSchematic(CachedNetworkId::cms_cachedInvalid, CachedNetworkId::cms_cachedInvalid, true);
		if (object != nullptr)
		{
			object->permanentlyDestroy(DeleteReasons::Consumed);
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_clearCraftingManufactureSchematic, 0);
	}
}	// TangibleObject::clearCraftingManufactureSchematic

//----------------------------------------------------------------------

/**
 * Gets the temporary prototype used during a crafting session. May also be used
 * to give the prototype to the player at the end of the session.
 *
 * @return the prototype
 */
ServerObject * TangibleObject::getCraftingPrototype(void) const
{
	const std::string myId(getNetworkId().getValueString());
	const char * myIdString = myId.c_str();
	UNREF(myIdString);		// needed for release mode

	// make sure we are a crafting tool
	if (!isCraftingTool() || getSynchronizedUi() == nullptr)
	{
		DEBUG_WARNING(true, ("Tried to get non-crafting tool "
			"object %s", myIdString));
		return nullptr;
	}

	const CraftingToolSyncUi * sync = dynamic_cast<const CraftingToolSyncUi *>(
		getSynchronizedUi());
	if (sync == nullptr)
		return nullptr;

	return safe_cast<ServerObject *>(sync->getPrototype().getObject());
}	// TangibleObject::getCraftingPrototype

//----------------------------------------------------------------------

/**
 * Stores the temporary prototype object used during crafting in this
 * crafting tool.
 *
 * @param prototype		the prototype
 */
void TangibleObject::setCraftingPrototype(ServerObject & prototype)
{
	if (isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		// make sure we are a crafting tool
		if (!isCraftingTool() || getSynchronizedUi() == nullptr)
		{
			DEBUG_WARNING(true, ("Tried to get non-crafting tool "
				"object %s", myIdString));
			return;
		}

		CraftingToolSyncUi * sync = dynamic_cast<CraftingToolSyncUi *>(
			getSynchronizedUi());
		if (sync == nullptr)
			return;

		// see if there is already a prototype object set
		const CachedNetworkId & prototypeId = sync->getPrototype();
		if (prototypeId == prototype.getNetworkId())
			return;

		// remove the old prototype
		ServerObject * object = safe_cast<ServerObject *>(prototypeId.getObject());
		if (object == nullptr)
		{
			if (prototypeId != NetworkId::cms_invalid)
			{
				DEBUG_WARNING(true, ("Can't find object for prototype id %s",
					prototypeId.getValueString().c_str()));
				// go on anyway
			}
		}
		else
		{
			sync->setPrototype(CachedNetworkId::cms_cachedInvalid);
			object->permanentlyDestroy(DeleteReasons::Consumed);
		}

		// put the prototype in the tool's slot so the crafter will get updates
		// about it
		Container::ContainerErrorCode tmp = Container::CEC_Success;
		if (!ContainerInterface::transferItemToSlottedContainer(*this, prototype,
			getCraftingPrototypeSlotId(), nullptr, tmp))
		{
			// see if there is something in the slot, and delete it if there is
			bool failed = true;
			SlottedContainer const * const container = ContainerInterface::getSlottedContainer(*this);
			NOT_NULL(container);
			const Container::ContainedItem & oldItem = container->getObjectInSlot(
				getCraftingPrototypeSlotId(), tmp);
			if (oldItem != prototype.getNetworkId())
			{
				if (oldItem.getObject() != nullptr)
				{
					safe_cast<ServerObject*>(oldItem.getObject())->permanentlyDestroy(
						DeleteReasons::Consumed);
					// try the transfer again
					if (ContainerInterface::transferItemToSlottedContainer(*this,
						prototype, getCraftingPrototypeSlotId(), nullptr, tmp))
					{
						failed = false;
					}
				}
				if (failed)
				{
					DEBUG_WARNING(true, ("Failed to transfer prototype %s to crafting "
						"tool %s", prototype.getNetworkId().getValueString().c_str(),
						myIdString));
					return;
				}
			}
		}

		sync->setPrototype(CachedNetworkId(prototype));
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setCraftingPrototype, new MessageQueueGenericValueType<NetworkId>(prototype.getNetworkId()));
	}
}	// TangibleObject::setCraftingPrototype

//----------------------------------------------------------------------

/**
 * Removes the temporary prototype from the crafting tool and
 * deletes it.
 */
void TangibleObject::clearCraftingPrototype(void)
{
	if(isAuthoritative())
	{
		const std::string myId(getNetworkId().getValueString());
		const char * myIdString = myId.c_str();
		UNREF(myIdString);		// needed for release mode

		// make sure we are a crafting tool
		if (!isCraftingTool() || getSynchronizedUi() == nullptr)
		{
			DEBUG_WARNING(true, ("Tried to get non-crafting tool "
				"object %s", myIdString));
			return;
		}

		CraftingToolSyncUi * sync = dynamic_cast<CraftingToolSyncUi *>(
			getSynchronizedUi());
		if (sync == nullptr)
			return;

		const CachedNetworkId & prototypeId = sync->getPrototype();
		ServerObject * object = safe_cast<ServerObject *>(prototypeId.getObject());
		if (object != nullptr)
		{
			object->permanentlyDestroy(DeleteReasons::Consumed);
		}
		else
		{
			// double-check the prototype slot to make sure nothing is in it
			SlottedContainer const * const container = ContainerInterface::getSlottedContainer(*this);
			NOT_NULL(container);
			Container::ContainerErrorCode error;
			const Container::ContainedItem & contents = container->getObjectInSlot(
				getCraftingPrototypeSlotId(), error);
			if (contents.getObject() != nullptr)
			{
				safe_cast<ServerObject *>(contents.getObject())->permanentlyDestroy(
					DeleteReasons::Consumed);
			}

		}
		sync->setPrototype(CachedNetworkId::cms_cachedInvalid);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_clearCraftingPrototype, 0);
	}
}	// TangibleObject::clearCraftingPrototype

// ----------------------------------------------------------------------

/**
 * Sets the visible component string for this object. Note m_components is
 * write-only on the server because this variable exists only to provide
 * visual data to the client.
 *
 * @param components		component table ids of visible components attached to this object
 */
void TangibleObject::setVisibleComponents(const std::vector<int> & components)
{
	if(isAuthoritative())
	{
		m_components.clear ();

		for (uint i = 0; i < components.size (); ++i)
			m_components.insert(components [i]);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setVisibleComponents, new MessageQueueGenericValueType<std::vector<int> >(components));
	}
}

// ----------------------------------------------------------------------

void TangibleObject::forceExecuteCommand(Command const &command, NetworkId const &targetId, Unicode::String const &params, Command::ErrorCode &status, bool commandIsFromCommandQueue)
{
	if (!isAuthoritative())
	{
		Controller *controller = getController();
		if (controller)
		{
			safe_cast<TangibleController *>(controller)->appendMessage(
				CM_commandQueueForceExecuteCommandFwd,
				0.0f,
				new MessageQueueCommandQueueForceExecuteCommandFwd(command.m_commandHash, targetId, params, status, commandIsFromCommandQueue),
				GameControllerMessageFlags::SEND |
				GameControllerMessageFlags::RELIABLE |
				GameControllerMessageFlags::DEST_AUTH_SERVER);
		}
		return;
	}

	if (status == Command::CEC_Success && ConfigServerGame::getLogAllCommands())
	{
		LOGU(
			"Command",
			("%s>%s %s",
				getNetworkId().getValueString().c_str(),
				targetId.getValueString().c_str(),
				command.m_commandName.c_str()),
				params);
	}

	if (getClient() && getClient()->isGod())
	{
		CreatureObject* creatureObject = asCreatureObject();
		if (creatureObject)
		{
			LOGU(
				"CustomerService",
				("Avatar: %s executed command: (%s) %s",
				 PlayerObject::getAccountDescription(creatureObject).c_str(),
				 targetId.getValueString().c_str(),
				 command.m_commandName.c_str()),
				params);
		}
	}

	if (getObjVars().hasItem("cheater"))
	{
		CreatureObject* creatureObject = asCreatureObject();
		if (creatureObject)
		{
			LOGU(
				"CustomerService",
				("SuspectedCheaterChannel: %s executed command: (%s) %s",
				 PlayerObject::getAccountDescription(creatureObject).c_str(),
				 targetId.getValueString().c_str(),
				 command.m_commandName.c_str()),
				params);
		}
	}

	PROFILER_BLOCK_DEFINE(profileBlockCommandIsFromCommandQueue, "TangibleObject::forceExecuteCommand commandIsFromCommandQueue");
	if (commandIsFromCommandQueue)
		PROFILER_BLOCK_ENTER(profileBlockCommandIsFromCommandQueue);

	// attach temp script
	bool needDetach = false;
	if (command.m_tempScript.length())
	{
		int ret = getScriptObject()->attachScript(command.m_tempScript, true);
		if (ret != SCRIPT_OVERRIDE)
			needDetach = true;
	}

	// do script call
	bool commandOverride = false;
	if (status == Command::CEC_Success)
	{
		if (!command.m_scriptHook.empty())
		{
			ScriptParams scriptParams;
			scriptParams.addParam(targetId);
			scriptParams.addParam(params);
			scriptParams.addParam(command.m_defaultTime);
			if (getScriptObject()->callScriptCommandHandler(command.m_scriptHook, scriptParams) == SCRIPT_OVERRIDE)
				commandOverride = true;
		}
	}
	else
	{
		if (!command.m_failScriptHook.empty())
		{
			ScriptParams scriptParams;
			scriptParams.addParam(targetId);
			scriptParams.addParam(params);
			scriptParams.addParam(command.m_defaultTime);
			if (getScriptObject()->callScriptCommandHandler(command.m_failScriptHook, scriptParams) == SCRIPT_OVERRIDE)
			{
				commandOverride = true;
				// an override in a failure script handler should mean to mark it as a success (used for standing from feign death)
				status = Command::CEC_Success;
			}
		}
	}

	// detach temp scripts
	if (needDetach)
		getScriptObject()->detachScript(command.m_tempScript);

	if ( commandOverride && commandIsFromCommandQueue )
	{
		CommandQueue * const queue = getCommandQueue();

		if (queue != nullptr)
		{
			queue->cancelCurrentCommand();
		}
	}

	// do c function call if not overridden by script
	if (!commandOverride)
	{
		if (status == Command::CEC_Success)
		{
			if (command.m_cppHook)
				(*command.m_cppHook)(command, getNetworkId(), targetId, params);
		}
		else
		{
			if (command.m_failCppHook)
				(*command.m_failCppHook)(command, getNetworkId(), targetId, params);
		}
	}

	if (commandIsFromCommandQueue)
		PROFILER_BLOCK_LEAVE(profileBlockCommandIsFromCommandQueue);
}

// ----------------------------------------------------------------------

void TangibleObject::setCondition(int condition)
{
	if (isAuthoritative())
		m_condition = (m_condition.get() | condition);
	else
		sendControllerMessageToAuthServer(CM_setCondition, new MessageQueueGenericValueType<int>(condition));
}

// ----------------------------------------------------------------------

void TangibleObject::clearCondition(int condition)
{
	if (isAuthoritative())
		m_condition = (m_condition.get() & (~condition));
	else
		sendControllerMessageToAuthServer(CM_clearCondition, new MessageQueueGenericValueType<int>(condition));
}

// ----------------------------------------------------------------------

bool TangibleObject::isDisabled() const
{
	return hasCondition(ServerTangibleObjectTemplate::C_disabled);
}

// ----------------------------------------------------------------------

void TangibleObject::setDisabled(bool disabled)
{
	if (disabled)
		setCondition(ServerTangibleObjectTemplate::C_disabled);
	else
		clearCondition(ServerTangibleObjectTemplate::C_disabled);
}

// ----------------------------------------------------------------------


const NetworkId & TangibleObject::getOwnerId() const
{
	return m_ownerId.get();
}

// ----------------------------------------------------------------------

bool TangibleObject::isOwner(NetworkId const &id, Client const *client) const
{
	if (id == getOwnerId())
		return true;
	if (client && client->isGod())
		return true;
	return false;
}

// ----------------------------------------------------------------------

/**
 * Determine the initial visibility state of an object when it is created.
 */
void TangibleObject::initializeVisibility()
{
	if (isAuthoritative())
	{
		const ServerTangibleObjectTemplate * myTemplate = safe_cast<
			const ServerTangibleObjectTemplate *>(getObjectTemplate());
		if (myTemplate != nullptr)
		{
			bool visible = false;
			for (size_t i = 0; i < myTemplate->getVisibleFlagsCount(); ++i)
			{
				if (myTemplate->getVisibleFlags(i) == ServerObjectTemplate::VF_player)
				{
					visible = true;
					break;
				}
			}
			setVisible(visible);
		}
	}
	else
	{
		WARNING(true, ("TangibleObject::initializeVisibility called on non-authoritative object %s", 
			getNetworkId().getValueString().c_str()));
	}
}

// ----------------------------------------------------------------------

void TangibleObject::setVisible(bool isVisible)
{
	if (isAuthoritative())
		m_visible = isVisible;
	else
		sendControllerMessageToAuthServer(CM_setVisible, new MessageQueueGenericValueType<bool>(isVisible));
}

// ----------------------------------------------------------------------

void TangibleObject::setHidden(bool const hide)
{
	if (isAuthoritative())
		m_hideFromClient = hide;
	else
		sendControllerMessageToAuthServer(CM_hideFromClient, new MessageQueueGenericValueType<bool>(hide));
}

// ----------------------------------------------------------------------

void TangibleObject::visibilityDataModified()
{
	if (isInWorld())
	{
		if (isVisible() && !isHidden())
		{
			// show the object
			const TriggerVolume * triggerVolume = getTriggerVolume(NetworkTriggerVolumeNamespace::NetworkTriggerVolumeName);
			if (triggerVolume != nullptr)
			{
				std::vector<ServerObject *> observers;
				ServerWorld::findPlayerCreaturesInRange(getPosition_w(), triggerVolume->getRadius(), observers);
				if (!observers.empty())
					ObserveTracker::onObjectMadeVisibleTo(*this, observers);
			}
		}
		else
		{
			// hide the object
			ObserveTracker::onObjectMadeInvisible(*this);

			// if the object is hidden, show the object to
			// players who have passively revealed the object
			if (isVisible() && isHidden() && !m_passiveRevealPlayerCharacter.empty())
			{
				const TriggerVolume * triggerVolume = getTriggerVolume(NetworkTriggerVolumeNamespace::NetworkTriggerVolumeName);
				if (triggerVolume != nullptr)
				{
					std::vector<ServerObject *> possibleObservers;
					ServerWorld::findPlayerCreaturesInRange(getPosition_w(), triggerVolume->getRadius(), possibleObservers);
					if (!possibleObservers.empty())
					{
						std::vector<ServerObject *> allowedObservers;
						for (std::vector<ServerObject *>::const_iterator i = possibleObservers.begin(); i != possibleObservers.end(); ++i)
						{
							if (m_passiveRevealPlayerCharacter.contains((*i)->getNetworkId()))
								allowedObservers.push_back(*i);
						}

						if (!allowedObservers.empty())
							ObserveTracker::onObjectMadeVisibleTo(*this, allowedObservers);
					}
				}
			}
		}
	}
}
bool TangibleObject::isVisibleOnClient(Client const &client) const
{
	// Everyone can see visible objects.
	// Gods and clients on the buildcluster can see everything.
	if (client.isGod() || ConfigServerGame::getBuildCluster() || (isVisible() && !isHidden()))
		return true;

	// Invisible objects are visible to a non-god client if the client's
	// creature contains (or indirectly contains) the invisible object.

	ServerObject const * const characterObject = client.getCharacterObject();

	{
		for (ServerObject const *o = this; o; o = safe_cast<ServerObject const *>(ContainerInterface::getContainedByObject(*o)))
		{
			if (o == characterObject)
				return true;
			if (o->isInWorld() || o->asCellObject())
				break;
		}
	}

	// Invisible objects are visible to a non-god client if they are in
	// the client's character's containment chain.
	{
		for (ServerObject const *o = characterObject; o; o = safe_cast<ServerObject const *>(ContainerInterface::getContainedByObject(*o)))
			if (o == this)
				return true;
	}

	if (isVisible())
	{
		// if the client's character is the owner, then he can see it.
		const CachedNetworkId ownerId(getOwnerId());
		if (ownerId == characterObject->getNetworkId())
			return true;

		// if the client's character is grouped with the the owner, then he can see it.
		const CreatureObject * owner = safe_cast<const CreatureObject *>(ownerId.getObject());
		if (owner != nullptr)
		{
			const GroupObject * group = owner->getGroup();
			if (group != nullptr)
			{
				if (group->isGroupMember(characterObject->getNetworkId()))
					return true;
			}
		}

		// if the client's character has passively revealed the hidden object, then he can see it.
		if (m_passiveRevealPlayerCharacter.contains(characterObject->getNetworkId()))
			return true;
	}
	return false;
}

// ----------------------------------------------------------------------

/**
 * Tests if this item will encumber a player when equipped.
 *
 * @return true if the item has encumbrance data
 */
bool TangibleObject::hasEncumbrances() const
	{
	return getObjVars().hasItem(OBJVAR_ARMOR_BASE + '.' + OBJVAR_ARMOR_ENCUMBRANCE);
}	// TangibleObject::hasEncumbrances

// ----------------------------------------------------------------------

/**
 * Returns the encumbrance values this object applies to a player when equipped.
 *
 * @param encumbrances	vector to be filled in with encumbrance values for the health,
 *						action, and mind groups; will be empty if this object does not
 *						encumber a creature
 *
 * @return true if encumbrances has good data, false if not
 */
bool TangibleObject::getEncumbrances(std::vector<int> & encumbrances) const
		{
static int datatable_type_col            = -1;
static int datatable_min_encumbrance_col = -1;
static int datatable_max_encumbrance_col = -1;

	// NOTE: it might be better if we do all this via script, to reduce
	// duplicate functionality

	encumbrances.clear();

	DataTable * dt = DataTableManager::getTable(DATATABLE_ARMOR, true);
	if (dt == nullptr)
		return false;
	else if (datatable_type_col == -1)
	{
		// cache the column indexes
		datatable_type_col            = dt->findColumnNumber(DATATABLE_TYPE_COL);
		datatable_min_encumbrance_col = dt->findColumnNumber(DATATABLE_MIN_ENCUMBRANCE_COL);
		datatable_max_encumbrance_col = dt->findColumnNumber(DATATABLE_MAX_ENCUMBRANCE_COL);
		if (datatable_type_col < 0 ||
			datatable_min_encumbrance_col < 0 ||
			datatable_max_encumbrance_col < 0)
		{
			WARNING_STRICT_FATAL(true, ("TangibleObject::getEncumbrances: armor datatable missing expected columns"));
			return false;
		}
	}

	// get the encumbrance values from the objvars
	float baseEncumbrance = 0;
	if (!getObjVars().getItem(OBJVAR_ARMOR_BASE + '.' + OBJVAR_ARMOR_ENCUMBRANCE, baseEncumbrance))
			return false;
	std::vector<float> encumbranceSplit;
	if (!getObjVars().getItem(OBJVAR_ARMOR_BASE + '.' + OBJVAR_ENCUMBRANCE_SPLIT, encumbranceSplit))
			return false;
	if (encumbranceSplit.size() != 3)
		return false;

	// get the actual encumbrance range from the armor datatable
	int armorLevel;
	if (!getObjVars().getItem(OBJVAR_ARMOR_BASE + "." + OBJVAR_ARMOR_LEVEL, armorLevel))
			return false;
	char armorLevelRowName[64];
	snprintf(armorLevelRowName, sizeof(armorLevelRowName), "%s%d", DATATABLE_FINAL_ROW.c_str(), armorLevel);
	int armorLevelRow = dt->searchColumnInt(datatable_type_col, Crc::calculate(armorLevelRowName));
	if (armorLevelRow < 0)
		return false;
	int minEncumbrance = dt->getIntValue(datatable_min_encumbrance_col, armorLevelRow);
	int maxEncumbrance = dt->getIntValue(datatable_max_encumbrance_col, armorLevelRow);

	// scale the base encumbrance by the actual encumbrance range
	baseEncumbrance = baseEncumbrance * (maxEncumbrance - minEncumbrance) + minEncumbrance;

	// split the base encumbrace into the three attrib groups
	encumbrances.resize(encumbranceSplit.size());
	std::vector<int>::iterator j = encumbrances.begin();
	for (std::vector<float>::const_iterator i = encumbranceSplit.begin(); i != encumbranceSplit.end(); ++i, ++j)
	{
		*j = static_cast<int>((*i) * baseEncumbrance);
		}
		return true;
	}

// ----------------------------------------------------------------------

/**
 * Returns the slot id of the slot we use to hold temporary crafting prototypes.
 *
 * @return the prototype slot id
 */
const SlotId & TangibleObject::getCraftingPrototypeSlotId()
{
static const SlotId prototypeSlotId(SlotIdManager::findSlotId(TEMP_PROTOTYPE_SLOT_NAME));

	return prototypeSlotId;
}

// ----------------------------------------------------------------------

/**
 * Returns the slot id of the slot we use to hold temporary manf schematics.
 *
 * @return the schematic slot id
 */
const SlotId & TangibleObject::getCraftingManufactureSchematicSlotId()
{
static const SlotId schematicSlotId(SlotIdManager::findSlotId(TEMP_SCHEMATIC_SLOT_NAME));

	return schematicSlotId;
}

// ----------------------------------------------------------------------

/**
 * Generates an id to allow players to identify a crafted component. NOTE: this
 * returns a pointer to a static buffer, so make sure you save off the number
 * before calling this function again.
 *
 * @param id		network id that the serial number will be generated from
 *
 * @return the serial number
 */
const char * TangibleObject::generateSerialNumber(const NetworkId & id)
{
static char buffer[11] = {'(', 0, 0, 0, 0, 0, 0, 0, 0, ')', 0};
int i;
char c;
uint64 mask;

#if defined(WIN32)
	uint64 value = (id.getValue() & 0x000000FFFFFFFFFFi64);
#elif defined(linux)
	uint64 value = (id.getValue() & 0x000000FFFFFFFFFFULL);
#else
#error unsupported OS
#endif

	for (i = 0; i < 10; ++i)
	{
		mask = ((value >> 4) | ((value & 0x0F) << 36));
		value ^= mask;
	}

	for (i = 1; i < 9; ++i)
	{
		c = static_cast<char>(value & 0x1F);
		value >>= 5;
		if (c < 10)
			c += '0';
		else
			c += ('a' - 10);
		buffer[i] = c;
	}
	return buffer;
}	// TangibleObject::generateSerialNumber

//----------------------------------------------------------------------

/**
 * Returns the attribute bonuses this object grants when equipped.
 *
 * @param attribBonuses		list to be filled in with the bonuses
 */
void TangibleObject::getAttribBonuses(std::vector<std::pair<int, int> > & attribBonuses) const
{
	attribBonuses.clear();

	DynamicVariableList::NestedList mods(getObjVars(), OBJVAR_ATTRIBUTE_BONUS);

	for (DynamicVariableList::NestedList::const_iterator i = mods.begin();
		i != mods.end(); ++i)
	{
		int bonus = 0;
		i.getValue(bonus);
		int attribute = atoi(i.getName().c_str());
		if (isdigit(i.getName()[0]) && attribute >= 0 &&
			attribute < Attributes::NumberOfAttributes)
		{
			if (bonus > ConfigServerGame::getMaxItemAttribBonus())
				bonus = ConfigServerGame::getMaxItemAttribBonus();
			else if (bonus < -ConfigServerGame::getMaxItemAttribBonus())
				bonus = -ConfigServerGame::getMaxItemAttribBonus();
			attribBonuses.push_back(std::make_pair(attribute, bonus));
		}
		else
		{
			WARNING(true, ("Object %s has invalid attribute bonus objvar name of %s",
				getNetworkId().getValueString().c_str(), i.getName().c_str()));
		}
	}
}	// TangibleObject::getAttribBonuses

/**
 * Returns the attribute bonuses this object grants when equipped.
 *
 * @param attribBonuses		list of length 9 to be filled in with the bonuses
 *
 * @return true if we have bonuses, false if not
 */
bool TangibleObject::getAttribBonuses(std::vector<int> & attribBonuses) const
{
	attribBonuses.clear();
	attribBonuses.resize(Attributes::NumberOfAttributes, 0);

	bool hasBonues = false;
	typedef std::vector<std::pair<int, int> > BonusPairs;
	BonusPairs pairedBonuses;
	getAttribBonuses(pairedBonuses);
	for (BonusPairs::const_iterator i = pairedBonuses.begin(); i != pairedBonuses.end(); ++i)
	{
		int index = (*i).first;
		if (index >=0 && index < Attributes::NumberOfAttributes)
		{
			attribBonuses[index] = (*i).second;
			hasBonues = true;
		}
	}
	return hasBonues;
}	// TangibleObject::getAttribBonuses

//----------------------------------------------------------------------

/**
 * Returns the attribute bonus of this object for a given attribute.
 *
 * @param attribute		the attribute
 *
 * @return the bonus for the attribute
 */
int TangibleObject::getAttribBonus(int attribute) const
{
	if (attribute < 0 || attribute >= Attributes::NumberOfAttributes)
		return 0;

	DynamicVariableList::NestedList mods(getObjVars(), OBJVAR_ATTRIBUTE_BONUS);
	char buffer[8];
	snprintf(buffer, sizeof(buffer), "%d", attribute);
	int bonus = 0;
	IGNORE_RETURN(mods.getItem(buffer, bonus));

	if (bonus > ConfigServerGame::getMaxItemAttribBonus())
		bonus = ConfigServerGame::getMaxItemAttribBonus();
	else if (bonus < -ConfigServerGame::getMaxItemAttribBonus())
		bonus = -ConfigServerGame::getMaxItemAttribBonus();
	return bonus;
}

//----------------------------------------------------------------------

/**
 * Sets the attribute bonus this object applies when equipped.
 *
 * @param attribute		the attribute
 * @param bonus			the bonus
 */
void TangibleObject::setAttribBonus(int attribute, int bonus)
{
	if (attribute < 0 || attribute >= Attributes::NumberOfAttributes)
		return;

	char buffer[8];
	snprintf(buffer, sizeof(buffer), "%d", attribute);
	if (bonus != 0)
		setObjVarItem(OBJVAR_ATTRIBUTE_BONUS + '.' + buffer, bonus);
	else
		removeObjVarItem(OBJVAR_ATTRIBUTE_BONUS + '.' + buffer);
}

//----------------------------------------------------------------------

/**
 * Returns the skill mod bonuses this object grants when equipped.
 *
 * @param skillModBonuses		list to be filled in with the bonuses
 */
void TangibleObject::getSkillModBonuses(std::vector<std::pair<std::string, int> > & skillModBonuses, bool includeCategorizedSkillmod /*= true*/) const
{
	static std::map<std::string, int> mapSkillModBonuses;
	mapSkillModBonuses.clear();

	DynamicVariableList::NestedList mods(getObjVars(), OBJVAR_SKILLMOD_BONUS);
	for (DynamicVariableList::NestedList::const_iterator i = mods.begin();
		i != mods.end(); ++i)
	{
		int bonus = 0;
		i.getValue(bonus);

		std::string const name = i.getName();
		std::map<std::string, int>::iterator iterFind = mapSkillModBonuses.find(name);
		if (iterFind != mapSkillModBonuses.end())
			iterFind->second += bonus;
		else
			IGNORE_RETURN(mapSkillModBonuses.insert(std::make_pair(name, bonus)));
	}

	if (includeCategorizedSkillmod)
		getCategorizedSkillModBonuses(*this, OBJVAR_CATEGORIZED_SKILLMOD_BONUS, mapSkillModBonuses);

	skillModBonuses.clear();
	for (std::map<std::string, int>::const_iterator iterMapSkillModBonuses = mapSkillModBonuses.begin(); iterMapSkillModBonuses != mapSkillModBonuses.end(); ++iterMapSkillModBonuses)
	{
		if (iterMapSkillModBonuses->second != 0)
			skillModBonuses.push_back(std::make_pair(iterMapSkillModBonuses->first, iterMapSkillModBonuses->second));
	}
}	// TangibleObject::getSkillModBonuses

//----------------------------------------------------------------------

/**
 * Returns the skill mod bonus of this object for a given skill mod.
 *
 * @param skillMod		the skill mod name
 *
 * @return the bonus for the skill mod
 */
int TangibleObject::getSkillModBonus(const std::string & skillMod, bool includeCategorizedSkillmod /*= true*/) const
{
	DynamicVariableList::NestedList mods(getObjVars(), OBJVAR_SKILLMOD_BONUS);
	int value = 0;
	IGNORE_RETURN(mods.getItem(skillMod, value));

	if (includeCategorizedSkillmod)
		value += getCategorizedSkillModBonus(*this, OBJVAR_CATEGORIZED_SKILLMOD_BONUS, skillMod);

	return value;
}	// TangibleObject::getSkillModBonus

//----------------------------------------------------------------------

/**
 * Sets the base skill mod bonus this object applies when equipped.
 *
 * @param skillMod		the skill mod name
 * @param bonus			the bonus
 */
void TangibleObject::setSkillModBonus(const std::string & skillMod, int bonus)
{
	std::string const skillModObjvar = OBJVAR_SKILLMOD_BONUS + '.' + skillMod;

	if (bonus < 0)
	{
		setObjVarItem(skillModObjvar, bonus);
		return;
	}
	else if (bonus == 0)
	{
		removeObjVarItem(skillModObjvar);
		return;
	}

	int const currentTotalBonus = getSkillModBonus(skillMod);
	int const currentBonus = getSkillModBonus(skillMod, false);
	if ((currentTotalBonus - currentBonus + bonus) > ConfigServerGame::getMaxObjectSkillModBonus())
		bonus = std::max(0, ConfigServerGame::getMaxObjectSkillModBonus() - currentTotalBonus + currentBonus);

	if (bonus != 0)
		setObjVarItem(skillModObjvar, bonus);
	else
		removeObjVarItem(skillModObjvar);
}	// TangibleObject::setSkillModBonus

//----------------------------------------------------------------------

void TangibleObject::setSkillModBonus(const std::string & category, const std::string & skillMod, int bonus)
{
	if (category.empty())
		return;

	std::string const skillModObjvar = OBJVAR_CATEGORIZED_SKILLMOD_BONUS + '.' + category + '.' + skillMod;
	if (bonus < 0)
	{
		setObjVarItem(skillModObjvar, bonus);
		return;
	}
	else if (bonus == 0)
	{
		removeObjVarItem(skillModObjvar);
		return;
	}

	int const currentTotalBonus = getSkillModBonus(skillMod);
	int currentBonus = 0;
	IGNORE_RETURN(getObjVars().getItem(skillModObjvar, currentBonus));

	if ((currentTotalBonus - currentBonus + bonus) > ConfigServerGame::getMaxObjectSkillModBonus())
		bonus = std::max(0, ConfigServerGame::getMaxObjectSkillModBonus() - currentTotalBonus + currentBonus);

	if (bonus != 0)
		setObjVarItem(skillModObjvar, bonus);
	else
		removeObjVarItem(skillModObjvar);	
}	// TangibleObject::setSkillModBonus

//----------------------------------------------------------------------

void TangibleObject::removeCategorizedSkillModBonuses(const std::string & category)
{
	if (category.empty())
		return;

	std::string const skillModObjvar = OBJVAR_CATEGORIZED_SKILLMOD_BONUS + '.' + category;
	if (getObjVars().hasItem(skillModObjvar) && (getObjVars().getType(skillModObjvar) == DynamicVariable::LIST))
		removeObjVarItem(skillModObjvar);
}

//----------------------------------------------------------------------

/**
 * Adds a skill mod bonus to a skill mod socket, if the object has an open socket.
 *
 * @param skillMod		the skill mod name
 * @param bonus			the bonus
 *
 * @return true if the bonus was added, false if the object didn't have an open socket
 */
bool TangibleObject::addSkillModSocketBonus(const std::string & skillMod, int bonus)
{
	if (bonus > ConfigServerGame::getMaxSocketSkillModBonus())
		bonus = ConfigServerGame::getMaxSocketSkillModBonus();

	int sockets = getSkillModSockets();
	if (sockets > 0)
	{
		int currentBonus = getSkillModBonus(skillMod, false);

		// only apply the bonus if it's greater than the current bonus
		/*if (bonus <= currentBonus) // This breaks multi-socketed items, so now we combine all values.
			return false;*/          // Not sure if this code was ever hit in the first place.

		setSkillModBonus(skillMod, currentBonus + bonus);

		// trigger scripts
		ScriptParams params;
		std::vector<const char *> skillModParam;
		skillModParam.push_back(skillMod.c_str());
		std::vector<int> bonusParam;
		bonusParam.push_back(bonus);
		params.addParam(skillModParam);
		params.addParam(bonusParam);
		IGNORE_RETURN(getScriptObject()->trigAllScripts(Scripting::TRIG_SOCKET_USED, params));
	}
	return sockets > 0;
}	// TangibleObject::addSkillModSocketBonus

//----------------------------------------------------------------------

/**
 * Adds multiple skill mod bonuses to a skill mod socket, if the object has an
 * open socket. Note that this call will only use up one socket, no matter how
 * many bonuses are added.
 *
 * @param skillModBonuses		the bonuses
 *
 * @return true if the bonus was added, false if the object didn't have an open socket
 */
bool TangibleObject::addSkillModSocketBonuses(const std::vector<std::pair<std::string,
	int> > & skillModBonuses)
{
	int sockets = getSkillModSockets();
	if (sockets > 0)
	{
		int i;
		int count = skillModBonuses.size();
		std::vector<int> bonusOrder(1, 0);
		for (i = 1; i < count; ++i)
		{
			std::vector<int>::iterator j;
			for (j = bonusOrder.begin(); j != bonusOrder.end(); ++j)
			{
				if (skillModBonuses[i].second > skillModBonuses[*j].second)
				{
					bonusOrder.insert(j, i);
					break;
				}
			}
			if (j == bonusOrder.end())
				bonusOrder.push_back(i);
		}

		bool someSucceeded = false;
		for (i = 0; i < count; ++i)
		{
			int index = bonusOrder[i];
			if(addSkillModSocketBonus(skillModBonuses[index].first,
				skillModBonuses[index].second))
				someSucceeded = true;
		}
		if(someSucceeded)
		{
			setSkillModSockets(sockets - 1);
			return true;
		}
	}
	return sockets > 0;
}	// TangibleObject::addSkillModSocketBonuses

//----------------------------------------------------------------------

/**
 * Returns the number of available skill mod sockets.
 *
 * @return the number of sockets
 */
int TangibleObject::getSkillModSockets(void) const
{
	int sockets = 0;

	if (getGameObjectType() == SharedObjectTemplate::GOT_misc_container_wearable)
		return 0;

	bool const isCybernetic = getGameObjectType() == SharedObjectTemplate::GOT_cybernetic_arm || getGameObjectType() == SharedObjectTemplate::GOT_cybernetic_legs;

	IGNORE_RETURN(getObjVars().getItem(OBJVAR_SKILLMOD_SOCKETS, sockets));
	return isCybernetic ? sockets : std::min(sockets, ConfigServerGame::getMaxSkillModSockets());
}	// TangibleObject::getSkillModSockets

//----------------------------------------------------------------------

/**
 * Sets the number of available skill mod sockets.
 *
 * @param sockets		the number of sockets
 */
void TangibleObject::setSkillModSockets(int sockets)
{
	bool const isCybernetic = getGameObjectType() == SharedObjectTemplate::GOT_cybernetic_arm || getGameObjectType() == SharedObjectTemplate::GOT_cybernetic_legs;

	if (sockets > ConfigServerGame::getMaxSkillModSockets() && !isCybernetic)
		sockets = ConfigServerGame::getMaxSkillModSockets();
	if (sockets > 0)
		setObjVarItem(OBJVAR_SKILLMOD_SOCKETS, sockets);
	else
		removeObjVarItem(OBJVAR_SKILLMOD_SOCKETS);
}	// TangibleObject::setSkillModSockets

//----------------------------------------------------------------------

/**
 * Returns the amount of time left before a crafted prototype object will be done.
 *
 * @return the time left, in secs
 */
int TangibleObject::getCraftingToolPrototypeTime() const
{
	int timeLeft = -1;
	float prototypeTime = 0;
	getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_TIME,prototypeTime);

	if (prototypeTime > 0)
	{
		int startTime =0;
		if (getObjVars().getItem(OBJVAR_CRAFTING_PROTOTYPE_START_TIME,startTime))
		{
			const int currentTime = static_cast<int>(ServerClock::getInstance().getGameTimeSeconds());
			timeLeft = static_cast<int>(prototypeTime + 0.5f) - (currentTime - startTime);
		}
	}
	return timeLeft;
}	// TangibleObject::getCraftingToolPrototypeTime

//----------------------------------------------------------------------

void TangibleObject::getAttributesForCraftingTool (AttributeVector & data) const
{
	char valueBuffer[32];

	static const Unicode::String CRAFT_TOOL_WORKING  (Unicode::narrowToWide("@crafting:tool_status_working"));
	static const Unicode::String CRAFT_TOOL_FINISHED (Unicode::narrowToWide("@crafting:tool_status_finished"));
	static const Unicode::String CRAFT_TOOL_READY    (Unicode::narrowToWide("@crafting:tool_status_ready"));
	static const SlotId outputSlotId(SlotIdManager::findSlotId(MANF_OUTPUT_SLOT_NAME));

	float craft_effectiveness = 0.0f;
	getObjVars ().getItem(OBJVAR_CRAFTING_STATIONMOD, craft_effectiveness);
	snprintf (valueBuffer, sizeof (valueBuffer), "%.2f", craft_effectiveness);
	data.push_back (std::make_pair(SharedObjectAttributes::craft_tool_effectiveness, Unicode::narrowToWide(valueBuffer)));

	// add some internal attribs for the crafting station state

	// see if we are making a prototype

	int prototypeTimeLeft = getCraftingToolPrototypeTime();
	if (prototypeTimeLeft > 0)
	{
		data.push_back(std::make_pair(SharedObjectAttributes::craft_tool_status, CRAFT_TOOL_WORKING));
		_itoa(prototypeTimeLeft, valueBuffer, 10);
		data.push_back(std::make_pair(SharedObjectAttributes::craft_tool_time, Unicode::narrowToWide(valueBuffer)));
	}
	else
	{
		// see if there is an object in the output hopper
		bool hasPrototype = false;
		SlottedContainer const * const slotContainer = ContainerInterface::getSlottedContainer(*this);
		if (slotContainer != nullptr)
		{
			Container::ContainerErrorCode tmp = Container::CEC_Success;
			if (slotContainer->getObjectInSlot(outputSlotId, tmp) != Container::ContainedItem::cms_invalid)
			{
				hasPrototype = true;
			}
		}
		if (hasPrototype)
			data.push_back(std::make_pair(SharedObjectAttributes::craft_tool_status, CRAFT_TOOL_FINISHED));
		else
			data.push_back(std::make_pair(SharedObjectAttributes::craft_tool_status, CRAFT_TOOL_READY));
	}
}

// ----------------------------------------------------------------------

void TangibleObject::getAttributes(const NetworkId & playerId, AttributeVector &data) const
{
	ServerObject::getAttributes(playerId, data);
}

// ----------------------------------------------------------------------

void TangibleObject::getAttributesForShipComponent (AttributeVector & data) const
{
	ShipComponentData * const componentData = ShipComponentDataManager::create(*this);
	if(componentData)
	{
		componentData->getAttributes(data);
		delete componentData;
	}
}

// ----------------------------------------------------------------------

void TangibleObject::getAttributes(AttributeVector & data) const
{
	ServerObject::getAttributes(data);

	char valueBuffer[32];

	CreatureObject const * const thisCreature = asCreatureObject();

	if (!thisCreature)
	{
		const int maxHit = getMaxHitPoints ();
		// Only consider tangible invulnerability rules for this case
		if (maxHit > 0 && !TangibleObject::isInvulnerable())
		{
			snprintf (valueBuffer, sizeof (valueBuffer), "%d/%d", maxHit - getDamageTaken (), maxHit);
			data.push_back (std::make_pair (SharedObjectAttributes::condition, Unicode::narrowToWide (valueBuffer)));
		}

		const ServerObjectTemplate * const targetTemplate = safe_cast<const ServerObjectTemplate *>(getObjectTemplate());
		if (targetTemplate)
		{
			bool playerCanMove = false;

			for (size_t i = 0; i < targetTemplate->getMoveFlagsCount(); ++i)
			{
				if (targetTemplate->getMoveFlags(i) == ServerObjectTemplate::MF_player)
				{
					playerCanMove = true;
					break;
				}
			}

			if (getCacheVersion() <= 0 && playerCanMove)
			{
				snprintf (valueBuffer, sizeof (valueBuffer), "%d", std::max (1, getVolume ()));
				data.push_back (std::make_pair (SharedObjectAttributes::volume, Unicode::narrowToWide (valueBuffer)));
			}
		}
	}

	const int got = getGameObjectType ();

	switch (got)
	{
	case SharedObjectTemplate::GOT_misc_drink:
	case SharedObjectTemplate::GOT_misc_food:
	case SharedObjectTemplate::GOT_misc_pharmaceutical:
	case SharedObjectTemplate::GOT_weapon_ranged_thrown:
	case SharedObjectTemplate::GOT_weapon_heavy_misc:
	case SharedObjectTemplate::GOT_misc_fishing_bait:
		{
			const int count = m_count.get ();
			if (count)
			{
				snprintf (valueBuffer, sizeof (valueBuffer), "%d", m_count.get ());
				data.push_back (std::make_pair (SharedObjectAttributes::counter_uses_remaining, Unicode::narrowToWide (valueBuffer)));
			}
		}
		break;
	case SharedObjectTemplate::GOT_resource_container_pseudo:
		{
			int resourceValue = 0;
			for (int i = 0; i < Crafting::RA_numResourceAttributes; ++i)
			{

				const std::string ovName(OBJVAR_COMPONENT_DATA + "." +
					Crafting::getResourceAttributeName(i));
				if (getObjVars().getItem(ovName, resourceValue))
				{
					char buffer[24];
					snprintf(buffer, sizeof(buffer), "%i", resourceValue);
					data.push_back(std::make_pair(Crafting::getResourceAttributeName(i),Unicode::narrowToWide(buffer)));
				}
			}
		}
		break;
	default:
		break;
	}

	const VolumeContainer * const volumeContainer = ContainerInterface::getVolumeContainer (*this);
	if (volumeContainer)
	{
		if (got != SharedObjectTemplate::GOT_misc_factory_crate)
		{
			const int curVol = volumeContainer->getCurrentVolume ();
			const int totVol = volumeContainer->getTotalVolume ();

			if (curVol >= 0 && totVol >= 0)
			{
				snprintf (valueBuffer, sizeof (valueBuffer), "%d/%d", curVol, totVol);
				data.push_back (std::make_pair (SharedObjectAttributes::contents, Unicode::narrowToWide (valueBuffer)));
			}
		}
	}

	std::vector<std::pair<std::string, int> > skillModBonuses;
	getSkillModBonuses(skillModBonuses);
	if (!skillModBonuses.empty())
	{
		char buffer[16];
		int count = skillModBonuses.size();
		for (int i = 0; i < count; ++i)
		{
			_itoa(skillModBonuses[i].second, buffer, 10);
			data.push_back(std::make_pair(SharedObjectAttributes::cat_skill_mod_bonus + "." + "@stat_n:" + skillModBonuses[i].first, Unicode::narrowToWide(buffer)));
		}
	}

	std::vector<int> attribBonuses;
	if (getAttribBonuses(attribBonuses) && !attribBonuses.empty())
	{
		char buffer[16];
		int const attribCount = attribBonuses.size();
		for (int attrib = 0; attrib < attribCount; ++attrib)
		{
			int const bonus = attribBonuses[attrib];
			if (bonus != 0)
			{
				_itoa(bonus, buffer, 10);
				data.push_back(std::make_pair(SharedObjectAttributes::cat_attrib_mod_bonus + "." + "@att_bonus_n:" + s_attributeBonusNames[attrib], Unicode::narrowToWide(buffer)));
			}
		}
	}

	int sockets = getSkillModSockets();
	if (sockets > 0)
	{
		char buffer[16];
		_itoa(sockets, buffer, 10);
		data.push_back(std::make_pair(SharedObjectAttributes::sockets, Unicode::narrowToWide(buffer)));
	}

	if (isCraftingTool())
		getAttributesForCraftingTool (data);

	if (getCreatorId() != NetworkId::cms_invalid)
		data.push_back(std::make_pair(SharedObjectAttributes::crafter,       getCreatorName()));

	if (isCrafted())
		data.push_back(std::make_pair(SharedObjectAttributes::serial_number, Unicode::narrowToWide(generateSerialNumber(getCraftedId()))));

	if(GameObjectTypes::isTypeOf(got, SharedObjectTemplate::GOT_ship_component))
	{
		getAttributesForShipComponent(data);
	}

	{
		// If this is a fake ship component in the interior of a ship, get its attributes from the ship

		ServerObject const * const cell = safe_cast<ServerObject const *>(ContainerInterface::getContainedByObject(*this));
		if (cell && cell->asCellObject())
		{
			ServerObject const * const shipAsObject = safe_cast<ServerObject const *>(ContainerInterface::getContainedByObject(*cell));
			if (shipAsObject)
			{
				ShipObject const * const ship = shipAsObject->asShipObject();
				if (ship)
				{
					int slot;
					if (getObjVars().getItem("intSlot",slot))
					{
						ShipComponentData const * const componentData = ship->createShipComponentData(slot);
						if (componentData)
						{
							componentData->getAttributes(data);
							delete componentData;
						}
					}
				}
			}
		}
	}

	if (getObjVars().hasItem(OBJVAR_BIO_LINK_ID))
	{
		// display the bio-link info
		if (getBioLink() == PENDING_BIO_LINK_NETWORK_ID)
		{
			data.push_back(std::make_pair(SharedObjectAttributes::bio_link, Unicode::narrowToWide("@"+SharedStringIds::bio_link_pending.getCanonicalRepresentation())));
		}
		else
		{
			const std::string & bioLinkName = NameManager::getInstance().getPlayerFullName(getBioLink());
			if (!bioLinkName.empty())
			{
				data.push_back(std::make_pair(SharedObjectAttributes::bio_link, Unicode::narrowToWide(bioLinkName)));
			}
			else
			{
				LOG("CustomerService", ("Biolink: object %s is flagged as bio-linked "
					"to player %s that we can't find a name for",
					getNetworkId().getValueString().c_str(),
					PlayerObject::getAccountDescription(getBioLink()).c_str()));
				data.push_back(std::make_pair(SharedObjectAttributes::bio_link, Unicode::narrowToWide("@"+SharedStringIds::bio_link_unknown.getCanonicalRepresentation())));
			}
		}
	}

	// No Trade
	{
		if (markedNoTrade())
		{
			data.push_back(std::make_pair(SharedObjectAttributes::no_trade, Unicode::emptyString));

			if (markedNoTradeShared(true))
			{
				data.push_back(std::make_pair(SharedObjectAttributes::no_trade_shared, Unicode::emptyString));
			}
		}
		else
		{
			GameScriptObject * const gso = const_cast<GameScriptObject *>(getScriptObject());
			if (gso && gso->hasScript(NOMOVE_SCRIPT))
			{
				data.push_back(std::make_pair(SharedObjectAttributes::no_trade, Unicode::emptyString));

				if (markedNoTradeShared(false))
				{
					data.push_back(std::make_pair(SharedObjectAttributes::no_trade_shared, Unicode::emptyString));
				}
			}
		}
	}
}

// ----------------------------------------------------------------------

void TangibleObject::getAttributesForAuction(AttributeVector &data) const
{
	static const std::string attributeAppearanceData("AppearanceData");

	DEBUG_FATAL(getAppearanceData().length() > 1000,("Appearance data was more than 1000 characters long, too long to save."));
	data.push_back(std::make_pair(attributeAppearanceData, Unicode::narrowToWide(getAppearanceData())));
	ServerObject::getAttributesForAuction(data);
}

// ----------------------------------------------------------------------

void TangibleObject::showFlyText(const StringId &outputText, float scale, int r, int g, int b)
{
	//-- Get the controller.
	Controller *const controller = getController();
	if (!controller)
	{
		DEBUG_WARNING(true, ("showFlyText(): object [%s] has no controller.", getNetworkId().getValueString().c_str()));
		return;
	}

	//-- Send the showFlyText message to all clients.
	uint32 flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_PROXY_CLIENT;
	if (isPlayerControlled())
		flags |= GameControllerMessageFlags::DEST_AUTH_CLIENT;
	controller->appendMessage(static_cast<int>(CM_showFlyText), 0.0f, new MessageQueueShowFlyText(getNetworkId(), outputText, scale, r, g, b, 0), flags);
}

// ----------------------------------------------------------------------

void TangibleObject::showFlyText(const Unicode::String &outputTextOOB, float scale, int r, int g, int b)
{
	//-- Get the controller.
	Controller *const controller = getController();
	if (!controller)
	{
		DEBUG_WARNING(true, ("showFlyText(): object [%s] has no controller.", getNetworkId().getValueString().c_str()));
		return;
	}

	//-- Send the showFlyText message to all clients.
	uint32 flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_PROXY_CLIENT;
	if (isPlayerControlled())
		flags |= GameControllerMessageFlags::DEST_AUTH_CLIENT;
	controller->appendMessage(static_cast<int>(CM_showFlyText), 0.0f, new MessageQueueShowFlyText(getNetworkId(), outputTextOOB, scale, r, g, b, 0), flags);
}

// ----------------------------------------------------------------------

void TangibleObject::showFlyText(const Unicode::String &outputTextOOB, float scale, int r, int g, int b, int flags)
{
	//-- Get the controller.
	Controller *const controller = getController();
	if (!controller)
	{
		DEBUG_WARNING(true, ("showFlyText(): object [%s] has no controller.", getNetworkId().getValueString().c_str()));
		return;
	}

	//-- Send the showFlyText message to all clients.
	uint32 netFlags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_PROXY_CLIENT;
	if (isPlayerControlled())
		flags |= GameControllerMessageFlags::DEST_AUTH_CLIENT;
	controller->appendMessage(static_cast<int>(CM_showFlyText), 0.0f, new MessageQueueShowFlyText(getNetworkId(), outputTextOOB, scale, r, g, b,  flags), netFlags);
}

// ----------------------------------------------------------------------

void TangibleObject::showCombatText(const TangibleObject & attackerObject, const StringId &outputText, float scale, int r, int g, int b)
{
	//-- Get the controller.
	Controller *const controller = getController();
	if (!controller)
	{
		DEBUG_WARNING(true, ("showFlyText(): object [%s] has no controller.", getNetworkId().getValueString().c_str()));
		return;
	}

	//-- Send the showFlyText message to all clients.
	uint32 flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_PROXY_CLIENT;
	if (isPlayerControlled())
		flags |= GameControllerMessageFlags::DEST_AUTH_CLIENT;
	controller->appendMessage(static_cast<int>(CM_showCombatText), 0.0f, new MessageQueueShowCombatText(getNetworkId(), attackerObject.getNetworkId(), outputText, scale, r, g, b, 0), flags);
}

// ----------------------------------------------------------------------

void TangibleObject::showCombatText(const TangibleObject & attackerObject, const Unicode::String &outputTextOOB, float scale, int r, int g, int b)
{
	//-- Get the controller.
	Controller *const controller = getController();
	if (!controller)
	{
		DEBUG_WARNING(true, ("showFlyText(): object [%s] has no controller.", getNetworkId().getValueString().c_str()));
		return;
	}

	//-- Send the showFlyText message to all clients.
	uint32 flags = GameControllerMessageFlags::SEND | GameControllerMessageFlags::RELIABLE | GameControllerMessageFlags::DEST_PROXY_CLIENT;
	if (isPlayerControlled())
		flags |= GameControllerMessageFlags::DEST_AUTH_CLIENT;
	controller->appendMessage(static_cast<int>(CM_showCombatText), 0.0f, new MessageQueueShowCombatText(getNetworkId(), attackerObject.getNetworkId(), outputTextOOB, scale, r, g, b, 0), flags);
}

// ----------------------------------------------------------------------

void TangibleObject::handleCMessageTo(const MessageToPayload &message)
{
	static const std::string methodname_updateCounter = "C++updateCounter";

	if (message.getMethod() == methodname_updateCounter)
	{
		if (isCraftingTool())
		{
			int timeLeft = getCraftingToolPrototypeTime();
			if (timeLeft <= 0)
			{
				// done
				timeLeft = 0;
			}
			else
			{
				// update the count 5 sec from now
				MessageToQueue::getInstance().sendMessageToC (getNetworkId(), methodname_updateCounter, std::string (), 5, false);
			}
			m_count = timeLeft;
		}
	}
	else if (message.getMethod() == "C++UpdatePvp")
	{
		Pvp::forceStatusUpdate(*this);
	}
	else if (message.getMethod() == "C++PurgeStructure")
	{
		LOG("CustomerService",("Purge:  Purging structure %s",getNetworkId().getValueString().c_str()));
		rollupStructure(NetworkId(message.getDataAsString()),false);
	}
	else if (message.getMethod() == "C++WarnAboutPurge")
	{
		LOG("CustomerService",("Purge:  Sending warning emails to the admins of structure %s",getNetworkId().getValueString().c_str()));
		rollupStructure(NetworkId(message.getDataAsString()),true);
	}
	else if (message.getMethod() == "C++PurgeVendor")
	{
		LOG("CustomerService",("Purge:  Purging vendor %s",getNetworkId().getValueString().c_str()));
		permanentlyDestroy(DeleteReasons::Decay); // Script's onDestroy() method will handle cleanup
	}
	else if (message.getMethod() == "C++CheckPvpRegion")
	{
		// check for pvp region change
		if (isAuthoritative() && !isInvulnerable() && !isNonPvpObject())
		{
			Pvp::checkForRegionChange(*this);
		}

		if (isPlayerControlled())
		{
			std::map<NetworkId, LfgCharacterData> const & connectedCharacterLfgData = ServerUniverse::getConnectedCharacterLfgData();
			std::map<NetworkId, LfgCharacterData>::const_iterator iterFind = connectedCharacterLfgData.find(getNetworkId());
			if (iterFind != connectedCharacterLfgData.end())
			{
				std::string regionName;
				std::string playerCityName;

				std::string const & sceneId = ServerWorld::getSceneId();
				Vector const &position = findPosition_w();
				Region const * const region = RegionMaster::getSmallestVisibleRegionAtPoint(sceneId, position.x, position.z);
				if (region != nullptr)
					regionName = Unicode::wideToNarrow(region->getName());

				int const cityId = CityInterface::getCityAtLocation(sceneId, static_cast<int>(position.x), static_cast<int>(position.z), 0);
				if (cityId > 0)
					playerCityName = CityInterface::getCityInfo(cityId).getCityName();

				if (iterFind->second.locationRegion != regionName)
					ServerUniverse::setConnectedCharacterRegionData(getNetworkId(), regionName);

				if (iterFind->second.locationPlayerCity != playerCityName)
					ServerUniverse::setConnectedCharacterPlayerCityData(getNetworkId(), playerCityName);

				PlayerObject * const player = PlayerCreatureController::getPlayerObject(asCreatureObject());
				if (player)
					player->updateGcwRegionInfo();
			}
		}
	}
	else if (message.getMethod() == "C++ProcessHateOverTime")
	{
		if (!m_hateOverTime.empty())
		{
			std::list<NetworkId> expired;
			unsigned long const currentGameTime =  ServerClock::getInstance().getGameTimeSeconds();

			for (std::map<NetworkId, std::pair<float, std::pair<unsigned long, unsigned long> > >::const_iterator iter = m_hateOverTime.begin(); iter != m_hateOverTime.end(); ++iter)
			{
				if (iter->second.second.first < currentGameTime)
				{
					IGNORE_RETURN(addHate(iter->first, iter->second.first * static_cast<float>(std::min(currentGameTime, iter->second.second.second) - iter->second.second.first)));

					if (iter->second.second.second <= currentGameTime)
					{
						expired.push_back(iter->first);
					}
					else
					{
						m_hateOverTime.set(iter->first, std::make_pair(iter->second.first, std::make_pair(currentGameTime, iter->second.second.second)));
					}
				}
			}

			if (!expired.empty())
			{
				for (std::list<NetworkId>::const_iterator iterExpired = expired.begin(); iterExpired != expired.end(); ++iterExpired)
				{
					IGNORE_RETURN(m_hateOverTime.erase(*iterExpired));
				}
			}

			if (!m_hateOverTime.empty())
			{
				MessageToQueue::sendMessageToC(getNetworkId(), "C++ProcessHateOverTime", std::string(), 1, false);
			}
		}
	}
	else if (message.getMethod() == "C++DestroyDupedTcg5AtAtHeadItv")
	{
		LOG("CustomerService",("VeteranRewards:  destroying duped tcg5_atat_head_itv item (%s) owned by (%s)", getNetworkId().getValueString().c_str(), getOwnerId().getValueString().c_str()));
		permanentlyDestroy(DeleteReasons::Replaced);
	}
	else
		ServerObject::handleCMessageTo(message);
}

//----------------------------------------------------------------------

CustomizationData *TangibleObject::fetchCustomizationData ()
{
	CustomizationDataProperty * const cdprop = safe_cast<CustomizationDataProperty *>(getProperty (CustomizationDataProperty::getClassPropertyId()));
	return cdprop ? cdprop->fetchCustomizationData () : 0;
}

//----------------------------------------------------------------------

const CustomizationData *TangibleObject::fetchCustomizationData () const
{
	const CustomizationDataProperty * const cdprop = safe_cast<const CustomizationDataProperty *>(getProperty (CustomizationDataProperty::getClassPropertyId()));
	return cdprop ? cdprop->fetchCustomizationData () : 0;
}

// ----------------------------------------------------------------------

void TangibleObject::calcPvpableState()
{
	// a tangible is pvpable if it has hit points, has a shared template, does not have an arrangement descriptor, and is not player movable
	// or m_attackableOverride is true
	if (m_attackableOverride.get())
	{
		setPvpable(true);
		return;
	}

	setPvpable(false);
	if (getMaxHitPoints() && getSharedTemplate() && !getSharedTemplate()->getArrangementDescriptor())
	{
		ServerObjectTemplate const *objTemplate = safe_cast<ServerObjectTemplate const *>(getObjectTemplate());
		for (size_t i = 0; i < objTemplate->getMoveFlagsCount(); ++i)
			if (objTemplate->getMoveFlags(i) == ServerObjectTemplate::MF_player)
				return;
		setPvpable(true);
	}
}

// ----------------------------------------------------------------------

bool TangibleObject::isInvulnerable() const
{
	return hasCondition(ServerTangibleObjectTemplate::C_invulnerable);
}

// ----------------------------------------------------------------------

void TangibleObject::setInvulnerable(bool invulnerable)
{
	if (invulnerable)
	{
		setCondition(ServerTangibleObjectTemplate::C_invulnerable);
	}
	else
	{
		clearCondition(ServerTangibleObjectTemplate::C_invulnerable);
	}

	GameScriptObject * const gameScriptObject = getScriptObject();

	if (gameScriptObject != nullptr)
	{
		ScriptParams scriptParams;
		scriptParams.addParam(hasCondition(ServerTangibleObjectTemplate::C_invulnerable));
		IGNORE_RETURN(gameScriptObject->trigAllScripts(Scripting::TRIG_INVULNERABLE_CHANGED, scriptParams));
	}
}

// ----------------------------------------------------------------------

bool TangibleObject::isInsured(void) const
{
	return hasCondition(ServerTangibleObjectTemplate::C_insured);
}

// ----------------------------------------------------------------------

void TangibleObject::setInsured(bool insured)
{
	if (insured)
		setCondition(ServerTangibleObjectTemplate::C_insured);
	else
		clearCondition(ServerTangibleObjectTemplate::C_insured);
}

// ----------------------------------------------------------------------

bool TangibleObject::isUninsurable(void) const
{
	return hasCondition(ServerTangibleObjectTemplate::C_uninsurable);
}

// ----------------------------------------------------------------------

void TangibleObject::setUninsurable(bool uninsurable)
{
	if (uninsurable)
		setCondition(ServerTangibleObjectTemplate::C_uninsurable);
	else
		clearCondition(ServerTangibleObjectTemplate::C_uninsurable);
}

// ----------------------------------------------------------------------

bool TangibleObject::wantSawAttackTriggers() const
{
	if (isAuthoritative())
		return hasCondition(ServerTangibleObjectTemplate::C_wantSawAttackTrigger);
	return false;
}

// ----------------------------------------------------------------------

void TangibleObject::setWantSawAttackTriggers(bool enable)
{
	if (enable)
		setCondition(ServerTangibleObjectTemplate::C_wantSawAttackTrigger);
	else
		clearCondition(ServerTangibleObjectTemplate::C_wantSawAttackTrigger);
}

//----------------------------------------------------------------------

int TangibleObject::getCombatSkeleton() const
{
	return static_cast<int>((safe_cast<const ServerTangibleObjectTemplate *>(getObjectTemplate()))->getCombatSkeleton());
}	// TangibleObject::getCombatSkeleton()

//-----------------------------------------------------------------------

bool TangibleObject::canDropInWorld() const
{
	return true;
}

//------------------------------------------------------------------------------------------

int TangibleObject::getNumberOfLots() const
{

//-- fetch the shared object template
	const SharedTangibleObjectTemplate* const tangibleObjectTemplate = safe_cast<const SharedTangibleObjectTemplate*> (getSharedTemplate());
	if (!tangibleObjectTemplate)
	{
		return 0;
	}

	//-- get the structure footprint
	const StructureFootprint* const structureFootprint = tangibleObjectTemplate->getStructureFootprint ();
	if (!structureFootprint)
	{
		return 0;
	}

	//-- return the number of lots
	return structureFootprint->getNumberOfStructureReservations ();
}

//------------------------------------------------------------------------------------------

bool TangibleObject::isOnAdminList(const CreatureObject& player) const
{
	//shortcut for owners
	if (getOwnerId() == player.getNetworkId())
		return true;

	if(player.getClient() && player.getClient()->isGod())
		return true;

	// In script, the admin list is stored as a list of strings.
	// The format of the list is:
	// 1) All player are stored as network IDs represented as a string
	// 2) All guilds are stored as guild IDs represted as a string and prefixed with "guild:"

	typedef std::vector<Unicode::String> StringVector;
	static StringVector sv;
	sv.clear ();

	if (getObjVars().getItem("player_structure.admin.adminList", sv))
	{
		const NetworkId requestorNetworkId = player.getNetworkId();

		// Convert the network ID to a Unicode string
		Unicode::String requestorNetworkIdString = Unicode::narrowToWide( requestorNetworkId.getValueString() );

		// Try to match the string
		for (StringVector::const_iterator it = sv.begin(); it != sv.end(); ++it)
		{
			const Unicode::String & adminString = *it;
			if (Unicode::caseInsensitiveCompare(adminString, requestorNetworkIdString))
			{
				return true;
			}
		}
	}

	return false;
}

//------------------------------------------------------------------------------------------

bool TangibleObject::isOnHopperList(const CreatureObject& player) const
{
	if (getOwnerId() == player.getNetworkId())
		return true;

	if(player.getClient() && player.getClient()->isGod())
		return true;

	// In script, the hopper list is stored as a list of strings.
	// The format of the list is:
	// 1) All player are stored as network IDs represented as a string
	// 2) All guilds are stored as guild IDs represted as a string and prefixed with "guild:"

	typedef std::vector<Unicode::String> StringVector;
	static StringVector sv;
	sv.clear ();

	if (getObjVars().getItem("player_structure.hopper.hopperList", sv))
	{
		const NetworkId requestorNetworkId = player.getNetworkId();

		// Convert the network ID to a Unicode string
		Unicode::String requestorNetworkIdString = Unicode::narrowToWide( requestorNetworkId.getValueString() );

		// Try to match the string
		for (StringVector::const_iterator it = sv.begin(); it != sv.end(); ++it)
		{
			const Unicode::String & adminString = *it;
			if (Unicode::caseInsensitiveCompare(adminString, requestorNetworkIdString))
			{
				return true;
			}
		}
	}
	return false;
}

// ----------------------------------------------------------------------

bool TangibleObject::isCustomizationDataModified() const
{
	return getLocalFlag(LocalObjectFlags::TangibleObject_CustomizationDataModified);
}

// ----------------------------------------------------------------------

void TangibleObject::setCustomizationDataModified(bool modified)
{
	setLocalFlag(LocalObjectFlags::TangibleObject_CustomizationDataModified, modified);
	scheduleForAlter();
}

// ----------------------------------------------------------------------

void TangibleObject::setPvpable(bool pvpable)
{
	bool oldPvpable = !isNonPvpObject();
	setLocalFlag(LocalObjectFlags::TangibleObject_Pvpable, pvpable);

	if ((oldPvpable != pvpable) && !getObservers().empty())
	{
		// did the object's "pvp sync" status change because of the Pvpable change?
		bool const wasPvpSync = PvpUpdateObserver::satisfyPvpSyncCondition(!oldPvpable, hasCondition(ServerTangibleObjectTemplate::C_invulnerable), (asCreatureObject() != nullptr), getPvpFaction());
		bool const isPvpSync = PvpUpdateObserver::satisfyPvpSyncCondition(!pvpable, hasCondition(ServerTangibleObjectTemplate::C_invulnerable), (asCreatureObject() != nullptr), getPvpFaction());

		if (wasPvpSync != isPvpSync)
		{
			// force pvp status update
			Pvp::forceStatusUpdate(*this);

			const std::set<Client *> &observers = getObservers();
			for (std::set<Client *>::const_iterator i = observers.begin(); i != observers.end(); ++i)
			{
				if (isPvpSync)
					(*i)->addObservingPvpSync(this);
				else
					(*i)->removeObservingPvpSync(this);
			}
		}
	}

	if (isAuthoritative())
	{
		if (pvpable && !oldPvpable)
			MessageToQueue::sendRecurringMessageToC(getNetworkId(), "C++CheckPvpRegion", std::string(), static_cast<int>(getPvpRegionCheckTime()));
		else if (!pvpable && oldPvpable)
			MessageToQueue::cancelRecurringMessageTo(getNetworkId(), "C++CheckPvpRegion");
	}
}

// ----------------------------------------------------------------------

int TangibleObject::getInterestRadius() const
{
	if (isPlayerControlled())
		return ConfigServerGame::getPlayerInterestRange();
	ServerTangibleObjectTemplate const *myTemplate = safe_cast<ServerTangibleObjectTemplate const *>(getObjectTemplate());
	return myTemplate->getInterestRadius();
}

// ----------------------------------------------------------------------

void TangibleObject::getRequiredCertifications(std::vector<std::string> & results) const
{
	const SharedTangibleObjectTemplate *const sharedObjectTemplate = safe_cast<const SharedTangibleObjectTemplate*>(getSharedTemplate());
	if (sharedObjectTemplate)
	{
		const int numRequired = sharedObjectTemplate->getCertificationsRequiredCount();
		for(int i=0; i<numRequired; ++i)
		{
			results.push_back(sharedObjectTemplate->getCertificationsRequired(i));
		}
	}
}

// ======================================================================

void TangibleObject::MaxHitPointsCallback::modified(TangibleObject &target, int oldValue, int value, bool) const
{
	// if the values actually changed, and if it changed from 0 or to 0, then we need to recalculate pvp state
	if ((oldValue != value) && ((oldValue == 0) || (value == 0)))
	{
		bool oldNonPvpState = target.isNonPvpObject();
		target.calcPvpableState();

		// if pvp state changed, need to update clients
		if (oldNonPvpState != target.isNonPvpObject())
			Pvp::forceStatusUpdate(target);
	}
}

// ----------------------------------------------------------------------

void TangibleObject::AttackableOverrideCallback::modified(TangibleObject &target, bool oldValue, bool value, bool) const
{
	// if the values actually changed, then we need to recalculate pvp state
	if (oldValue != value)
	{
		bool oldNonPvpState = target.isNonPvpObject();
		target.calcPvpableState();

		// if pvp state changed, need to update clients
		if (oldNonPvpState != target.isNonPvpObject())
			Pvp::forceStatusUpdate(target);
	}
}

// ----------------------------------------------------------------------

void TangibleObject::AppearanceDataCallback::modified(TangibleObject &target, const std::string &oldValue, const std::string &value, bool isLocal) const
{
	UNREF(isLocal);

	//-- Log any time an authoritative, player-controlled object has customizations set to the empty string.
	//   This most likely is something very wrong since the player is going to get reset to default cusotmizations.
	if (value.empty() && !oldValue.empty())
	{
		if (target.isPlayerControlled() && target.isAuthoritative())
		{
			std::string const oldAppearanceDataDump = CustomizationData::makeSelectDumpFormat(oldValue.c_str());
			LOG("customization-data-loss", ("non-empty old customization data getting wiped, id=[%s],template=[%s],old m_appearanceData in SELECT DUMP format:[%s]", target.getNetworkId().getValueString().c_str(), target.getObjectTemplateName(), oldAppearanceDataDump.c_str() ));
		}
	}

	//-- Notify the object that its appearance data has been modified.
	target.appearanceDataModified(value);

	Object * const objectContainer = ContainerInterface::getContainedByObject(target);
	ServerObject * const serverContainer = objectContainer ? objectContainer->asServerObject() : nullptr;
	TangibleObject * const tangibleContainer = serverContainer ? serverContainer->asTangibleObject() : nullptr;
	if(tangibleContainer)
		tangibleContainer->onContainedItemAppearanceDataModified(target, oldValue, value);
}

// ----------------------------------------------------------------------

void TangibleObject::onContainedItemAppearanceDataModified(TangibleObject const & , std::string const & , std::string const & )
{
	//do nothing.  Tangibles don't care if their contained objects have their appearance data change
}

// ----------------------------------------------------------------------

void TangibleObject::getByteStreamFromAutoVariable(const std::string & name, Archive::ByteStream & target) const
{
	if(name == "customAppearance")
	{
		m_customAppearance.pack(target);
	}
	else if(name == "appearanceData")
	{
		m_appearanceData.pack(target);
	}
	else if(name == "incubatorResourceData")
	{
		std::string resourceData;

		static const uint32 crcIncubator = CrcLowerString::calculateCrc("object/tangible/crafting/station/incubator_station.iff");
		if (getTemplateCrc() == crcIncubator)
		{
			DynamicVariableList const & objvars = getObjVars();
			if (objvars.hasItem("power.name") && (DynamicVariable::STRING == objvars.getType("power.name")))
			{
				std::string resourceName;
				IGNORE_RETURN(objvars.getItem("power.name", resourceName));

				ResourceTypeObject const * const rto = ServerUniverse::getInstance().getResourceTypeByName(resourceName);
				if (rto && !rto->getParentClass().isRecycled() && !rto->getParentClass().isPermanent())
					resourceData = rto->getResourceTypeDataForExport();
			}
		}

		Archive::put(target, resourceData);
	}
	else
	{
		ServerObject::getByteStreamFromAutoVariable(name, target);
	}
}

// ----------------------------------------------------------------------

void TangibleObject::setAutoVariableFromByteStream(const std::string & name, const Archive::ByteStream & source)
{
    Archive::ReadIterator ri(source);
    if(name == "customAppearance")
    {
        m_customAppearance.unpackDelta(ri);
    }
	else if(name == "appearanceData")
	{
		m_appearanceData.unpackDelta(ri);
	}
	else if(name == "incubatorResourceData")
	{
		std::string resourceData;
		Archive::get(ri, resourceData);

		static const uint32 crcIncubator = CrcLowerString::calculateCrc("object/tangible/crafting/station/incubator_station.iff");
		if ((getTemplateCrc() == crcIncubator) && !resourceData.empty())
		{
			DynamicVariableList const & objvars = getObjVars();
			if (objvars.hasItem("power.name") && (DynamicVariable::STRING == objvars.getType("power.name")))
			{
				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();
						}
					}

					ResourceTypeObject const * const rto = ServerUniverse::getInstance().getResourceTypeById(importedResourceType);
					if (rto)
					{
						IGNORE_RETURN(setObjVarItem("power.name", rto->getResourceName()));

						if (importedResourceType.getValue() > NetworkId::cms_maxNetworkIdWithoutClusterId)
							IGNORE_RETURN(setObjVarItem("power.resourceData", resourceData));
						else
							removeObjVarItem("power.resourceData");
					}
				}
			}
		}
	}
    else
    {
        ServerObject::setAutoVariableFromByteStream(name, source);
    }
	addObjectToConcludeList();
}

// ----------------------------------------------------------------------

/**
 * Sets a bio-link on this object. This object will only be able to be equipped by
 * the player who's id matches the bio-link id.
 *
 * @param playerId		the player to link this object to
 */
void TangibleObject::setBioLink(const NetworkId & playerId)
{
	if (playerId != NetworkId::cms_invalid)
	{
		if (isAuthoritative())
		{
			setObjVarItem(OBJVAR_BIO_LINK_ID, playerId);
		}
		else
		{
			sendControllerMessageToAuthServer(CM_setBioLink, new MessageQueueGenericValueType<NetworkId>(playerId));
		}
	}
}

// ----------------------------------------------------------------------

/**
 * Sets the pending bio-link on this object. This will prevent the object from
 * being equipped by any player until a real bio-link is set.
 */
void TangibleObject::setPendingBioLink()
{
	setBioLink(PENDING_BIO_LINK_NETWORK_ID);
}

// ----------------------------------------------------------------------

/**
 * Removes the bio-link on this object.
 */
void TangibleObject::clearBioLink()
{
	if (isAuthoritative())
	{
		removeObjVarItem(OBJVAR_BIO_LINK);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setBioLink, new MessageQueueGenericValueType<NetworkId>(NetworkId::cms_invalid));
	}
}

// ----------------------------------------------------------------------

/**
 * Returns the bio-link id of this object.
 *
 * @return the bio-link player id (NetworkId::cms_invalid if the object isn't bio-linked)
 */
NetworkId TangibleObject::getBioLink() const
{
	NetworkId linkId;
	if (getObjVars().getItem(OBJVAR_BIO_LINK_ID, linkId))
		return linkId;
	return NetworkId::cms_invalid;
}

// ----------------------------------------------------------------------

/**
 * Returns whether an object, or any contained object, is biolinked
 *
 * @return true if a biolinked object is found
 */
bool TangibleObject::isBioLinked() const
{
	NetworkId const linkId = getBioLink();

	if( (linkId != NetworkId::cms_invalid) && (linkId != PENDING_BIO_LINK_NETWORK_ID) )
	{
		// If the network ID is valid and does not correspond to a pending bio-link
		// then we will assume that the tangible object is bio-linked
		return true;
	}

	return false;
}

// ----------------------------------------------------------------------

void TangibleObject::rollupStructure(NetworkId const & owner, bool warnOnly)
{
	GameScriptObject* gso = getScriptObject();
	if (gso)
	{
		gso->attachScript("structure.structure_rollup", true);
		ScriptParams params;
		params.addParam(owner);
		params.addParam(warnOnly);
		gso->trigAllScripts(Scripting::TRIG_DO_STRUCTURE_ROLLUP, params);
		gso->detachScript("structure.structure_rollup");
	}
}

// ----------------------------------------------------------------------

CommandQueue * TangibleObject::getCommandQueue() const
{
	return CommandQueue::getCommandQueue(*const_cast<TangibleObject *>(this));
}

//-----------------------------------------------------------------------

void TangibleObject::commandQueueEnqueue(Command const &command, NetworkId const &targetId, Unicode::String const &params, uint32 const sequenceId, bool const clearable, Command::Priority const priority, bool fromServer)
{
	if(!fromServer)
	{
		if(command.m_fromServerOnly)
		{
			LOG("CustomerService", ("CheatChannel: attempt to queue server-only command %s from client on object %s", command.m_commandName.c_str(), getNetworkId().getValueString().c_str()));
			return;
		}
	}
	if (!isAuthoritative())
	{
		sendControllerMessageToAuthServer(CM_commandQueueEnqueueFwd, new MessageQueueCommandQueueEnqueueFwd(sequenceId, command.m_commandHash, targetId, params, clearable, static_cast<int>(priority), fromServer));
	}
	else
	{
		CommandQueue * const queue = getCommandQueue();
		if (queue != nullptr)
		{
			queue->enqueue(command, targetId, params, sequenceId, clearable, priority);
		}
	}
}

//-----------------------------------------------------------------------

void TangibleObject::commandQueueRemove(uint32 const sequenceId)
{
	if (!isAuthoritative())
	{
		sendControllerMessageToAuthServer(CM_commandQueueRemove, new MessageQueueCommandQueueRemove(sequenceId, 0.0f, static_cast<int>(Command::CEC_Success), 0));
	}
	else
	{
		CommandQueue * const queue = getCommandQueue();
		if (queue != nullptr)
		{
			queue->remove(sequenceId);
		}
	}
}

//-----------------------------------------------------------------------

void TangibleObject::commandQueueClear()
{
	// commandQueueRemove clears the queue when passed a 0 sequenceId
	commandQueueRemove(0);
}

//-----------------------------------------------------------------------

bool TangibleObject::commandQueueHasCommandFromGroup(uint32 groupHash) const
{
	CommandQueue * const queue = getCommandQueue();
	if (queue != nullptr)
	{
		return queue->hasCommandFromGroup(groupHash);
	}
	return false;
}

//-----------------------------------------------------------------------

void TangibleObject::commandQueueClearCommandsFromGroup(uint32 groupHash, bool force)
{
	CommandQueue * const queue = getCommandQueue();
	if (queue != nullptr)
	{
		queue->clearCommandsFromGroup(groupHash, force);
	}
}

//-----------------------------------------------------------------------

CombatEngineData::CombatData * TangibleObject::getCombatData()
{
	return m_combatData;
}

//-----------------------------------------------------------------------

CombatEngineData::CombatData const * TangibleObject::getCombatData() const
{
	return m_combatData;
}

//-----------------------------------------------------------------------

void TangibleObject::CombatStateChangedCallback::modified(TangibleObject & target, bool /*oldValue*/, bool newValue, bool /*local*/) const
{
	if (newValue)
	{
		// Starting Combat

		LOGC(ConfigServerGame::isAiLoggingEnabled(), "debug_ai", ("TangibleObject::Callbacks::CombatStateChanged::modified() STARTING COMBAT owner(%s:%s)", target.getNetworkId().getValueString().c_str(), Unicode::wideToNarrow(target.getEncodedObjectName()).c_str()));

		if (target.isAuthoritative())
		{
			target.createCombatData();

			target.m_combatStartTime = Os::getRealSystemTime();

			if (!target.isPlayerControlled())
			{
				ObjectTracker::addCombatAI();
			}

			AICreatureController * const aiCreatureController = AICreatureController::asAiCreatureController(target.getController());

			if (aiCreatureController != nullptr)
			{
				aiCreatureController->markCombatStartLocation();
			}

			if (target.getScriptObject() != nullptr)
			{
				ScriptParams params;
				IGNORE_RETURN(target.getScriptObject()->trigAllScripts(Scripting::TRIG_ENTERED_COMBAT, params));
			}

			target.scheduleForAlter();
		}
	}
	else
	{
		// Exiting Combat

		LOGC(ConfigServerGame::isAiLoggingEnabled(), "debug_ai", ("TangibleObject::Callbacks::CombatStateChanged::modified() EXITING COMBAT owner(%s:%s)", target.getNetworkId().getValueString().c_str(), Unicode::wideToNarrow(target.getEncodedObjectName()).c_str()));

		if (target.isAuthoritative())
		{
			if (!target.isPlayerControlled())
			{
				ObjectTracker::removeCombatAI();
			}

			//target.clearHateList();

			target.commandQueueClearCommandsFromGroup(COMMAND_GROUP_COMBAT.getCrc());

			if (target.getScriptObject() != nullptr)
			{
				ScriptParams params;
				IGNORE_RETURN(target.getScriptObject()->trigAllScripts(Scripting::TRIG_EXITED_COMBAT, params));
			}

			target.scheduleForAlter();
		}
	}
}

//-----------------------------------------------------------------------
int TangibleObject::getCombatDuration() const
{
	int result = 0;

	if (isInCombat())
	{
		result = static_cast<int>(Os::getRealSystemTime() - m_combatStartTime.get());
	}

	return result;
}

//-----------------------------------------------------------------------
void TangibleObject::setAttackableOverride(bool attackable)
{
	if (isAuthoritative())
	{
		if (m_attackableOverride.get() != attackable)
			m_attackableOverride = attackable;
	}
	else
	{
		sendControllerMessageToAuthServer(CM_setAttackableOverride, new MessageQueueGenericValueType<bool>(attackable));
	}
}

//-----------------------------------------------------------------------
bool TangibleObject::hasAutoDeclineDuel() const
{
	return getObjVars().hasItem(OBJVAR_DECLINE_DUEL);
}

//----------------------------------------------------------------------

int TangibleObject::getPassiveRevealRange(NetworkId const & target) const
{
	Archive::AutoDeltaMap<NetworkId, int>::const_iterator i = m_passiveReveal.find(target);
	if (i == m_passiveReveal.end())
		return -1;

	return i->second;
}

//----------------------------------------------------------------------

void TangibleObject::addPassiveReveal(TangibleObject const & target, int range)
{
	addPassiveReveal(target.getNetworkId(), range, (nullptr != PlayerCreatureController::getPlayerObject(target.asCreatureObject())));
}

//----------------------------------------------------------------------

void TangibleObject::addPassiveReveal(NetworkId const & target, int range, bool isTargetPlayerCharacter)
{
	// and object cannot passively reveal itself
	if (target == getNetworkId())
		return;

	// range must be > 0
	if (range <= 0)
		return;

	if(isAuthoritative())
	{
		m_passiveReveal.set(target, range);

		if (isTargetPlayerCharacter)
			m_passiveRevealPlayerCharacter.insert(target);
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addPassiveReveal, new MessageQueueGenericValueType<std::pair<std::pair<int, int>, NetworkId> >(std::make_pair(std::make_pair(range, (isTargetPlayerCharacter ? 1 : 0)), target)));
	}
}

//----------------------------------------------------------------------

void TangibleObject::removePassiveReveal(NetworkId const & target)
{
	if(isAuthoritative())
	{
		IGNORE_RETURN(m_passiveReveal.erase(target));
		IGNORE_RETURN(m_passiveRevealPlayerCharacter.erase(target));
	}
	else
	{
		sendControllerMessageToAuthServer(CM_removePassiveReveal, new MessageQueueGenericValueType<NetworkId>(target));
	}
}

//----------------------------------------------------------------------

void TangibleObject::removeAllPassiveReveal()
{
	if(isAuthoritative())
	{
		m_passiveReveal.clear();
		m_passiveRevealPlayerCharacter.clear();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_removeAllPassiveReveal, 0);
	}
}

//----------------------------------------------------------------------

bool TangibleObject::setOverrideMapColor(unsigned char r, unsigned char g, unsigned char b)
{
	if(!isAuthoritative())
	{
		return false;
	}

	PackedArgb color(255, r, g, b);
	m_mapColorOverride.set(color.getArgb());
	return true;
}

//----------------------------------------------------------------------

bool TangibleObject::clearOverrideMapColor()
{
	if(!isAuthoritative())
	{
		return false;
	}

	m_mapColorOverride.set(0);
	return true;
}

//----------------------------------------------------------------------

bool TangibleObject::getOverrideMapColor(unsigned char & r, unsigned char & g, unsigned char & b) const
{
	PackedArgb color(m_mapColorOverride.get());
	if(color.getA() == 0)
	{
		return false;
	}

	r = color.getR();
	g = color.getG();
	b = color.getB();
	return true;
}

//----------------------------------------------------------------------

void TangibleObject::handleIncubatorCommit(CreatureObject const & owner, IncubatorCommitMessage const & msg)
{
	// this operation *REQUIRES* that both the incubator and the
	// character manipulating the incubator be authoritative on
	// the same game server, so it's really not possible to
	// pass this handler over to the authoritative incubator,
	// since there's no guarantee that the character will be
	// authoritative on that game server either
	if (!isAuthoritative() || !owner.isAuthoritative())
		return;

	GameScriptObject * const scriptObject = getScriptObject();
	if (scriptObject)
	{
		ScriptParams params;
		params.addParam(owner.getNetworkId(), "playerId");
		params.addParam(getNetworkId(), "terminalId");
		params.addParam(msg.getSlot1Id(), "slot1Id");
		params.addParam(msg.getSlot2Id(), "slot2Id");
		params.addParam(msg.getSlot3Id(), "slot3Id");
		params.addParam(msg.getSlot4Id(), "slot4Id");
		params.addParam(msg.getInitialPointsSurvival(), "initialPointsSurvival");
		params.addParam(msg.getInitialPointsBeastialResilience(),"initialPointsBeastialResilience");
		params.addParam(msg.getInitialPointsCunning(),"initialPointsCunning");
		params.addParam(msg.getInitialPointsIntelligence(),"initialPointsIntelligence");
		params.addParam(msg.getInitialPointsAggression(),"initialPointsAggression");
		params.addParam(msg.getInitialPointsHuntersInstinct(),"initialPointsHuntersInstinct");
		params.addParam(msg.getTotalPointsSurvival(),"totalPointsSurvival");
		params.addParam(msg.getTotalPointsBeastialResilience(),"totalPointsBeastialResilience");
		params.addParam(msg.getTotalPointsCunning(),"totalPointsCunning");
		params.addParam(msg.getTotalPointsIntelligence(),"totalPointsIntelligence");
		params.addParam(msg.getTotalPointsAggression(),"totalPointsAggression");
		params.addParam(msg.getTotalPointsHuntersInstinct(),"totalPointsHuntersInstinct");
		params.addParam(msg.getTemperatureGauge(),"temperatureGauge");
		params.addParam(msg.getNutrientGauge(),"nutrientGauge");
		params.addParam(msg.getNewCreatureColorIndex(), "newCreatureColorIndex");

		IGNORE_RETURN(scriptObject->trigAllScripts(Scripting::TRIG_INCUBATOR_COMMITTED, params));	
	}
}

void TangibleObject::handleIncubatorCancel(CreatureObject const & owner)
{
	// this operation *REQUIRES* that both the incubator and the
	// character manipulating the incubator be authoritative on
	// the same game server, so it's really not possible to
	// pass this handler over to the authoritative incubator,
	// since there's no guarantee that the character will be
	// authoritative on that game server either
	if (!isAuthoritative() || !owner.isAuthoritative())
		return;

	GameScriptObject * const scriptObject = getScriptObject();
	if (scriptObject)
	{
		ScriptParams params;
		params.addParam(owner.getNetworkId(), "playerId");
		params.addParam(getNetworkId(), "terminalId");

		IGNORE_RETURN(scriptObject->trigAllScripts(Scripting::TRIG_INCUBATOR_CANCELED, params));	
	}
}

void TangibleObject::forceHateTarget(NetworkId const & target)
{
	if(isAuthoritative())
	{
		m_hateList.forceHateTarget(target);
		scheduleForAlter();
	}
	else
	{
		sendControllerMessageToAuthServer(CM_forceHateTarget, new MessageQueueGenericValueType<NetworkId>(target));
	}

}

bool TangibleObject::isUserOnAccessList(NetworkId const user) const
{
	return (std::find(m_accessList.begin(), m_accessList.end(), user) != m_accessList.end());
}

bool TangibleObject::isGuildOnAccessList(int guildId) const
{
	return (std::find(m_guildAccessList.begin(), m_guildAccessList.end(), guildId) != m_guildAccessList.end());
}

void TangibleObject::addUserToAccessList(const NetworkId user)
{
	if(isAuthoritative())
	{
		if(m_accessList.find(user) == m_accessList.end())
		{
			if(!hasCondition(C_locked)) // If we weren't locked before, we are now.
				setCondition(C_locked);

			m_accessList.insert(user);
			
			copyUserAccessListToObjVars();
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addUserToAccessList, new MessageQueueGenericValueType< std::pair<int, NetworkId> >(std::make_pair(-1, user) ) );
	}
}

void TangibleObject::addGuildToAccessList(int guildId)
{
	if(isAuthoritative())
	{
		if(m_guildAccessList.find(guildId) == m_guildAccessList.end())
		{
			if(!hasCondition(C_locked)) // If we weren't locked before, we are now.
				setCondition(C_locked);

			m_guildAccessList.insert(guildId);

			copyGuildAccessListToObjVars();
		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_addUserToAccessList, new MessageQueueGenericValueType< std::pair<int, NetworkId> >(std::make_pair(guildId, NetworkId::cms_invalid) ) );
	}
}


void TangibleObject::removeUserFromAccessList(const NetworkId user)
{
	if(isAuthoritative())
	{
		Archive::AutoDeltaSet<NetworkId>::const_iterator iter = m_accessList.find(user);
		if(iter != m_accessList.end())
		{
			m_accessList.erase(iter);
			if(m_accessList.empty())
			{
				if(hasCondition(C_locked))
					clearCondition(C_locked); // Containers with no access list are open to whomever.

				removeObjVarItem(s_accessListScriptVar); // Remove our objvars, no need for them anymore.

				return;
			}

			copyUserAccessListToObjVars();

		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_removeUserFromAccessList, new MessageQueueGenericValueType< std::pair<int, NetworkId> >(std::make_pair(-1, user)));
	}
}

void TangibleObject::removeGuildFromAccessList(int guildId)
{
	if(isAuthoritative())
	{
		Archive::AutoDeltaSet<int>::const_iterator iter = m_guildAccessList.find(guildId);
		if(iter != m_guildAccessList.end())
		{
			m_guildAccessList.erase(iter);
			if(m_guildAccessList.empty())
			{
				if(hasCondition(C_locked) && m_accessList.empty())
					clearCondition(C_locked); // Containers with no access list are open to whomever.

				removeObjVarItem(s_guildAccessListScriptVar); // Remove our objvars, no need for them anymore.

				return;
			}

			copyGuildAccessListToObjVars();

		}
	}
	else
	{
		sendControllerMessageToAuthServer(CM_removeUserFromAccessList, new MessageQueueGenericValueType< std::pair<int, NetworkId> >(std::make_pair(guildId, NetworkId::cms_invalid)));
	}
}

void TangibleObject::copyUserAccessListToObjVars()
{
	std::vector<NetworkId> newAccessList;
	Archive::AutoDeltaSet<NetworkId>::const_iterator iter = m_accessList.begin();
	for(; iter != m_accessList.end(); ++iter)
		newAccessList.push_back((*iter));

	setObjVarItem(s_accessListScriptVar, newAccessList);
}

void TangibleObject::copyGuildAccessListToObjVars()
{
	std::vector<int> newAccessList;
	Archive::AutoDeltaSet<int>::const_iterator iter = m_guildAccessList.begin();
	for(; iter != m_guildAccessList.end(); ++iter)
		newAccessList.push_back((*iter));

	setObjVarItem(s_guildAccessListScriptVar, newAccessList);
}


void TangibleObject::readInUserAccessListObjVars()
{
	std::vector<NetworkId> inputVector;
	if(getObjVars().getItem(s_accessListScriptVar, inputVector))
	{
		// We have some obj vars to add to our list!
		std::vector<NetworkId>::size_type i = 0;
		for( ; i < inputVector.size(); ++i)
		{
			m_accessList.insert(inputVector[i]);
		}
	}
}

void TangibleObject::readInGuildAccessListObjVars()
{
	std::vector<int> inputVector;
	if(getObjVars().getItem(s_guildAccessListScriptVar, inputVector))
	{
		// We have some obj vars to add to our list!
		std::vector<int>::size_type i = 0;
		for( ; i < inputVector.size(); ++i)
		{
			m_guildAccessList.insert(inputVector[i]);
		}
	}
}

void TangibleObject::clearUserAccessList()
{
	if(isAuthoritative())
	{
		if(hasCondition(C_locked) && m_guildAccessList.empty()) // No access list = total unlock.
			clearCondition(C_locked);

		m_accessList.clear();

		removeObjVarItem(s_accessListScriptVar); // Remove our objvars, no need for them anymore.

	}
	else
	{
		sendControllerMessageToAuthServer(CM_clearUserAccessList, new MessageQueueGenericValueType<bool>(false));
	}
}

void TangibleObject::clearGuildAccessList()
{
	if(isAuthoritative())
	{
		if(hasCondition(C_locked) && m_accessList.empty()) // No access list = total unlock.
			clearCondition(C_locked);

		m_guildAccessList.clear();

		removeObjVarItem(s_guildAccessListScriptVar); // Remove our objvars, no need for them anymore.

	}
	else
	{
		sendControllerMessageToAuthServer(CM_clearUserAccessList, new MessageQueueGenericValueType<bool>(true));
	}
}

void TangibleObject::getUserAccessList(std::vector<NetworkId> & ids)
{
	getObjVars().getItem(s_accessListScriptVar, ids);
}

void TangibleObject::getGuildAccessList(std::vector<int> & ids)
{
	getObjVars().getItem(s_guildAccessListScriptVar, ids);
}

void TangibleObject::addObjectEffect(std::string const & filename, std::string const & hardpoint, Vector const & offset, float scale, std::string const & label)
{
	if(isAuthoritative())
	{
		if(m_effectsMap.find(label) != m_effectsMap.end())
			return;

		m_effectsMap.insert(label, std::make_pair(filename, std::make_pair( hardpoint, std::make_pair(offset, scale ))));
	}
	else
		sendControllerMessageToAuthServer(CM_addObjectEffect, 
		new MessageQueueGenericValueType<std::pair<std::string, std::pair<std::string, std::pair<std::string, std::pair<Vector, float > > > > >(std::make_pair(label, std::make_pair(filename, std::make_pair(hardpoint, std::make_pair(offset, scale))))));
}

void TangibleObject::removeObjectEffect(std::string const & label)
{
	if(isAuthoritative())
	{
		Archive::AutoDeltaMap<std::string, std::pair<std::string, std::pair<std::string, std::pair<Vector, float> > > >::const_iterator iter = m_effectsMap.find(label);
		if(iter == m_effectsMap.end())
			return;

		m_effectsMap.erase(iter);
	}
	else
		sendControllerMessageToAuthServer(CM_removeObjectEffect, new MessageQueueGenericValueType<std::string>(label));
}

bool TangibleObject::hasObjectEffect(std::string const & label)
{
	return m_effectsMap.find(label) != m_effectsMap.end();
}

void TangibleObject::removeAllObjectEffects()
{
	if(isAuthoritative())
	{
		m_effectsMap.clear();
	}
	else
		sendControllerMessageToAuthServer(CM_removeAllObjectEffect, new MessageQueueGenericValueType<bool>(true));
}
// ======================================================================
