Source: entity.js

// Note: As API3.Template is needed for definition of API3.Entity
//       with files being loaded in lexicographic order, there is
//       no clean way to place a template.js file before an entity.js
//       file, so both classes have to go into the same file

/**
 * @class
 * @summary Represents an entity template which defines a class of
 *          game objects.
 * @description In 0AD, each object found in a game is an instance of
 *              an entity template. The "template" is similar to a
 *              class definition in a regular programming language.
 * @param template {Object} The DOM of the entity template definition
 *                          as layed down in the XML documents of the
 *                          active mods. For regular 0AD entities, the
 *                          templates are found in
 * <code>/binaries/data/mods/public/simulation/templates</code>
 */
API3.Template =
	function(template)
	{
		this._template = template;
		this._tpCache = new Map();
	};

	// helper function to return a template value, optionally adjusting for tech.
	// TODO: there's no support for "_string" values here.
API3.Template.prototype.get = function(string) {
	var value = this._template;
	if (this._auraTemplateModif && this._auraTemplateModif.has(string))		{
		return this._auraTemplateModif.get(string);
	} else if (this._techModif && this._techModif.has(string)) {
		return this._techModif.get(string);
	} else {
		if (!this._tpCache.has(string))
		{
			var args = string.split("/");
			for (var i = 0; i < args.length; ++i)
				if (value[args[i]])
					value = value[args[i]];
				else
				{
					value = undefined;
					break;
				}
			this._tpCache.set(string, value);
		}
		return this._tpCache.get(string);
	}

};

API3.Template.prototype.genericName = function() {
	if (!this.get("Identity") || !this.get("Identity/GenericName"))
		return undefined;
	return this.get("Identity/GenericName");
};

API3.Template.prototype.rank = function() {
	if (!this.get("Identity"))
		return undefined;
	return this.get("Identity/Rank");
};

API3.Template.prototype.classes = function() {
	var template = this.get("Identity");
	if (!template)
		return undefined;
	return GetIdentityClasses(template);
};
	
API3.Template.prototype.requiredTech = function() {
	return this.get("Identity/RequiredTechnology");
};

API3.Template.prototype.available = function(gameState) {
	if (this.requiredTech() === undefined)
		return true;
	return gameState.isResearched(this.get("Identity/RequiredTechnology"));
};

API3.Template.prototype.phase = function() {
	if (!this.get("Identity/RequiredTechnology"))
		return 0;
	if (this.get("Identity/RequiredTechnology") == "phase_village")
		return 1;
	if (this.get("Identity/RequiredTechnology") == "phase_town")
		return 2;
	if (this.get("Identity/RequiredTechnology") == "phase_city")
		return 3;
	return 0;
};

API3.Template.prototype.hasClass = function(name) {
	if (!this._classes)
		this._classes = this.classes();
	var classes = this._classes;
	return (classes && classes.indexOf(name) != -1);
};

API3.Template.prototype.hasClasses = function(testClasses) {
	if (!this._classes)
		this._classes = this.classes();
	let classes = this._classes;
	if (!classes)
		return false;

	for (var i = 0; i < testClasses.length; i++)
		if (classes.indexOf(testClasses[i]) === -1)
			return false;
	return true;
};

API3.Template.prototype.civ = function() {
	return this.get("Identity/Civ");
};

API3.Template.prototype.cost = function() {
	if (!this.get("Cost"))
		return undefined;

	var ret = {};
	for (var type in this.get("Cost/Resources"))
		ret[type] = +this.get("Cost/Resources/" + type);
	return ret;
};

API3.Template.prototype.costSum = function() {
	if (!this.get("Cost"))
		return undefined;

	var ret = 0;
	for (var type in this.get("Cost/Resources"))
		ret += +this.get("Cost/Resources/" + type);
	return ret;
};

/**
 * @summary Computes the size of a circular "bounding cylinder" around
 *          the entity.
 * @return {Number} The radius of a circle surrounding this entity's
 *                  obstruction shape, or undefined if the entity does
 *                  not obstruct anything.
 */
API3.Template.prototype.obstructionRadius = function() {
	if (!this.get("Obstruction"))
		return undefined;

	if (this.get("Obstruction/Static"))
	{
		var w = +this.get("Obstruction/Static/@width");
		var h = +this.get("Obstruction/Static/@depth");
		return Math.sqrt(w*w + h*h) / 2;
	}

	if (this.get("Obstruction/Unit"))
		return +this.get("Obstruction/Unit/@radius");

	return 0; // this should never happen
};

/**
 * Returns the radius of a circle surrounding this entity's
 * footprint.
 * @return {Number} The radius of the smallest circle the entity's
 *                  footprint will fit in.
 */
API3.Template.prototype.footprintRadius = function() {
	if (!this.get("Footprint"))
		return undefined;

	if (this.get("Footprint/Square"))
	{
		var w = +this.get("Footprint/Square/@width");
		var h = +this.get("Footprint/Square/@depth");
		return Math.sqrt(w*w + h*h) / 2;
	}

	if (this.get("Footprint/Circle"))
		return +this.get("Footprint/Circle/@radius");

	return 0; // this should never happen
};

API3.Template.prototype.maxHitpoints = function()
{
	if (this.get("Health") !== undefined)
		return +this.get("Health/Max");
	return 0;
};

