Index: ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js (revision 19504) +++ ps/trunk/binaries/data/mods/public/simulation/components/BuildRestrictions.js (revision 19505) @@ -1,347 +1,347 @@ function BuildRestrictions() {} BuildRestrictions.prototype.Schema = "Specifies building placement restrictions as they relate to terrain, territories, and distance." + "" + "" + "land" + "own" + "Special" + "" + "CivilCentre" + "40" + "" + "" + "" + "" + "" + "land" + "shore" + "land-shore"+ "" + "" + "" + "" + "" + "" + "own" + "ally" + "neutral" + "enemy" + "" + "" + "" + "" + - "" + + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; BuildRestrictions.prototype.Init = function() { this.territories = this.template.Territory.split(/\s+/); }; /** * Checks whether building placement is valid * 1. Visibility is not hidden (may be fogged or visible) * 2. Check foundation * a. Doesn't obstruct foundation-blocking entities * b. On valid terrain, based on passability class * 3. Territory type is allowed (see note below) * 4. Dock is on shoreline and facing into water * 5. Distance constraints satisfied * * Returns result object: * { * "success": true iff the placement is valid, else false * "message": message to display in UI for invalid placement, else "" * "parameters": parameters to use in the GUI message * "translateMessage": always true * "translateParameters": list of parameters to translate * "pluralMessage": we might return a plural translation instead (optional) * "pluralCount": plural translation argument (optional) * } * * Note: The entity which is used to check this should be a preview entity * (template name should be "preview|"+templateName), as otherwise territory * checks for buildings with territory influence will not work as expected. */ BuildRestrictions.prototype.CheckPlacement = function() { var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); var name = cmpIdentity ? cmpIdentity.GetGenericName() : "Building"; var result = { "success": false, "message": markForTranslation("%(name)s cannot be built due to unknown error"), "parameters": { "name": name, }, "translateMessage": true, "translateParameters": ["name"], }; // TODO: AI has no visibility info var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); if (!cmpPlayer.IsAI()) { // Check whether it's in a visible or fogged region var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpRangeManager || !cmpOwnership) return result; // Fail var explored = (cmpRangeManager.GetLosVisibility(this.entity, cmpOwnership.GetOwner()) != "hidden"); if (!explored) { result.message = markForTranslation("%(name)s cannot be built in unexplored area"); return result; // Fail } } // Check obstructions and terrain passability var passClassName = ""; switch (this.template.PlacementType) { case "shore": passClassName = "building-shore"; break; case "land-shore": // 'default-terrain-only' is everywhere a normal unit can go, ignoring // obstructions (i.e. on passable land, and not too deep in the water) passClassName = "default-terrain-only"; break; case "land": default: passClassName = "building-land"; } var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); if (!cmpObstruction) return result; // Fail if (this.template.Category == "Wall") { // for walls, only test the center point var ret = cmpObstruction.CheckFoundation(passClassName, true); } else { var ret = cmpObstruction.CheckFoundation(passClassName, false); } if (ret != "success") { switch (ret) { case "fail_error": case "fail_no_obstruction": error("CheckPlacement: Error returned from CheckFoundation"); break; case "fail_obstructs_foundation": result.message = markForTranslation("%(name)s cannot be built on another building or resource"); break; case "fail_terrain_class": // TODO: be more specific and/or list valid terrain? result.message = markForTranslation("%(name)s cannot be built on invalid terrain"); } return result; // Fail } // Check territory restrictions var cmpTerritoryManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager); var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!(cmpTerritoryManager && cmpPlayer && cmpPosition && cmpPosition.IsInWorld())) return result; // Fail var pos = cmpPosition.GetPosition2D(); var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y); var isConnected = !cmpTerritoryManager.IsTerritoryBlinking(pos.x, pos.y); var isOwn = tileOwner == cmpPlayer.GetPlayerID(); var isMutualAlly = cmpPlayer.IsExclusiveMutualAlly(tileOwner); var isNeutral = tileOwner == 0; var invalidTerritory = ""; if (isOwn) { if (!this.HasTerritory("own")) // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.". invalidTerritory = markForTranslationWithContext("Territory type", "own"); else if (!isConnected && !this.HasTerritory("neutral")) // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.". invalidTerritory = markForTranslationWithContext("Territory type", "unconnected own"); } else if (isMutualAlly) { if (!this.HasTerritory("ally")) // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.". invalidTerritory = markForTranslationWithContext("Territory type", "allied"); else if (!isConnected && !this.HasTerritory("neutral")) // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.". invalidTerritory = markForTranslationWithContext("Territory type", "unconnected allied"); } else if (isNeutral) { if (!this.HasTerritory("neutral")) // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.". invalidTerritory = markForTranslationWithContext("Territory type", "neutral"); } else { // consider everything else enemy territory if (!this.HasTerritory("enemy")) // Translation: territoryType being displayed in a translated sentence in the form: "House cannot be built in %(territoryType)s territory.". invalidTerritory = markForTranslationWithContext("Territory type", "enemy"); } if (invalidTerritory) { result.message = markForTranslation("%(name)s cannot be built in %(territoryType)s territory. Valid territories: %(validTerritories)s"); result.translateParameters.push("territoryType"); result.translateParameters.push("validTerritories"); result.parameters.territoryType = {"context": "Territory type", "message": invalidTerritory}; // gui code will join this array to a string result.parameters.validTerritories = {"context": "Territory type list", "list": this.GetTerritories()}; return result; // Fail } // Check special requirements if (this.template.Category == "Dock") { // TODO: Probably should check unit passability classes here, to determine if: // 1. ships can be spawned "nearby" // 2. builders can pass the terrain where the dock is placed (don't worry about paths) // so it's correct even if the criteria changes for these units var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); if (!cmpFootprint) return result; // Fail // Get building's footprint var shape = cmpFootprint.GetShape(); var halfSize = 0; if (shape.type == "square") halfSize = shape.depth/2; else if (shape.type == "circle") halfSize = shape.radius; var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain); var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager); if (!cmpTerrain || !cmpWaterManager) return result; // Fail var ang = cmpPosition.GetRotation().y; var sz = halfSize * Math.sin(ang); var cz = halfSize * Math.cos(ang); if ((cmpWaterManager.GetWaterLevel(pos.x + sz, pos.y + cz) - cmpTerrain.GetGroundLevel(pos.x + sz, pos.y + cz)) < 1.0 // front || (cmpWaterManager.GetWaterLevel(pos.x - sz, pos.y - cz) - cmpTerrain.GetGroundLevel(pos.x - sz, pos.y - cz)) > 2.0) // back { result.message = markForTranslation("%(name)s must be built on a valid shoreline"); return result; // Fail } } // Check distance restriction if (this.template.Distance) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); var cat = this.template.Distance.FromClass; var filter = function(id) { var cmpIdentity = Engine.QueryInterface(id, IID_Identity); return cmpIdentity.GetClassesList().indexOf(cat) > -1; }; if (this.template.Distance.MinDistance) { var dist = +this.template.Distance.MinDistance; var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter); if (nearEnts.length) { var result = markForPluralTranslation( "%(name)s too close to a %(category)s, must be at least %(distance)s meter away", "%(name)s too close to a %(category)s, must be at least %(distance)s meters away", +this.template.Distance.MinDistance); result.success = false; result.translateMessage = true; result.parameters = { "name": name, "category": cat, "distance": this.template.Distance.MinDistance }; result.translateParameters = ["name", "category"]; return result; // Fail } } if (this.template.Distance.MaxDistance) { var dist = +this.template.Distance.MaxDistance; var nearEnts = cmpRangeManager.ExecuteQuery(this.entity, 0, dist, [cmpPlayer.GetPlayerID()], IID_BuildRestrictions).filter(filter); if (!nearEnts.length) { var result = markForPluralTranslation( "%(name)s too far from a %(category)s, must be within %(distance)s meter", "%(name)s too far from a %(category)s, must be within %(distance)s meters", +this.template.Distance.MinDistance); result.success = false; result.translateMessage = true; result.parameters = { "name": name, "category": cat, "distance": this.template.Distance.MaxDistance }; result.translateParameters = ["name", "category"]; return result; // Fail } } } // Success result.success = true; result.message = ""; return result; }; BuildRestrictions.prototype.GetCategory = function() { return this.template.Category; }; BuildRestrictions.prototype.GetTerritories = function() { return ApplyValueModificationsToEntity("BuildRestrictions/Territory", this.territories, this.entity); }; BuildRestrictions.prototype.HasTerritory = function(territory) { return (this.GetTerritories().indexOf(territory) != -1); }; // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally". markForTranslationWithContext("Territory type list", "own"); // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally". markForTranslationWithContext("Territory type list", "ally"); // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally". markForTranslationWithContext("Territory type list", "neutral"); // Translation: Territory types being displayed as part of a list like "Valid territories: own, ally". markForTranslationWithContext("Territory type list", "enemy"); Engine.RegisterComponentType(IID_BuildRestrictions, "BuildRestrictions", BuildRestrictions); Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml (revision 19504) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml (revision 19505) @@ -1,106 +1,115 @@ -5.0 -5.0 - -2.0 + -1.0 1.0 5.0 1.0 0.0 25.0 0.0 - 80.0 + 50.0 12.0 75.0 1200 2000 1.5 1 15 1 - own neutral enemy - Fortress + neutral enemy + ArmyCamp + + ArmyCamp + 80 + + + 1500 + 10.0 + 3.0 + 5 250 - 400 + 500 0 12.0 40 Support Infantry Cavalry Siege 1 6 2500 rubble/rubble_rome_sb rome Entrenched Army Camp Castrum Vallum ArmyCamp structures/roman_camp.png Build anywhere on the map, even in enemy territory. Construct siege weapons and train citizen-soldiers. Heal garrisoned units slowly. Sometimes it was a temporary camp built facing the route by which the army is to march, other times a defensive or offensive (for sieges) structure. Within this gate the tents of the first centuries or cohorts are pitched, and the dragons (ensigns of cohorts) and other ensigns planted. The Decumane gate is directly opposite to the Praetorian in the rear of the camp, and through this the soldiers are conducted to the place appointed for punishment or execution. 100 100 0.7 units/{civ}_infantry_swordsman_b units/{civ}_infantry_spearman_a units/{civ}_infantry_javelinist_b units/{civ}_cavalry_spearman_b units/{civ}_mechanical_siege_ballista_packed units/{civ}_mechanical_siege_scorpio_packed units/{civ}_mechanical_siege_oxybeles_packed units/{civ}_mechanical_siege_lithobolos_packed units/{civ}_mechanical_siege_ram units/{civ}_mechanical_siege_tower interface/complete/building/complete_broch.xml attack/destruction/building_collapse_large.xml 37.5 60 structures/romans/camp.xml structures/fndn_8x8.xml