Index: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js (revision 20078)
+++ ps/trunk/binaries/data/mods/public/globalscripts/Templates.js (revision 20079)
@@ -1,486 +1,486 @@
/**
* Gets an array of all classes for this identity template
*/
function GetIdentityClasses(template)
{
var classList = [];
if (template.Classes && template.Classes._string)
classList = classList.concat(template.Classes._string.split(/\s+/));
if (template.VisibleClasses && template.VisibleClasses._string)
classList = classList.concat(template.VisibleClasses._string.split(/\s+/));
if (template.Rank)
classList = classList.concat(template.Rank);
return classList;
}
/**
* Gets an array with all classes for this identity template
* that should be shown in the GUI
*/
function GetVisibleIdentityClasses(template)
{
if (template.VisibleClasses && template.VisibleClasses._string)
return template.VisibleClasses._string.split(/\s+/);
return [];
}
/**
* Check if the classes given in the identity template
* match a list of classes
* @param classes List of the classes to check against
* @param match Either a string in the form
* "Class1 Class2+Class3"
* where spaces are handled as OR and '+'-signs as AND,
* and ! is handled as NOT, thus Class1+!Class2 = Class1 AND NOT Class2
* Or a list in the form
* [["Class1"], ["Class2", "Class3"]]
* where the outer list is combined as OR, and the inner lists are AND-ed
* Or a hybrid format containing a list of strings, where the list is
* combined as OR, and the strings are split by space and '+' and AND-ed
*
* @return undefined if there are no classes or no match object
* true if the the logical combination in the match object matches the classes
* false otherwise
*/
function MatchesClassList(classes, match)
{
if (!match || !classes)
return undefined;
// transform the string to an array
if (typeof match == "string")
match = match.split(/\s+/);
for (var sublist of match)
{
// if the elements are still strings, split them by space or by '+'
if (typeof sublist == "string")
sublist = sublist.split(/[+\s]+/);
if (sublist.every(c => (c[0] == "!" && classes.indexOf(c.substr(1)) == -1)
|| (c[0] != "!" && classes.indexOf(c) != -1)))
return true;
}
return false;
}
/**
* Gets the value originating at the value_path as-is, with no modifiers applied.
*
* @param {object} template - A valid template as returned from a template loader.
* @param {string} value_path - Route to value within the xml template structure.
* @return {number}
*/
function GetBaseTemplateDataValue(template, value_path)
{
let current_value = template;
for (let property of value_path.split("/"))
current_value = current_value[property] || 0;
return +current_value;
}
/**
* Gets the value originating at the value_path with the modifiers dictated by the mod_key applied.
*
* @param {object} template - A valid template as returned from a template loader.
* @param {string} value_path - Route to value within the xml template structure.
* @param {string} mod_key - Tech modification key, if different from value_path.
* @param {number} player - Optional player id.
* @param {object} modifiers - Value modifiers from auto-researched techs, unit upgrades,
* etc. Optional as only used if no player id provided.
* @return {number} Modifier altered value.
*/
function GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers={})
{
let current_value = GetBaseTemplateDataValue(template, value_path);
mod_key = mod_key || value_path;
if (player)
current_value = ApplyValueModificationsToTemplate(mod_key, current_value, player, template);
else if (modifiers)
current_value = GetTechModifiedProperty(modifiers, GetIdentityClasses(template.Identity), mod_key, current_value);
// Using .toFixed() to get around spidermonkey's treatment of numbers (3 * 1.1 = 3.3000000000000003 for instance).
return +current_value.toFixed(8);
}
/**
* Get information about a template with or without technology modifications.
*
* NOTICE: The data returned here should have the same structure as
* the object returned by GetEntityState and GetExtendedEntityState!
*
* @param {object} template - A valid template as returned by the template loader.
* @param {number} player - An optional player id to get the technology modifications
* of properties.
* @param {object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
* @param {object} resources - An instance of the Resources prototype.
* @param {object} modifiers - Modifications from auto-researched techs, unit upgrades
* etc. Optional as only used if there's no player
* id provided.
*/
function GetTemplateDataHelper(template, player, auraTemplates, resources, modifiers={})
{
// Return data either from template (in tech tree) or sim state (ingame).
// @param {string} value_path - Route to the value within the template.
// @param {string} mod_key - Modification key, if not the same as the value_path.
let getEntityValue = function(value_path, mod_key) {
return GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers);
};
let ret = {};
if (template.Armour)
ret.armour = {
"hack": getEntityValue("Armour/Hack"),
"pierce": getEntityValue("Armour/Pierce"),
"crush": getEntityValue("Armour/Crush")
};
if (template.Attack)
{
ret.attack = {};
for (let type in template.Attack)
{
let getAttackStat = function(stat) {
return getEntityValue("Attack/" + type + "/" + stat);
};
if (type == "Capture")
ret.attack.Capture = {
"value": getAttackStat("Value")
};
else
{
ret.attack[type] = {
"hack": getAttackStat("Hack"),
"pierce": getAttackStat("Pierce"),
"crush": getAttackStat("Crush"),
"minRange": getAttackStat("MinRange"),
"maxRange": getAttackStat("MaxRange"),
"elevationBonus": getAttackStat("ElevationBonus")
};
ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
(2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
}
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
if (template.Attack[type].Splash)
ret.attack[type].splash = {
"hack": getAttackStat("Splash/Hack"),
"pierce": getAttackStat("Splash/Pierce"),
"crush": getAttackStat("Splash/Crush"),
// true if undefined
"friendlyFire": template.Attack[type].Splash.FriendlyFire != "false",
"shape": template.Attack[type].Splash.Shape
};
}
}
if (template.DeathDamage)
{
ret.deathDamage = {
"hack": getEntityValue("DeathDamage/Hack"),
"pierce": getEntityValue("DeathDamage/Pierce"),
"crush": getEntityValue("DeathDamage/Crush"),
"friendlyFire": template.DeathDamage.FriendlyFire != "false"
};
}
if (template.Auras)
{
ret.auras = {};
for (let auraID of template.Auras._string.split(/\s+/))
{
let aura = auraTemplates[auraID];
ret.auras[auraID] = {
"name": aura.auraName,
"description": aura.auraDescription || null,
"radius": aura.radius || null
};
}
}
if (template.BuildingAI)
ret.buildingAI = {
"defaultArrowCount": Math.round(getEntityValue("BuildingAI/DefaultArrowCount")),
"garrisonArrowMultiplier": getEntityValue("BuildingAI/GarrisonArrowMultiplier"),
"maxArrowCount": Math.round(getEntityValue("BuildingAI/MaxArrowCount"))
};
if (template.BuildRestrictions)
{
// required properties
ret.buildRestrictions = {
"placementType": template.BuildRestrictions.PlacementType,
"territory": template.BuildRestrictions.Territory,
"category": template.BuildRestrictions.Category,
};
// optional properties
if (template.BuildRestrictions.Distance)
{
ret.buildRestrictions.distance = {
"fromClass": template.BuildRestrictions.Distance.FromClass,
};
if (template.BuildRestrictions.Distance.MinDistance)
- ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance;
+ ret.buildRestrictions.distance.min = getEntityValue("BuildRestrctions/Distance/MinDistance");
if (template.BuildRestrictions.Distance.MaxDistance)
- ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
+ ret.buildRestrictions.distance.max = getEntityValue("BuildRestrctions/Distance/MaxDistance");
}
}
if (template.TrainingRestrictions)
ret.trainingRestrictions = {
"category": template.TrainingRestrictions.Category,
};
if (template.Cost)
{
ret.cost = {};
for (let resCode in template.Cost.Resources)
ret.cost[resCode] = getEntityValue("Cost/Resources/" + resCode);
if (template.Cost.Population)
ret.cost.population = getEntityValue("Cost/Population");
if (template.Cost.PopulationBonus)
ret.cost.populationBonus = getEntityValue("Cost/PopulationBonus");
if (template.Cost.BuildTime)
ret.cost.time = getEntityValue("Cost/BuildTime");
}
if (template.Footprint)
{
ret.footprint = { "height": template.Footprint.Height };
if (template.Footprint.Square)
ret.footprint.square = {
"width": +template.Footprint.Square["@width"],
"depth": +template.Footprint.Square["@depth"]
};
else if (template.Footprint.Circle)
ret.footprint.circle = { "radius": +template.Footprint.Circle["@radius"] };
else
warn("GetTemplateDataHelper(): Unrecognized Footprint type");
}
if (template.GarrisonHolder)
{
ret.garrisonHolder = {
"buffHeal": getEntityValue("GarrisonHolder/BuffHeal")
};
if (template.GarrisonHolder.Max)
ret.garrisonHolder.capacity = getEntityValue("GarrisonHolder/Max");
}
if (template.Heal)
ret.heal = {
"hp": getEntityValue("Heal/HP"),
"range": getEntityValue("Heal/Range"),
"rate": getEntityValue("Heal/Rate")
};
if (template.ResourceGatherer)
{
ret.resourceGatherRates = {};
let baseSpeed = getEntityValue("ResourceGatherer/BaseSpeed");
for (let type in template.ResourceGatherer.Rates)
ret.resourceGatherRates[type] = getEntityValue("ResourceGatherer/Rates/"+ type) * baseSpeed;
}
if (template.ResourceTrickle)
{
ret.resourceTrickle = {
"interval": +template.ResourceTrickle.Interval,
"rates": {}
};
for (let type in template.ResourceTrickle.Rates)
ret.resourceTrickle.rates[type] = getEntityValue("ResourceTrickle/Rates/" + type);
}
if (template.Loot)
{
ret.loot = {};
for (let type in template.Loot)
ret.loot[type] = getEntityValue("Loot/"+ type);
}
if (template.Obstruction)
{
ret.obstruction = {
"active": ("" + template.Obstruction.Active == "true"),
"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
"shape": {}
};
if (template.Obstruction.Static)
{
ret.obstruction.shape.type = "static";
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
}
else if (template.Obstruction.Unit)
{
ret.obstruction.shape.type = "unit";
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
}
else
ret.obstruction.shape.type = "cluster";
}
if (template.Pack)
ret.pack = {
"state": template.Pack.State,
"time": getEntityValue("Pack/Time"),
};
if (template.Health)
ret.health = Math.round(getEntityValue("Health/Max"));
if (template.Identity)
{
ret.selectionGroupName = template.Identity.SelectionGroupName;
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
}
if (template.UnitMotion)
{
ret.speed = {
"walk": getEntityValue("UnitMotion/WalkSpeed"),
};
if (template.UnitMotion.Run)
ret.speed.run = getEntityValue("UnitMotion/Run/Speed");
}
if (template.Upgrade)
{
ret.upgrades = [];
for (let upgradeName in template.Upgrade)
{
let upgrade = template.Upgrade[upgradeName];
let cost = {};
if (upgrade.Cost)
for (let res in upgrade.Cost)
cost[res] = getEntityValue("Upgrade/" + upgradeName + "/Cost/" + res, "Upgrade/Cost/" + res);
if (upgrade.Time)
cost.time = getEntityValue("Upgrade/" + upgradeName + "/Time", "Upgrade/Time");
ret.upgrades.push({
"entity": upgrade.Entity,
"tooltip": upgrade.Tooltip,
"cost": cost,
"icon": upgrade.Icon || undefined,
"requiredTechnology": upgrade.RequiredTechnology || undefined
});
}
}
if (template.ProductionQueue)
{
ret.techCostMultiplier = {};
for (let res in template.ProductionQueue.TechCostMultiplier)
ret.techCostMultiplier[res] = getEntityValue("ProductionQueue/TechCostMultiplier/" + res);
}
if (template.Trader)
ret.trader = {
"GainMultiplier": getEntityValue("Trader/GainMultiplier")
};
if (template.WallSet)
ret.wallSet = {
"templates": {
"tower": template.WallSet.Templates.Tower,
"gate": template.WallSet.Templates.Gate,
"long": template.WallSet.Templates.WallLong,
"medium": template.WallSet.Templates.WallMedium,
"short": template.WallSet.Templates.WallShort,
},
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
"minTowerOverlap": +template.WallSet.MinTowerOverlap,
};
if (template.WallPiece)
ret.wallPiece = { "length": +template.WallPiece.Length };
return ret;
}
/**
* Get basic information about a technology template.
* @param {object} template - A valid template as obtained by loading the tech JSON file.
* @param {string} civ - Civilization for which the tech requirements should be calculated.
*/
function GetTechnologyBasicDataHelper(template, civ)
{
return {
"name": {
"generic": template.genericName
},
"icon": template.icon ? "technologies/" + template.icon : undefined,
"description": template.description,
"reqs": DeriveTechnologyRequirements(template, civ),
"modifications": template.modifications,
"affects": template.affects
};
}
/**
* Get information about a technology template.
* @param {object} template - A valid template as obtained by loading the tech JSON file.
* @param {string} civ - Civilization for which the specific name and tech requirements should be returned.
*/
function GetTechnologyDataHelper(template, civ, resources)
{
let ret = GetTechnologyBasicDataHelper(template, civ);
if (template.specificName)
ret.name.specific = template.specificName[civ] || template.specificName.generic;
ret.cost = { "time": template.researchTime ? +template.researchTime : 0 };
for (let type of resources.GetCodes())
ret.cost[type] = +(template.cost && template.cost[type] || 0);
ret.tooltip = template.tooltip;
ret.requirementsTooltip = template.requirementsTooltip || "";
return ret;
}
function calculateCarriedResources(carriedResources, tradingGoods)
{
var resources = {};
if (carriedResources)
for (let resource of carriedResources)
resources[resource.type] = (resources[resource.type] || 0) + resource.amount;
if (tradingGoods && tradingGoods.amount)
resources[tradingGoods.type] =
(resources[tradingGoods.type] || 0) +
(tradingGoods.amount.traderGain || 0) +
(tradingGoods.amount.market1Gain || 0) +
(tradingGoods.amount.market2Gain || 0);
return resources;
}
Index: ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js (revision 20078)
+++ ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js (revision 20079)
@@ -1,347 +1,350 @@
function BuildRestrictions() {}
BuildRestrictions.prototype.Schema =
"Specifies building placement restrictions as they relate to terrain, territories, and distance." +
"" +
"" +
"land" +
"own" +
"Special" +
"" +
"CivilCentre" +
"40" +
"" +
"" +
"" +
"" +
"" +
"land" +
"shore" +
"land-shore"+
"" +
"" +
"" +
"" +
"" +
"" +
"own" +
"ally" +
"neutral" +
"enemy" +
"" +
"" +
"
" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
BuildRestrictions.prototype.Init = function()
{
this.territories = this.template.Territory.split(/\s+/);
};
/**
* Checks whether building placement is valid
* 1. Visibility is not hidden (may be fogged or visible)
* 2. Check foundation
* a. Doesn't obstruct foundation-blocking entities
* b. On valid terrain, based on passability class
* 3. Territory type is allowed (see note below)
* 4. Dock is on shoreline and facing into water
* 5. Distance constraints satisfied
*
* Returns result object:
* {
* "success": true iff the placement is valid, else false
* "message": message to display in UI for invalid placement, else ""
* "parameters": parameters to use in the GUI message
* "translateMessage": always true
* "translateParameters": list of parameters to translate
* "pluralMessage": we might return a plural translation instead (optional)
* "pluralCount": plural translation argument (optional)
* }
*
* Note: The entity which is used to check this should be a preview entity
* (template name should be "preview|"+templateName), as otherwise territory
* checks for buildings with territory influence will not work as expected.
*/
BuildRestrictions.prototype.CheckPlacement = function()
{
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
var result = {
"success": false,
"message": markForTranslation("%(name)s cannot be built due to unknown error"),
"parameters": {
"name": name,
},
"translateMessage": true,
"translateParameters": ["name"],
};
// TODO: AI has no visibility info
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (!cmpPlayer.IsAI())
{
// Check whether it's in a visible or fogged region
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpRangeManager || !cmpOwnership)
return result; // Fail
var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) != "hidden");
if (!explored)
{
result.message = markForTranslation("%(name)s cannot be built in unexplored area");
return result; // Fail
}
}
// Check obstructions and terrain passability
var passClassName = "";
switch (this.template.PlacementType)
{
case "shore":
passClassName = "building-shore";
break;
case "land-shore":
// 'default-terrain-only' is everywhere a normal unit can go, ignoring
// obstructions (i.e. on passable land, and not too deep in the water)
passClassName = "default-terrain-only";
break;
case "land":
default:
passClassName = "building-land";
}
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
if (!cmpObstruction)
return result; // Fail
if (this.template.Category == "Wall")
{
// for walls, only test the center point
var ret = cmpObstruction.CheckFoundation(passClassName, true);
}
else
{
var ret = cmpObstruction.CheckFoundation(passClassName, false);
}
if (ret != "success")
{
switch (ret)
{
case "fail_error":
case "fail_no_obstruction":
error("CheckPlacement: Error returned from CheckFoundation");
break;
case "fail_obstructs_foundation":
result.message = markForTranslation("%(name)s cannot be built on another building or resource");
break;
case "fail_terrain_class":
// TODO: be more specific and/or list valid terrain?
result.message = markForTranslation("%(name)s cannot be built on invalid terrain");
}
return result; // Fail
}
// Check territory restrictions
var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld()))
return result; // Fail
var pos = cmpPosition.GetPosition2D();
var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
var isConnected = !cmpTerritoryManager.IsTerritoryBlinking(pos.x, pos.y);
var isOwn = tileOwner == cmpPlayer.GetPlayerID();
var isMutualAlly = cmpPlayer.IsExclusiveMutualAlly(tileOwner);
var isNeutral = tileOwner == 0;
var invalidTerritory = "";
if (isOwn)
{
if (!this.HasTerritory("own"))
// Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
invalidTerritory = markForTranslationWithContext("Territory type", "own");
else if (!isConnected && !this.HasTerritory("neutral"))
// Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
invalidTerritory = markForTranslationWithContext("Territory type", "unconnected own");
}
else if (isMutualAlly)
{
if (!this.HasTerritory("ally"))
// Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
invalidTerritory = markForTranslationWithContext("Territory type", "allied");
else if (!isConnected && !this.HasTerritory("neutral"))
// Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
invalidTerritory = markForTranslationWithContext("Territory type", "unconnected allied");
}
else if (isNeutral)
{
if (!this.HasTerritory("neutral"))
// Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
invalidTerritory = markForTranslationWithContext("Territory type", "neutral");
}
else
{
// consider everything else enemy territory
if (!this.HasTerritory("enemy"))
// Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
invalidTerritory = markForTranslationWithContext("Territory type", "enemy");
}
if (invalidTerritory)
{
result.message = markForTranslation("%(name)s cannot be built in %(territoryType)s territory. Valid territories: %(validTerritories)s");
result.translateParameters.push("territoryType");
result.translateParameters.push("validTerritories");
result.parameters.territoryType = {"context": "Territory type", "message": invalidTerritory};
// gui code will join this array to a string
result.parameters.validTerritories = {"context": "Territory type list", "list": this.GetTerritories()};
return result; // Fail
}
// Check special requirements
if (this.template.PlacementType == "shore")
{
// TODO: Probably should check unit passability classes here, to determine if:
// 1. ships can be spawned "nearby"
// 2. builders can pass the terrain where the dock is placed (don't worry about paths)
// so it's correct even if the criteria changes for these units
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
if (!cmpFootprint)
return result; // Fail
// Get building's footprint
var shape = cmpFootprint.GetShape();
var halfSize = 0;
if (shape.type == "square")
halfSize = shape.depth/2;
else if (shape.type == "circle")
halfSize = shape.radius;
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
if (!cmpTerrain || !cmpWaterManager)
return result; // Fail
var ang = cmpPosition.GetRotation().y;
var sz = halfSize * Math.sin(ang);
var cz = halfSize * Math.cos(ang);
if ((cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz) - cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz)) < 1.0 // front
|| (cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz) - cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz)) > 2.0) // back
{
result.message = markForTranslation("%(name)s must be built on a valid shoreline");
return result; // Fail
}
}
+
+ let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+
+ let templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
+ let template = cmpTemplateManager.GetTemplate(templateName.substr(templateName.lastIndexOf("|") + 1));
// Check distance restriction
if (this.template.Distance)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var cat = this.template.Distance.FromClass;
var filter = function(id)
{
var cmpIdentity = Engine.QueryInterface(id, IID_Identity);
return cmpIdentity.GetClassesList().indexOf(cat) > -1;
};
- if (this.template.Distance.MinDistance)
+ if (this.template.Distance.MinDistance !== undefined)
{
- var dist = +this.template.Distance.MinDistance;
- var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
- if (nearEnts.length)
+ let minDistance = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MinDistance", +this.template.Distance.MinDistance, cmpPlayer.GetPlayerID(), template);
+ if (cmpRangeManager.ExecuteQuery(this.entity, 0, minDistance, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).some(filter))
{
- var result = markForPluralTranslation(
+ let result = markForPluralTranslation(
"%(name)s too close to a %(category)s, must be at least %(distance)s meter away",
"%(name)s too close to a %(category)s, must be at least %(distance)s meters away",
- +this.template.Distance.MinDistance);
+ minDistance);
result.success = false;
result.translateMessage = true;
result.parameters = {
"name": name,
"category": cat,
- "distance": this.template.Distance.MinDistance
+ "distance": minDistance
};
result.translateParameters = ["name", "category"];
return result; // Fail
}
}
- if (this.template.Distance.MaxDistance)
+ if (this.template.Distance.MaxDistance !== undefined)
{
- var dist = +this.template.Distance.MaxDistance;
- var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
- if (!nearEnts.length)
+ let maxDistance = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MaxDistance", +this.template.Distance.MaxDistance, cmpPlayer.GetPlayerID(), template);
+ if (!cmpRangeManager.ExecuteQuery(this.entity, 0, maxDistance, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).some(filter))
{
- var result = markForPluralTranslation(
+ let result = markForPluralTranslation(
"%(name)s too far from a %(category)s, must be within %(distance)s meter",
"%(name)s too far from a %(category)s, must be within %(distance)s meters",
- +this.template.Distance.MaxDistance);
+ maxDistance);
result.success = false;
result.translateMessage = true;
result.parameters = {
"name": name,
"category": cat,
- "distance": this.template.Distance.MaxDistance
+ "distance": maxDistance
};
result.translateParameters = ["name", "category"];
return result; // Fail
}
}
}
// Success
result.success = true;
result.message = "";
return result;
};
BuildRestrictions.prototype.GetCategory = function()
{
return this.template.Category;
};
BuildRestrictions.prototype.GetTerritories = function()
{
return ApplyValueModificationsToEntity("BuildRestrictions/Territory", this.territories, this.entity);
};
BuildRestrictions.prototype.HasTerritory = function(territory)
{
return (this.GetTerritories().indexOf(territory) != -1);
};
// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
markForTranslationWithContext("Territory type list", "own");
// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
markForTranslationWithContext("Territory type list", "ally");
// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
markForTranslationWithContext("Territory type list", "neutral");
// Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
markForTranslationWithContext("Territory type list", "enemy");
Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);