API3.Template.prototype.isHealable = function()
{
	if (this.get("Health") !== undefined)
		return this.get("Health/Unhealable") !== "true";
	return false;
};

API3.Template.prototype.isRepairable = function()
{
	if (this.get("Health") !== undefined)
		return this.get("Health/Repairable") === "true";
	return false;
};

API3.Template.prototype.getPopulationBonus = function() {
	return this.get("Cost/PopulationBonus");
};

API3.Template.prototype.armourStrengths = function() {
	if (!this.get("Armour"))
		return undefined;

	return {
		hack: +this.get("Armour/Hack"),
		pierce: +this.get("Armour/Pierce"),
		crush: +this.get("Armour/Crush")
	};
};

API3.Template.prototype.attackTypes = function() {
	if (!this.get("Attack"))
		return undefined;

	var ret = [];
	for (var type in this.get("Attack"))
		ret.push(type);

	return ret;
};

API3.Template.prototype.attackRange = function(type) {
	if (!this.get("Attack/" + type +""))
		return undefined;

	return {
			max: +this.get("Attack/" + type +"/MaxRange"),
			min: +(this.get("Attack/" + type +"/MinRange") || 0)
	};
};

API3.Template.prototype.attackStrengths = function(type) {
	if (!this.get("Attack/" + type +""))
		return undefined;

	return {
		hack: +(this.get("Attack/" + type + "/Hack") || 0),
		pierce: +(this.get("Attack/" + type + "/Pierce") || 0),
		crush: +(this.get("Attack/" + type + "/Crush") || 0)
	};
};

API3.Template.prototype.attackTimes = function(type) {
	if (!this.get("Attack/" + type +""))
		return undefined;

	return {
		prepare: +(this.get("Attack/" + type + "/PrepareTime") || 0),
		repeat: +(this.get("Attack/" + type + "/RepeatTime") || 1000)
	};
};

// returns the classes this templates counters:
// Return type is [ [-neededClasses- , multiplier], ... ].
API3.Template.prototype.getCounteredClasses = function() {
	if (!this.get("Attack"))
		return undefined;

	var Classes = [];
	for (var i in this.get("Attack"))
	{
		if (!this.get("Attack/" + i + "/Bonuses"))
			continue;
		for (var o in this.get("Attack/" + i + "/Bonuses"))
			if (this.get("Attack/" + i + "/Bonuses/" + o + "/Classes"))
				Classes.push([this.get("Attack/" + i +"/Bonuses/" + o +"/Classes").split(" "), +this.get("Attack/" + i +"/Bonuses" +o +"/Multiplier")]);
	}
	return Classes;
};

// returns true if the entity counters those classes.
// TODO: refine using the multiplier
API3.Template.prototype.countersClasses = function(classes) {
	if (!this.get("Attack"))
		return false;
	var mcounter = [];
	for (var i in this.get("Attack"))
	{
		if (!this.get("Attack/" + i + "/Bonuses"))
			continue;
		for (var o in this.get("Attack/" + i + "/Bonuses"))
			if (this.get("Attack/" + i + "/Bonuses/" + o + "/Classes"))
				mcounter.concat(this.get("Attack/" + i + "/Bonuses/" + o + "/Classes").split(" "));
	}
	for (var i in classes)
	{
		if (mcounter.indexOf(classes[i]) !== -1)
			return true;
	}
	return false;
};

// returns, if it exists, the multiplier from each attack against a given class
API3.Template.prototype.getMultiplierAgainst = function(type, againstClass) {
	if (!this.get("Attack/" + type +""))
		return undefined;

	if (this.get("Attack/" + type + "/Bonuses"))
		for (var o in this.get("Attack/" + type + "/Bonuses"))
		{
			if (!this.get("Attack/" + type + "/Bonuses/" + o + "/Classes"))
				continue;
			var total = this.get("Attack/" + type + "/Bonuses/" + o + "/Classes").split(" ");
			for (var j in total)
				if (total[j] === againstClass)
					return this.get("Attack/" + type + "/Bonuses/" + o + "/Multiplier");
		}
	return 1;
};

// returns true if the entity can attack the given class
API3.Template.prototype.canAttackClass = function(saidClass) {
	if (!this.get("Attack"))
		return false;

	for (var i in this.get("Attack")) {
		if (!this.get("Attack/" + i + "/RestrictedClasses") || !this.get("Attack/" + i + "/RestrictedClasses/_string"))
			continue;
		var cannotAttack = this.get("Attack/" + i + "/RestrictedClasses/_string").split(" ");
		if (cannotAttack.indexOf(saidClass) !== -1)
			return false;
	}
	return true;
};

API3.Template.prototype.buildableEntities = function() {
	if (!this.get("Builder/Entities/_string"))
		return [];
	var civ = this.civ();
	var templates = this.get("Builder/Entities/_string").replace(/\{civ\}/g, civ).split(/\s+/);
	return templates; // TODO: map to Entity?
};

API3.Template.prototype.trainableEntities = function() {
	if (!this.get("ProductionQueue/Entities/_string"))
		return undefined;
	var civ = this.civ();
	var templates = this.get("ProductionQueue/Entities/_string").replace(/\{civ\}/g, civ).split(/\s+/);
	return templates;
};

API3.Template.prototype.researchableTechs = function() {
	if (!this.get("ProductionQueue/Technologies/_string"))
		return undefined;
	var templates = this.get("ProductionQueue/Technologies/_string").split(/\s+/);
	return templates;
};

