// 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
// -------------------------------------------------------------------