Ticket #3811: t3811_buildingDensity_4.diff

File t3811_buildingDensity_4.diff, 32.9 KB (added by bb, 8 years ago)

added ai support for the changed component, still a TODO in it, not sure if that is vital (as it won't happen too often). The new case in map-module.js changes in L144.

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

     
    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
  • binaries/data/mods/public/gui/session/input.js

     
    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,
     
    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
  • binaries/data/mods/public/simulation/ai/common-api/entity.js

     
    485485        return +this.get("Cost/BuildTime");
    486486    },
    487487
    488     buildDistance: function() {
    489         if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Distance"))
     488    buildDensity: function() {
     489        if (!this.get("BuildRestrictions") || !this.get("BuildRestrictions/Density"))
    490490            return undefined;
    491         return this.get("BuildRestrictions/Distance");
     491        return this.get("BuildRestrictions/Density");
    492492    },
    493493
    494494    buildPlacementType: function() {
  • binaries/data/mods/public/simulation/ai/common-api/map-module.js

     
    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;
    125125                    if (this.map[w] + strength < 0)
    126126                        this.map[w] = 0;
    127127                    else if (this.map[w] + strength > this.maxVal)
     
    132132            }
    133133        }
    134134    }
     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;
     147                    if (this.map[w] + strength < 0)
     148                        this.map[w] = 0;
     149                    else if (this.map[w] + strength > this.maxVal)
     150                        this.map[w] = this.maxVal;  // avoids overflow.
     151                    else
     152                        this.map[w] += strength;
     153                }
     154            }
     155        }
     156    }
    135157};
    136158
    137159m.Map.prototype.multiplyInfluence = function(cx, cy, maxDist, strength, type)
  • binaries/data/mods/public/simulation/ai/petra/mapModule.js

     
    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                }
    104111            }
     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                }
     124            }
    105125        }
    106126    }
    107127
  • binaries/data/mods/public/simulation/components/BuildRestrictions.js

     
    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.'>" +
     
    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>";
     
    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/**
     
    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 *  {
     
    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": {
     
    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
     
    111118    }
    112119
    113120    // Check obstructions and terrain passability
    114     var passClassName = "";
     121    let passClassName = "";
    115122    switch (this.template.PlacementType)
    116123    {
    117124    case "shore":
     
    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 
     143        // for walls, only test the center point
    137144    if (this.template.Category == "Wall")
    138     {
    139         // for walls, only test the center point
    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    {
     
    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");
     
    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.".
     
    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.".
     
    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.".
     
    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        {
     
    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);
     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);
    281281
    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             }
     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;  // Fail
    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);
     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);
    303299
    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             }
     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;  // Fail
    314309        }
    315310    }
    316311
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    988988 */
    989989GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
    990990{
    991     let result = {
    992         "success": false,
    993         "message": "",
    994         "parameters": {},
    995         "translateMessage": false,
    996         "translateParameters": [],
    997     };
     991    let result = {
     992        "success": false,
     993        "message": "",
     994        "parameters": {},
     995        "translateMessage": false,
     996        "translateParameters": [],
     997    };
    998998
    999999    // See if we're changing template
    10001000    if (!this.placementEntity || this.placementEntity[0] != cmd.template)
     
    12441244    // calculate wall placement and position preview entities
    12451245
    12461246    let result = {
     1247        "success": true,
    12471248        "pieces": [],
    12481249        "cost": { "food": 0, "wood": 0, "stone": 0, "metal": 0, "population": 0, "populationBonus": 0, "time": 0 },
    12491250    };
     
    14841485                continue;
    14851486            }
    14861487
    1487             // TODO: Handle results of CheckPlacement
    1488             validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement().success);
     1488            let placementResult = cmpBuildRestrictions.CheckPlacement()
     1489            if (!placementResult.success)
     1490            {
     1491                result.success = false;
     1492                result.message = placementResult.message;
     1493                result.parameters = placementResult.parameters;
     1494                result.translateMessage = placementResult.translateMessage;
     1495                result.translateParameters = placementResult.translateParameters;
     1496            }
    14891497
     1498            validPlacement = (cmpBuildRestrictions && placementResult.success);
     1499
    14901500            // If a wall piece has two control groups, it's likely a segment that spans
    14911501            // between two existing towers. To avoid placing a duplicate wall segment,
    14921502            // check for collisions with entities that share both control groups.
     
    15501560    // If any were entities required to build the wall, but none of them could be validly positioned, return failure
    15511561    // (see method-level documentation).
    15521562    if (numRequiredPieces > 0 && result.pieces.length == 0)
    1553         return false;
     1563        return result;
    15541564
    15551565    if (start.snappedEnt && start.snappedEnt != INVALID_ENTITY)
    15561566        result.startSnappedEnt = start.snappedEnt;
  • binaries/data/mods/public/simulation/helpers/Walls.js

     
    2020 */
    2121function GetWallPlacement(placementData, wallSet, start, end)
    2222{
    23     var result = [];
     23    let result = [];
    2424   
    25     var candidateSegments = [
    26         {"template": wallSet.templates.long,   "len": placementData[wallSet.templates.long].templateData.wallPiece.length},
    27         {"template": wallSet.templates.medium, "len": placementData[wallSet.templates.medium].templateData.wallPiece.length},
    28         {"template": wallSet.templates.short,  "len": placementData[wallSet.templates.short].templateData.wallPiece.length},
     25    let candidateSegments = [
     26        { "template": wallSet.templates.long,   "len": placementData[wallSet.templates.long].templateData.wallPiece.length },
     27        { "template": wallSet.templates.medium, "len": placementData[wallSet.templates.medium].templateData.wallPiece.length },
     28        { "template": wallSet.templates.short,  "len": placementData[wallSet.templates.short].templateData.wallPiece.length },
    2929    ];
    3030   
    31     var towerWidth = placementData[wallSet.templates.tower].templateData.wallPiece.length;
     31    let towerWidth = placementData[wallSet.templates.tower].templateData.wallPiece.length;
    3232   
    33     var dir = {"x": end.pos.x - start.pos.x, "z": end.pos.z - start.pos.z};
    34     var len = Math.sqrt(dir.x * dir.x + dir.z * dir.z);
     33    let dir = { "x": end.pos.x - start.pos.x, "z": end.pos.z - start.pos.z };
     34    let len = Math.sqrt(dir.x * dir.x + dir.z * dir.z);
    3535   
    3636    // we'll need room for at least our starting and ending towers to fit next to eachother
    3737    if (len <= towerWidth)
    3838        return result;
    3939   
    40     var placement = GetWallSegmentsRec(len, candidateSegments, wallSet.minTowerOverlap, wallSet.maxTowerOverlap, towerWidth, 0, []);
     40    let placement = GetWallSegmentsRec(len, candidateSegments, wallSet.minTowerOverlap, wallSet.maxTowerOverlap, towerWidth, 0, []);
    4141   
    4242    // TODO: make sure intermediate towers are spaced out far enough for their obstructions to not overlap, implying that
    4343    // tower's wallpiece lengths should be > their obstruction width, which is undesirable because it prevents towers with
     
    4444    // wide bases
    4545    if (placement)
    4646    {
    47         var placedEntities = placement.segments; // list of chosen candidate segments
    48         var r = placement.r; // remaining distance to target without towers (must be <= (N-1) * towerWidth)
    49         var s = r / (2 * placedEntities.length); // spacing
     47        let placedEntities = placement.segments; // list of chosen candidate segments
     48        let r = placement.r; // remaining distance to target without towers (must be <= (N-1) * towerWidth)
     49        let s = r / (2 * placedEntities.length); // spacing
    5050       
    51         var dirNormalized = {"x": dir.x / len, "z": dir.z / len};
    52         var angle = -Math.atan2(dir.z, dir.x);    // angle of this wall segment (relative to world-space X/Z axes)
     51        let dirNormalized = { "x": dir.x / len, "z": dir.z / len };
     52        let angle = -Math.atan2(dir.z, dir.x);    // angle of this wall segment (relative to world-space X/Z axes)
    5353       
    54         var progress = 0;
    55         for (var i = 0; i < placedEntities.length; i++)
     54        let progress = 0;
     55        for (let i = 0; i < placedEntities.length; i++)
    5656        {
    57             var placedEntity = placedEntities[i];
    58             var targetX = start.pos.x + (progress + s + placedEntity.len/2) * dirNormalized.x;
    59             var targetZ = start.pos.z + (progress + s + placedEntity.len/2) * dirNormalized.z;
     57            let placedEntity = placedEntities[i];
     58            let targetX = start.pos.x + (progress + s + placedEntity.len/2) * dirNormalized.x;
     59            let targetZ = start.pos.z + (progress + s + placedEntity.len/2) * dirNormalized.z;
    6060           
    6161            result.push({
    6262                "template": placedEntity.template,
    63                 "pos": {"x": targetX, "z": targetZ},
     63                "pos": { "x": targetX, "z": targetZ },
    6464                "angle": angle,
    6565            });
    6666           
    6767            if (i < placedEntities.length - 1)
    6868            {
    69                 var towerX = start.pos.x + (progress + placedEntity.len + 2*s) * dirNormalized.x;
    70                 var towerZ = start.pos.z + (progress + placedEntity.len + 2*s) * dirNormalized.z;
     69                let towerX = start.pos.x + (progress + placedEntity.len + 2*s) * dirNormalized.x;
     70                let towerZ = start.pos.z + (progress + placedEntity.len + 2*s) * dirNormalized.z;
    7171               
    72                 result.push({
    73                     "template": wallSet.templates.tower,
    74                     "pos": {"x": towerX, "z": towerZ},
    75                     "angle": angle,
    76                 });
     72                    result.push({
     73                        "template": wallSet.templates.tower,
     74                        "pos": { "x": towerX, "z": towerZ },
     75                        "angle": angle,
     76                    });
    7777            }
    7878           
    7979            progress += placedEntity.len + 2*s;
     
    160160    // backtrack and try a wall segment of the next length instead. Note that we should prefer to use the long segments first since
    161161    // they can be replaced by gates.
    162162   
    163     for each (var candSegment in candidateSegments)
     163    for (let candSegment of candidateSegments)
    164164    {
    165165        segments.push(candSegment);
    166166       
    167         var newDistSoFar = distSoFar + candSegment.len;
    168         var r = d - newDistSoFar;
     167        let newDistSoFar = distSoFar + candSegment.len;
     168        let r = d - newDistSoFar;
    169169       
    170         var rLowerBound = (1 - 2 * maxOverlap) * segments.length * t;
    171         var rUpperBound = (1 - 2 * minOverlap) * segments.length * t;
     170        let rLowerBound = (1 - 2 * maxOverlap) * segments.length * t;
     171        let rUpperBound = (1 - 2 * minOverlap) * segments.length * t;
    172172       
    173173        if (r < rLowerBound)
    174174        {
     
    179179        }
    180180        else if (r > rUpperBound)
    181181        {
    182             var recursiveResult = GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, newDistSoFar, segments);
     182            let recursiveResult = GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, newDistSoFar, segments);
    183183            if (!recursiveResult)
    184184            {
    185185                // recursive search with this piece yielded no results, pop it and try the next one
     
    192192        else
    193193        {
    194194            // found a placement
    195             return {"segments": segments, "r": r};
     195            return { "segments": segments, "r": r };
    196196        }
    197197    }
    198198   
  • binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml

     
    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>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml

     
    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>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml

     
    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>
     
    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>
  • binaries/data/mods/public/simulation/templates/template_structure_defense_wooden_tower.xml

     
    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>
  • binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml

     
    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>