API3.Template.prototype.resourceSupplyType = function() {
	if (!this.get("ResourceSupply"))
		return undefined;
	var [type, subtype] = this.get("ResourceSupply/Type").split('.');
	return { "generic": type, "specific": subtype };
};

	// will return either "food", "wood", "stone", "metal" and not treasure.
/**
 * @summary Provides the type of resource to be gathered from this
 *          entity type.
 * @return {String} The name of the resource type provided by the
 *                  entity emplate. Currently these are
 *                  <code>"food"</code>, <code>"metal"</code>,
 *                  <code>"stone"</code>, <code>"wood"</code>
 */
API3.Template.prototype.getResourceType = function() {
	if (!this.get("ResourceSupply"))
		return undefined;
	var [type, subtype] = this.get("ResourceSupply/Type").split('.');
	if (type == "treasure")
		return subtype;
	return type;
};

API3.Template.prototype.resourceSupplyMax = function() {
	if (!this.get("ResourceSupply"))
		return undefined;
	return +this.get("ResourceSupply/Amount");
};

API3.Template.prototype.maxGatherers = function()
{
	if (this.get("ResourceSupply") !== undefined)
		return +this.get("ResourceSupply/MaxGatherers");
	return 0;
};
	
API3.Template.prototype.resourceGatherRates = function() {
	if (!this.get("ResourceGatherer"))
		return undefined;
	var ret = {};
	var baseSpeed = +this.get("ResourceGatherer/BaseSpeed");
	for (var r in this.get("ResourceGatherer/Rates"))
		ret[r] = +this.get("ResourceGatherer/Rates/" + r) * baseSpeed;
	return ret;
};

API3.Template.prototype.resourceDropsiteTypes = function() {
		if (!this.get("ResourceDropsite"))
			return undefined;

		let types = this.get("ResourceDropsite/Types");
		return types ? types.split(/\s+/) : [];
	};


API3.Template.prototype.garrisonableClasses = function() {
		if (!this.get("GarrisonHolder"))
			return undefined;
		return this.get("GarrisonHolder/List/_string");
	};

API3.Template.prototype.garrisonMax = function() {
		if (!this.get("GarrisonHolder"))
			return undefined;
		return this.get("GarrisonHolder/Max");
	};

API3.Template.prototype.getDefaultArrow = function() {
		if (!this.get("BuildingAI"))
			return undefined;
		return +this.get("BuildingAI/DefaultArrowCount");
	};

API3.Template.prototype.getArrowMultiplier = function() {
		if (!this.get("BuildingAI"))
			return undefined;
		return +this.get("BuildingAI/GarrisonArrowMultiplier");
	};

API3.Template.prototype.buffHeal = function() {
		if (!this.get("GarrisonHolder"))
			return undefined;
		return +this.get("GarrisonHolder/BuffHeal");
	};

API3.Template.prototype.promotion = function() {
		if (!this.get("Promotion"))
			return undefined;
		return this.get("Promotion/Entity");
	};

/**
 * Returns whether this is an animal that is too difficult to hunt.
 * (Any non domestic currently.)
 * @return {Boolean} <code>true</code> iff the entity will flee or
 *                   counter-attack when hunted.
 */
API3.Template.prototype.isUnhuntable = function() {   // used by Aegis
		if (!this.get("UnitAI") || !this.get("UnitAI/NaturalBehaviour"))
			return false;

		// only attack domestic animals since they won't flee nor retaliate.
		return this.get("UnitAI/NaturalBehaviour") !== "domestic";
	};

API3.Template.prototype.isHuntable = function() {     // used by Petra
		if(!this.get("ResourceSupply") || !this.get("ResourceSupply/KillBeforeGather"))
			return false;

		// special case: rabbits too difficult to hunt for such a small food amount
		if (this.get("Identity") && this.get("Identity/SpecificName") && this.get("Identity/SpecificName") === "Rabbit")
			return false;

		// do not hunt retaliating animals (animals without UnitAI are dead animals)
		return !this.get("UnitAI") || !(this.get("UnitAI/NaturalBehaviour") === "violent" ||
			this.get("UnitAI/NaturalBehaviour") === "aggressive" ||
			this.get("UnitAI/NaturalBehaviour") === "defensive");
	};

API3.Template.prototype.walkSpeed = function() {
		if (!this.get("UnitMotion") || !this.get("UnitMotion/WalkSpeed"))
			 return undefined;
		return this.get("UnitMotion/WalkSpeed");
	};

API3.Template.prototype.buildCategory = function() {
		if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Category"))
			return undefined;
		return this.get("BuildRestrictions/Category");
	};
	
API3.Template.prototype.buildTime = function() {
		if (!this.get("Cost") || !this.get("Cost/BuildTime"))
			return undefined;
		return this.get("Cost/BuildTime");
	},

API3.Template.prototype.buildDistance = function() {
		if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Distance"))
			return undefined;
		return this.get("BuildRestrictions/Distance");
	};

API3.Template.prototype.buildPlacementType = function() {
		if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/PlacementType"))
			return undefined;
		return this.get("BuildRestrictions/PlacementType");
	};

API3.Template.prototype.buildTerritories = function() {
		if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Territory"))
			return undefined;
		return this.get("BuildRestrictions/Territory").split(/\s+/);
	};

API3.Template.prototype.hasBuildTerritory = function(territory) {
		var territories = this.buildTerritories();
		return (territories && territories.indexOf(territory) != -1);
	};

API3.Template.prototype.hasTerritoryInfluence = function() {
		return (this.get("TerritoryInfluence") !== undefined);
	};

API3.Template.prototype.territoryInfluenceRadius = function() {
		if (this.get("TerritoryInfluence") !== undefined)
			return (this.get("TerritoryInfluence/Radius"));
		else
			return -1;
	};

API3.Template.prototype.territoryInfluenceWeight = function() {
		if (this.get("TerritoryInfluence") !== undefined)
			return (this.get("TerritoryInfluence/Weight"));
		else
			return -1;
	};

API3.Template.prototype.visionRange = function() {
		return this.get("Vision/Range");
	};

/**
 * @class
 * @extends API3.Template
 * @summary Represents a single object (entity) in the game.
 * @description In 0AD, each object found on the map of a game is an
 *              entity, for example trees, people, animals, buildings.
 *              The common properties of a set of entities are laid
 *              down in a [template]{@link API3.Template}.
 *
 *              In contrast to an entity template, the query functions
 *              of this class consider aura and tech modifications.
 * @param sharedAI {API3.SharedScript} The common data container which
 *                                     holds the raw engine data.
 * @param entity {Object} The raw entity description as provided by
 *                        the pyrogenesis engine.
 */
API3.Entity = function(sharedAI, entity)
{
	API3.Template.call(this, sharedAI.GetTemplate(entity.template));

	this._templateName = entity.template;
	this._entity = entity;
	this._auraTemplateModif = new Map();	// template modification from auras. this is only for this entity.

	/**
	 * @summary Reference to the common data container of the
	 *          simulation.
	 * @type API3.SharedScript
	 * @private
	 */
	this._ai = sharedAI;

	if (!sharedAI._techModifications[entity.owner][this._templateName])
		sharedAI._techModifications[entity.owner][this._templateName] = new Map();
	this._techModif = sharedAI._techModifications[entity.owner][this._templateName]; // save a reference to the template tech modifications
};

API3.Entity.prototype = new API3.Template();

/**
 * @summary Provides a quick overview of the entity for listing it up.
 * @return {String} The id and template name of this entity.
 */
API3.Entity.prototype.toString = function() {
	return "[Entity " + this.id() + " " + this.templateName() + "]";
};

/**
 * @summary Retrieves the id of the entity.
 * @return {Number} The id used by the pyrogenesis engine to refer to
 *                  this entity.
 */
API3.Entity.prototype.id = function() {
	return this._entity.id;
};

/**
 * @summary Retrieves the name of the entitie's template.
 * @return {String} The instanciated name of the entitie's template,
 *                  e.g. <code>"structures/celt_barracks"</code>.
 */
API3.Entity.prototype.templateName = function() {
	return this._templateName;
};

/**
 * @summary Reads a user-defined property from the entity.
 * @description An AI script may add any value to an entity for
 *              administrative purposes, just like any regular
 *              object property. This metadata should not be shared
 *              with other AI scripts.
 * @param player {Number} Numeric id of the player whose user
 *                        properties are queried.
 * @param key {String} Identifier of the user property to fetch.
 * @return {Object} An object which has been attached to the entity,
 *                  or <code>undefined</code> if property with the
 *                  given <code>key</code> and <code>player</code>-id
 *                  was defined before.
 * @see {@link API3.Entity.setMetadata}
 */
API3.Entity.prototype.getMetadata = function(player, key) {
	return this._ai.getMetadata(player, this, key);
};

/**
 * @summary Creates a new player-dependant property at the entity
 *          object.
 * @description An AI script may add any value to an entity for
 *              administrative purposes, just like any regular
 *              object property. This metadata should not be shared
 *              with other AI scripts.
 * @param player {Number} Numeric id of the player whose user
 *                        properties are to be defined.
 * @param key {String} Identifier of the new user property.
 * @param value {Object} The object to attach to the entity for later
 *                       reference.
 * @return undefined
 */
API3.Entity.prototype.setMetadata = function(player, key, value) {
	this._ai.setMetadata(player, this, key, value);
};

/**
 * @summary Discards all user-defined properties.
 * @param player {Number} Numeric id of the player whose extra
 *                        properties will be removed from the entity.
 * @return undefined
 * @see {@link API3.Entity.getMetadata}
 */
API3.Entity.prototype.deleteAllMetadata = function(player) {
	delete this._ai._entityMetadata[player][this.id()];
};

/**
 * @summary Removes a user-defined property from this entity.
 * @param player {Number} Numeric id of the player whose extra
 *                        properties will be modified.
 * @param key {String} Identifier of the user property to be removed.
 * @return undefined
 * @see {@link API3.Entity.getMetadata}
 */
API3.Entity.prototype.deleteMetadata = function(player, key) {
	this._ai.deleteMetadata(player, this, key);
};

/**
 * @summary Retrieves the current location of the entity on the map.
 * @return {Array.<Number>} A two-dimensional array whose elements
 *                          give the entity position in x- and
 *                          z-direction. If the entity is garrisoned,
 *                          the function returns
 *                          <code>undefined</code>.
 */
API3.Entity.prototype.position = function() {
	return this._entity.position;
};

