diff --git a/binaries/data/mods/public/globalscripts/Templates.js b/binaries/data/mods/public/globalscripts/Templates.js
index d558a663c3..cc300bff7a 100644
--- a/binaries/data/mods/public/globalscripts/Templates.js
+++ b/binaries/data/mods/public/globalscripts/Templates.js
@@ -219,7 +219,7 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
ret.deathDamage[damageType] = getEntityValue("DeathDamage/" + damageType);
}
- if (template.Auras)
+ if (template.Auras && auraTemplates)
{
ret.auras = {};
for (let auraID of template.Auras._string.split(/\s+/))
@@ -438,20 +438,32 @@ function GetTemplateDataHelper(template, player, auraTemplates, resources, damag
};
if (template.WallSet)
+ {
ret.wallSet = {
"templates": {
"tower": template.WallSet.Templates.Tower,
"gate": template.WallSet.Templates.Gate,
+ "fort": template.WallSet.Templates.Fort || "structures/" + template.Identity.Civ + "_fortress",
"long": template.WallSet.Templates.WallLong,
"medium": template.WallSet.Templates.WallMedium,
- "short": template.WallSet.Templates.WallShort,
+ "short": template.WallSet.Templates.WallShort
},
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
- "minTowerOverlap": +template.WallSet.MinTowerOverlap,
+ "minTowerOverlap": +template.WallSet.MinTowerOverlap
};
+ if (template.WallSet.Templates.WallEnd)
+ ret.wallSet.templates.end = template.WallSet.Templates.WallEnd;
+ if (template.WallSet.Templates.WallCurves)
+ ret.wallSet.templates.curves = template.WallSet.Templates.WallCurves.split(" ");
+ }
if (template.WallPiece)
- ret.wallPiece = { "length": +template.WallPiece.Length };
+ ret.wallPiece = {
+ "length": +template.WallPiece.Length,
+ "angle": +(template.WallPiece.Orientation || 1) * Math.PI,
+ "indent": +(template.WallPiece.Indent || 0),
+ "bend": +(template.WallPiece.Bend || 0) * Math.PI
+ };
return ret;
}
diff --git a/binaries/data/mods/public/gui/reference/common/load.js b/binaries/data/mods/public/gui/reference/common/load.js
index 088fba49d1..8bab66fa96 100644
--- a/binaries/data/mods/public/gui/reference/common/load.js
+++ b/binaries/data/mods/public/gui/reference/common/load.js
@@ -228,6 +228,9 @@ function loadStructure(templateName)
for (let wSegm in structure.wallSet.templates)
{
+ if (wSegm == "fort" || wSegm == "curves")
+ continue;
+
let wPart = loadStructure(structure.wallSet.templates[wSegm]);
structure.wallset[wSegm] = wPart;
@@ -249,6 +252,15 @@ function loadStructure(templateName)
health.min = Math.min(health.min, wPart.health);
health.max = Math.max(health.max, wPart.health);
}
+
+ if (structure.wallSet.templates.curves)
+ for (let curve of structure.wallSet.templates.curves)
+ {
+ let wPart = loadStructure(curve);
+ health.min = Math.min(health.min, wPart.health);
+ health.max = Math.max(health.max, wPart.health);
+ }
+
if (health.min == health.max)
structure.health = health.min;
else
diff --git a/binaries/data/mods/public/maps/random/rmgen/library.js b/binaries/data/mods/public/maps/random/rmgen/library.js
index 72acf5481c..d1fc0da65d 100644
--- a/binaries/data/mods/public/maps/random/rmgen/library.js
+++ b/binaries/data/mods/public/maps/random/rmgen/library.js
@@ -5,6 +5,8 @@ const SEA_LEVEL = 20.0;
const HEIGHT_UNITS_PER_METRE = 92;
const MAP_BORDER_WIDTH = 3;
+const g_DamageTypes = new DamageTypes();
+
/**
* Constants needed for heightmap_manipulation.js
*/
diff --git a/binaries/data/mods/public/maps/random/rmgen/wall_builder.js b/binaries/data/mods/public/maps/random/rmgen/wall_builder.js
index 9fcc1d6c84..cef436727c 100644
--- a/binaries/data/mods/public/maps/random/rmgen/wall_builder.js
+++ b/binaries/data/mods/public/maps/random/rmgen/wall_builder.js
@@ -1,907 +1,811 @@
-////////////////////////////////////////////////////////////////////
-// This file contains functionality to place walls on random maps //
-////////////////////////////////////////////////////////////////////
+/**
+ * @file Contains functionality to place walls on random maps.
+ */
-// To do:
-// Check if all wall placement methods work with wall elements with entity === undefined (some still might raise errors in that case)
-// Rename wall elements to fit the entity names so that entity = "structures/" + "civ + "_" + wallElement.type in the common case (as far as possible)
-// Perhaps add Roman army camp to style palisades and add upgraded/balanced default palisade fortress types matching civ default fortresses strength
-// Perhaps add further wall elements cornerInHalf, cornerOutHalf (banding PI/4) and adjust default fortress types to better fit in the octagonal territory of a civil center
-// Perhaps swap angle and width in WallElement class(?) definition
-// Adjust argument order to be always the same:
-// Coordinates (center/start/target)
-// Wall element arguments (wall/wallPart/fortressType/cornerElement)
-// playerId (optional, default is 0/gaia)
-// wallStyle (optional, default is the players civ/"palisades for gaia")
-// angle/orientation (optional, default is 0)
-// other (all optional) arguments especially those hard to define (wallPartsAssortment, maybe make an own function for it)
-// Some arguments don't clearly match to this concept:
-// endWithFirst (wall or other)
-// skipFirstWall (wall or other)
-// gateOccurence (wall or other)
-// numCorners (wall or other)
-// skipFirstWall (wall or other)
-// maxAngle (angle or other)
-// maxBendOff (angle or other, unused ATM!!!)
-// irregularity
-// maxTrys
-// Add treasures to wall style "others"
-// Adjust documentation
-// Perhaps rename "endLeft" to "start" and "endRight" to "end"
-// ?Use available civ-type wall elements rather than palisades: Remove "endLeft" and "endRight" as default wall elements and adjust default palisade fortress types?
-// ?Remove "endRight", "endLeft" and adjust generic fortress types palisades?
-// ?Think of something to enable splitting walls into two walls so more complex walls can be build and roads can have branches/crossroads?
-// ?Readjust placement angle for wall elements with bending when used in linear/circular walls by their bending?
+/**
+ * Set some globals for this module.
+ */
+var g_WallStyles = loadWallsetsFromCivData();
+var g_FortressTypes = createDefaultFortressTypes();
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// WallElement class definition
-//
-// Concept: If placed unrotated the wall's course is towards positive Y (top) with "outside" right (+X) and "inside" left (-X) like unrotated entities has their drop-points right (in rmgen)
-// The course of the wall will be changed by corners (bending != 0) and so the "inside"/"outside" direction
-//
-// type Descriptive string, example: "wallLong". NOTE: Not really needed. Mainly for custom wall elements and to get the wall element type in code
-// entity Optional. Template name string of the entity to be placed, example: "structures/cart_wall_long". Default is undefined (No entity placed)
-// angle Optional. The angle (float) added to place the entity so "outside" is right when the wall element is placed unrotated. Default is 0
-// width Optional. How far this wall element lengthens the wall (float), if unrotated the Y space needed. Default is 0
-// indent Optional. The lateral indentation of the entity, drawn "inside" (positive values) or pushed "outside" (negative values). Default is 0
-// bending Optional. How the course of the wall is changed after this element, positive is bending "in"/left/counter clockwise (like entity placement)
-// NOTE: Bending is not supported by all placement functions (see there)
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function WallElement(type, entity, angle, width, indent, bending)
+/**
+ * Fetches wallsets from {civ}.json files, and then uses them to load
+ * basic wall elements.
+ */
+function loadWallsetsFromCivData()
+{
+ let wallsets = {};
+ for (let civ in g_CivData)
+ {
+ let civInfo = g_CivData[civ];
+ if (!civInfo.WallSets)
+ continue;
+
+ for (let path of civInfo.WallSets)
+ {
+ // File naming conventions:
+ // - other/wallset_{style}
+ // - structures/{civ}_wallset_{style}
+ let style = basename(path).split("_");
+ style = style[0] == "wallset" ? style[1] : style[0] + "_" + style[2];
+
+ if (!wallsets[style])
+ wallsets[style] = loadWallset(Engine.GetTemplate(path), civ);
+ }
+ }
+ return wallsets;
+}
+
+function loadWallset(wallsetPath, civ)
+{
+ let newWallset = { "curves": [] };
+ let wallsetData = GetTemplateDataHelper(wallsetPath).wallSet;
+
+ for (let element in wallsetData.templates)
+ if (element == "curves")
+ for (let filename of wallsetData.templates.curves)
+ newWallset.curves.push(readyWallElement(filename, civ));
+ else
+ newWallset[element] = readyWallElement(wallsetData.templates[element], civ);
+
+ newWallset.overlap = wallsetData.minTowerOverlap * newWallset.tower.length;
+
+ return newWallset;
+}
+
+/**
+ * Fortress class definition
+ *
+ * We use "fortress" to describe a closed wall built of multiple wall
+ * elements attached together surrounding a central point. We store the
+ * abstract of the wall (gate, tower, wall, ...) and only apply the style
+ * when we get to build it.
+ *
+ * @param {string} type - Descriptive string, example: "tiny". Not really needed (WallTool.wallTypes["type string"] is used). Mainly for custom wall elements.
+ * @param {array} [wall] - Array of wall element strings. May be defined at a later point.
+ * Example: ["medium", "cornerIn", "gate", "cornerIn", "medium", "cornerIn", "gate", "cornerIn"]
+ * @param {object} [centerToFirstElement] - Vector from the visual center of the fortress to the first wall element.
+ * @param {number} [centerToFirstElement.x]
+ * @param {number} [centerToFirstElement.y]
+ */
+function Fortress(type, wall=[], centerToFirstElement=undefined)
{
this.type = type;
- // Default wall element type documentation:
- // Lengthening straight blocking (mainly left/right symmetric) wall elements (Walls and wall fortifications)
- // "wall" A blocking straight wall element that mainly lengthens the wall, self-explanatory
- // "wallShort" self-explanatory
- // "wallLong" self-explanatory
- // "tower" A blocking straight wall element with damage potential (but for palisades) that slightly lengthens the wall, example: wall tower, palisade tower(No attack)
- // "wallFort" A blocking straight wall element with massive damage potential that lengthens the wall, example: fortress, palisade fort
- // Lengthening straight non/custom blocking (mainly left/right symmetric) wall elements (Gates and entries)
- // "gate" A blocking straight wall element with passability determined by owner, example: gate (Functionality not yet implemented)
- // "entry" A non-blocking straight wall element (same width as gate) but without an actual template or just a flag/column/obelisk
- // "entryTower" A non-blocking straight wall element (same width as gate) represented by a single (maybe indented) template, example: defence tower, wall tower, outpost, watchtower
- // "entryFort" A non-blocking straight wall element represented by a single (maybe indented) template, example: fortress, palisade fort
- // Bending wall elements (Wall corners)
- // "cornerIn" A wall element bending the wall by PI/2 "inside" (left, +, see above), example: wall tower, palisade curve
- // "cornerOut" A wall element bending the wall by PI/2 "outside" (right, -, see above), example: wall tower, palisade curve
- // "cornerHalfIn" A wall element bending the wall by PI/4 "inside" (left, +, see above), example: wall tower, palisade curve. NOTE: Not yet implemented
- // "cornerHalfOut" A wall element bending the wall by PI/4 "outside" (right, -, see above), example: wall tower, palisade curve. NOTE: Not yet implemented
- // Zero length straight indented (mainly left/right symmetric) wall elements (Outposts/watchtowers and non-defensive base structures)
- // "outpost" A zero-length wall element without bending far indented so it stands outside the wall, example: outpost, defence tower, watchtower
- // "house" A zero-length wall element without bending far indented so it stands inside the wall that grants population bonus, example: house, hut, longhouse
- // "barracks" A zero-length wall element without bending far indented so it stands inside the wall that grants unit production, example: barracks, tavern, ...
- this.entity = entity;
- this.angle = angle !== undefined ? angle : 0;
- this.width = width !== undefined ? width : 0;
- this.indent = indent !== undefined ? indent : 0;
- this.bending = bending !== undefined ? bending : 0;
+ this.wall = wall;
+ this.centerToFirstElement = centerToFirstElement;
}
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// Fortress class definition
-//
-// A "fortress" here is a closed wall build of multiple wall elements attached together defined in Fortress.wall
-// It's mainly the abstract shape defined in a Fortress instances wall because different styles can be used for it (see wallStyles)
-//
-// type Descriptive string, example: "tiny". Not really needed (WallTool.wallTypes["type string"] is used). Mainly for custom wall elements
-// wall Optional. Array of wall element strings. Can be set afterwards. Default is an epty array.
-// Example: ["entrance", "wall", "cornerIn", "wall", "gate", "wall", "entrance", "wall", "cornerIn", "wall", "gate", "wall", "cornerIn", "wall"]
-// centerToFirstElement Optional. Object with properties "x" and "y" representing a vector from the visual center to the first wall element. Default is undefined
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function Fortress(type, wall, centerToFirstElement)
+function createDefaultFortressTypes()
{
- this.type = type; // Only usefull to get the type of the actual fortress
- this.wall = wall !== undefined ? wall : [];
- this.centerToFirstElement = undefined;
-}
+ let defaultFortresses = {};
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// wallStyles data structure for default wall styles
-//
-// A wall style is an associative array with all wall elements of that style in it associated with the wall element type string
-// wallStyles holds all the wall styles within an associative array with the civ string or another descriptive strings as key
-// Examples: "athen", "rome_siege", "palisades", "fence", "road"
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-var wallStyles = {};
+ /**
+ * Define some basic default fortress types.
+ */
+ let addFortress = (type, walls) => defaultFortresses[type] = { "wall": walls.concat(walls, walls, walls) };
+ addFortress("tiny", ["gate", "tower", "short", "cornerIn", "short", "tower"]);
+ addFortress("small", ["gate", "tower", "medium", "cornerIn", "medium", "tower"]);
+ addFortress("medium", ["gate", "tower", "long", "cornerIn", "long", "tower"]);
+ addFortress("normal", ["gate", "tower", "medium", "cornerIn", "medium", "cornerOut", "medium", "cornerIn", "medium", "tower"]);
+ addFortress("large", ["gate", "tower", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "tower"]);
+ addFortress("veryLarge", ["gate", "tower", "medium", "cornerIn", "medium", "cornerOut", "long", "cornerIn", "long", "cornerOut", "medium", "cornerIn", "medium", "tower"]);
+ addFortress("giant", ["gate", "tower", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "tower"]);
-// Generic civ dependent wall style definition. "rome_siege" needs some tweek...
-var wallScaleByType = {
- "athen": 1.5,
- "brit": 1.5,
- "cart": 1.8,
- "gaul": 1.5,
- "iber": 1.5,
- "mace": 1.5,
- "maur": 1.5,
- "pers": 1.5,
- "ptol": 1.5,
- "rome": 1.5,
- "sele": 1.5,
- "spart": 1.5,
- "rome_siege": 1.5
-};
-
-for (let style in wallScaleByType)
-{
- let civ = style;
- if (style == "rome_siege")
- civ = "rome";
-
- wallStyles[style] = {
- // Default wall elements
- "tower": new WallElement("tower", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]),
- "endLeft": new WallElement("endLeft", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), // Same as tower. To be compatible with palisades...
- "endRight": new WallElement("endRight", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), // Same as tower. To be compatible with palisades...
- "cornerIn": new WallElement("cornerIn", "structures/" + style + "_wall_tower", 5/4*PI, 0, 0.35 * wallScaleByType[style], PI/2), // 2^0.5 / 4 ~= 0.35 ~= 1/3
- "cornerOut": new WallElement("cornerOut", "structures/" + style + "_wall_tower", 3/4*PI, 0.71 * wallScaleByType[style], 0, -PI/2), // 2^0.5 / 2 ~= 0.71 ~= 2/3
- "wallShort": new WallElement("wallShort", "structures/" + style + "_wall_short", 0, 2*wallScaleByType[style]),
- "wall": new WallElement("wall", "structures/" + style + "_wall_medium", 0, 4*wallScaleByType[style]),
- "wallMedium": new WallElement("wall", "structures/" + style + "_wall_medium", 0, 4*wallScaleByType[style]),
- "wallLong": new WallElement("wallLong", "structures/" + style + "_wall_long", 0, 6*wallScaleByType[style]),
-
- // Gate and entrance wall elements
- "gate": new WallElement("gate", "structures/" + style + "_wall_gate", PI, 6*wallScaleByType[style]),
- "entry": new WallElement("entry", undefined, 0, 6*wallScaleByType[style]),
- "entryTower": new WallElement("entryTower", "structures/" + civ + "_defense_tower", PI, 6*wallScaleByType[style], -4*wallScaleByType[style]),
- "entryFort": new WallElement("entryFort", "structures/" + civ + "_fortress", 0, 8*wallScaleByType[style], 6*wallScaleByType[style]),
-
- // Defensive wall elements with 0 width outside the wall
- "outpost": new WallElement("outpost", "structures/" + civ + "_outpost", PI, 0, -4*wallScaleByType[style]),
- "defenseTower": new WallElement("defenseTower", "structures/" + civ + "_defense_tower", PI, 0, -4*wallScaleByType[style]),
-
- // Base buildings wall elements with 0 width inside the wall
- "barracks": new WallElement("barracks", "structures/" + civ + "_barracks", PI, 0, 4.5*wallScaleByType[style]),
- "civilCentre": new WallElement("civilCentre", "structures/" + civ + "_civil_centre", PI, 0, 4.5*wallScaleByType[style]),
- "farmstead": new WallElement("farmstead", "structures/" + civ + "_farmstead", PI, 0, 4.5*wallScaleByType[style]),
- "field": new WallElement("field", "structures/" + civ + "_field", PI, 0, 4.5*wallScaleByType[style]),
- "fortress": new WallElement("fortress", "structures/" + civ + "_fortress", PI, 0, 4.5*wallScaleByType[style]),
- "house": new WallElement("house", "structures/" + civ + "_house", PI, 0, 4.5*wallScaleByType[style]),
- "market": new WallElement("market", "structures/" + civ + "_market", PI, 0, 4.5*wallScaleByType[style]),
- "storehouse": new WallElement("storehouse", "structures/" + civ + "_storehouse", PI, 0, 4.5*wallScaleByType[style]),
- "temple": new WallElement("temple", "structures/" + civ + "_temple", PI, 0, 4.5*wallScaleByType[style]),
-
- // Generic space/gap wall elements
- "space1": new WallElement("space1", undefined, 0, 1*wallScaleByType[style]),
- "space2": new WallElement("space2", undefined, 0, 2*wallScaleByType[style]),
- "space3": new WallElement("space3", undefined, 0, 3*wallScaleByType[style]),
- "space4": new WallElement("space4", undefined, 0, 4*wallScaleByType[style])
- };
-}
-
-// Add wall fortresses for all generic styles
-wallStyles.athen.wallFort = new WallElement("wallFort", "structures/athen_fortress", 2*PI/2, 5.1, 1.9);
-wallStyles.brit.wallFort = new WallElement("wallFort", "structures/brit_fortress", PI, 2.8);
-wallStyles.cart.wallFort = new WallElement("wallFort", "structures/cart_fortress", PI, 5.1, 1.6);
-wallStyles.gaul.wallFort = new WallElement("wallFort", "structures/gaul_fortress", PI, 4.2, 1.5);
-wallStyles.iber.wallFort = new WallElement("wallFort", "structures/iber_fortress", PI, 5, 0.2);
-wallStyles.mace.wallFort = new WallElement("wallFort", "structures/mace_fortress", 2*PI/2, 5.1, 1.9);
-wallStyles.maur.wallFort = new WallElement("wallFort", "structures/maur_fortress", PI, 5.5);
-wallStyles.pers.wallFort = new WallElement("wallFort", "structures/pers_fortress", PI, 5.6, 1.9);
-wallStyles.ptol.wallFort = new WallElement("wallFort", "structures/ptol_fortress", 2*PI/2, 5.1, 1.9);
-wallStyles.rome.wallFort = new WallElement("wallFort", "structures/rome_fortress", PI, 6.3, 2.1);
-wallStyles.sele.wallFort = new WallElement("wallFort", "structures/sele_fortress", 2*PI/2, 5.1, 1.9);
-wallStyles.spart.wallFort = new WallElement("wallFort", "structures/spart_fortress", 2*PI/2, 5.1, 1.9);
-
-// Adjust "rome_siege" style
-wallStyles.rome_siege.wallFort = new WallElement("wallFort", "structures/rome_army_camp", PI, 7.2, 2);
-wallStyles.rome_siege.entryFort = new WallElement("entryFort", "structures/rome_army_camp", PI, 12, 7);
-wallStyles.rome_siege.house = new WallElement("house", "structures/rome_tent", PI, 0, 4);
-
-// Add special wall styles not well to implement generic (and to show how custom styles can be added)
-
-wallScaleByType.palisades = 0.55;
-let gate = new WallElement("gate", "other/palisades_rocks_gate", PI, 3.6);
-wallStyles.palisades = {
- "wall": new WallElement("wall", "other/palisades_rocks_medium", 0, 2.3),
- "wallMedium": new WallElement("wall", "other/palisades_rocks_medium", 0, 2.3),
- "wallLong": new WallElement("wall", "other/palisades_rocks_long", 0, 3.5),
- "wallShort": new WallElement("wall", "other/palisades_rocks_short", 0, 1.2),
- "tower": new WallElement("tower", "other/palisades_rocks_tower", -PI/2, 0.7),
- "wallFort": new WallElement("wallFort", "other/palisades_rocks_fort", PI, 1.7),
- "gate": gate,
- "entry": new WallElement("entry", undefined, gate.angle, gate.width),
- "entryTower": new WallElement("entryTower", "other/palisades_rocks_watchtower", 0, gate.width, -3),
- "entryFort": new WallElement("entryFort", "other/palisades_rocks_fort", PI, 6, 3),
- "cornerIn": new WallElement("cornerIn", "other/palisades_rocks_curve", 3*PI/4, 2.1, 0.7, PI/2),
- "cornerOut": new WallElement("cornerOut", "other/palisades_rocks_curve", 5*PI/4, 2.1, -0.7, -PI/2),
- "outpost": new WallElement("outpost", "other/palisades_rocks_outpost", PI, 0, -2),
- "house": new WallElement("house", "other/celt_hut", PI, 0, 5),
- "barracks": new WallElement("barracks", "structures/gaul_tavern", PI, 0, 5),
- "endRight": new WallElement("endRight", "other/palisades_rocks_end", -PI/2, 0.2),
- "endLeft": new WallElement("endLeft", "other/palisades_rocks_end", PI/2, 0.2)
-};
-
-// NOTE: This is not a wall style in the common sense. Use with care!
-wallStyles.road = {
- "short": new WallElement("road", "actor|props/special/eyecandy/road_temperate_short.xml", PI/2, 4.5),
- "long": new WallElement("road", "actor|props/special/eyecandy/road_temperate_long.xml", PI/2, 9.5),
-
- // Correct width by -2*indent to fit xStraicht/corner
- "cornerLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_corner.xml", -PI/2, 4.5-2*1.25, 1.25, PI/2),
- "cornerRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_corner.xml", 0, 4.5-2*1.25, -1.25, -PI/2),
- "curveLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_curve_small.xml", -PI/2, 4.5+2*0.2, -0.2, PI/2),
- "curveRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_curve_small.xml", 0, 4.5+2*0.2, 0.2, -PI/2),
-
- "start": new WallElement("road", "actor|props/special/eyecandy/road_temperate_end.xml", PI/2, 2),
- "end": new WallElement("road", "actor|props/special/eyecandy/road_temperate_end.xml", -PI/2, 2),
- "xStraight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5),
- "xLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5, 0, PI/2),
- "xRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5, 0, -PI/2),
- "tLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_T.xml", PI, 4.5, 1.25),
- "tRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_T.xml", 0, 4.5, -1.25),
-};
-
-// NOTE: This is not a wall style in the common sense. Use with care!
-wallStyles.other = {
- "fence": new WallElement("fence", "other/fence_long", -PI/2, 3.1),
- "fence_medium": new WallElement("fence", "other/fence_long", -PI/2, 3.1),
- "fence_short": new WallElement("fence_short", "other/fence_short", -PI/2, 1.5),
- "fence_stone": new WallElement("fence_stone", "other/fence_stone", -PI/2, 2.5),
- "palisade": new WallElement("palisade", "other/palisades_rocks_short", 0, 1.2),
- "column": new WallElement("column", "other/column_doric", 0, 1),
- "obelisk": new WallElement("obelisk", "other/obelisk", 0, 2),
- "spike": new WallElement("spike", "other/palisades_angle_spike", -PI/2, 1),
- "bench": new WallElement("bench", "other/bench", PI/2, 1.5),
- "benchForTable": new WallElement("benchForTable", "other/bench", 0, 0.5),
- "table": new WallElement("table", "other/table_rectangle", 0, 1),
- "table_square": new WallElement("table_square", "other/table_square", PI/2, 1),
- "flag": new WallElement("flag", "special/rallypoint", PI, 1),
- "standing_stone": new WallElement("standing_stone", "gaia/special_ruins_standing_stone", PI, 1),
- "settlement": new WallElement("settlement", "gaia/special_settlement", PI, 6),
- "gap": new WallElement("gap", undefined, 0, 2),
- "gapSmall": new WallElement("gapSmall", undefined, 0, 1),
- "gapLarge": new WallElement("gapLarge", undefined, 0, 4),
- "cornerIn": new WallElement("cornerIn", undefined, 0, 0, 0, PI/2),
- "cornerOut": new WallElement("cornerOut", undefined, 0, 0, 0, -PI/2)
-};
-
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// fortressTypes data structure for some default fortress types
-//
-// A fortress type is just an instance of the Fortress class with actually something in it
-// fortressTypes holds all the fortresses within an associative array with a descriptive string as key (e.g. matching the map size)
-// Examples: "tiny", "veryLarge"
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-var fortressTypes = {};
-
-{
- let wallParts = {
- "tiny": ["gate", "tower", "wallShort", "cornerIn", "wallShort", "tower"],
- "small": ["gate", "tower", "wall", "cornerIn", "wall", "tower"],
- "medium": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "tower"],
- "normal": ["gate", "tower", "wall", "cornerIn", "wall",
- "cornerOut", "wall", "cornerIn", "wall", "tower"],
- "large": ["gate", "tower", "wallLong", "cornerIn", "wallLong",
- "cornerOut", "wallLong", "cornerIn", "wallLong", "tower"],
- "veryLarge": ["gate", "tower", "wall", "cornerIn", "wall", "cornerOut", "wallLong",
- "cornerIn", "wallLong", "cornerOut", "wall", "cornerIn", "wall", "tower"],
- "giant": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "cornerOut", "wallLong",
- "cornerIn", "wallLong", "cornerOut", "wallLong", "cornerIn", "wallLong", "tower"]
- };
-
- for (let type in wallParts)
+ /**
+ * Define some fortresses based on those above, but designed for use
+ * with the "palisades" wallset.
+ */
+ for (let fortressType in defaultFortresses)
{
- fortressTypes[type] = new Fortress(type);
+ const fillTowersBetween = ["short", "medium", "long", "start", "end", "cornerIn", "cornerOut"];
+ const newKey = fortressType + "Palisades";
+ const oldWall = defaultFortresses[fortressType].wall;
- let wp = wallParts[type];
- fortressTypes[type].wall = wp.concat(wp, wp, wp);
+ defaultFortresses[newKey] = { "wall": [] };
+ for (let j = 0; j < oldWall.length; ++j)
+ {
+ defaultFortresses[newKey].wall.push(oldWall[j]);
+
+ if (j + 1 < oldWall.length &&
+ fillTowersBetween.indexOf(oldWall[j]) != -1 &&
+ fillTowersBetween.indexOf(oldWall[j + 1]) != -1)
+ {
+ defaultFortresses[newKey].wall.push("tower");
+ }
+ }
}
+
+ return defaultFortresses;
}
-// Setup some better looking semi default fortresses for "palisades" style
-for (let type in fortressTypes)
+/**
+ * Define some helper functions
+ */
+
+/**
+ * Get a wall element of a style.
+ *
+ * Valid elements:
+ * long, medium, short, start, end, cornerIn, cornerOut, tower, fort, gate, entry, entryTower, entryFort
+ *
+ * Dynamic elements:
+ * `gap_{x}` returns a non-blocking gap of length `x` meters.
+ * `turn_{x}` returns a zero-length turn of angle `x` radians.
+ *
+ * Any other arbitrary string passed will be attempted to be used as: `structures/{civ}_{arbitrary_string}`.
+ *
+ * @param {string} element - What sort of element to fetch.
+ * @param {string} [style] - The style from which this element should come from.
+ * @returns {object} The wall element requested. Or a tower element.
+ */
+function getWallElement(element, style)
{
- let newKey = type + "Palisades";
- let oldWall = fortressTypes[type].wall;
- fortressTypes[newKey] = new Fortress(newKey);
- let fillTowersBetween = ["wallShort", "wall", "wallLong", "endLeft", "endRight", "cornerIn", "cornerOut"];
- for (let j = 0; j < oldWall.length; ++j)
+ style = validateStyle(style);
+ if (g_WallStyles[style][element])
+ return g_WallStyles[style][element];
+
+ // Attempt to derive any unknown elements.
+ // Defaults to a wall tower piece
+ const quarterBend = Math.PI / 2;
+ let wallset = g_WallStyles[style];
+ let civ = style.split("_")[0];
+ let ret = wallset.tower ? clone(wallset.tower) : { "angle": 0, "bend": 0, "length": 0, "indent": 0 };
+
+ switch (element)
{
- fortressTypes[newKey].wall.push(oldWall[j]); // Only works if the first element is not in fillTowersBetween (e.g. entry or gate like it should be)
- if (j+1 < oldWall.length)
- if (fillTowersBetween.indexOf(oldWall[j]) > -1 && fillTowersBetween.indexOf(oldWall[j+1]) > -1) // ... > -1 means "exists" here
- fortressTypes[newKey].wall.push("tower");
+ case "cornerIn":
+ if (wallset.curves)
+ for (let curve of wallset.curves)
+ if (curve.bend == quarterBend)
+ ret = curve;
+
+ if (ret.bend != quarterBend)
+ {
+ ret.angle += Math.PI / 4;
+ ret.indent = ret.length / 4;
+ ret.length = 0;
+ ret.bend = Math.PI / 2;
+ }
+ break;
+
+ case "cornerOut":
+ if (wallset.curves)
+ for (let curve of wallset.curves)
+ if (curve.bend == quarterBend)
+ {
+ ret = clone(curve);
+ ret.angle += Math.PI / 2;
+ ret.indent -= ret.indent * 2;
+ }
+
+ if (ret.bend != quarterBend)
+ {
+ ret.angle -= Math.PI / 4;
+ ret.indent = -ret.length / 4;
+ ret.length = 0;
+ }
+ ret.bend = -Math.PI / 2;
+ break;
+
+ case "entry":
+ ret.templateName = undefined;
+ ret.length = wallset.gate.length;
+ break;
+
+ case "entryTower":
+ ret.templateName = g_CivData[civ] ? "structures/" + civ + "_defense_tower" : "other/palisades_rocks_watchtower";
+ ret.indent = ret.length * -3;
+ ret.length = wallset.gate.length;
+ break;
+
+ case "entryFort":
+ ret = clone(wallset.fort);
+ ret.angle -= Math.PI;
+ ret.length *= 1.5;
+ ret.indent = ret.length;
+ break;
+
+ case "start":
+ if (wallset.end)
+ {
+ ret = clone(wallset.end);
+ ret.angle += Math.PI;
+ }
+ break;
+
+ case "end":
+ if (wallset.end)
+ ret = wallset.end;
+ break;
+
+ default:
+ if (element.startsWith("gap_"))
+ {
+ ret.templateName = undefined;
+ ret.angle = 0;
+ ret.length = +element.slice("gap_".length);
+ }
+ else if (element.startsWith("turn_"))
+ {
+ ret.templateName = undefined;
+ ret.bend = +element.slice("turn_".length) * Math.PI;
+ ret.length = 0;
+ }
+ else
+ {
+ if (!g_CivData[civ])
+ civ = Object.keys(g_CivData)[0];
+
+ let templateName = "structures/" + civ + "_" + element;
+ if (Engine.TemplateExists(templateName))
+ {
+ ret.indent = ret.length * (element == "outpost" || element.endsWith("_tower") ? -3 : 3.5);
+ ret.templateName = templateName;
+ ret.length = 0;
+ }
+ else
+ warn("Unrecognised wall element: '" + element + "' (" + style + "). Defaulting to " + (wallset.tower ? "'tower'." : "a blank element."));
+ }
}
+
+ // Cache to save having to calculate this element again.
+ g_WallStyles[style][element] = deepfreeze(ret);
+
+ return ret;
}
-// Setup some balanced (to civ type fortresses) semi default fortresses for "palisades" style
-// TODO
-
-// Add some "fortress types" for roads (will only work with style "road")
+/**
+ * Prepare a wall element for inclusion in a style.
+ *
+ * @param {string} path - The template path to read values from
+ */
+function readyWallElement(path, civCode)
{
- // ["start", "short", "xRight", "xLeft", "cornerLeft", "xStraight", "long", "xLeft", "xRight", "cornerRight", "tRight", "tLeft", "xRight", "xLeft", "curveLeft", "xStraight", "curveRight", "end"];
- let roadTypes = {
- "road01": ["short", "curveLeft", "short", "curveLeft", "short", "curveLeft", "short", "curveLeft"],
- "road02": ["short", "cornerLeft", "short", "cornerLeft", "short", "cornerLeft", "short", "cornerLeft"],
- "road03": ["xStraight", "curveLeft", "xStraight", "curveLeft", "xStraight", "curveLeft", "xStraight", "curveLeft"],
- "road04": ["start", "curveLeft", "tRight", "cornerLeft", "tRight", "curveRight", "short", "xRight", "curveLeft", "xRight", "short", "cornerLeft", "tRight", "short",
- "curveLeft", "short", "tRight", "cornerLeft", "short", "xRight", "curveLeft", "xRight", "short", "curveRight", "tRight", "cornerLeft", "tRight", "curveLeft", "end"],
- "road05": ["start", "tLeft", "short", "xRight",
- "curveLeft", "xRight", "tRight", "cornerLeft", "tRight",
- "curveLeft", "short", "tRight", "cornerLeft", "xRight",
- "cornerLeft", "xRight", "short", "tRight", "curveLeft", "end"],
- };
+ path = path.replace(/\{civ\}/g, civCode);
+ let template = GetTemplateDataHelper(Engine.GetTemplate(path), null, null, {}, g_DamageTypes, {});
+ let length = template.wallPiece ? template.wallPiece.length : template.obstruction.shape.width;
- for (let type in roadTypes)
- fortressTypes[type] = new Fortress(type, roadTypes[type]);
+ return deepfreeze({
+ "templateName": path,
+ "angle": template.wallPiece ? template.wallPiece.angle : Math.PI,
+ "length": length / TERRAIN_TILE_SIZE,
+ "indent": template.wallPiece ? template.wallPiece.indent / TERRAIN_TILE_SIZE : 0,
+ "bend": template.wallPiece ? template.wallPiece.bend : 0
+ });
}
-///////////////////////////////
-// Define some helper functions
-///////////////////////////////
-
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// getWallAlignment
-//
-// Returns a list of objects containing all information to place all the wall elements entities with placeObject (but the player ID)
-// Placing the first wall element at startX/startY placed with an angle given by orientation
-// An alignment can be used to get the "center" of a "wall" (more likely used for fortresses) with getCenterToFirstElement
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function getWallAlignment(startX, startY, wall, style, orientation)
+/**
+ * Returns a list of objects containing all information to place all the wall elements entities with placeObject (but the player ID)
+ * Placing the first wall element at startX/startY placed with an angle given by orientation
+ * An alignment can be used to get the "center" of a "wall" (more likely used for fortresses) with getCenterToFirstElement
+ *
+ * @param {number} startX
+ * @param {number} startY
+ * @param {array} [wall]
+ * @param {string} [style]
+ * @param {number} [orientation]
+ * @returns {array}
+ */
+function getWallAlignment(startX, startY, wall = [], style = "athen_stone", orientation = 0)
{
- // Graciously handle arguments
- if (wall === undefined)
- wall = [];
- if (!wallStyles.hasOwnProperty(style))
- {
- warn("Function getWallAlignment: Unknown style: " + style + ' (falling back to "athen")');
- style = "athen";
- }
- orientation = orientation || 0;
-
+ style = validateStyle(style);
let alignment = [];
let wallX = startX;
let wallY = startY;
+
for (let i = 0; i < wall.length; ++i)
{
- let element = wallStyles[style][wall[i]];
- if (element === undefined && i == 0)
- warn("No valid wall element: " + wall[i]);
+ let element = getWallElement(wall[i], style);
+ if (!element && i == 0)
+ {
+ warn("Not a valid wall element: style = " + style + ", wall[" + i + "] = " + wall[i] + "; " + uneval(element));
+ continue;
+ }
// Indentation
- let placeX = wallX - element.indent * cos(orientation);
- let placeY = wallY - element.indent * sin(orientation);
+ let placeX = wallX - element.indent * Math.cos(orientation);
+ let placeY = wallY - element.indent * Math.sin(orientation);
// Add wall elements entity placement arguments to the alignment
alignment.push({
"x": placeX,
"y": placeY,
- "entity": element.entity,
+ "templateName": element.templateName,
"angle": orientation + element.angle
});
// Preset vars for the next wall element
- if (i+1 < wall.length)
+ if (i + 1 < wall.length)
{
- orientation += element.bending;
- let nextElement = wallStyles[style][wall[i+1]];
- if (nextElement === undefined)
- warn("No valid wall element: " + wall[i+1]);
- let distance = (element.width + nextElement.width)/2;
+ orientation += element.bend;
+ let nextElement = getWallElement(wall[i + 1], style);
+ if (!nextElement)
+ {
+ warn("Not a valid wall element: style = " + style + ", wall[" + (i + 1) + "] = " + wall[i + 1] + "; " + uneval(nextElement));
+ continue;
+ }
+
+ let distance = (element.length + nextElement.length) / 2 - g_WallStyles[style].overlap;
+
// Corrections for elements with indent AND bending
let indent = element.indent;
- let bending = element.bending;
- if (bending !== 0 && indent !== 0)
+ let bend = element.bend;
+ if (bend != 0 && indent != 0)
{
// Indent correction to adjust distance
- distance += indent*sin(bending);
+ distance += indent * Math.sin(bend);
+
// Indent correction to normalize indentation
- wallX += indent * cos(orientation);
- wallY += indent * sin(orientation);
+ wallX += indent * Math.cos(orientation);
+ wallY += indent * Math.sin(orientation);
}
// Set the next coordinates of the next element in the wall without indentation adjustment
- wallX -= distance * sin(orientation);
- wallY += distance * cos(orientation);
+ wallX -= distance * Math.sin(orientation);
+ wallY += distance * Math.cos(orientation);
}
}
return alignment;
}
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// getCenterToFirstElement
-//
-// Center calculation works like getting the center of mass assuming all wall elements have the same "weight"
-//
-// It returns the vector from the center to the first wall element
-// Used to get centerToFirstElement of fortresses by default
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+/**
+ * Center calculation works like getting the center of mass assuming all wall elements have the same "weight"
+ *
+ * Used to get centerToFirstElement of fortresses by default
+ *
+ * @param {number} alignment
+ * @returns {object} Vector from the center of the set of aligned wallpieces to the first wall element.
+ */
function getCenterToFirstElement(alignment)
{
let centerToFirstElement = { "x": 0, "y": 0 };
- for (let i = 0; i < alignment.length; ++i)
+ for (let align of alignment)
{
- centerToFirstElement.x -= alignment[i].x/alignment.length;
- centerToFirstElement.y -= alignment[i].y/alignment.length;
+ centerToFirstElement.x -= align.x / alignment.length;
+ centerToFirstElement.y -= align.y / alignment.length;
}
return centerToFirstElement;
}
-//////////////////////////////////////////////////////////////////
-// getWallLength
-//
-// NOTE: Does not support bending wall elements like corners!
-// e.g. used by placeIrregularPolygonalWall
-//////////////////////////////////////////////////////////////////
-function getWallLength(wall, style)
+/**
+ * Does not support bending wall elements like corners.
+ *
+ * @param {string} style
+ * @param {array} wall
+ * @returns {number} The sum length (in terrain cells, not meters) of the provided wall.
+ */
+function getWallLength(style, wall)
{
- // Graciously handle arguments
- if (wall === undefined)
- wall = [];
- if (!wallStyles.hasOwnProperty(style))
- {
- warn("Function getWallLength: Unknown style: " + style + ' (falling back to "athen")');
- style = "athen";
- }
+ style = validateStyle(style);
let length = 0;
- for (let i = 0; i < wall.length; ++i)
- length += wallStyles[style][wall[i]].width;
+ let overlap = g_WallStyles[style].overlap;
+ for (let element of wall)
+ length += getWallElement(element, style).length - overlap;
return length;
}
-/////////////////////////////////////////////
-// Define the different wall placer functions
-/////////////////////////////////////////////
-
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeWall
-//
-// Places a wall with wall elements attached to another like determined by WallElement properties.
-//
-// startX, startY Where the first wall element should be placed
-// wall Array of wall element type strings. Example: ["endLeft", "wallLong", "tower", "wallLong", "endRight"]
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// playerId Optional. Number of the player the wall will be placed for. Default is 0 (gaia)
-// orientation Optional. Angle the first wall element is placed. Default is 0
-// 0 means "outside" or "front" of the wall is right (positive X) like placeObject
-// It will then be build towards top/positive Y (if no bending wall elements like corners are used)
-// Raising orientation means the wall is rotated counter-clockwise like placeObject
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeWall(startX, startY, wall, style, playerId, orientation)
+/**
+ * Makes sure the style exists and, if not, provides a fallback.
+ *
+ * @param {string} style
+ * @param {number} [playerId]
+ * @returns {string} Valid style.
+ */
+function validateStyle(style, playerId = 0)
{
- // Graciously handle arguments
- if (wall === undefined)
- wall = [];
- playerId = playerId || 0;
- if (!wallStyles.hasOwnProperty(style))
+ if (!style || !g_WallStyles[style])
{
if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
- orientation = orientation || 0;
+ return Object.keys(g_WallStyles)[0];
- // Get wall alignment
- let AM = getWallAlignment(startX, startY, wall, style, orientation);
- // Place the wall
- for (let iWall = 0; iWall < wall.length; ++iWall)
- {
- let entity = AM[iWall].entity;
- if (entity !== undefined)
- placeObject(AM[iWall].x, AM[iWall].y, entity, playerId, AM[iWall].angle);
+ style = getCivCode(playerId) + "_stone";
+ return !g_WallStyles[style] ? Object.keys(g_WallStyles)[0] : style;
}
+ return style;
}
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeCustomFortress
-//
-// Place a fortress (mainly a closed wall build like placeWall) centered at centerX/centerY
-// The fortress wall should always start with the main entrance (like "entry" or "gate") to get the orientation right (like placeObject)
-//
-// fortress An instance of Fortress with a wall defined
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// playerId Optional. Number of the player the wall will be placed for. Default is 0 (gaia)
-// orientation Optional. Angle the first wall element (should be a gate or entrance) is placed. Default is BUILDING_ORIENTATION
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeCustomFortress(centerX, centerY, fortress, style, playerId = 0, orientation = BUILDING_ORIENTATION)
+/**
+ * Define the different wall placer functions
+ */
+
+/**
+ * Places an abitrary wall beginning at the location comprised of the array of elements provided.
+ *
+ * @param {number} startX
+ * @param {number} startY
+ * @param {array} [wall] - Array of wall element types. Example: ["start", "long", "tower", "long", "end"]
+ * @param {string} [style] - Wall style string.
+ * @param {number} [playerId] - Identifier of the player for whom the wall will be placed.
+ * @param {number} [orientation] - Angle at which the first wall element is placed.
+ * 0 means "outside" or "front" of the wall is right (positive X) like placeObject
+ * It will then be build towards top/positive Y (if no bending wall elements like corners are used)
+ * Raising orientation means the wall is rotated counter-clockwise like placeObject
+ */
+function placeWall(startX, startY, wall = [], style, playerId = 0, orientation = 0)
{
- // Graciously handle arguments
- fortress = fortress || fortressTypes.medium;
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
+ style = validateStyle(style, playerId);
+
+ let AM = getWallAlignment(startX, startY, wall, style, orientation);
+ for (let iWall = 0; iWall < wall.length; ++iWall)
+ if (AM[iWall].templateName)
+ placeObject(AM[iWall].x, AM[iWall].y, AM[iWall].templateName, playerId, AM[iWall].angle);
+}
+
+/**
+ * Places an abitrarily designed "fortress" (closed loop of wall elements)
+ * centered around a given point.
+ *
+ * The fortress wall should always start with the main entrance (like
+ * "entry" or "gate") to get the orientation correct.
+ *
+ * @param {number} centerX
+ * @param {number} centerY
+ * @param {object} [fortress] - If not provided, defaults to the predefined "medium" fortress type.
+ * @param {string} [style] - Wall style string.
+ * @param {number} [playerId] - Identifier of the player for whom the wall will be placed.
+ * @param {number} [orientation] - Angle the first wall element (should be a gate or entrance) is placed. Default is 0
+ */
+function placeCustomFortress(centerX, centerY, fortress, style, playerId = 0, orientation = 0)
+{
+ fortress = fortress || g_FortressTypes.medium;
+ style = validateStyle(style, playerId);
// Calculate center if fortress.centerToFirstElement is undefined (default)
let centerToFirstElement = fortress.centerToFirstElement;
if (centerToFirstElement === undefined)
centerToFirstElement = getCenterToFirstElement(getWallAlignment(0, 0, fortress.wall, style));
+
// Placing the fortress wall
- let startX = centerX + centerToFirstElement.x * cos(orientation) - centerToFirstElement.y * sin(orientation);
- let startY = centerY + centerToFirstElement.y * cos(orientation) + centerToFirstElement.x * sin(orientation);
+ let startX = centerX + centerToFirstElement.x * Math.cos(orientation) - centerToFirstElement.y * Math.sin(orientation);
+ let startY = centerY + centerToFirstElement.y * Math.cos(orientation) + centerToFirstElement.x * Math.sin(orientation);
placeWall(startX, startY, fortress.wall, style, playerId, orientation);
}
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeFortress
-//
-// Like placeCustomFortress just it takes type (a fortress type string, has to be in fortressTypes) instead of an instance of Fortress
-///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeFortress(centerX, centerY, type, style, playerId, orientation)
+/**
+ * Places a predefined fortress centered around the provided point.
+ *
+ * @see Fortress
+ *
+ * @param {string} [type] - Predefined fortress type, as used as a key in g_FortressTypes.
+ */
+function placeFortress(centerX, centerY, type = "medium", style, playerId = 0, orientation = 0)
{
- // Graciously handle arguments
- type = type || "medium";
- playerId = playerId || 0;
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
- orientation = orientation || 0;
-
// Call placeCustomFortress with the given arguments
- placeCustomFortress(centerX, centerY, fortressTypes[type], style, playerId, orientation);
+ placeCustomFortress(centerX, centerY, g_FortressTypes[type], style, playerId, orientation);
}
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeLinearWall
-//
-// Places a straight wall from a given coordinate to an other repeatedly using the wall parts.
-//
-// startX/startY Coordinate of the approximate beginning of the wall (Not the place of the first wall element)
-// targetX/targetY Coordinate of the approximate ending of the wall (Not the place of the last wall element)
-// wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["tower", "wallLong"]
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// playerId Optional. Integer number of the player. Default is 0 (gaia)
-// endWithFirst Optional. A boolean value. If true the 1st wall element in the wallPart array will finalize the wall. Default is true
-//
-// TODO: Maybe add angle offset for more generic looking?
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeLinearWall(startX, startY, targetX, targetY, wallPart, style, playerId, endWithFirst)
+/**
+ * Places a straight wall from a given point to another, using the provided
+ * wall parts repeatedly.
+ *
+ * Note: Any "bending" wall pieces passed will be complained about.
+ *
+ * @param {number} startX - Approximate start point of the wall.
+ * @param {number} startY - Approximate start point of the wall.
+ * @param {number} targetX - Approximate end point of the wall.
+ * @param {number} targetY - Approximate end point of the wall.
+ * @param {array} [wallPart=["tower", "long"]]
+ * @param {number} [playerId]
+ * @param {boolean} [endWithFirst] - If true, the first wall element will also be the last.
+ */
+function placeLinearWall(startX, startY, targetX, targetY, wallPart = undefined, style, playerId = 0, endWithFirst = true)
{
- // Setup optional arguments to the default
- wallPart = wallPart || ["tower", "wallLong"];
- playerId = playerId || 0;
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
- endWithFirst = typeof endWithFirst == "undefined" ? true : endWithFirst;
+ wallPart = wallPart || ["tower", "long"];
+ style = validateStyle(style, playerId);
// Check arguments
- for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
- {
- let bending = wallStyles[style][wallPart[elementIndex]].bending;
- if (bending != 0)
- warn("Bending is not supported by placeLinearWall but a bending wall element is used: " + wallPart[elementIndex] + " -> wallStyles[style][wallPart[elementIndex]].entity");
- }
+ for (let element of wallPart)
+ if (getWallElement(element, style).bend != 0)
+ warn("placeLinearWall : Bending is not supported by this function, but the following bending wall element was used: " + element);
+
// Setup number of wall parts
+
let totalLength = Math.euclidDistance2D(startX, startY, targetX, targetY);
- let wallPartLength = 0;
- for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
- wallPartLength += wallStyles[style][wallPart[elementIndex]].width;
- let numParts = 0;
+ let wallPartLength = getWallLength(style, wallPart);
+ let numParts = Math.ceil(totalLength / wallPartLength);
if (endWithFirst)
- numParts = ceil((totalLength - wallStyles[style][wallPart[0]].width) / wallPartLength);
- else
- numParts = ceil(totalLength / wallPartLength);
+ numParts = Math.ceil((totalLength - getWallElement(wallPart[0], style).length) / wallPartLength);
+
// Setup scale factor
- let scaleFactor = 1;
+ let scaleFactor = totalLength / (numParts * wallPartLength);
if (endWithFirst)
- scaleFactor = totalLength / (numParts * wallPartLength + wallStyles[style][wallPart[0]].width);
- else
- scaleFactor = totalLength / (numParts * wallPartLength);
+ scaleFactor = totalLength / (numParts * wallPartLength + getWallElement(wallPart[0], style).length);
+
// Setup angle
let wallAngle = getAngle(startX, startY, targetX, targetY); // NOTE: function "getAngle()" is about to be changed...
- let placeAngle = wallAngle - PI/2;
+ let placeAngle = wallAngle - Math.PI / 2;
+
// Place wall entities
let x = startX;
let y = startY;
+ let overlap = g_WallStyles[style].overlap;
for (let partIndex = 0; partIndex < numParts; ++partIndex)
{
for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
{
- let wallEle = wallStyles[style][wallPart[elementIndex]];
- // Width correction
- x += scaleFactor * wallEle.width/2 * cos(wallAngle);
- y += scaleFactor * wallEle.width/2 * sin(wallAngle);
+ let wallEle = getWallElement(wallPart[elementIndex], style);
+ let wallLength = (wallEle.length - overlap) / 2;
+ let distX = scaleFactor * wallLength * Math.cos(wallAngle);
+ let distY = scaleFactor * wallLength * Math.sin(wallAngle);
+
+ // Length correction
+ x += distX;
+ y += distY;
+
// Indent correction
- let placeX = x - wallEle.indent * sin(wallAngle);
- let placeY = y + wallEle.indent * cos(wallAngle);
+ let placeX = x - wallEle.indent * Math.sin(wallAngle);
+ let placeY = y + wallEle.indent * Math.cos(wallAngle);
+
// Placement
- let entity = wallEle.entity;
- if (entity !== undefined)
- placeObject(placeX, placeY, entity, playerId, placeAngle + wallEle.angle);
- x += scaleFactor * wallEle.width/2 * cos(wallAngle);
- y += scaleFactor * wallEle.width/2 * sin(wallAngle);
+ if (wallEle.templateName)
+ placeObject(placeX, placeY, wallEle.templateName, playerId, placeAngle + wallEle.angle);
+
+ // Prep for next object
+ x += distX;
+ y += distY;
}
}
if (endWithFirst)
{
- let wallEle = wallStyles[style][wallPart[0]];
- x += scaleFactor * wallEle.width/2 * cos(wallAngle);
- y += scaleFactor * wallEle.width/2 * sin(wallAngle);
- let entity = wallEle.entity;
- if (entity !== undefined)
- placeObject(x, y, entity, playerId, placeAngle + wallEle.angle);
+ let wallEle = getWallElement(wallPart[0], style);
+ let wallLength = (wallEle.length - overlap) / 2;
+ x += scaleFactor * wallLength * Math.cos(wallAngle);
+ y += scaleFactor * wallLength * Math.sin(wallAngle);
+ if (wallEle.templateName)
+ placeObject(x, y, wallEle.templateName, playerId, placeAngle + wallEle.angle);
}
}
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeCircularWall
-//
-// Place a circular wall of repeated wall elements given in the argument wallPart around centerX/centerY with the given radius
-// The wall can be opened forming more an arc than a circle if maxAngle < 2*PI
-// The orientation then determines where this open part faces (0 means right like unrotated building's drop-points)
-//
-// centerX/Y Coordinates of the circle's center
-// radius How wide the circle should be (approximate, especially if maxBendOff != 0)
-// wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["tower", "wallLong"]
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// playerId Optional. Integer number of the player. Default is 0 (gaia)
-// orientation Optional. Where the open part of the (circular) arc should face (if maxAngle is < 2*PI). Default is 0
-// maxAngle Optional. How far the wall should circumvent the center. Default is 2*PI (full circle)
-// endWithFirst Optional. Boolean. If true the 1st wall element in the wallPart array will finalize the wall. Default is false for full circles, else true
-// maxBendOff Optional. How irregular the circle should be. 0 means regular circle, PI/2 means very irregular. Default is 0 (regular circle)
-//
-// NOTE: Don't use wall elements with bending like corners!
-// TODO: Perhaps add eccentricity and maxBendOff functionality (untill now an unused argument)
-// TODO: Perhaps add functionality for spirals
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeCircularWall(centerX, centerY, radius, wallPart, style, playerId, orientation, maxAngle, endWithFirst, maxBendOff)
+/**
+ * Places a (semi-)circular wall of repeated wall elements around a central
+ * point at a given radius.
+ *
+ * The wall does not have to be closed, and can be left open in the form
+ * of an arc if maxAngle < 2 * Pi. In this case, the orientation determines
+ * where this open part faces, with 0 meaning "right" like an unrotated
+ * building's drop-point.
+ *
+ * Note: Any "bending" wall pieces passed will be complained about.
+ *
+ * @param {number} centerX - Center of the circle or arc.
+ * @param {number} centerY - Center of the circle or arc.
+ * @param (number} radius - Approximate radius of the circle. (Given the maxBendOff argument)
+ * @param {array} [wallPart]
+ * @param {string} [style]
+ * @param {number} [playerId]
+ * @param {number} [orientation] - Where the open part of the arc should face, if applicable.
+ * @param {number} [maxAngle] - How far the wall should circumscribe the center. Default is Pi * 2 (for a full circle).
+ * @param {boolean} [endWithFirst] - If true, the first wall element will also be the last. For full circles, the default is false. For arcs, true.
+ * @param {number} [maxBendOff] Optional. How irregular the circle should be. 0 means regular circle, PI/2 means very irregular. Default is 0 (regular circle)
+ */
+function placeCircularWall(centerX, centerY, radius, wallPart, style, playerId = 0, orientation = 0, maxAngle = Math.PI * 2, endWithFirst, maxBendOff = 0)
{
- // Setup optional arguments to the default
- wallPart = wallPart || ["tower", "wallLong"];
- playerId = playerId || 0;
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
- orientation = orientation || 0;
- maxAngle = maxAngle || 2*PI;
+ wallPart = wallPart || ["tower", "long"];
+ style = validateStyle(style, playerId);
if (endWithFirst === undefined)
- endWithFirst = maxAngle < 2*PI - 0.001; // Can this be done better?
-
- maxBendOff = maxBendOff || 0;
+ endWithFirst = maxAngle < Math.PI * 2 - 0.001; // Can this be done better?
// Check arguments
- if (maxBendOff > PI/2 || maxBendOff < 0)
- warn("placeCircularWall maxBendOff sould satisfy 0 < maxBendOff < PI/2 (~1.5) but it is: " + maxBendOff);
- for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
- {
- let bending = wallStyles[style][wallPart[elementIndex]].bending;
- if (bending != 0)
- warn("Bending is not supported by placeCircularWall but a bending wall element is used: " + wallPart[elementIndex]);
- }
+ if (maxBendOff > Math.PI / 2 || maxBendOff < 0)
+ warn("placeCircularWall : maxBendOff should satisfy 0 < maxBendOff < PI/2 (~1.5rad) but it is: " + maxBendOff);
+
+ for (let element of wallPart)
+ if (getWallElement(element, style).bend != 0)
+ warn("placeCircularWall : Bending is not supported by this function, but the following bending wall element was used: " + element);
+
// Setup number of wall parts
let totalLength = maxAngle * radius;
- let wallPartLength = 0;
- for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
- wallPartLength += wallStyles[style][wallPart[elementIndex]].width;
- let numParts = 0;
+ let wallPartLength = getWallLength(style, wallPart);
+ let numParts = Math.ceil(totalLength / wallPartLength);
if (endWithFirst)
- numParts = ceil((totalLength - wallStyles[style][wallPart[0]].width) / wallPartLength);
- else
- numParts = ceil(totalLength / wallPartLength);
+ numParts = Math.ceil((totalLength - getWallElement(wallPart[0], style).length) / wallPartLength);
// Setup scale factor
- let scaleFactor = 1;
+ let scaleFactor = totalLength / (numParts * wallPartLength);
if (endWithFirst)
- scaleFactor = totalLength / (numParts * wallPartLength + wallStyles[style][wallPart[0]].width);
- else
- scaleFactor = totalLength / (numParts * wallPartLength);
+ scaleFactor = totalLength / (numParts * wallPartLength + getWallElement(wallPart[0], style).length);
// Place wall entities
- let actualAngle = orientation + (2*PI - maxAngle) / 2;
- let x = centerX + radius*cos(actualAngle);
- let y = centerY + radius*sin(actualAngle);
+ let actualAngle = orientation + (Math.PI * 2 - maxAngle) / 2;
+ let x = centerX + radius * Math.cos(actualAngle);
+ let y = centerY + radius * Math.sin(actualAngle);
+ let overlap = g_WallStyles[style].overlap;
for (let partIndex = 0; partIndex < numParts; ++partIndex)
- for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex)
+ for (let wallEle of wallPart)
{
- let wallEle = wallStyles[style][wallPart[elementIndex]];
+ wallEle = getWallElement(wallEle, style);
+
// Width correction
- let addAngle = scaleFactor * wallEle.width / radius;
- let targetX = centerX + radius * cos(actualAngle + addAngle);
- let targetY = centerY + radius * sin(actualAngle + addAngle);
- let placeX = x + (targetX - x)/2;
- let placeY = y + (targetY - y)/2;
- let placeAngle = actualAngle + addAngle/2;
+ let addAngle = scaleFactor * (wallEle.length - overlap) / radius;
+ let targetX = centerX + radius * Math.cos(actualAngle + addAngle);
+ let targetY = centerY + radius * Math.sin(actualAngle + addAngle);
+ let placeX = x + (targetX - x) / 2;
+ let placeY = y + (targetY - y) / 2;
+ let placeAngle = actualAngle + addAngle / 2;
+
// Indent correction
- placeX -= wallEle.indent * cos(placeAngle);
- placeY -= wallEle.indent * sin(placeAngle);
+ placeX -= wallEle.indent * Math.cos(placeAngle);
+ placeY -= wallEle.indent * Math.sin(placeAngle);
+
// Placement
- let entity = wallEle.entity;
- if (entity !== undefined)
- placeObject(placeX, placeY, entity, playerId, placeAngle + wallEle.angle);
+ if (wallEle.templateName)
+ placeObject(placeX, placeY, wallEle.templateName, playerId, placeAngle + wallEle.angle);
+
// Prepare for the next wall element
actualAngle += addAngle;
- x = centerX + radius*cos(actualAngle);
- y = centerY + radius*sin(actualAngle);
+ x = centerX + radius * Math.cos(actualAngle);
+ y = centerY + radius * Math.sin(actualAngle);
}
if (endWithFirst)
{
- let wallEle = wallStyles[style][wallPart[0]];
- let addAngle = scaleFactor * wallEle.width / radius;
- let targetX = centerX + radius * cos(actualAngle + addAngle);
- let targetY = centerY + radius * sin(actualAngle + addAngle);
- let placeX = x + (targetX - x)/2;
- let placeY = y + (targetY - y)/2;
- let placeAngle = actualAngle + addAngle/2;
- placeObject(placeX, placeY, wallEle.entity, playerId, placeAngle + wallEle.angle);
+ let wallEle = getWallElement(wallPart[0], style);
+ let addAngle = scaleFactor * wallEle.length / radius;
+ let targetX = centerX + radius * Math.cos(actualAngle + addAngle);
+ let targetY = centerY + radius * Math.sin(actualAngle + addAngle);
+ let placeX = x + (targetX - x) / 2;
+ let placeY = y + (targetY - y) / 2;
+ let placeAngle = actualAngle + addAngle / 2;
+ placeObject(placeX, placeY, wallEle.templateName, playerId, placeAngle + wallEle.angle);
}
}
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placePolygonalWall
-//
-// Place a polygonal wall of repeated wall elements given in the argument wallPart around centerX/centerY with the given radius
-//
-// centerX/Y Coordinates of the polygon's center
-// radius How wide the circle should be in which the polygon fits
-// wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["wallLong", "tower"]
-// cornerWallElement Optional. Wall element to be placed at the polygon's corners. Default is "tower"
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// playerId Optional. Integer number of the player. Default is 0 (gaia)
-// orientation Optional. Angle from the center to the first linear wall part placed. Default is 0 (towards positive X/right)
-// numCorners Optional. How many corners the polygon will have. Default is 8 (matching a civ centers territory)
-// skipFirstWall Optional. Boolean. If the first linear wall part will be left opened as entrance. Default is true
-//
-// NOTE: Don't use wall elements with bending like corners!
-// TODO: Replace skipFirstWall with firstWallPart to enable gate/defended entrance placement
-// TODO: Check some arguments
-// TODO: Add eccentricity and perhaps make it just call placeIrregularPolygonalWall with irregularity = 0
-/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placePolygonalWall(centerX, centerY, radius, wallPart, cornerWallElement, style, playerId, orientation, numCorners, skipFirstWall = true)
+/**
+ * Places a polygonal wall of repeated wall elements around a central
+ * point at a given radius.
+ *
+ * Note: Any "bending" wall pieces passed will be ignored.
+ *
+ * @param {number} centerX
+ * @param {number} centerY
+ * @param {number} radius
+ * @param {array} [wallPart]
+ * @param {string} [cornerWallElement] - Wall element to be placed at the polygon's corners.
+ * @param {string} [style]
+ * @param {number} [playerId]
+ * @param {number} [orientation] - Direction the first wall piece or opening in the wall faces.
+ * @param {number} [numCorners] - How many corners the polygon will have.
+ * @param {boolean} [skipFirstWall] - If the first linear wall part will be left opened as entrance.
+ */
+function placePolygonalWall(centerX, centerY, radius, wallPart, cornerWallElement = "tower", style, playerId = 0, orientation = 0, numCorners = 8, skipFirstWall = true)
{
- // Setup optional arguments to the default
- wallPart = wallPart || ["wallLong", "tower"];
- cornerWallElement = cornerWallElement || "tower"; // Don't use wide elements for this. Not supported well...
- playerId = playerId || 0;
-
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
- orientation = orientation || 0;
- numCorners = numCorners || 8;
+ wallPart = wallPart || ["long", "tower"];
+ style = validateStyle(style, playerId);
// Setup angles
- let angleAdd = 2*PI/numCorners;
- let angleStart = orientation - angleAdd/2;
+ let angleAdd = Math.PI * 2 / numCorners;
+ let angleStart = orientation - angleAdd / 2;
+
// Setup corners
let corners = [];
for (let i = 0; i < numCorners; ++i)
- corners.push([centerX + radius*cos(angleStart + i*angleAdd), centerY + radius*sin(angleStart + i*angleAdd)]);
+ corners.push([
+ centerX + radius * Math.cos(angleStart + i * angleAdd),
+ centerY + radius * Math.sin(angleStart + i * angleAdd)
+ ]);
+
// Place Corners and walls
for (let i = 0; i < numCorners; ++i)
{
let angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY);
- placeObject(corners[i][0], corners[i][1], wallStyles[style][cornerWallElement].entity, playerId, angleToCorner);
+ placeObject(corners[i][0], corners[i][1], getWallElement(cornerWallElement, style).templateName, playerId, angleToCorner);
if (!skipFirstWall || i != 0)
+ {
+ let cornerLength = getWallElement(cornerWallElement, style).length / 2;
+ let cornerAngle = angleToCorner + angleAdd / 2;
+ let cornerX = cornerLength * Math.sin(cornerAngle);
+ let cornerY = cornerLength * Math.cos(cornerAngle);
+ let targetCorner = (i + 1) % numCorners;
placeLinearWall(
// Adjustment to the corner element width (approximately)
- corners[i][0] + wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAdd/2), // startX
- corners[i][1] - wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAdd/2), // startY
- corners[(i+1)%numCorners][0] - wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAdd/2), // targetX
- corners[(i+1)%numCorners][1] + wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAdd/2), // targetY
+ corners[i][0] + cornerX, // startX
+ corners[i][1] - cornerY, // startY
+ corners[targetCorner][0] - cornerX, // targetX
+ corners[targetCorner][1] + cornerY, // targetY
wallPart, style, playerId);
+ }
}
}
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeIrregularPolygonalWall
-//
-// Place an irregular polygonal wall of some wall parts to choose from around centerX/centerY with the given radius
-//
-// centerX/Y Coordinates of the polygon's center
-// radius How wide the circle should be in which the polygon fits
-// cornerWallElement Optional. Wall element to be placed at the polygon's corners. Default is "tower"
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// playerId Optional. Integer number of the player. Default is 0 (gaia)
-// orientation Optional. Angle from the center to the first linear wall part placed. Default is 0 (towards positive X/right)
-// numCorners Optional. How many corners the polygon will have. Default is 8 (matching a civ centers territory)
-// irregularity Optional. How irregular the polygon will be. 0 means regular, 1 means VERY irregular. Default is 0.5
-// skipFirstWall Optional. Boolean. If the first linear wall part will be left opened as entrance. Default is true
-// wallPartsAssortment Optional. An array of wall part arrays to choose from for each linear wall connecting the corners. Default is hard to describe ^^
-//
-// NOTE: wallPartsAssortment is put to the end because it's hardest to set
-// NOTE: Don't use wall elements with bending like corners!
-// TODO: Replace skipFirstWall with firstWallPart to enable gate/defended entrance placement
-// TODO: Check some arguments
-// TODO: Perhaps add eccentricity
-////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement, style, playerId, orientation, numCorners, irregularity, skipFirstWall, wallPartsAssortment)
+/**
+ * Places an irregular polygonal wall consisting of parts semi-randomly
+ * chosen from a provided assortment, built around a central point at a
+ * given radius.
+ *
+ * Note: Any "bending" wall pieces passed will be ... I'm not sure. TODO: test what happens!
+ *
+ * Note: The wallPartsAssortment is last because it's the hardest to set.
+ *
+ * @param {number} centerX
+ * @param {number} centerY
+ * @param {number} radius
+ * @param {string} [cornerWallElement] - Wall element to be placed at the polygon's corners.
+ * @param {string} [style]
+ * @param {number} [playerId]
+ * @param {number} [orientation] - Direction the first wallpiece or opening in the wall faces.
+ * @param {number} [numCorners] - How many corners the polygon will have.
+ * @param {number} [irregularity] - How irregular the polygon will be. 0 = regular, 1 = VERY irregular.
+ * @param {boolean} [skipFirstWall] - If true, the first linear wall part will be left open as an entrance.
+ * @param {array} [wallPartsAssortment] - An array of wall part arrays to choose from for each linear wall connecting the corners.
+ */
+function placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement = "tower", style, playerId = 0, orientation = 0, numCorners, irregularity = 0.5, skipFirstWall = false, wallPartsAssortment)
{
- // Setup optional arguments
- playerId = playerId || 0;
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
+ style = validateStyle(style, playerId);
+ numCorners = numCorners || randIntInclusive(5, 7);
// Generating a generic wall part assortment with each wall part including 1 gate lengthened by walls and towers
// NOTE: It might be a good idea to write an own function for that...
- let defaultWallPartsAssortment = [["wallShort"], ["wall"], ["wallLong"], ["gate", "tower", "wallShort"]];
+ let defaultWallPartsAssortment = [["short"], ["medium"], ["long"], ["gate", "tower", "short"]];
let centeredWallPart = ["gate"];
- let extandingWallPartAssortment = [["tower", "wallLong"], ["tower", "wall"]];
+ let extendingWallPartAssortment = [["tower", "long"], ["tower", "medium"]];
defaultWallPartsAssortment.push(centeredWallPart);
- for (let i = 0; i < extandingWallPartAssortment.length; ++i)
+ for (let assortment of extendingWallPartAssortment)
{
let wallPart = centeredWallPart;
for (let j = 0; j < radius; ++j)
{
- if (j%2 == 0)
- wallPart = wallPart.concat(extandingWallPartAssortment[i]);
+ if (j % 2 == 0)
+ wallPart = wallPart.concat(assortment);
else
{
- extandingWallPartAssortment[i].reverse();
- wallPart = extandingWallPartAssortment[i].concat(wallPart);
- extandingWallPartAssortment[i].reverse();
+ assortment.reverse();
+ wallPart = assortment.concat(wallPart);
+ assortment.reverse();
}
defaultWallPartsAssortment.push(wallPart);
}
}
// Setup optional arguments to the default
wallPartsAssortment = wallPartsAssortment || defaultWallPartsAssortment;
- cornerWallElement = cornerWallElement || "tower"; // Don't use wide elements for this. Not supported well...
- style = style || "palisades";
- playerId = playerId || 0;
- orientation = orientation || 0;
- numCorners = numCorners || randIntInclusive(5, 7);
- irregularity = irregularity || 0.5;
- skipFirstWall = skipFirstWall || false;
// Setup angles
- let angleToCover = 2*PI;
+ let angleToCover = Math.PI * 2;
let angleAddList = [];
for (let i = 0; i < numCorners; ++i)
{
// Randomize covered angles. Variety scales down with raising angle though...
- angleAddList.push(angleToCover/(numCorners-i) * (1 + randFloat(-irregularity, irregularity)));
+ angleAddList.push(angleToCover / (numCorners - i) * (1 + randFloat(-irregularity, irregularity)));
angleToCover -= angleAddList[angleAddList.length - 1];
}
+
// Setup corners
let corners = [];
- let angleActual = orientation - angleAddList[0]/2;
+ let angleActual = orientation - angleAddList[0] / 2;
for (let i = 0; i < numCorners; ++i)
{
- corners.push([centerX + radius*cos(angleActual), centerY + radius*sin(angleActual)]);
+ corners.push([
+ centerX + radius * Math.cos(angleActual),
+ centerY + radius * Math.sin(angleActual)
+ ]);
if (i < numCorners - 1)
- angleActual += angleAddList[i+1];
+ angleActual += angleAddList[i + 1];
}
+
// Setup best wall parts for the different walls (a bit confusing naming...)
let wallPartLengths = [];
let maxWallPartLength = 0;
- for (let partIndex = 0; partIndex < wallPartsAssortment.length; ++partIndex)
+ for (let wallPart of wallPartsAssortment)
{
- let length = wallPartLengths[partIndex];
- wallPartLengths.push(getWallLength(wallPartsAssortment[partIndex], style));
+ let length = getWallLength(style, wallPart);
+ wallPartLengths.push(length);
if (length > maxWallPartLength)
maxWallPartLength = length;
}
+
let wallPartList = []; // This is the list of the wall parts to use for the walls between the corners, not to confuse with wallPartsAssortment!
for (let i = 0; i < numCorners; ++i)
{
- let bestWallPart = []; // This is a simpel wall part not a wallPartsAssortment!
- let bestWallLength = 99999999;
- // NOTE: This is not exactly like the length the wall will be in the end. Has to be tweaked...
- let wallLength = Math.euclidDistance2D(corners[i][0], corners[i][1], corners[(i + 1) % numCorners][0], corners[(i + 1) % numCorners][1]);
- let numWallParts = ceil(wallLength/maxWallPartLength);
+ let bestWallPart = []; // This is a simple wall part not a wallPartsAssortment!
+ let bestWallLength = Infinity;
+ let targetCorner = (i + 1) % numCorners;
+ // NOTE: This is not quite the length the wall will be in the end. Has to be tweaked...
+ let wallLength = Math.euclidDistance2D(corners[i][0], corners[i][1], corners[targetCorner][0], corners[targetCorner][1]);
+ let numWallParts = Math.ceil(wallLength / maxWallPartLength);
for (let partIndex = 0; partIndex < wallPartsAssortment.length; ++partIndex)
{
- let linearWallLength = numWallParts*wallPartLengths[partIndex];
+ let linearWallLength = numWallParts * wallPartLengths[partIndex];
if (linearWallLength < bestWallLength && linearWallLength > wallLength)
{
bestWallPart = wallPartsAssortment[partIndex];
@@ -910,76 +814,79 @@ function placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement
}
wallPartList.push(bestWallPart);
}
+
// Place Corners and walls
for (let i = 0; i < numCorners; ++i)
{
let angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY);
- placeObject(corners[i][0], corners[i][1], wallStyles[style][cornerWallElement].entity, playerId, angleToCorner);
+ placeObject(corners[i][0], corners[i][1], getWallElement(cornerWallElement, style).templateName, playerId, angleToCorner);
if (!skipFirstWall || i != 0)
+ {
+ let cornerLength = getWallElement(cornerWallElement, style).length / 2;
+ let targetCorner = (i + 1) % numCorners;
+ let startAngle = angleToCorner + angleAddList[i] / 2;
+ let targetAngle = angleToCorner + angleAddList[targetCorner] / 2;
placeLinearWall(
// Adjustment to the corner element width (approximately)
- corners[i][0] + wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAddList[i]/2), // startX
- corners[i][1] - wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAddList[i]/2), // startY
- corners[(i+1)%numCorners][0] - wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAddList[(i+1)%numCorners]/2), // targetX
- corners[(i+1)%numCorners][1] + wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAddList[(i+1)%numCorners]/2), // targetY
+ corners[i][0] + cornerLength * Math.sin(startAngle), // startX
+ corners[i][1] - cornerLength * Math.cos(startAngle), // startY
+ corners[targetCorner][0] - cornerLength * Math.sin(targetAngle), // targetX
+ corners[targetCorner][1] + cornerLength * Math.cos(targetAngle), // targetY
wallPartList[i], style, playerId, false);
+ }
}
}
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-// placeGenericFortress
-//
-// Places a generic fortress with towers at the edges connected with long walls and gates (entries until gates work)
-// This is the default Iberian civ bonus starting wall
-//
-// centerX/Y The approximate center coordinates of the fortress
-// radius The approximate radius of the wall to be placed
-// playerId Optional. Integer number of the player. Default is 0 (gaia)
-// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia
-// irregularity Optional. Float between 0 (circle) and 1 (very spiky), default is 1/2
-// gateOccurence Optional. Integer number, every n-th walls will be a gate instead. Default is 3
-// maxTrys Optional. How often the function tries to find a better fitting shape at max. Default is 100
-//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-function placeGenericFortress(centerX, centerY, radius, playerId, style, irregularity, gateOccurence, maxTrys)
+/**
+ * Places a generic fortress with towers at the edges connected with long
+ * walls and gates, positioned around a central point at a given radius.
+ *
+ * The difference between this and the other two Fortress placement functions
+ * is that those place a predefined fortress, regardless of terrain type.
+ * This function attempts to intelligently place a wall circuit around
+ * the central point taking into account terrain and other obstacles.
+ *
+ * This is the default Iberian civ bonus starting wall.
+ *
+ * @param {number} centerX - The approximate center coordinates of the fortress
+ * @param {number} centerY - The approximate center coordinates of the fortress
+ * @param {number} [radius] - The approximate radius of the wall to be placed.
+ * @param {number} [playerId]
+ * @param {string} [style]
+ * @param {number} [irregularity] - 0 = circle, 1 = very spiky
+ * @param {number} [gateOccurence] - Integer number, every n-th walls will be a gate instead.
+ * @param {number} [maxTrys] - How often the function tries to find a better fitting shape.
+ */
+function placeGenericFortress(centerX, centerY, radius = 20, playerId = 0, style, irregularity = 0.5, gateOccurence = 3, maxTrys = 100)
{
- // Setup optional arguments
- radius = radius || 20;
- playerId = playerId || 0;
- if (!wallStyles.hasOwnProperty(style))
- {
- if (playerId == 0)
- style = style || "palisades";
- else
- style = getCivCode(playerId);
- }
- irregularity = irregularity || 1/2;
- gateOccurence = gateOccurence || 3;
- maxTrys = maxTrys || 100;
+ style = validateStyle(style, playerId);
// Setup some vars
- let startAngle = randFloat(0, 2*PI);
- let actualOffX = radius*cos(startAngle);
- let actualOffY = radius*sin(startAngle);
+ let startAngle = randFloat(0, Math.PI * 2);
+ let actualOffX = radius * Math.cos(startAngle);
+ let actualOffY = radius * Math.sin(startAngle);
let actualAngle = startAngle;
- let pointDistance = wallStyles[style].wallLong.width + wallStyles[style].tower.width;
+ let pointDistance = getWallLength(style, ["long", "tower"]);
+
// Searching for a well fitting point derivation
let tries = 0;
- let bestPointDerivation = undefined;
+ let bestPointDerivation;
let minOverlap = 1000;
- let overlap = undefined;
- while (tries < maxTrys && minOverlap > wallStyles[style].tower.width / 10)
+ let overlap;
+ while (tries < maxTrys && minOverlap > g_WallStyles[style].overlap)
{
let pointDerivation = [];
let distanceToTarget = 1000;
let targetReached = false;
while (!targetReached)
{
- let indent = randFloat(-irregularity*pointDistance, irregularity*pointDistance);
+ let indent = randFloat(-irregularity * pointDistance, irregularity * pointDistance);
let tmpAngle = getAngle(actualOffX, actualOffY,
- (radius + indent)*cos(actualAngle + (pointDistance / radius)),
- (radius + indent)*sin(actualAngle + (pointDistance / radius)));
- actualOffX += pointDistance*cos(tmpAngle);
- actualOffY += pointDistance*sin(tmpAngle);
+ (radius + indent) * Math.cos(actualAngle + pointDistance / radius),
+ (radius + indent) * Math.sin(actualAngle + pointDistance / radius)
+ );
+ actualOffX += pointDistance * Math.cos(tmpAngle);
+ actualOffY += pointDistance * Math.sin(tmpAngle);
actualAngle = getAngle(0, 0, actualOffX, actualOffY);
pointDerivation.push([actualOffX, actualOffY]);
distanceToTarget = Math.euclidDistance2D(actualOffX, actualOffY, ...pointDerivation[0]);
@@ -995,9 +902,10 @@ function placeGenericFortress(centerX, centerY, radius, playerId, style, irregul
}
}
}
- +tries;
+ ++tries;
}
log("placeGenericFortress: Reduced overlap to " + minOverlap + " after " + tries + " tries");
+
// Place wall
for (let pointIndex = 0; pointIndex < bestPointDerivation.length; ++pointIndex)
{
@@ -1006,27 +914,29 @@ function placeGenericFortress(centerX, centerY, radius, playerId, style, irregul
let targetX = centerX + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][0];
let targetY = centerY + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][1];
let angle = getAngle(startX, startY, targetX, targetY);
- let wallElement = "wallLong";
- if ((pointIndex + 1) % gateOccurence == 0)
- wallElement = "gate";
- let entity = wallStyles[style][wallElement].entity;
- if (entity)
+
+ let element = (pointIndex + 1) % gateOccurence == 0 ? "gate" : "long";
+ element = getWallElement(element, style);
+ if (element.templateName)
+ {
+ let dist = Math.euclidDistance2D(startX, startY, targetX, targetY) / 2;
placeObject(
- startX + (Math.euclidDistance2D(startX, startY, targetX, targetY) / 2) * Math.cos(angle),
- startY + (Math.euclidDistance2D(startX, startY, targetX, targetY) / 2) * Math.sin(angle),
- entity,
- playerId,
- angle - Math.PI / 2 + wallStyles[style][wallElement].angle);
+ startX + dist * Math.cos(angle), // placeX
+ startY + dist * Math.sin(angle), // placeY
+ element.templateName, playerId, angle - Math.PI / 2 + element.angle
+ );
+ }
// Place tower
startX = centerX + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][0];
startY = centerY + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][1];
angle = getAngle(startX, startY, targetX, targetY);
+
+ let tower = getWallElement("tower", style);
placeObject(
centerX + bestPointDerivation[pointIndex][0],
centerY + bestPointDerivation[pointIndex][1],
- wallStyles[style].tower.entity,
- playerId,
- angle - PI/2 + wallStyles[style].tower.angle);
+ tower.templateName, playerId, angle - Math.PI / 2 + tower.angle
+ );
}
}
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 22d80334c8..d992009b09 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -1247,6 +1247,9 @@ GuiInterface.prototype.SetWallPlacementPreview = function(player, cmd)
// Create cache entries for templates we haven't seen before
for (let type in wallSet.templates)
{
+ if (type == "curves")
+ continue;
+
let tpl = wallSet.templates[type];
if (!(tpl in this.placementWallEntities))
{
diff --git a/binaries/data/mods/public/simulation/components/WallSet.js b/binaries/data/mods/public/simulation/components/WallSet.js
index 52eb35a6a3..f112ea6897 100644
--- a/binaries/data/mods/public/simulation/components/WallSet.js
+++ b/binaries/data/mods/public/simulation/components/WallSet.js
@@ -21,6 +21,21 @@ WallSet.prototype.Schema =
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
diff --git a/binaries/data/mods/public/simulation/data/civs/athen.json b/binaries/data/mods/public/simulation/data/civs/athen.json
index d525ad0673..60b73bee1f 100644
--- a/binaries/data/mods/public/simulation/data/civs/athen.json
+++ b/binaries/data/mods/public/simulation/data/civs/athen.json
@@ -110,6 +110,11 @@
"Special":"Train heroes and research technology pertaining to heroes."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/athen_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/brit.json b/binaries/data/mods/public/simulation/data/civs/brit.json
index d9ce7d4cf3..3f71b139d6 100644
--- a/binaries/data/mods/public/simulation/data/civs/brit.json
+++ b/binaries/data/mods/public/simulation/data/civs/brit.json
@@ -88,6 +88,11 @@
"Special": ""
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/brit_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/cart.json b/binaries/data/mods/public/simulation/data/civs/cart.json
index ebfcfcc167..39e29f47b0 100644
--- a/binaries/data/mods/public/simulation/data/civs/cart.json
+++ b/binaries/data/mods/public/simulation/data/civs/cart.json
@@ -113,6 +113,12 @@
"Special":"Hire Iberian mercenaries."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/cart_wallset_short",
+ "structures/cart_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/gaul.json b/binaries/data/mods/public/simulation/data/civs/gaul.json
index 1b6ac80af8..1646b2bf56 100644
--- a/binaries/data/mods/public/simulation/data/civs/gaul.json
+++ b/binaries/data/mods/public/simulation/data/civs/gaul.json
@@ -88,6 +88,11 @@
"Special": ""
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/gaul_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/iber.json b/binaries/data/mods/public/simulation/data/civs/iber.json
index f8231f8ddf..ab3cdf5196 100644
--- a/binaries/data/mods/public/simulation/data/civs/iber.json
+++ b/binaries/data/mods/public/simulation/data/civs/iber.json
@@ -86,6 +86,11 @@
"Special": "Defensive Aura - Gives all Iberian units and buildings within vision range of the monument a 10-15% attack boost. Build Limit: Only 5 may be built per map."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/iber_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/mace.json b/binaries/data/mods/public/simulation/data/civs/mace.json
index 5e1d78329f..940088457a 100644
--- a/binaries/data/mods/public/simulation/data/civs/mace.json
+++ b/binaries/data/mods/public/simulation/data/civs/mace.json
@@ -115,6 +115,11 @@
"Special":"Constructs and upgrades all Macedonian siege engines."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/mace_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/maur.json b/binaries/data/mods/public/simulation/data/civs/maur.json
index f615870490..9e71481f8f 100644
--- a/binaries/data/mods/public/simulation/data/civs/maur.json
+++ b/binaries/data/mods/public/simulation/data/civs/maur.json
@@ -96,6 +96,11 @@
"Special":"Contentment: +10% Health and +10% resource gathering rates for all citizens and allied citizens within its range. Can be built anywhere except in enemy territory. Max Built: 10."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/maur_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/pers.json b/binaries/data/mods/public/simulation/data/civs/pers.json
index 9ee0e9f175..be181a75a9 100644
--- a/binaries/data/mods/public/simulation/data/civs/pers.json
+++ b/binaries/data/mods/public/simulation/data/civs/pers.json
@@ -106,6 +106,11 @@
"Special": "Train heroes and Persian Immortals. Gives a slow trickle of all resources as 'Satrapy Tribute.'"
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/pers_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/ptol.json b/binaries/data/mods/public/simulation/data/civs/ptol.json
index ce6781e6eb..2115beae51 100644
--- a/binaries/data/mods/public/simulation/data/civs/ptol.json
+++ b/binaries/data/mods/public/simulation/data/civs/ptol.json
@@ -115,6 +115,11 @@
"Special":"When built along the shoreline, removes shroud of darkness over all the water, revealing all the coast lines on the map. Limit: 1."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/ptol_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/rome.json b/binaries/data/mods/public/simulation/data/civs/rome.json
index ae9daf3405..b161d11f42 100644
--- a/binaries/data/mods/public/simulation/data/civs/rome.json
+++ b/binaries/data/mods/public/simulation/data/civs/rome.json
@@ -90,6 +90,11 @@
"Special": "Can be built in neutral and enemy territory to strangle enemy towns."
}
],
+ "WallSets":
+ [
+ "structures/rome_wallset_stone",
+ "structures/rome_wallset_siege"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/sele.json b/binaries/data/mods/public/simulation/data/civs/sele.json
index ba41f982bd..32397b3c8d 100644
--- a/binaries/data/mods/public/simulation/data/civs/sele.json
+++ b/binaries/data/mods/public/simulation/data/civs/sele.json
@@ -114,6 +114,11 @@
"Special":"This is the Seleucid expansion building, similar to Civic Centers for other factions. It is weaker and carries a smaller territory influence, but is cheaper and built faster."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/sele_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/data/civs/spart.json b/binaries/data/mods/public/simulation/data/civs/spart.json
index 29dfc86965..751c787aaf 100644
--- a/binaries/data/mods/public/simulation/data/civs/spart.json
+++ b/binaries/data/mods/public/simulation/data/civs/spart.json
@@ -106,6 +106,11 @@
"Special":"Train heroes and Spartiates and research technologies related to them."
}
],
+ "WallSets":
+ [
+ "other/wallset_palisade",
+ "structures/spart_wallset_stone"
+ ],
"StartEntities":
[
{
diff --git a/binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml b/binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml
index 33a56dde47..35050481a1 100644
--- a/binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml
+++ b/binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml
@@ -12,9 +12,14 @@
other/palisades_rocks_tower
other/palisades_rocks_gate
+ other/palisades_rocks_fort
other/palisades_rocks_long
other/palisades_rocks_medium
other/palisades_rocks_short
+
+ other/palisades_rocks_curve
+
+ other/palisades_rocks_end
diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml b/binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml
index 6de8ff01f4..6ea2367c21 100644
--- a/binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml
+++ b/binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml
@@ -12,6 +12,7 @@
structures/rome_siege_wall_tower
structures/rome_siege_wall_gate
+ structures/rome_army_camp
structures/rome_siege_wall_long
structures/rome_siege_wall_medium
structures/rome_siege_wall_short