Ticket #3811: t3811_buildingDensity_5.diff

File t3811_buildingDensity_5.diff, 29.9 KB (added by elexis, 8 years ago)

Rebased, removed committed stylefix hunks and bad hunk from GUIInterface. Still contains stylefixes which should be done separately as they draw attention when reading the patch.

  • binaries/data/mods/public/globalscripts/Templates.js

    function GetTemplateDataHelper(template,  
    140140            "territory": template.BuildRestrictions.Territory,
    141141            "category": template.BuildRestrictions.Category,
    142142        };
    143143
    144144        // optional properties
    145         if (template.BuildRestrictions.Distance)
     145        if (template.BuildRestrictions.Density)
    146146        {
    147             ret.buildRestrictions.distance = {
    148                 "fromClass": template.BuildRestrictions.Distance.FromClass,
     147            ret.buildRestrictions.density = {
     148                "fromClass": template.BuildRestrictions.Density.FromClass,
    149149            };
    150             if (template.BuildRestrictions.Distance.MinDistance) ret.buildRestrictions.distance.min = +template.BuildRestrictions.Distance.MinDistance;
    151             if (template.BuildRestrictions.Distance.MaxDistance) ret.buildRestrictions.distance.max = +template.BuildRestrictions.Distance.MaxDistance;
     150            if (template.BuildRestrictions.Density.MinEntity)
     151                ret.buildRestrictions.density.min = +template.BuildRestrictions.Density.MinEntity;
     152            if (template.BuildRestrictions.Density.MaxEntity)
     153                ret.buildRestrictions.density.max = +template.BuildRestrictions.Density.MaxEntity;
    152154        }
    153155    }
    154156
    155157    if (template.TrainingRestrictions)
    156158    {
  • binaries/data/mods/public/gui/session/input.js

    function updateBuildingPlacementPreview(  
    9898
    9999    if (placementSupport.mode === "building")
    100100    {
    101101        if (placementSupport.template && placementSupport.position)
    102102        {
    103             var result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
     103            let result = Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {
    104104                "template": placementSupport.template,
    105105                "x": placementSupport.position.x,
    106106                "z": placementSupport.position.z,
    107107                "angle": placementSupport.angle,
    108108                "actorSeed": placementSupport.actorSeed
    function updateBuildingPlacementPreview(  
    158158                placementSupport.wallSnapEntitiesIncludeOffscreen,
    159159                true, // require exact template match
    160160                true  // include foundations
    161161            );
    162162
    163             return Engine.GuiInterfaceCall("SetWallPlacementPreview", {
     163            let result = Engine.GuiInterfaceCall("SetWallPlacementPreview", {
    164164                "wallSet": placementSupport.wallSet,
    165165                "start": placementSupport.position,
    166166                "end": placementSupport.wallEndPosition,
    167167                "snapEntities": placementSupport.wallSnapEntities,  // snapping entities (towers) for starting a wall segment
    168168            });
     169
     170            // Show placement info tooltip if invalid position
     171            placementSupport.tooltipError = !result.success;
     172            placementSupport.tooltipMessage = "";
     173            if (!result.success)
     174            {
     175                if (result.message && result.parameters)
     176                {
     177                    let message = result.message;
     178                    if (result.translateMessage)
     179                        if (result.pluralMessage)
     180                            message = translatePlural(result.message, result.pluralMessage, result.pluralCount);
     181                        else
     182                            message = translate(message);
     183                    let parameters = result.parameters;
     184                    if (result.translateParameters)
     185                        translateObjectKeys(parameters, result.translateParameters);
     186                    placementSupport.tooltipMessage = sprintf(message, parameters);
     187                }
     188            }
     189            return result;
    169190        }
    170191    }
    171192
    172193    return false;
    173194}
  • binaries/data/mods/public/simulation/ai/common-api/entity.js

    m.Template = m.Class({  
    464464
    465465    buildTime: function() {
    466466        return +this.get("Cost/BuildTime");
    467467    },
    468468
    469     buildDistance: function() {
    470         return this.get("BuildRestrictions/Distance");
     469    buildDensity: function() {
     470        return this.get("BuildRestrictions/Density");
    471471    },
    472472
    473473    buildPlacementType: function() {
    474474        return this.get("BuildRestrictions/PlacementType");
    475475    },
  • binaries/data/mods/public/simulation/ai/common-api/map-module.js

    m.Map.prototype.addInfluence = function(  
    119119            {
    120120                let dx = x - cx;
    121121                let r2 = dx*dx + dy*dy;
    122122                if (r2 < maxDist2)
    123123                {
    124                     let w = x + y * this.width;             
     124                    let w = x + y * this.width;
     125                    if (this.map[w] + strength < 0)
     126                        this.map[w] = 0;
     127                    else if (this.map[w] + strength > this.maxVal)
     128                        this.map[w] = this.maxVal;  // avoids overflow.
     129                    else
     130                        this.map[w] += strength;
     131                }
     132            }
     133        }
     134    }
     135    else if (type === 'inverseConstant' || type === "inverseConstant") // TODO needed for other cases too?
     136    {
     137        for (let y = y0; y < y1; ++y)
     138        {
     139            let dy = y - cy;
     140            for (let x = x0; x < x1; ++x)
     141            {
     142                let dx = x - cx;
     143                let r2 = dx*dx + dy*dy;
     144                if (r2 > maxDist2)
     145                {
     146                    let w = x + y * this.width;
    125147                    if (this.map[w] + strength < 0)
    126148                        this.map[w] = 0;
    127149                    else if (this.map[w] + strength > this.maxVal)
    128150                        this.map[w] = this.maxVal;  // avoids overflow.
    129151                    else
  • binaries/data/mods/public/simulation/ai/petra/mapModule.js

    m.createObstructionMap = function(gameSt  
    8282    }
    8383
    8484    var map = new API3.Map(gameState.sharedScript, "passability", obstructionTiles);
    8585    map.setMaxVal(255);
    8686
    87     if (template && template.buildDistance())
     87    if (template && template.buildDensity())
    8888    {
    89         let minDist = +template.buildDistance().MinDistance;
    90         let fromClass = template.buildDistance().FromClass;
    91         if (minDist && fromClass)
     89        let buildDensity = template.buildDensity();
     90        if (buildDensity)
    9291        {
     92            let Dist = +buildDensity.Distance;
     93            let fromClass = buildDensity.FromClass;
    9394            let cellSize = passabilityMap.cellSize;
    94             let cellDist = 1 + minDist / cellSize;
     95            let cellDist = 1 + Dist / cellSize;
    9596            let structures = gameState.getOwnStructures().filter(API3.Filters.byClass(fromClass));
    96             for (let ent of structures.values())
     97            // TODO the following will have problems when both MaxEntity and MinEntity are specified
     98            // and that is allowed in template. Probably it needs to get some more efficient too.
     99            if (buildDensity.MaxEntity)
    97100            {
    98                 if (!ent.position())
    99                     continue;
    100                 let pos = ent.position();
    101                 let x = Math.round(pos[0] / cellSize);
    102                 let z = Math.round(pos[1] / cellSize);
    103                 map.addInfluence(x, z, cellDist, -255, "constant");
     101                let influence = Math.floor(-255 / (+buildDensity.MaxEntity + 1));
     102                for (let ent of structures.values())
     103                {
     104                    if (!ent.position())
     105                        continue;
     106                    let pos = ent.position();
     107                    let x = Math.round(pos[0] / cellSize);
     108                    let z = Math.round(pos[1] / cellSize);
     109                    map.addInfluence(x, z, cellDist, influence, "constant");
     110                }
     111            }
     112            if (buildDensity.MinEntity)
     113            {
     114                let influence = Math.floor(-255 / (+buildDensity.MinEntity));
     115                for (let ent of structures.values())
     116                {
     117                    if (!ent.position())
     118                        continue;
     119                    let pos = ent.position();
     120                    let x = Math.round(pos[0] / cellSize);
     121                    let z = Math.round(pos[1] / cellSize);
     122                    map.addInfluence(x, z, cellDist, influence, "inverseConstant");
     123                }
    104124            }
    105125        }
    106126    }
    107127
    108128    return map;
  • binaries/data/mods/public/simulation/components/BuildRestrictions.js

    BuildRestrictions.prototype.Schema =  
    55    "<a:example>" +
    66        "<BuildRestrictions>" +
    77            "<PlacementType>land</PlacementType>" +
    88            "<Territory>own</Territory>" +
    99            "<Category>Special</Category>" +
    10             "<Distance>" +
     10            "<Density>" +
    1111                "<FromClass>CivilCentre</FromClass>" +
    12                 "<MaxDistance>40</MaxDistance>" +
    13             "</Distance>" +
     12                "<Distance>200</Distance>" +
     13                "<MaxEntity>3</MaxEntity>" +
     14                "<MinEntity>1</MinEntity>" +
     15            "</Density>" +
    1416        "</BuildRestrictions>" +
    1517    "</a:example>" +
    1618    "<element name='PlacementType' a:help='Specifies the terrain type restriction for this building.'>" +
    1719        "<choice>" +
    1820            "<value>land</value>" +
    BuildRestrictions.prototype.Schema =  
    3032                    "<value>enemy</value>" +
    3133                "</choice>" +
    3234            "</oneOrMore>" +
    3335        "</list>" +
    3436    "</element>" +
    35     "<element name='Category' a:help='Specifies the category of this building, for satisfying special constraints. Choices include: CivilCentre, House, DefenseTower, Farmstead, Market, Barracks, Dock, Fortress, Field, Temple, Wall, Fence, Storehouse, Stoa, Resource, Special, Wonder, Apadana, Embassy, Monument'>" +
     37    "<element name='Category' a:help='Specifies the category of this building, for satisfying special constraints. Choices include: Apadana, Barracks, CivilCentre, DefenseTower, Dock, Embassy, Farmstead, Field, Fence, Fortress, House, Market, Monument, Resource, Special, Stoa, Storehouse, Temple, Tower, Wall, WallTower, Wonder'>" +
    3638        "<text/>" +
    3739    "</element>" +
    3840    "<optional>" +
    39         "<element name='Distance' a:help='Specifies distance restrictions on this building, relative to buildings from the given category.'>" +
     41        "<element name='Density' a:help='Specifies distance restrictions on this building, relative to buildings from the given category.'>" +
    4042            "<interleave>" +
    4143                "<element name='FromClass'>" +
    4244                    "<text/>" +
    4345                "</element>" +
    44                 "<optional><element name='MinDistance'><data type='positiveInteger'/></element></optional>" +
    45                 "<optional><element name='MaxDistance'><data type='positiveInteger'/></element></optional>" +
     46                "<element name='Distance'><data type='positiveInteger'/></element>" +
     47                "<optional><element name='MinEntity'><data type='nonNegativeInteger'/></element></optional>" +
     48                "<optional><element name='MaxEntity'><data type='nonNegativeInteger'/></element></optional>" +
    4649            "</interleave>" +
    4750        "</element>" +
    4851    "</optional>";
    4952
    5053BuildRestrictions.prototype.Init = function()
    5154{
    5255    this.territories = this.template.Territory.split(/\s+/);
     56    if (this.template.Density)
     57    {
     58        this.minEntity = +(this.template.Density.MinEntity || 0);
     59        this.maxEntity = +(this.template.Density.MaxEntity || Infinity);
     60    }
    5361};
    5462
    5563/**
    5664 * Checks whether building placement is valid
    5765 *  1. Visibility is not hidden (may be fogged or visible)
    5866 *  2. Check foundation
    5967 *      a. Doesn't obstruct foundation-blocking entities
    6068 *      b. On valid terrain, based on passability class
    6169 *  3. Territory type is allowed (see note below)
    6270 *  4. Dock is on shoreline and facing into water
    63  *  5. Distance constraints satisfied
     71 *  5. Density constraints satisfied
    6472 *
    6573 * Returns result object:
    6674 *  {
    6775 *      "success":             true iff the placement is valid, else false
    6876 *      "message":             message to display in UI for invalid placement, else ""
    BuildRestrictions.prototype.Init = funct  
    7785 *  (template name should be "preview|"+templateName), as otherwise territory
    7886 *  checks for buildings with territory influence will not work as expected.
    7987 */
    8088BuildRestrictions.prototype.CheckPlacement = function()
    8189{
    82     var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
    83     var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
     90    let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
     91    let name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
    8492
    85     var result = {
     93    let result = {
    8694        "success": false,
    8795        "message": markForTranslation("%(name)s cannot be built due to unknown error"),
    8896        "parameters": {
    8997            "name": name,
    9098        },
    9199        "translateMessage": true,
    92100        "translateParameters": ["name"],
    93101    };
    94102
    95103    // TODO: AI has no visibility info
    96     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     104    let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    97105    if (!cmpPlayer.IsAI())
    98106    {
    99107        // Check whether it's in a visible or fogged region
    100         var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    101         var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     108        let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     109        let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    102110        if (!cmpRangeManager || !cmpOwnership)
    103111            return result; // Fail
    104112
    105         var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) != "hidden");
    106         if (!explored)
     113        if (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) == "hidden")
    107114        {
    108115            result.message = markForTranslation("%(name)s cannot be built in unexplored area");
    109116            return result; // Fail
    110117        }
    111118    }
    112119
    113120    // Check obstructions and terrain passability
    114     var passClassName = "";
     121    let passClassName = "";
    115122    switch (this.template.PlacementType)
    116123    {
    117124    case "shore":
    118125        passClassName = "building-shore";
    119126        break;
    BuildRestrictions.prototype.CheckPlaceme  
    127134    case "land":
    128135    default:
    129136        passClassName = "building-land";
    130137    }
    131138
    132     var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     139    let cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
    133140    if (!cmpObstruction)
    134141        return result; // Fail
    135142
    136 
    137     if (this.template.Category == "Wall")
    138     {
    139143        // for walls, only test the center point
     144    if (this.template.Category == "Wall")
    140145        var ret = cmpObstruction.CheckFoundation(passClassName, true);
    141     }
    142146    else
    143     {
    144147        var ret = cmpObstruction.CheckFoundation(passClassName, false);
    145     }
    146148
    147149    if (ret != "success")
    148150    {
    149151        switch (ret)
    150152        {
    151153        case "fail_error":
    152154        case "fail_no_obstruction":
    153155            error("CheckPlacement: Error returned from CheckFoundation");
    154156            break;
     157
    155158        case "fail_obstructs_foundation":
    156159            result.message = markForTranslation("%(name)s cannot be built on another building or resource");
    157160            break;
     161
    158162        case "fail_terrain_class":
    159163            // TODO: be more specific and/or list valid terrain?
    160164            result.message = markForTranslation("%(name)s cannot be built on invalid terrain");
    161165        }
    162166        return result; // Fail
    163167    }
    164168
    165169    // Check territory restrictions
    166     var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
    167     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    168     var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
    169     if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld()))
     170    let cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
     171    let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     172    let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     173    if (!cmpTerritoryManager || !cmpPlayer || !cmpPosition || !cmpPosition.IsInWorld())
    170174        return result;  // Fail
    171175
    172     var pos = cmpPosition.GetPosition2D();
    173     var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
    174     var isConnected = !cmpTerritoryManager.IsTerritoryBlinking(pos.x, pos.y);
    175     var isOwn = tileOwner == cmpPlayer.GetPlayerID();
    176     var isMutualAlly = cmpPlayer.IsExclusiveMutualAlly(tileOwner);
    177     var isNeutral = tileOwner == 0;
     176    let pos = cmpPosition.GetPosition2D();
     177    let tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
     178    let isConnected = !cmpTerritoryManager.IsTerritoryBlinking(pos.x, pos.y);
    178179
    179     var invalidTerritory = "";
    180     if (isOwn)
     180    let invalidTerritory = "";
     181    if (tileOwner == cmpPlayer.GetPlayerID()) // Own territory
    181182    {
    182183        if (!this.HasTerritory("own"))
    183184            // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
    184185            invalidTerritory = markForTranslationWithContext("Territory type", "own");
    185186        else if (!isConnected && !this.HasTerritory("neutral"))
    186187            // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
    187188            invalidTerritory = markForTranslationWithContext("Territory type", "unconnected own");
    188189    }
    189     else if (isMutualAlly)
     190    else if (cmpPlayer.IsExclusiveMutualAlly(tileOwner))
    190191    {
    191192        if (!this.HasTerritory("ally"))
    192193            // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
    193194            invalidTerritory = markForTranslationWithContext("Territory type", "allied");
    194195        else if (!isConnected && !this.HasTerritory("neutral"))
    195196            // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
    196197            invalidTerritory = markForTranslationWithContext("Territory type", "unconnected allied");
    197198    }
    198     else if (isNeutral)
     199    else if (tileOwner == 0) // Neutral territory
    199200    {
    200201        if (!this.HasTerritory("neutral"))
    201202            // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
    202203            invalidTerritory = markForTranslationWithContext("Territory type", "neutral");
    203204    }
    BuildRestrictions.prototype.CheckPlaceme  
    225226    {
    226227        // TODO: Probably should check unit passability classes here, to determine if:
    227228        //      1. ships can be spawned "nearby"
    228229        //      2. builders can pass the terrain where the dock is placed (don't worry about paths)
    229230        //  so it's correct even if the criteria changes for these units
    230         var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
     231        let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
    231232        if (!cmpFootprint)
    232233            return result;  // Fail
    233234
    234235        // Get building's footprint
    235         var shape = cmpFootprint.GetShape();
    236         var halfSize = 0;
     236        let shape = cmpFootprint.GetShape();
     237        let halfSize = 0;
    237238        if (shape.type == "square")
    238             halfSize = shape.depth/2;
     239            halfSize = shape.depth / 2;
    239240        else if (shape.type == "circle")
    240241            halfSize = shape.radius;
    241242
    242         var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
    243         var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
     243        let cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     244        let cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
    244245        if (!cmpTerrain || !cmpWaterManager)
    245246            return result;  // Fail
    246247
    247         var ang = cmpPosition.GetRotation().y;
    248         var sz = halfSize * Math.sin(ang);
    249         var cz = halfSize * Math.cos(ang);
     248        let ang = cmpPosition.GetRotation().y;
     249        let sz = halfSize * Math.sin(ang);
     250        let cz = halfSize * Math.cos(ang);
    250251        if ((cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz) - cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz)) < 1.0 // front
    251252            || (cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz) - cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz)) > 2.0) // back
    252253        {
    253254            result.message = markForTranslation("%(name)s must be built on a valid shoreline");
    254255            return result;  // Fail
    255256        }
    256257    }
    257258
    258259    // Check distance restriction
    259     if (this.template.Distance)
     260    if (this.template.Density)
    260261    {
    261         var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    262         var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
    263         var cat = this.template.Distance.FromClass;
     262        let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     263        let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     264        let cat = this.template.Density.FromClass;
     265        let dist = +this.template.Density.Distance
    264266
    265         var filter = function(id)
     267        let filter = function(id)
    266268        {
    267             var cmpIdentity = Engine.QueryInterface(id, IID_Identity);
     269            let cmpIdentity = Engine.QueryInterface(id, IID_Identity);
    268270            return cmpIdentity.GetClassesList().indexOf(cat) > -1;
    269271        };
    270272       
    271         if (this.template.Distance.MinDistance)
     273        let nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
     274
     275        if (nearEnts.length > this.maxEntity)
    272276        {
    273             var dist = +this.template.Distance.MinDistance;
    274             var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
    275             if (nearEnts.length)
    276             {
    277                 var result = markForPluralTranslation(
    278                     "%(name)s too close to a %(category)s, must be at least %(distance)s meter away",
    279                     "%(name)s too close to a %(category)s, must be at least %(distance)s meters away",
    280                     +this.template.Distance.MinDistance);
    281 
    282                 result.success = false;
    283                 result.translateMessage = true;
    284                 result.parameters = {
    285                     "name": name,
    286                     "category": cat,
    287                     "distance": this.template.Distance.MinDistance
    288                 };
    289                 result.translateParameters = ["name", "category"];
    290                 return result;  // Fail
    291             }
     277            let result = markForPluralTranslation(
     278                "%(name)s too close to a %(category)s, must be at least %(distance)s meter away",
     279                "%(name)s too close to a %(category)s, must be at least %(distance)s meters away",
     280                +this.template.Density.Distance);
     281
     282            result.success = false;
     283            result.translateMessage = true;
     284            result.parameters = {
     285                "name": name,
     286                "category": cat,
     287                "distance": this.template.Density.Distance
     288            };
     289            result.translateParameters = ["name", "category"];
     290            return result;
    292291        }
    293         if (this.template.Distance.MaxDistance)
     292
     293        if (nearEnts.length < this.minEntity)
    294294        {
    295             var dist = +this.template.Distance.MaxDistance;
    296             var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter);
    297             if (!nearEnts.length)
    298             {
    299                 var result = markForPluralTranslation(
    300                     "%(name)s too far from a %(category)s, must be within %(distance)s meter",
    301                     "%(name)s too far from a %(category)s, must be within %(distance)s meters",
    302                     +this.template.Distance.MinDistance);
    303 
    304                 result.success = false;
    305                 result.translateMessage = true;
    306                 result.parameters = {
    307                     "name": name,
    308                     "category": cat,
    309                     "distance": this.template.Distance.MaxDistance
    310                 };
    311                 result.translateParameters = ["name", "category"];
    312                 return result;  // Fail
    313             }
     295            let result = markForPluralTranslation(
     296                "%(name)s too far from a %(category)s, must be within %(distance)s meter",
     297                "%(name)s too far from a %(category)s, must be within %(distance)s meters",
     298                +this.template.Density.Distance);
     299
     300            result.success = false;
     301            result.translateMessage = true;
     302            result.parameters = {
     303                "name": name,
     304                "category": cat,
     305                "distance": this.template.Density.Distance
     306            };
     307            result.translateParameters = ["name", "category"];
     308            return result;
    314309        }
    315310    }
    316311
    317312    // Success
    318313    result.success = true;
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    GuiInterface.prototype.SetWallPlacementP  
    12431243
    12441244    // --------------------------------------------------------------------------------
    12451245    // calculate wall placement and position preview entities
    12461246
    12471247    let result = {
     1248        "success": true,
    12481249        "pieces": [],
    12491250        "cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0 },
    12501251    };
    12511252
    12521253    let previewEntities = [];
    GuiInterface.prototype.SetWallPlacementP  
    14831484            {
    14841485                error("[SetWallPlacementPreview] cmpBuildRestrictions not defined for preview entity of template '" + tpl + "'");
    14851486                continue;
    14861487            }
    14871488
    1488             // TODO: Handle results of CheckPlacement
    1489             validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
     1489            let placementResult = cmpBuildRestrictions.CheckPlacement()
     1490            if (!placementResult.success)
     1491            {
     1492                result.success = false;
     1493                result.message = placementResult.message;
     1494                result.parameters = placementResult.parameters;
     1495                result.translateMessage = placementResult.translateMessage;
     1496                result.translateParameters = placementResult.translateParameters;
     1497            }
     1498
     1499            validPlacement = (cmpBuildRestrictions && placementResult.success);
    14901500
    14911501            // If a wall piece has two control groups, it's likely a segment that spans
    14921502            // between two existing towers. To avoid placing a duplicate wall segment,
    14931503            // check for collisions with entities that share both control groups.
    14941504            if (validPlacement && entInfo.controlGroups && entInfo.controlGroups.length > 1)
    GuiInterface.prototype.SetWallPlacementP  
    15491559    }
    15501560
    15511561    // If any were entities required to build the wall, but none of them could be validly positioned, return failure
    15521562    // (see method-level documentation).
    15531563    if (numRequiredPieces > 0 && result.pieces.length == 0)
    1554         return false;
     1564        return result;
    15551565
    15561566    if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
    15571567        result.startSnappedEnt = start.snappedEnt;
    15581568
    15591569    // We should only return that we snapped to an entity if all pieces up until that entity can be validly constructed,
  • binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

     
    2727    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2828  </BuildingAI>
    2929  <BuildRestrictions>
    3030    <Territory>own neutral</Territory>
    3131    <Category>CivilCentre</Category>
    32     <Distance>
     32    <Density>
    3333      <FromClass>CivilCentre</FromClass>
    34       <MinDistance>200</MinDistance>
    35     </Distance>
     34      <Distance>200</Distance>
     35      <MaxEntity>0</MaxEntity>
     36    </Density>
    3637  </BuildRestrictions>
    3738  <Capturable>
    3839    <CapturePoints>2500</CapturePoints>
    3940    <RegenRate>5.0</RegenRate>
    4041  </Capturable>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml

     
    2020    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2121    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>
    2222  </BuildingAI>
    2323  <BuildRestrictions>
    2424    <Category>DefenseTower</Category>
    25     <Distance>
     25    <Density>
    2626      <FromClass>DefenseTower</FromClass>
    27       <MinDistance>60</MinDistance>
    28     </Distance>
     27      <Distance>60</Distance>
     28      <MaxEntity>0</MaxEntity>
     29    </Density>
    2930  </BuildRestrictions>
    3031  <Cost>
    3132    <BuildTime>150</BuildTime>
    3233    <Resources>
    3334      <wood>100</wood>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml

     
    1919    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2020    <GarrisonArrowClasses>Infantry</GarrisonArrowClasses>
    2121  </BuildingAI>
    2222  <BuildRestrictions>
    2323    <PlacementType>land-shore</PlacementType>
    24     <Category>Wall</Category>
     24    <Category>WallTower</Category>
     25    <Density>
     26      <FromClass>WallTower</FromClass>
     27      <Distance>40</Distance>
     28      <MaxEntity>2</MaxEntity>
     29    </Density>
    2530  </BuildRestrictions>
    2631  <Capturable disable=""/>
    2732  <Repairable>
    2833    <RepairTimeRatio>4.5</RepairTimeRatio>
    2934  </Repairable>
     
    5055    <SpawnEntityOnDeath>rubble/rubble_stone_wall_tower</SpawnEntityOnDeath>
    5156  </Health>
    5257  <Identity>
    5358    <GenericName>Wall Turret</GenericName>
    5459    <Tooltip>Shoots arrows. Garrison to defend a city wall against attackers.</Tooltip>
    55     <Classes datatype="tokens">-ConquestCritical StoneWall Tower</Classes>
     60    <Classes datatype="tokens">-ConquestCritical StoneWall Tower WallTower</Classes>
    5661    <Icon>structures/tower.png</Icon>
    5762    <RequiredTechnology>phase_town</RequiredTechnology>
    5863  </Identity>
    5964  <Loot>
    6065    <xp>100</xp>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wooden_tower.xml

     
    1414      <MinRange>4.0</MinRange>
    1515    </Ranged>
    1616  </Attack>
    1717  <BuildRestrictions>
    1818    <Category>DefenseTower</Category>
    19     <Distance>
     19    <Density>
    2020      <FromClass>DefenseTower</FromClass>
    21       <MinDistance>40</MinDistance>
    22     </Distance>
     21      <Distance>40</Distance>
     22      <MaxEntity>0</MaxEntity>
     23    </Density>
    2324  </BuildRestrictions>
    2425  <Cost>
    2526    <BuildTime>40</BuildTime>
    2627    <Resources>
    2728      <wood>100</wood>
  • binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml

     
    2424    <GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
    2525    <GarrisonArrowClasses>Infantry Ranged</GarrisonArrowClasses>
    2626  </BuildingAI>
    2727  <BuildRestrictions>
    2828    <Category>Fortress</Category>
    29     <Distance>
     29    <Density>
    3030      <FromClass>Fortress</FromClass>
    31       <MinDistance>80</MinDistance>
    32     </Distance>
     31      <Distance>80</Distance>
     32      <MaxEntity>0</MaxEntity>
     33    </Density>
    3334  </BuildRestrictions>
    3435  <Capturable>
    3536    <CapturePoints>4000</CapturePoints>
    3637    <RegenRate>10.0</RegenRate>
    3738  </Capturable>