/**
 * @summary Checks whether this entity is without task.
 * @return {Boolean} Whether the entity is idle; if the entity state
 *                   is unknown, it returns <code>undefined</code>.
 */
API3.Entity.prototype.isIdle = function() {
	if (typeof this._entity.idle === "undefined")
		return undefined;
	return this._entity.idle;
};

API3.Entity.prototype.unitAIState = function() {
	return this._entity.unitAIState;
};

API3.Entity.prototype.unitAIOrderData = function() {
	return this._entity.unitAIOrderData;
};

/**
 * @summary Retrieves the entity's current hit points (absolute
 *          health).
 * @return {Number} The number of hit points which may be removed from
 *                  the entity before it is dead.
 * @todo understand why we have sometimes rounding problems with
 *       maxHitpoints ? making wrongly isHurt=true
 *       problem seems to be with hele civs (i.e. spart)
 */
API3.Entity.prototype.hitpoints = function() {
	if (this._entity.hitpoints !== undefined)
		return this._entity.hitpoints;
	return undefined;
};

/**
 * @summary Checks whether the entity has already lost some hit points.
 * @return {Boolean} <code>true</code> iff the entity does not have
 *                   all of its hitpoints.
 * @see {@link API3.Entity.prototype.healthLevel}
 * @see {@link API3.Entity.prototype.hitpoints}
 * @see {@link API3.Entity.prototype.needsHeal}
 * @see {@link API3.Entity.prototype.needsRepair}
 */
API3.Entity.prototype.isHurt = function() {
	return (this.hitpoints() + 1) < this.maxHitpoints();
};

/**
 * @summary Retrieves the entity's relative health.
 * @return {Number} A float value in the interval [0,...,1], where 0
 *                  indicates death and 1 indicates full health.
 * @see {@link API3.prototype.hitpoints}
 * @see {@link API3.prototype.isHurt}
 */
API3.Entity.prototype.healthLevel = function() {
	return (this.hitpoints() / this.maxHitpoints());
};

/**
 * @summary Checks whether the entity should be tasked to heal.
 * @description Some entities might be defined as non-healable (e.g.
 *              animals), which makes a difference to
 *              [isHurt]{@link API3.Entity.isHurt}.
 * @return {Boolean} <code>true</code> iff it is possible to heal
 *                   the entity to full hit points (<code>false</code>
 *                   if the entity is at full health or unhealable).
 * @see {@link API3.Entity.prototype.isHurt}
 * @see {@link API3.Entity.prototype.needsRepair}
 */
API3.Entity.prototype.needsHeal = function() {
	return this.isHurt() && this.isHealable();
};

/**
 * @summary Checks whether a builder should be tasked to repair the
 *          entity.
 * @description The concept of hit points is used for structures and
 *              siege devices as well as for live objects, but the
 *              hit point restauration is different.
 * @return {Boolean} <code>true</code> iff it is possible to restore
 *                   the entity to full hit points by repairing
 *                   (<code>false</code> if the entity is at full
 *                   health or unrepairable).
 */
API3.Entity.prototype.needsRepair = function() {
	return this.isHurt() && this.isRepairable();
};

/**
 * @summary Checks whether the entity is auto-loosing hit points.
 * @description When a structure is built outside a player's territory
 *              (i.e. the terrain of that structure has no civic
 *              centre inside), the structure will continually loose
 *              hit points.
 * @return {Boolean} <code>true</code> iff the entity is currently
 *                   loosing hit points due to being out of the
 *                   player's territory.
 */
API3.Entity.prototype.decaying = function() {
	if (this._entity.decaying !== undefined)
		return this._entity.decaying;
	return undefined;
};

/**
 * @summary Retrieves the raw training queue state of the entity.
 * @return {Array.<Object>} The current training queue state, like
 *   <code>[ { "id": 0, "template": "...", "count": 1, "progress": 0.5, "metadata": ... }, ... ]</code>
 */
API3.Entity.prototype.trainingQueue = function() {
	var queue = this._entity.trainingQueue;
	return queue;
};

API3.Entity.prototype.trainingQueueTime = function() {
	var queue = this._entity.trainingQueue;
	if (!queue)
		return undefined;
	var time = 0;
	for (var i = 0; i < queue.length; i++)
		time += queue[i].timeRemaining;
	return time/1000;
};

API3.Entity.prototype.foundationProgress = function() {
	if (this._entity.foundationProgress === undefined)
		return undefined;
	return this._entity.foundationProgress;
};

API3.Entity.prototype.getBuilders = function() {
	if (this._entity.foundationProgress === undefined)
		return undefined;
	if (this._entity.foundationBuilders === undefined)
		return [];
	return this._entity.foundationBuilders;
};

API3.Entity.prototype.getBuildersNb = function() {
	if (this._entity.foundationProgress === undefined)
		return undefined;
	if (this._entity.foundationBuilders === undefined)
		return 0;
	return this._entity.foundationBuilders.length;
};

/**
 * @summary Retrieves the player who owns the entity.
 * @return {Number} The numeric id of the player owning the entity.
 */
API3.Entity.prototype.owner = function() {
	return this._entity.owner;
};

/**
 * @summary Checks whether the entity belongs to a certain player.
 * @param player {Number} Numeric id of the player to check.
 * @return {Boolean} <code>true</code> if this entity belongs to the
 *                   player with the given id, so he can control it.
 */
API3.Entity.prototype.isOwn = function(player) {
	if (typeof(this._entity.owner) === "undefined")
		return false;
	return this._entity.owner === player;
};

