Advertisement
Guest User

Untitled

a guest
Mar 29th, 2017
56
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Diff 13.73 KB | None | 0 0
  1. function BuildRestrictions() {}
  2.  
  3. 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>Special</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: CivilCentre, House, DefenseTower, Farmstead, Market, Barracks, Dock, Fortress, Field, Temple, Wall, Fence, Storehouse, Stoa, Resource, Special, Wonder, Apadana, Embassy, Monument'>" +
  36.         "<text/>" +
  37.     "</element>" +
  38.     "<optional>" +
  39.         "<element name='Distance' a:help='Specifies distance restrictions on this building, relative to buildings from the given category.'>" +
  40.             "<interleave>" +
  41.                 "<element name='FromClass'>" +
  42.                     "<text/>" +
  43.                 "</element>" +
  44.                 "<optional><element name='MinDistance'><data type='positiveInteger'/></element></optional>" +
  45.                 "<optional><element name='MaxDistance'><data type='positiveInteger'/></element></optional>" +
  46.             "</interleave>" +
  47.         "</element>" +
  48.     "</optional>";
  49.  
  50. BuildRestrictions.prototype.Init = function()
  51. {
  52.     this.territories = this.template.Territory.split(/\s+/);
  53. };
  54.  
  55. /**
  56.  * Checks whether building placement is valid
  57.  *  1. Visibility is not hidden (may be fogged or visible)
  58.  *  2. Check foundation
  59.  *      a. Doesn't obstruct foundation-blocking entities
  60.  *      b. On valid terrain, based on passability class
  61.  *  3. Territory type is allowed (see note below)
  62.  *  4. Dock is on shoreline and facing into water
  63.  *  5. Distance constraints satisfied
  64.  *
  65.  * Returns result object:
  66.  *  {
  67.  *      "success":             true iff the placement is valid, else false
  68.  *      "message":             message to display in UI for invalid placement, else ""
  69.  *      "parameters":          parameters to use in the GUI message
  70.  *      "translateMessage":    always true
  71.  *      "translateParameters": list of parameters to translate
  72.  *      "pluralMessage":       we might return a plural translation instead (optional)
  73.  *      "pluralCount":         plural translation argument (optional)
  74.  *  }
  75.  *
  76.  * Note: The entity which is used to check this should be a preview entity
  77.  *  (template name should be "preview|"+templateName), as otherwise territory
  78.  *  checks for buildings with territory influence will not work as expected.
  79.  */
  80. BuildRestrictions.prototype.CheckPlacement = function()
  81. {
  82.     var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
  83.     var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building";
  84.  
  85.     var result = {
  86.         "success": false,
  87.         "message": markForTranslation("%(name)s cannot be built due to unknown error"),
  88.         "parameters": {
  89.             "name": name,
  90.         },
  91.         "translateMessage": true,
  92.         "translateParameters": ["name"],
  93.     };
  94.  
  95.     // TODO: AI has no visibility info
  96.     var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
  97.     if (!cmpPlayer.IsAI())
  98.     {
  99.         // 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);
  102.         if (!cmpRangeManager || !cmpOwnership)
  103.             return result; // Fail
  104.  
  105.         var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) != "hidden");
  106.         if (!explored)
  107.         {
  108.             result.message = markForTranslation("%(name)s cannot be built in unexplored area");
  109.             return result; // Fail
  110.         }
  111.     }
  112.  
  113.     // Check obstructions and terrain passability
  114.     var passClassName = "";
  115.     switch (this.template.PlacementType)
  116.     {
  117.     case "shore":
  118.         passClassName = "building-shore";
  119.         break;
  120.  
  121.     case "land-shore":
  122.         // 'default-terrain-only' is everywhere a normal unit can go, ignoring
  123.         // obstructions (i.e. on passable land, and not too deep in the water)
  124.         passClassName = "default-terrain-only";
  125.         break;
  126.  
  127.     case "land":
  128.     default:
  129.         passClassName = "building-land";
  130.     }
  131.  
  132.     var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
  133.     if (!cmpObstruction)
  134.         return result; // Fail
  135.  
  136.  
  137.     if (this.template.Category == "Wall")
  138.     {
  139.         // for walls, only test the center point
  140.         var ret = cmpObstruction.CheckFoundation(passClassName, true);
  141.     }
  142.     else
  143.     {
  144.         var ret = cmpObstruction.CheckFoundation(passClassName, false);
  145.     }
  146.  
  147.     if (ret != "success")
  148.     {
  149.         switch (ret)
  150.         {
  151.         case "fail_error":
  152.         case "fail_no_obstruction":
  153.             error("CheckPlacement: Error returned from CheckFoundation");
  154.             break;
  155.         case "fail_obstructs_foundation":
  156.             result.message = markForTranslation("%(name)s cannot be built on another building or resource");
  157.             break;
  158.         case "fail_terrain_class":
  159.             // TODO: be more specific and/or list valid terrain?
  160.             result.message = markForTranslation("%(name)s cannot be built on invalid terrain");
  161.         }
  162.         return result; // Fail
  163.     }
  164.  
  165.     // 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.         return result;  // Fail
  171.  
  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;
  178.  
  179.     var invalidTerritory = "";
  180.     if (isOwn)
  181.     {
  182.         if (!this.HasTerritory("own"))
  183.             // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
  184.             invalidTerritory = markForTranslationWithContext("Territory type", "own");
  185.         else if (!isConnected && !this.HasTerritory("neutral"))
  186.             // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
  187.             invalidTerritory = markForTranslationWithContext("Territory type", "unconnected own");
  188.     }
  189.     else if (isMutualAlly)
  190.     {
  191.         if (!this.HasTerritory("ally"))
  192.             // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
  193.             invalidTerritory = markForTranslationWithContext("Territory type", "allied");
  194.         else if (!isConnected && !this.HasTerritory("neutral"))
  195.             // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
  196.             invalidTerritory = markForTranslationWithContext("Territory type", "unconnected allied");
  197.     }
  198.     else if (isNeutral)
  199.     {
  200.         if (!this.HasTerritory("neutral"))
  201.             // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
  202.             invalidTerritory = markForTranslationWithContext("Territory type", "neutral");
  203.     }
  204.     else
  205.     {
  206.         // consider everything else enemy territory
  207.         if (!this.HasTerritory("enemy"))
  208.             // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.".
  209.             invalidTerritory = markForTranslationWithContext("Territory type", "enemy");
  210.     }
  211.  
  212.     if (invalidTerritory)
  213.     {
  214.         result.message = markForTranslation("%(name)s cannot be built in %(territoryType)s territory. Valid territories: %(validTerritories)s");
  215.         result.translateParameters.push("territoryType");
  216.         result.translateParameters.push("validTerritories");
  217.         result.parameters.territoryType = {"context": "Territory type", "message": invalidTerritory};
  218.         // gui code will join this array to a string
  219.         result.parameters.validTerritories = {"context": "Territory type list", "list": this.GetTerritories()};
  220.         return result;  // Fail
  221.     }
  222.  
  223.     // Check special requirements
  224.     if (this.template.Category == "Dock")
  225.     {
  226.         // TODO: Probably should check unit passability classes here, to determine if:
  227.         //      1. ships can be spawned "nearby"
  228.         //      2. builders can pass the terrain where the dock is placed (don't worry about paths)
  229.         //  so it's correct even if the criteria changes for these units
  230.         var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
  231.         if (!cmpFootprint)
  232.             return result;  // Fail
  233.  
  234.         // Get building's footprint
  235.         var shape = cmpFootprint.GetShape();
  236.         var halfSize = 0;
  237.         if (shape.type == "square")
  238.             halfSize = shape.depth/2;
  239.         else if (shape.type == "circle")
  240.             halfSize = shape.radius;
  241.  
  242.         var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
  243.         var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
  244.         if (!cmpTerrain || !cmpWaterManager)
  245.             return result;  // Fail
  246.  
  247.         var ang = cmpPosition.GetRotation().y;
  248.         var sz = halfSize * Math.sin(ang);
  249.         var cz = halfSize * Math.cos(ang);
  250.         if ((cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz) - cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz)) < 1.0 // front
  251.             || (cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz) - cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz)) > 2.0) // back
  252.         {
  253.             result.message = markForTranslation("%(name)s must be built on a valid shoreline");
  254.             return result;  // Fail
  255.         }
  256.     }
  257.  
  258.     // Check distance restriction
  259.     if (this.template.Distance)
  260.     {
  261.         var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
  262.         var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
  263.         var cat = this.template.Distance.FromClass;
  264.  
  265.         var filter = function(id)
  266.         {
  267.             var cmpIdentity = Engine.QueryInterface(id, IID_Identity);
  268.             return cmpIdentity.GetClassesList().indexOf(cat) > -1;
  269.         };
  270.  
  271.         if (this.template.Distance.MinDistance)
  272.         {
  273.             let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
  274.             let templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
  275.             templateName = templateName.substr(templateName.lastIndexOf("|") + 1);
  276.             let distMult = ApplyValueModificationsToTemplate("BuildRestrictions/Distance/MinDistance", +this.template.Distance.MinDistance, cmpPlayer.GetPlayerID(), templateName);
  277.             //let dist = ApplyValueModificationsToEntity("BuildRestrictions/Distance/MinDistance", +this.template.Distance.MinDistance, this.entity);
  278.             var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, distMult, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).some(filter);
  279.             if (nearEnts)
  280.             {
  281.                 var result = markForPluralTranslation(
  282.                     "%(name)s too close to a %(category)s, must be at least %(distance)s meter away",
  283.                     "%(name)s too close to a %(category)s, must be at least %(distance)s meters away",
  284.                     distMult);
  285.  
  286.                 result.success = false;
  287.                 result.translateMessage = true;
  288.                 result.parameters = {
  289.                     "name": name,
  290.                     "category": cat,
  291.                     "distance": distMult
  292.                 };
  293.                 result.translateParameters = ["name", "category"];
  294.                 return result;  // Fail
  295.             }
  296.         }
  297.         if (this.template.Distance.MaxDistance)
  298.         {
  299.             //let dist = ApplyValueModificationsToEntity("BuildRestrictions/Distance/MaxDistance", +this.template.Distance.MaxDistance, this.entity);
  300.             var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).some(filter);
  301.             if (!nearEnts)
  302.             {
  303.                 var result = markForPluralTranslation(
  304.                     "%(name)s too far from a %(category)s, must be within %(distance)s meter",
  305.                     "%(name)s too far from a %(category)s, must be within %(distance)s meters",
  306.                     +this.template.Distance.MaxDistance);
  307.  
  308.                 result.success = false;
  309.                 result.translateMessage = true;
  310.                 result.parameters = {
  311.                     "name": name,
  312.                     "category": cat,
  313.                     "distance": +this.template.Distance.MaxDistance
  314.                 };
  315.                 result.translateParameters = ["name", "category"];
  316.                 return result;  // Fail
  317.             }
  318.         }
  319.     }
  320.  
  321.     // Success
  322.     result.success = true;
  323.     result.message = "";
  324.     return result;
  325. };
  326.  
  327. BuildRestrictions.prototype.GetCategory = function()
  328. {
  329.     return this.template.Category;
  330. };
  331.  
  332. BuildRestrictions.prototype.GetTerritories = function()
  333. {
  334.     return ApplyValueModificationsToEntity("BuildRestrictions/Territory", this.territories, this.entity);
  335. };
  336.  
  337. BuildRestrictions.prototype.HasTerritory = function(territory)
  338. {
  339.     return (this.GetTerritories().indexOf(territory) != -1);
  340. };
  341.  
  342. // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
  343. markForTranslationWithContext("Territory type list", "own");
  344. // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
  345. markForTranslationWithContext("Territory type list", "ally");
  346. // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
  347. markForTranslationWithContext("Territory type list", "neutral");
  348. // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally".
  349. markForTranslationWithContext("Territory type list", "enemy");
  350.  
  351. Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions);
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement