LCOV - code coverage report
Current view: top level - simulation/components - BuildRestrictions.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 117 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 7 0.0 %

          Line data    Source code
       1             : function BuildRestrictions() {}
       2             : 
       3           0 : BuildRestrictions.prototype.Schema =
       4             :         "<a:help>Specifies building placement restrictions as they relate to terrain, territories, and distance.</a:help>" +
       5             :         "<a:example>" +
       6             :                 "<BuildRestrictions>" +
       7             :                         "<PlacementType>land</PlacementType>" +
       8             :                         "<Territory>own</Territory>" +
       9             :                         "<Category>Structure</Category>" +
      10             :                         "<Distance>" +
      11             :                                 "<FromClass>CivilCentre</FromClass>" +
      12             :                                 "<MaxDistance>40</MaxDistance>" +
      13             :                         "</Distance>" +
      14             :                 "</BuildRestrictions>" +
      15             :         "</a:example>" +
      16             :         "<element name='PlacementType' a:help='Specifies the terrain type restriction for this building.'>" +
      17             :                 "<choice>" +
      18             :                         "<value>land</value>" +
      19             :                         "<value>shore</value>" +
      20             :                         "<value>land-shore</value>"+
      21             :                 "</choice>" +
      22             :         "</element>" +
      23             :         "<element name='Territory' a:help='Specifies territory type restrictions for this building.'>" +
      24             :                 "<list>" +
      25             :                         "<oneOrMore>" +
      26             :                                 "<choice>" +
      27             :                                         "<value>own</value>" +
      28             :                                         "<value>ally</value>" +
      29             :                                         "<value>neutral</value>" +
      30             :                                         "<value>enemy</value>" +
      31             :                                 "</choice>" +
      32             :                         "</oneOrMore>" +
      33             :                 "</list>" +
      34             :         "</element>" +
      35             :         "<element name='Category' a:help='Specifies the category of this building, for satisfying special constraints. Choices include: Apadana, CivilCentre, Council, Embassy, Fortress, Gladiator, Hall, Hero, Juggernaut, Library, Lighthouse, Monument, Pillar, PyramidLarge, PyramidSmall, Stoa, TempleOfAmun, Theater, Tower, UniqueBuilding, WarDog, Wonder'>" +
      36             :                 "<text/>" +
      37             :         "</element>" +
      38             :         "<optional>" +
      39             :                 "<element name='MatchLimit' a:help='Specifies how many times this entity can be created during a match.'>" +
      40             :                         "<data type='positiveInteger'/>" +
      41             :                 "</element>" +
      42             :         "</optional>" +
      43             :         "<optional>" +
      44             :                 "<element name='Distance' a:help='Specifies distance restrictions on this building, relative to buildings from the given category.'>" +
      45             :                         "<interleave>" +
      46             :                                 "<element name='FromClass'>" +
      47             :                                         "<text/>" +
      48             :                                 "</element>" +
      49             :                                 "<optional><element name='MinDistance'><data type='positiveInteger'/></element></optional>" +
      50             :                                 "<optional><element name='MaxDistance'><data type='positiveInteger'/></element></optional>" +
      51             :                         "</interleave>" +
      52             :                 "</element>" +
      53             :         "</optional>";
      54             : 
      55           0 : BuildRestrictions.prototype.Init = function()
      56             : {
      57             : };
      58             : 
      59             : /**
      60             :  * Checks whether building placement is valid
      61             :  *      1. Visibility is not hidden (may be fogged or visible)
      62             :  *      2. Check foundation
      63             :  *              a. Doesn't obstruct foundation-blocking entities
      64             :  *              b. On valid terrain, based on passability class
      65             :  *      3. Territory type is allowed (see note below)
      66             :  *      4. Dock is on shoreline and facing into water
      67             :  *      5. Distance constraints satisfied
      68             :  *
      69             :  * Returns result object:
      70             :  *      {
      71             :  *              "success":             true iff the placement is valid, else false
      72             :  *              "message":             message to display in UI for invalid placement, else ""
      73             :  *              "parameters":          parameters to use in the GUI message
      74             :  *              "translateMessage":    always true
      75             :  *              "translateParameters": list of parameters to translate
      76             :  *              "pluralMessage":       we might return a plural translation instead (optional)
      77             :  *              "pluralCount":         plural translation argument (optional)
      78             :  *  }
      79             :  *
      80             :  * Note: The entity which is used to check this should be a preview entity
      81             :  *  (template name should be "preview|"+templateName), as otherwise territory
      82             :  *  checks for buildings with territory influence will not work as expected.
      83             :  */
      84           0 : BuildRestrictions.prototype.CheckPlacement = function()
      85             : {
      86           0 :         var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
      87           0 :         var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
      88             : 
      89           0 :         var result = {
      90             :                 "success": false,
      91             :                 "message": markForTranslation("%(name)s cannot be built due to unknown error"),
      92             :                 "parameters": {
      93             :                         "name": name,
      94             :                 },
      95             :                 "translateMessage": true,
      96             :                 "translateParameters": ["name"],
      97             :         };
      98             : 
      99           0 :         var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
     100           0 :         if (!cmpPlayer)
     101           0 :                 return result; // Fail
     102             : 
     103             :         // TODO: AI has no visibility info
     104           0 :         if (!cmpPlayer.IsAI())
     105             :         {
     106             :                 // Check whether it's in a visible or fogged region
     107           0 :                 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     108           0 :                 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     109           0 :                 if (!cmpRangeManager || !cmpOwnership)
     110           0 :                         return result; // Fail
     111             : 
     112           0 :                 var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) != "hidden");
     113           0 :                 if (!explored)
     114             :                 {
     115           0 :                         result.message = markForTranslation("%(name)s cannot be built in unexplored area");
     116           0 :                         return result; // Fail
     117             :                 }
     118             :         }
     119             : 
     120             :         // Check obstructions and terrain passability
     121           0 :         var passClassName = "";
     122           0 :         switch (this.template.PlacementType)
     123             :         {
     124             :         case "shore":
     125           0 :                 passClassName = "building-shore";
     126           0 :                 break;
     127             : 
     128             :         case "land-shore":
     129             :                 // 'default-terrain-only' is everywhere a normal unit can go, ignoring
     130             :                 // obstructions (i.e. on passable land, and not too deep in the water)
     131           0 :                 passClassName = "default-terrain-only";
     132           0 :                 break;
     133             : 
     134             :         case "land":
     135             :         default:
     136           0 :                 passClassName = "building-land";
     137             :         }
     138             : 
     139           0 :         var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
     140           0 :         if (!cmpObstruction)
     141           0 :                 return result; // Fail
     142             : 
     143             : 
     144           0 :         if (this.template.Category == "Wall")
     145             :         {
     146             :                 // for walls, only test the center point
     147           0 :                 var ret = cmpObstruction.CheckFoundation(passClassName, true);
     148             :         }
     149             :         else
     150             :         {
     151           0 :                 var ret = cmpObstruction.CheckFoundation(passClassName, false);
     152             :         }
     153             : 
     154           0 :         if (ret != "success")
     155             :         {
     156           0 :                 switch (ret)
     157             :                 {
     158             :                 case "fail_error":
     159             :                 case "fail_no_obstruction":
     160           0 :                         error("CheckPlacement: Error returned from CheckFoundation");
     161           0 :                         break;
     162             :                 case "fail_obstructs_foundation":
     163           0 :                         result.message = markForTranslation("%(name)s cannot be built on another building or resource");
     164           0 :                         break;
     165             :                 case "fail_terrain_class":
     166             :                         // TODO: be more specific and/or list valid terrain?
     167           0 :                         result.message = markForTranslation("%(name)s cannot be built on invalid terrain");
     168             :                 }
     169           0 :                 return result; // Fail
     170             :         }
     171             : 
     172             :         // Check territory restrictions
     173           0 :         var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager);
     174           0 :         var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     175           0 :         if (!cmpTerritoryManager || !cmpPosition || !cmpPosition.IsInWorld())
     176           0 :                 return result;  // Fail
     177             : 
     178           0 :         var pos = cmpPosition.GetPosition2D();
     179           0 :         var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
     180           0 :         var isConnected = !cmpTerritoryManager.IsTerritoryBlinking(pos.x, pos.y);
     181           0 :         var isOwn = tileOwner == cmpPlayer.GetPlayerID();
     182           0 :         var isMutualAlly = cmpPlayer.IsExclusiveMutualAlly(tileOwner);
     183           0 :         var isNeutral = tileOwner == 0;
     184             : 
     185           0 :         var invalidTerritory = "";
     186           0 :         if (isOwn)
     187             :         {
     188           0 :                 if (!this.HasTerritory("own"))
     189             :                         // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
     190           0 :                         invalidTerritory = markForTranslationWithContext("Territory type", "own");
     191           0 :                 else if (!isConnected && !this.HasTerritory("neutral"))
     192             :                         // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
     193           0 :                         invalidTerritory = markForTranslationWithContext("Territory type", "unconnected own");
     194             :         }
     195           0 :         else if (isMutualAlly)
     196             :         {
     197           0 :                 if (!this.HasTerritory("ally"))
     198             :                         // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
     199           0 :                         invalidTerritory = markForTranslationWithContext("Territory type", "allied");
     200           0 :                 else if (!isConnected && !this.HasTerritory("neutral"))
     201             :                         // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
     202           0 :                         invalidTerritory = markForTranslationWithContext("Territory type", "unconnected allied");
     203             :         }
     204           0 :         else if (isNeutral)
     205             :         {
     206           0 :                 if (!this.HasTerritory("neutral"))
     207             :                         // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
     208           0 :                         invalidTerritory = markForTranslationWithContext("Territory type", "neutral");
     209             :         }
     210             :         else
     211             :         {
     212             :                 // consider everything else enemy territory
     213           0 :                 if (!this.HasTerritory("enemy"))
     214             :                         // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
     215           0 :                         invalidTerritory = markForTranslationWithContext("Territory type", "enemy");
     216             :         }
     217             : 
     218           0 :         if (invalidTerritory)
     219             :         {
     220           0 :                 result.message = markForTranslation("%(name)s cannot be built in %(territoryType)s territory. Valid territories: %(validTerritories)s");
     221           0 :                 result.translateParameters.push("territoryType");
     222           0 :                 result.translateParameters.push("validTerritories");
     223           0 :                 result.parameters.territoryType = { "context": "Territory type", "_string": invalidTerritory };
     224             :                 // gui code will join this array to a string
     225           0 :                 result.parameters.validTerritories = { "context": "Territory type list", "list": this.GetTerritories() };
     226           0 :                 return result;  // Fail
     227             :         }
     228             : 
     229             :         // Check special requirements
     230           0 :         if (this.template.PlacementType == "shore")
     231             :         {
     232           0 :                 if (!cmpObstruction.CheckShorePlacement())
     233             :                 {
     234           0 :                         result.message = markForTranslation("%(name)s must be built on a valid shoreline");
     235           0 :                         return result;  // Fail
     236             :                 }
     237             :         }
     238             : 
     239           0 :         let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
     240             : 
     241           0 :         let templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
     242           0 :         let template = cmpTemplateManager.GetTemplate(removeFiltersFromTemplateName(templateName));
     243             : 
     244             :         // Check distance restriction
     245           0 :         if (this.template.Distance)
     246             :         {
     247           0 :                 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     248           0 :                 var cat = this.template.Distance.FromClass;
     249             : 
     250           0 :                 var filter = function(id)
     251             :                 {
     252           0 :                         var cmpIdentity = Engine.QueryInterface(id, IID_Identity);
     253           0 :                         return cmpIdentity.GetClassesList().indexOf(cat) > -1;
     254             :                 };
     255             : 
     256           0 :                 if (this.template.Distance.MinDistance !== undefined)
     257             :                 {
     258           0 :                         let minDistance = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MinDistance", +this.template.Distance.MinDistance, cmpPlayer.GetPlayerID(), template);
     259           0 :                         if (cmpRangeManager.ExecuteQuery(this.entity, 0, minDistance, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions, false).some(filter))
     260             :                         {
     261           0 :                                 let result = markForPluralTranslation(
     262             :                                         "%(name)s too close to a %(category)s, must be at least %(distance)s meter away",
     263             :                                         "%(name)s too close to a %(category)s, must be at least %(distance)s meters away",
     264             :                                         minDistance);
     265             : 
     266           0 :                                 result.success = false;
     267           0 :                                 result.translateMessage = true;
     268           0 :                                 result.parameters = {
     269             :                                         "name": name,
     270             :                                         "category": cat,
     271             :                                         "distance": minDistance
     272             :                                 };
     273           0 :                                 result.translateParameters = ["name", "category"];
     274           0 :                                 return result;  // Fail
     275             :                         }
     276             :                 }
     277           0 :                 if (this.template.Distance.MaxDistance !== undefined)
     278             :                 {
     279           0 :                         let maxDistance = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MaxDistance", +this.template.Distance.MaxDistance, cmpPlayer.GetPlayerID(), template);
     280           0 :                         if (!cmpRangeManager.ExecuteQuery(this.entity, 0, maxDistance, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions, false).some(filter))
     281             :                         {
     282           0 :                                 let result = markForPluralTranslation(
     283             :                                         "%(name)s too far from a %(category)s, must be within %(distance)s meter",
     284             :                                         "%(name)s too far from a %(category)s, must be within %(distance)s meters",
     285             :                                         maxDistance);
     286             : 
     287           0 :                                 result.success = false;
     288           0 :                                 result.translateMessage = true;
     289           0 :                                 result.parameters = {
     290             :                                         "name": name,
     291             :                                         "category": cat,
     292             :                                         "distance": maxDistance
     293             :                                 };
     294           0 :                                 result.translateParameters = ["name", "category"];
     295           0 :                                 return result;  // Fail
     296             :                         }
     297             :                 }
     298             :         }
     299             : 
     300             :         // Success
     301           0 :         result.success = true;
     302           0 :         result.message = "";
     303           0 :         return result;
     304             : };
     305             : 
     306           0 : BuildRestrictions.prototype.GetCategory = function()
     307             : {
     308           0 :         return this.template.Category;
     309             : };
     310             : 
     311           0 : BuildRestrictions.prototype.GetTerritories = function()
     312             : {
     313           0 :         return ApplyValueModificationsToEntity("BuildRestrictions/Territory", this.template.Territory, this.entity).split(/\s+/);
     314             : };
     315             : 
     316           0 : BuildRestrictions.prototype.HasTerritory = function(territory)
     317             : {
     318           0 :         return (this.GetTerritories().indexOf(territory) != -1);
     319             : };
     320             : 
     321             : // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
     322           0 : markForTranslationWithContext("Territory type list", "own");
     323             : // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
     324           0 : markForTranslationWithContext("Territory type list", "ally");
     325             : // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
     326           0 : markForTranslationWithContext("Territory type list", "neutral");
     327             : // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
     328           0 : markForTranslationWithContext("Territory type list", "enemy");
     329             : 
     330           0 : Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);

Generated by: LCOV version 1.14