/**
 * @summary Checks whether this entity is friendly (non-attacking) to
 *          the entities of a certain player.
 * @param player {Number} Numeric id of the player to check.
 * @return {Boolean} <code>true</code> if this entity will not attack
 *                   entities of the specified player.
 * @todo Diplomacy is not considered by this function.
 */
API3.Entity.prototype.isFriendly = function(player) {
	return this.isOwn(player);
};

/**
 * @summary Checks whether this entity is inimicious to entities of
 *          a certain player.
 * @param player {Number} Numeric id of the player to check.
 * @return {Boolean} <code>true</code> if this entity is likely to
 *                   attack entities of the specified player.
 * @todo Diplomacy is not considered by this function.
 */
API3.Entity.prototype.isEnemy = function(player) {
	return !this.isOwn(player);
};

/**
 * @summary Retrieves the resources available of this entity.
 * @return {Number} The amount of resources which can still be
 *                  gathered from the entity before it is
 *                  exhausted. If the entity has no resources, the
 *                  function returns <code>undefined</code>. For
 *                  entities with unlimited supply, such as farms,
 *                  it may return <code>infinite</code>.
 */
API3.Entity.prototype.resourceSupplyAmount = function() {
	if(this._entity.resourceSupplyAmount === undefined)
		return undefined;
	return this._entity.resourceSupplyAmount;
};

API3.Entity.prototype.resourceSupplyGatherers = function()
{
	if (this._entity.resourceSupplyGatherers !== undefined)
		return this._entity.resourceSupplyGatherers;
	return [];
};

API3.Entity.prototype.isFull = function()
{
	if (this._entity.resourceSupplyGatherers !== undefined)
		return (this.maxGatherers() === this._entity.resourceSupplyGatherers.length);

	return undefined;
};

/**
 * @summary Checks whether the entity has been loaded with some
 *          resource.
 * @return {Boolean} <code>true</code> iff the entity has gathered
 *                   some resource which has not yet been disposed at
 *                   a dropsite.
 */
API3.Entity.prototype.resourceCarrying = function() {
	if(this._entity.resourceCarrying === undefined)
		return undefined;
	return this._entity.resourceCarrying; 
};

/**
 * @summary Retrieves the rate of the entity's current gathering
 *          activity.
 * @return {Number} The resource amount accumulated in one second.
 *                  If the entity is not gathering at the moment, the
 *                  function returns <code>undefined</code>.
 */
API3.Entity.prototype.currentGatherRate = function() {
	// returns the gather rate for the current target if applicable.
	if (!this.get("ResourceGatherer"))
		return undefined;

	if (this.unitAIOrderData().length &&
		(this.unitAIState().split(".")[1] === "GATHER" || this.unitAIState().split(".")[1] === "RETURNRESOURCE"))
	{
		var ress = undefined;
		// this is an abuse of "_ai" but it works.
		if (this.unitAIState().split(".")[1] === "GATHER" && this.unitAIOrderData()[0]["target"] !== undefined)
			ress = this._ai._entities.get(this.unitAIOrderData()[0]["target"]);
		else if (this.unitAIOrderData()[1] !== undefined && this.unitAIOrderData()[1]["target"] !== undefined)
			ress = this._ai._entities.get(this.unitAIOrderData()[1]["target"]);

		if (ress == undefined)
			return undefined;

		var type = ress.resourceSupplyType();
		var tstring = type.generic + "." + type.specific;

		if (type.generic == "treasure")
			return 1000;

		var speed = +this.get("ResourceGatherer/BaseSpeed");
		speed *= +this.get("ResourceGatherer/Rates/" +tstring);

		if (speed)
			return speed;
		return 0;
	}
	return undefined;
};

API3.Entity.prototype.isGarrisonHolder = function() {
	return this.get("GarrisonHolder");
};

API3.Entity.prototype.garrisoned = function() {
	return this._entity.garrisoned;
};

/**
 * @summary Checks whether any (more) entities may be garrisoned in
 *          this entity.
 * @return {Boolean} <code>true</code> iff the entity's garrison
 *                   capacity allows for at least one more entity.
 */
API3.Entity.prototype.canGarrisonInside = function() {
	return this._entity.garrisoned.length < this.garrisonMax();
};

	// TODO: visibility

API3.Entity.prototype.move = function(x, z, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "walk", "entities": [this.id()], "x": x, "z": z, "queued": queued });
	return this;
};

API3.Entity.prototype.moveToRange = function(x, z, min, max, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "walk-to-range", "entities": [this.id()], "x": x, "z": z, "min": min, "max": max, "queued": queued });
	return this;
};

API3.Entity.prototype.attackMove = function(x, z, targetClasses, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "attack-walk", "entities": [this.id()], "x": x, "z": z, "targetClasses": targetClasses, "queued": queued });
	return this;
};

/**
 * @summary Changes the default reaction of the entity towards enemy
 *          entities.
 * @param stance {String} The identifier of the new entity stance,
 *                        allowing <code>"violent"</code>,
 *                        <code>"aggressive"</code>,
 *                        <code>"defensive"</code>,
 *                        <code>"passive"</code>
 *                        <code>"standground"</code>.
 * @return {API3.Entity} The entity itself (for chaining commands).
 * @todo Link to the 0AD game description of the entity behavior at
 *       the various stances.
 */
API3.Entity.prototype.setStance = function(stance,queued){
	Engine.PostCommand(PlayerID,{"type": "stance", "entities": [this.id()], "name" : stance, "queued": queued });
	return this;
};

/**
 * @summary Commands the entity to stop at its momentary position.
 * @return undefined.
 */
API3.Entity.prototype.stopMoving = function() {
	Engine.PostCommand(PlayerID,{"type": "stop", "entities": [this.id()], "queued": false});
};

/**
 * @summary Ungarrisons a particular entity from this entity.
 * @param id {Number} The id of the entity to be ungarrisoned.
 * @return {API3.Entity} The entity itself (for chaining commands).
 * @see {@link API3.Entity.garrison}
 * @see {@link API3.Entity.id}
 */
API3.Entity.prototype.unload = function(id) {
	if (!this.get("GarrisonHolder"))
		return undefined;
	Engine.PostCommand(PlayerID,{"type": "unload", "garrisonHolder": this.id(), "entities": [id]});
	return this;
};

/**
 * @summary Ungarrisons all entities currently garrisoned.
 * @description Although any entities of the player will be
 *              ungarrisoned, allied player garrisoned within this
 *              entity will not be ungarrisoned by this function.
 * @return {API3.Entity} The entity itself (for command chaining).
 */
API3.Entity.prototype.unloadAll = function() {
	if (!this.get("GarrisonHolder"))
		return undefined;
	Engine.PostCommand(PlayerID,{"type": "unload-all-own", "garrisonHolders": [this.id()]});
	return this;
};

/**
 * @summary Have the entity hide itself in another entity.
 * @param target {API3.Entity} The entity to hide inside.
 * @param queued {Boolean} States whether the command shall be put
 *                         into the order queue of the entity.
 *                         Iff not <code>true</code>, the garrison
 *                         command is executed immediately.
 * @return {API3.Entity} The entity itself (for command chaining)
 */
API3.Entity.prototype.garrison = function(target, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "garrison", "entities": [this.id()], "target": target.id(),"queued": queued});
	return this;
};

/**
 * @summary Have the entity attack another entity.
 * @param unitId {Number} The numeric id of the entity to attack.
 * @return {API3.Entity} The entity itself (for command chaining).
 * @see {@link API3.Entity.id}
 */
API3.Entity.prototype.attack = function(unitId) {
	Engine.PostCommand(PlayerID,{"type": "attack", "entities": [this.id()], "target": unitId, "queued": false});
	return this;
};

/**
 * @summary Move the entity away from a given position.
 * @description The function draws a line between the entities current
 *              position and the specified point. It then moves the
 *              entity to a point on the line which is
 *              <code>dist</code> game meters off the target point.
 *              When the entity is <strong>on</strong> the target
 *              point, it is moved off in x-direction.
 * @param point {Array.<Number>} A two-dimensionl array whose elements
 *                               give the x- and z-position of the
 *                               repellent point.
 * @param dist {Number} The desired distance of the entity from the
 *                      repellent point.
 */
API3.Entity.prototype.moveApart = function(point, dist) {
	if (this.position() !== undefined) {
		var direction = [this.position()[0] - point[0], this.position()[1] - point[1]];
		var norm = API3.VectorDistance(point, this.position());
		if (norm === 0)
			direction = [1, 0];
		else
		{
			direction[0] /= norm;
			direction[1] /= norm;
		}
		Engine.PostCommand(PlayerID,{"type": "walk", "entities": [this.id()], "x": this.position()[0] + direction[0]*dist, "z": this.position()[1] + direction[1]*dist, "queued": false});
	}
	return this;
};

/**
 * @summary Move the entity away from another entity.
 * @description The function draws a line between the entity and the
 *              repellent entity. It then instructs the entity to move
 *              along the line, away from the repellent.
 * @param unitToFleeFrom {API3.Entity} The unit to evade.
 * @return {API3.Entity} The entity itself (for command chaining).
 */
API3.Entity.prototype.flee = function(unitToFleeFrom) {
	if (this.position() !== undefined && unitToFleeFrom.position() !== undefined) {
		var FleeDirection = [this.position()[0] - unitToFleeFrom.position()[0],this.position()[1] - unitToFleeFrom.position()[1]];
		var dist = API3.VectorDistance(unitToFleeFrom.position(), this.position() );
		FleeDirection[0] = (FleeDirection[0]/dist) * 8;
		FleeDirection[1] = (FleeDirection[1]/dist) * 8;

		Engine.PostCommand(PlayerID,{"type": "walk", "entities": [this.id()], "x": this.position()[0] + FleeDirection[0]*5, "z": this.position()[1] + FleeDirection[1]*5, "queued": false});
	}
	return this;
};

/**
 * @summary Gather resources from another entity.
 * @param target {API3.Entity} The entity to gather from.
 * @param queued {Boolean} States whether the command shall be put
 *                         into the order queue of the entity.
 *                         Iff not <code>true</code>, the entity will
 *                         start gathering immediately.
 * @return {API3.Entity} The entity itself (for command chaining)
 */
API3.Entity.prototype.gather = function(target, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "gather", "entities": [this.id()], "target": target.id(), "queued": queued});
	return this;
};

/**
 * @summary Repair a structure or siege device.
 * @param target {API3.Entity} The entity to repair.
 * @param queued {Boolean} States whether the command shall be put
 *                         into the order queue of the entity.
 *                         Iff not <code>true</code>, the entity will
 *                         start repairing immediately.
 * @return {API3.Entity} The entity itself (for command chaining).
 */
API3.Entity.prototype.repair = function(target, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "repair", "entities": [this.id()], "target": target.id(), "autocontinue": false, "queued": queued});
	return this;
};

API3.Entity.prototype.returnResources = function(target, queued) {
	queued = queued || false;
	Engine.PostCommand(PlayerID,{"type": "returnresource", "entities": [this.id()], "target": target.id(), "queued": queued});
	return this;
};

/**
 * @summary Kills or pulls down this entity.
 * @description Destruction of the entity is always done immediately,
 *              regardless of the queue state.
 * @return {API3.Entity} The entity itself (although no further
 *                       commands are likely to execute on the
 *                       entity).
 */
API3.Entity.prototype.destroy = function() {
	Engine.PostCommand(PlayerID,{"type": "delete-entities", "entities": [this.id()] });
	return this;
};

API3.Entity.prototype.barter = function(buyType, sellType, amount) {
	Engine.PostCommand(PlayerID,{"type": "barter", "sell" : sellType, "buy" : buyType, "amount" : amount });
	return this;
};

API3.Entity.prototype.tradeRoute = function(target, source) {
	Engine.PostCommand(PlayerID,{"type": "setup-trade-route", "entities": [this.id()], "target": target.id(), "source": source.id(), "route": undefined, "queued": false });
	return this;
};

API3.Entity.prototype.setRallyPoint = function(target, command) {
	var data = {"command": command, "target": target.id()};
	Engine.PostCommand(PlayerID, {"type": "set-rallypoint", "entities": [this.id()], "x": target.position()[0], "z": target.position()[1], "data": data});
	return this;
};

API3.Entity.prototype.unsetRallyPoint = function() {
	Engine.PostCommand(PlayerID, {"type": "unset-rallypoint", "entities": [this.id()]});
	return this;
};

/**
 * @summary Train some entities from this one.
 * @param type {String} The civilization-qualified template name
 *                      of the desired entity, e.g.
 *                      <code>"units/athen_infantry_archer"</code>.
 * @param count {Number} Number of entities to train in one batch. For
 *                       fairness against the human player(s), this
 *                       should always be 1 or a multiple of 5 (the
 *                       0AD GUI does not support arbitrary values).
 * @return {API3.Entity} The entity itself (for command chaining)
 * @todo Describe the metadata and promotedTypes parameters.
 */
API3.Entity.prototype.train = function(type, count, metadata, promotedTypes) {
	var trainable = this.trainableEntities();
	if (!trainable)
	{
		error("Called train("+type+", "+count+") on non-training entity "+this);
		return this;
	}
	if (trainable.indexOf(type) === -1)
	{
		error("Called train("+type+", "+count+") on entity "+this+" which can't train that");
		return this;
	}

	Engine.PostCommand(PlayerID,{
		"type": "train",
		"entities": [this.id()],
		"template": type,
		"count": count,
		"metadata": metadata,
		"promoted": promotedTypes
	});
	return this;
};

/**
 * @summary Build a structure at a specific position.
 * @param template {String} The civilization-qualified template name
 *                          of the desired structure, e.g.
 *                          <code>"structures/brit_dock"</code>
 * @param x {Number} Location of the new structure's center point
 *                   along the x-direction.
 * @param z {Number} Location of the new structure's center point
 *                   along the z-direction.
 * @param angle {Number} Rotation angle of the structure. To comply
 *                       with the general 45 degree orientation of
 *                       buildings in normal game plays, this value
 *                       should usually be set to
 *                       <code>0.75*Math.PI</code>.
 * @todo Describe metadata parameter of construct function.
 * @return {API3.Entity} The entity itself (for command chaining).
 */
API3.Entity.prototype.construct = function(template, x, z, angle, metadata) {
	// TODO: verify this unit can construct this, just for internal
	// sanity-checking and error reporting

	Engine.PostCommand(PlayerID,{
		"type": "construct",
		"entities": [this.id()],
		"template": template,
		"x": x,
		"z": z,
		"angle": angle,
		"autorepair": false,
		"autocontinue": false,
		"queued": false,
		"metadata" : metadata	// can be undefined
	});
	return this;
};

/**
 * @summary Research a technology.
 * @param template {String} The name of the technology to research.
 * @return {API3.Entity} The entity itself (for command chaining).
 */
API3.Entity.prototype.research = function(template) {
	Engine.PostCommand(PlayerID,{ "type": "research", "entity": this.id(), "template": template });
	return this;
};

API3.Entity.prototype.stopProduction = function(id) {
	Engine.PostCommand(PlayerID,{ "type": "stop-production", "entity": this.id(), "id": id });
	return this;
};

API3.Entity.prototype.stopAllProduction = function(percentToStopAt) {
	var queue = this._entity.trainingQueue;
	if (!queue)
		return true;	// no queue, so technically we stopped all production.
	for (var i = 0; i < queue.length; i++) {
		let item = queue[i];
		if (item.progress < percentToStopAt)
			Engine.PostCommand(PlayerID,{ "type": "stop-production", "entity": this.id(), "id": item.id });
	}
	return this;
};

// -------------------------------------------------------------------
//  End of file
// -------------------------------------------------------------------