Index: binaries/data/mods/public/globalscripts/Templates.js =================================================================== --- binaries/data/mods/public/globalscripts/Templates.js +++ binaries/data/mods/public/globalscripts/Templates.js @@ -219,7 +219,7 @@ 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 @@ }; if (template.WallSet) + { ret.wallSet = { "templates": { "tower": template.WallSet.Templates.Tower, "gate": template.WallSet.Templates.Gate, + "fort": template.WallSet.Templates.Fort || "structures/{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; } Index: binaries/data/mods/public/gui/reference/common/load.js =================================================================== --- binaries/data/mods/public/gui/reference/common/load.js +++ binaries/data/mods/public/gui/reference/common/load.js @@ -228,6 +228,9 @@ 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 @@ 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 Index: binaries/data/mods/public/maps/random/caledonian_meadows.js =================================================================== --- binaries/data/mods/public/maps/random/caledonian_meadows.js +++ binaries/data/mods/public/maps/random/caledonian_meadows.js @@ -65,24 +65,46 @@ } // Food, fences with domestic animals -wallStyles.other.sheepIn = new WallElement("sheepIn", "gaia/fauna_sheep", PI / 4, -1.5, 0.75, PI/2); -wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5); -wallStyles.other.sheep = new WallElement("sheep", "gaia/fauna_sheep", 0, 0, 0.75); -wallStyles.other.farm = new WallElement("farm", "structures/brit_farmstead", PI, 0, -3); +g_WallStyles.other = { + "overlap": 0, + "fence": readyWallElement("other/fence_long", "gaia"), + "fence_short": readyWallElement("other/fence_short", "gaia"), + "bench": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "entPath": "other/bench" }, + "sheep": { "angle": 0, "length": 0, "indent": 0.75, "bend": 0, "entPath": "gaia/fauna_sheep" }, + "foodBin": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "entPath": "gaia/special_treasure_food_bin" }, + "farmstead": { "angle": Math.PI, "length": 0, "indent": -3, "bend": 0, "entPath": "structures/brit_farmstead" } +}; + let fences = [ - new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]), - new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]), new Fortress("fence", [ - "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn", - "fence", "sheepIn", "fence_short", "sheep", "fence" + "foodBin", "farmstead", "bench", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence" + ]), + new Fortress("fence", [ + "foodBin", "farmstead", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "bench", "sheep", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence" + ]), + new Fortress("fence", [ + "foodBin", "farmstead", "turn_0.5", "bench", "turn_-0.5", "fence_short", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ - "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn", - "fence", "sheepIn", "fence_short", "sheep", "fence" + "foodBin", "farmstead", "turn_0.5", "fence_short", "turn_-0.5", "bench", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence" ]), new Fortress("fence", [ - "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", - "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence" + "foodBin", "farmstead", "fence", + "turn_0.25", "sheep", "turn_0.25", "bench", "sheep", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence", + "turn_0.25", "sheep", "turn_0.25", "fence_short", "sheep", "fence" ]) ]; let num = fences.length; Index: binaries/data/mods/public/maps/random/danubius.js =================================================================== --- binaries/data/mods/public/maps/random/danubius.js +++ binaries/data/mods/public/maps/random/danubius.js @@ -228,14 +228,18 @@ let rugRadius = mRadius * 0.6; let goatRadius = mRadius * 0.8; - wallStyles.celt_ritual = { - "female": new WallElement("female", oFemale, PI, femaleRadius, 0, 2 * PI / femaleCount), - "skirmisher": new WallElement("skirmisher", oSkirmisher, PI, maleRadius, 0, 2 * PI / maleCount), - "healer": new WallElement("healer", oHealer, PI, maleRadius, 0, 2 * PI / maleCount), - "fanatic": new WallElement("fanatic", oNakedFanatic, PI, maleRadius, 0, 2 * PI / maleCount), - "bench": new WallElement("bench", aBench, PI/2, benchRadius, 0, 2 * PI / benchCount), - "rug": new WallElement("rug", aRug, 0, rugRadius, 0, 2 * PI / rugCount), - "goat": new WallElement("goat", oGoat, PI, goatRadius, 0, 2 * PI / goatCount), + let calcBend = entCount => Math.PI * 2 / entCount; + let maleBend = calcBend(maleCount); + + g_WallStyles.celt_ritual = { + "overlap": 0, + "female": { "angle": Math.PI, "length": femaleRadius, "indent": 0, "bend": calcBend(femaleCount), "entPath": oFemale }, + "skirmisher": { "angle": Math.PI, "length": maleRadius, "indent": 0, "bend": maleBend, "entPath": oSkirmisher }, + "healer": { "angle": Math.PI, "length": maleRadius, "indent": 0, "bend": maleBend, "entPath": oHealer }, + "fanatic": { "angle": Math.PI, "length": maleRadius, "indent": 0, "bend": maleBend, "entPath": oNakedFanatic }, + "bench": { "angle": Math.PI / 2, "length": benchRadius, "indent": 0, "bend": calcBend(benchCount), "entPath": aBench }, + "rug": { "angle": 0, "length": rugRadius, "indent": 0, "bend": calcBend(rugCount), "entPath": aRug }, + "goat": { "angle": Math.PI, "length": goatRadius, "indent": 0, "bend": calcBend(goatCount), "entPath": oGoat } }; placeCustomFortress(mX, mZ, new Fortress("celt ritual females", new Array(femaleCount).fill("female")), "celt_ritual", 0, 0); @@ -258,46 +262,45 @@ // Place palisade fortress and some city buildings // Use actors to avoid players capturing the buildings - wallStyles.gaul.house = new WallElement("house", oHouse, PI, 0, 4); - wallStyles.gaul.hut = new WallElement("hut", oHut, PI, 0, 4); - wallStyles.gaul.longhouse = new WallElement("longhouse", oLongHouse, PI, 0, 4); - wallStyles.gaul.tavern = new WallElement("tavern", oTavern, PI * 3/2, 0, 4); - wallStyles.gaul.temple = new WallElement("temple", oTemple, PI * 3/2, 0, 4); - wallStyles.gaul.defense_tower = new WallElement("defense_tower", - mapSize >= normalMapSize ? oTower : oPalisadeTower, PI/2, 0, 4); - wallStyles.gaul.palisade_tower = wallStyles.palisades.tower; + g_WallStyles.gaul = clone(g_WallStyles.gaul_stone); + g_WallStyles.gaul.house = { "angle": Math.PI, "length": 0, "indent": 4, "bend": 0, "entPath": oHouse }; + g_WallStyles.gaul.hut = { "angle": Math.PI, "length": 0, "indent": 4, "bend": 0, "entPath": oHut }; + g_WallStyles.gaul.longhouse = { "angle": Math.PI, "length": 0, "indent": 4, "bend": 0, "entPath": oLongHouse }; + g_WallStyles.gaul.tavern = { "angle": Math.PI * 3/2, "length": 0, "indent": 4, "bend": 0, "entPath": oTavern }; + g_WallStyles.gaul.temple = { "angle": Math.PI * 3/2, "length": 0, "indent": 4, "bend": 0, "entPath": oTemple }; + g_WallStyles.gaul.defense_tower = { "angle": Math.PI / 2, "length": 0, "indent": 4, "bend": 0, "entPath": mapSize >= normalMapSize ? oTower : oPalisadeTower }; // Replace stone walls with palisade walls - for (let template of ["gate", "wallLong", "cornerIn", "cornerOut"]) - wallStyles.gaul[template] = wallStyles.palisades[template]; + for (let element of ["gate", "long", "short", "cornerIn", "cornerOut", "tower"]) + g_WallStyles.gaul[element] = getWallElement(element, "palisade"); let wall = [ - "gate", "hut", "palisade_tower", "wallLong", "wallLong", - "cornerIn", "defense_tower", "wallLong", "wallLong", "temple", - "palisade_tower", "wallLong", "house", "gate", "palisade_tower", "longhouse", "wallLong", "wallLong", - "cornerIn", "defense_tower", "wallLong", "tavern", "wallLong", "palisade_tower"]; + "gate", "hut", "tower", "long", "long", + "cornerIn", "defense_tower", "long", "long", "temple", + "tower", "long", "house", "short", "tower", "gate", "tower", "longhouse", "long", "long", + "cornerIn", "defense_tower", "long", "tavern", "long", "tower"]; wall = wall.concat(wall); - placeCustomFortress(gX, gZ, new Fortress("Geto-Dacian Tribal Confederation", wall), "gaul", 0, PI); + placeCustomFortress(gX, gZ, new Fortress("Geto-Dacian Tribal Confederation", wall), "gaul", 0, Math.PI); // Place spikes - wallStyles.palisades.tall_spikes = new WallElement("tall_spikes", oTallSpikes, PI/2, 2); - wallStyles.palisades.spikeIn = new WallElement("spikeIn", oAngleSpikes, -PI/4, 2.1, 0.7, PI/2); - wallStyles.palisades.spikeMid = new WallElement("spikeIn", oAngleSpikes, -PI/2, 0.7); - wallStyles.palisades.gateGap = new WallElement("gateGap", undefined, PI, 3.6); + g_WallStyles.palisade.spikes_tall = readyWallElement("other/palisades_tall_spikes", "gaia"); + g_WallStyles.palisade.spike_single = readyWallElement("other/palisades_angle_spike", "gaia"); - let fourSpikes = new Array(4).fill("tall_spikes"); - let sixSpikes = new Array(6).fill("tall_spikes"); + let threeSpikes = new Array(3).fill("spikes_tall"); + let fiveSpikes = new Array(5).fill("spikes_tall"); let spikes = [ - "gateGap", - "spikeMid", ...fourSpikes, - "spikeIn", ...sixSpikes, "spikeMid", - "gateGap", "spikeMid", ...fourSpikes, - "spikeIn", ...fourSpikes, - "spikeMid" + "gap_3.6", + "spike_single", ...threeSpikes, + "turn_0.25", "spike_single", "turn_0.25", + ...fiveSpikes, "spike_single", + "gap_3.6", "spike_single", ...threeSpikes, + "turn_0.25", "spike_single", "turn_0.25", + ...threeSpikes, + "spike_single" ]; spikes = spikes.concat(spikes); - placeCustomFortress(gX, gZ, new Fortress("spikes", spikes), "palisades", 0, PI); + placeCustomFortress(gX, gZ, new Fortress("spikes", spikes), "palisade", 0, PI); // Place treasure, potentially inside buildings for (let i = 0; i < gallicCCTreasureCount; ++i) Index: binaries/data/mods/public/maps/random/fortress.js =================================================================== --- binaries/data/mods/public/maps/random/fortress.js +++ binaries/data/mods/public/maps/random/fortress.js @@ -134,19 +134,19 @@ // Place custom fortress if (civ == "brit" || civ == "gaul" || civ == "iber") { - var wall = ["gate", "tower", "wallLong", - "cornerIn", "wallLong", "barracks", "tower", "wallLong", "tower", "house", "wallLong", - "cornerIn", "wallLong", "house", "tower", "gate", "tower", "house", "wallLong", - "cornerIn", "wallLong", "house", "tower", "wallLong", "tower", "house", "wallLong", - "cornerIn", "wallLong", "house", "tower"]; + var wall = ["gate", "tower", "long", + "cornerIn", "long", "barracks", "tower", "long", "tower", "house", "long", + "cornerIn", "long", "house", "tower", "gate", "tower", "house", "long", + "cornerIn", "long", "house", "tower", "long", "tower", "house", "long", + "cornerIn", "long", "house", "tower"]; } else { - var wall = ["gate", "tower", "wallLong", - "cornerIn", "wallLong", "barracks", "tower", "wallLong", "tower", "wallLong", - "cornerIn", "wallLong", "house", "tower", "gate", "tower", "wallLong", - "cornerIn", "wallLong", "house", "tower", "wallLong", "tower", "wallLong", - "cornerIn", "wallLong", "house", "tower"]; + var wall = ["gate", "tower", "long", + "cornerIn", "long", "barracks", "tower", "long", "tower", "long", + "cornerIn", "long", "house", "tower", "gate", "tower", "long", + "cornerIn", "long", "house", "tower", "long", "tower", "long", + "cornerIn", "long", "house", "tower"]; } placeCustomFortress(playerX[i], playerZ[i], new Fortress("Spahbod", wall), civ, playerIDs[i]); } Index: binaries/data/mods/public/maps/random/rmgen/library.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/library.js +++ binaries/data/mods/public/maps/random/rmgen/library.js @@ -5,6 +5,8 @@ const HEIGHT_UNITS_PER_METRE = 92; const MAP_BORDER_WIDTH = 3; +const g_DamageTypes = new DamageTypes(); + /** * Constants needed for heightmap_manipulation.js */ Index: binaries/data/mods/public/maps/random/rmgen/wall_builder.js =================================================================== --- binaries/data/mods/public/maps/random/rmgen/wall_builder.js +++ binaries/data/mods/public/maps/random/rmgen/wall_builder.js @@ -1,907 +1,811 @@ -//////////////////////////////////////////////////////////////////// -// This 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? - -////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// 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) +/** + * @file Contains functionality to place walls on random maps. + */ + +/** + * Set some globals for this module. + */ +var g_WallStyles = loadWallsetsFromCivData(); +var g_FortressTypes = createDefaultFortressTypes(); + +/** + * Fetches wallsets from {civ}.json files, and then uses them to load + * basic wall elements. + */ +function loadWallsetsFromCivData() { - 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; + 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; } -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// 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 loadWallset(wallsetPath, civ) { - this.type = type; // Only usefull to get the type of the actual fortress - this.wall = wall !== undefined ? wall : []; - this.centerToFirstElement = undefined; + 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; } -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// 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 = {}; - -// 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) +/** + * 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) { - 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]) - }; + this.type = type; + this.wall = wall; + this.centerToFirstElement = centerToFirstElement; } -// 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 = {}; - +function createDefaultFortressTypes() { - 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) + let defaultFortresses = {}; + + /** + * 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"]); + + /** + * 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; + + defaultFortresses[newKey] = { "wall": [] }; + for (let j = 0; j < oldWall.length; ++j) + { + defaultFortresses[newKey].wall.push(oldWall[j]); - let wp = wallParts[type]; - fortressTypes[type].wall = wp.concat(wp, wp, wp); + 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.entPath = undefined; + ret.length = wallset.gate.length; + break; + + case "entryTower": + ret.entPath = 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.entPath = undefined; + ret.angle = 0; + ret.length = +element.slice("gap_".length); + } + else if (element.startsWith("turn_")) + { + ret.entPath = undefined; + ret.bend = +element.slice("turn_".length) * Math.PI; + ret.length = 0; + } + else + { + if (!g_CivData[civ]) + civ = Object.keys(g_CivData)[0]; + + let entPath = "structures/" + civ + "_" + element; + if (Engine.TemplateExists(entPath)) + { + ret.indent = ret.length * (element == "outpost" || element.endsWith("_tower") ? -3 : 3.5); + ret.entPath = entPath; + ret.length = 0; + } + else + warn("Unrecognised wall element: '" + element + "' (" + style + "). Defaulting to " + (wallset.tower ? "'tower'." : "a blank element.")); + } } -} -// Setup some balanced (to civ type fortresses) semi default fortresses for "palisades" style -// TODO + // Cache to save having to calculate this element again. + g_WallStyles[style][element] = deepfreeze(ret); -// Add some "fortress types" for roads (will only work with style "road") -{ - // ["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"], - }; - - for (let type in roadTypes) - fortressTypes[type] = new Fortress(type, roadTypes[type]); + return ret; } -/////////////////////////////// -// 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) +/** + * Prepare a wall element for inclusion in a style. + * + * @param {string} path - The template path to read values from + */ +function readyWallElement(path, civCode) { - // 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; + 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; + + return deepfreeze({ + "entPath": 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 + }); +} +/** + * 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) +{ + 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, + "entPath": element.entPath, "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); + return Object.keys(g_WallStyles)[0]; + + style = getCivCode(playerId) + "_stone"; + return !g_WallStyles[style] ? Object.keys(g_WallStyles)[0] : style; } - orientation = orientation || 0; + return style; +} + +/** + * 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) +{ + style = validateStyle(style, playerId); - // 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); - } + if (AM[iWall].entPath) + placeObject(AM[iWall].x, AM[iWall].y, AM[iWall].entPath, playerId, AM[iWall].angle); } -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// 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) +/** + * 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) { - // Graciously handle arguments - fortress = fortress || fortressTypes.medium; - if (!wallStyles.hasOwnProperty(style)) - { - if (playerId == 0) - style = style || "palisades"; - else - style = getCivCode(playerId); - } + 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.entPath) + placeObject(placeX, placeY, wallEle.entPath, 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.entPath) + placeObject(x, y, wallEle.entPath, 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.entPath) + placeObject(placeX, placeY, wallEle.entPath, 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.entPath, 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).entPath, 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 @@ } 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).entPath, 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 @@ } } } - +tries; + ++tries; } log("placeGenericFortress: Reduced overlap to " + minOverlap + " after " + tries + " tries"); + // Place wall for (let pointIndex = 0; pointIndex < bestPointDerivation.length; ++pointIndex) { @@ -1006,27 +914,31 @@ 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"; + let wallElement = "long"; if ((pointIndex + 1) % gateOccurence == 0) wallElement = "gate"; - let entity = wallStyles[style][wallElement].entity; - if (entity) + + let entity = getWallElement(wallElement, style); + if (entity.entPath) + { + 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 + entity.entPath, playerId, angle - Math.PI / 2 + entity.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.entPath, playerId, angle - Math.PI / 2 + tower.angle + ); } } Index: binaries/data/mods/public/maps/random/wall_demo.js =================================================================== --- binaries/data/mods/public/maps/random/wall_demo.js +++ binaries/data/mods/public/maps/random/wall_demo.js @@ -2,161 +2,329 @@ InitMap(); +/** + * Demonstration code for wall placement. + * + * Some notes/reminders: + * - All angles (orientation) are in radians. + * - When looking at the map, with the x-axis being horizontal and the y-axis vertical: + * -- The origin point (0,0) of the map is in the bottom-left corner, + * -- A wall orientated at 0 has its "outside" facing right and its "inside" facing left. + * -- A wall orientated at Pi is reversed (obviously). + * -- A wall orientated at Pi/2 has its "outside" facing down and its "inside" facing up. + * - As a general rule, walls are drawn in a anti-clockwise direction. + * + * Some general notes concerning the arguments: + * + * - The first two arguments for most placement functions are x/y co-ordinates needed to position the wall. These are received via separate arguments, like in placeObject(), and their exact meaning differs between methods, but should be mostly self explanatory. The exception to this is placeLinearWall(), where the first four arguments are co-ordinates. However, whether two argument or four, the initial x/y co-ordinates are required parameters. + * + * - For some functions, the next argument is radius, indicating how far from a central point the wall should be drawn. The functions that use this are marked as doing so below. + * + * - The next argument is usually an array containing wall element type strings. (See the block comment for getWallElement() for a list of accepted type strings.) The exception to this is placeFortress(), which accepts a string here instead, identifying which of the predefined fortresses designs you wish to use. (See the example provided below for details.) + * + * Most functions will ask that you do not include "bending" wall elements in your array ("cornerIn", "cornerOut", "turn_{x}") and will complain if you attempt to do so. The ones that do this are clearly marked below. + * + * The array will generally look like: + * ["start", "medium", "tower", "gate", "tower", "medium", "end"] + * + * Remember that walls are drawn in an anti-clockwise direction. Thus, when looking at a wall element in-game, with the "outside" facing up, then the *next* wall element will be drawn to the left of *this* element. + * + * This argument is optional, and each function has a different default value. + * + * - The next argument is a string denoting the style of the wall. These are derived from the names of wallsets defined in 0ad: for example "athen_wallset_stone" becomes "athen_stone", and "rome_wallset_seige" becomes "rome_seige". (A full list can be found stored as the keys of the global constant g_WallStyles.) This argument is optional, and if not set, the civ's basic stone wallset will be used. + * + * - The next argument is the player-id of the player that is to own the wall. This argument is optional, and defaults to 0 (gaia). + * + * - The next argument is an angle defining the angle of orientation of the wall. The exact use differs slightly between functions, but hopefully the comments on the examples below should help. Also see the notes above about wall orientation. This argument is optional, and defaults to 0. + * + * - Any remaining arguments differ from function to function, but are all optional. Please read the comments below, and also the block comment of the function in wall_builder.js for further details. + * + * And have fun! + */ + var mapSize = getMapSize(); -//////////////////////////////////////// -// Demonstration code for wall placement -//////////////////////////////////////// - -// Some general notes to the arguments: - -// First all the place functions take the coordinates needed to place the wall -// X and Y coordinate are taken in seperate arguments like in placeObject -// Their meaning differs for different placement methods but are mainly self explanatory -// placeLinearWall takes 4 arguments here (2 coordinates) for startX, startY, targetX and targetY - -// The next argument is always the 'wall' definition, an array of wall element type strings in most cases -// That looks like ['endLeft', 'wall', 'tower', 'wall', 'endRight', 'entry', 'endLeft', 'wall', 'tower', 'wall', 'endRight'] -// For placeCircularWall and placeLinearWall only wall parts are needed like: ['tower', 'wall'] -// They will automatically end with the first wall element if that makes sense (e.g. the wall is not closed) -// NOTE: They take further optional arguments to adjust this behaviour (See the wall_builder.js for that) -// placeFortress just takes a fortress type string that includes the wall definition -// The default fortress type strings are made for easy placement of predefined fortresses -// They are chosen like map sizes: 'tiny', 'small', 'medium', 'normal', 'large', 'veryLarge' and 'giant' -// NOTE: To place a custom fortress use placeCustomFortress instead -// It takes an instance of the Fortress class instead of the default fortress type strings - -// The next argument is always the wall style string -// Wall styles are chosen by strings so the civ strings got by getCivCode() can be used -// Other styles may be present as well but besides the civ styles only 'palisades' includes all wall element types (yet) - -// The next argument is always the index of the player that owns the wall. -// 0 is Gaia, 1 is Player 1 (default color blue), 2 is Player 2 (default color red), ... - -// The next argument is an angle defining the orientation of the wall -// placeLinearWall does not need an angle since it's defined by startX/Y and targetX/Y -// Orientation works like the angle argument in placeObject -// 0 is always right (towards positive X) -// Raising the angle will rotate the wall counter-clockwise (mathmatical positive in default 2D) -// PI/2 faces top (positive Y) -// Orientation might be a little confusing for placeWall since it defines where the wall has its 'front' or 'outside' not the direction it will be build to. -// It's because all other methods work like that and it's intuitive there -// That means the walls outside by default (orientation = 0) faces positive X and (without bending wall elements) will be build towards positive Y - -// Some other arguments are taken but all of them are optional and in most cases not needed -// One example is maxAngle for placeCircularWall that defines how far the wall will circumvent the center. Default is 2*PI which makes a full circle - -// General wall placement setup +/** + * General wall placement setup + */ const distToMapBorder = 5; const distToOtherWalls = 10; var buildableMapSize = mapSize - 2 * distToMapBorder; var actualX = distToMapBorder; var actualY = distToMapBorder; -// Wall styles are chosen by strings so the civ strings got by getCivCode() can be used -// Other styles may be present as well but besides the civ styles only 'palisades' includes all wall element types (yet) -const wallStyleList = ["athen", "brit", "cart", "gaul", "iber", "mace", "maur", "pers", "ptol", "rome", "sele", "spart", "rome_siege", "palisades"]; - -//////////////////////////////////////// -// Custom wall placement (element based) -//////////////////////////////////////// -var wall = ['endLeft', 'wallLong', 'tower', 'wall', 'outpost', 'wall', 'cornerOut', 'wall', 'cornerIn', 'wall', 'house', 'endRight', 'entryTower', 'endLeft', 'wallShort', 'barracks', 'gate', 'tower', 'wall', 'wallFort', 'wall', 'endRight']; +var playerID = 0; +const wallStyleList = Object.keys(g_WallStyles); + +/** + * Custom wall placement (element based). + * + * Like most wall placement functions, we have to supply an x/y position. + * In this case, the x/y position marks the start of the wall. + * + * For this function, orientation indicates the angle at which the first + * wall element should be drawn. (The direction that the outside of the + * first wall element faces towards.) + * + * This function permits bending wall elements. + */ for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) { - let startX = actualX + styleIndex * buildableMapSize/wallStyleList.length; // X coordinate of the first wall element - let startY = actualY; // Y coordinate of the first wall element - let style = wallStyleList[styleIndex]; // // The wall's style like 'cart', 'iber', 'pers', 'rome', 'romeSiege' or 'palisades' - let orientation = styleIndex * PI/64; // Orientation of the first wall element. 0 means 'outside' or 'front' is right (positive X, like object placement) - // That means the wall will be build towards top (positive Y) if no corners are used - let playerId = 0; // Owner of the wall (like in placeObject). 0 is Gaia, 1 is Player 1 (default color blue), ... - placeWall(startX, startY, wall, style, playerId, orientation); // Actually placing the wall + let x = actualX + styleIndex * buildableMapSize / wallStyleList.length; + let y = actualY; + let wall = ['start', 'long', 'tower', 'tower', 'tower', 'medium', 'outpost', 'medium', 'cornerOut', 'medium', 'cornerIn', 'medium', 'house', 'end', 'entryTower', 'start', 'short', 'barracks', 'gate', 'tower', 'medium', 'fort', 'medium', 'end']; + let style = wallStyleList[styleIndex]; + let orientation = Math.PI / 16 * Math.sin(styleIndex * Math.PI / 4); + + placeWall(x, y, wall, style, playerID, orientation); } -actualX = distToMapBorder; // Reset actualX -actualY += 80 + distToOtherWalls; // Increase actualY for next wall placement method -////////////////////////////////////////////////////////////// -// Default fortress placement (chosen by fortress type string) -////////////////////////////////////////////////////////////// -var fortressRadius = 15; // The space the fortresses take in average. Just for design of this map +// Prep for next set of walls +actualX = distToMapBorder; +actualY += 80 + distToOtherWalls; + +/** + * Default fortress placement (chosen by fortress type string) + * + * The x/y position in this case marks the center point of the fortress. + * To make it clearer, we add an obilisk as a visual marker. + * + * This is the only wall placement function that does not take an array + * of elements as an argument. Instead, we provide a "type" that identifies + * a predefined design to draw. The list of possible types are: "tiny", + * "small", "medium", "normal", "large", "veryLarge", and "giant". + * + * For this function, orientation is the direction in which the main gate + * is facing. + */ +var fortressRadius = 15; // The space the fortresses take in average. Just for design of this map. Not passed to the function. + for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) { - let centerX = actualX + fortressRadius + styleIndex * buildableMapSize/wallStyleList.length; // X coordinate of the center of the fortress - let centerY = actualY + fortressRadius; // Y coordinate of the center of the fortress - let type = 'tiny'; // Default fortress types are like map sizes: 'tiny', 'small', 'medium', 'large', 'veryLarge', 'giant' - let style = wallStyleList[styleIndex]; // The wall's style like 'cart', 'iber', 'pers', 'rome', 'romeSiege' or 'palisades' - let playerId = 0; // Owner of the wall. 0 is Gaia, 1 is Player 1 (default color blue), ... - let orientation = styleIndex * PI/32; // Where the 'main entrance' of the fortress should face (like in placeObject). All fortresses walls should start with an entrance - placeFortress(centerX, centerY, type, style, playerId, orientation); // Actually placing the fortress - placeObject(centerX, centerY, 'other/obelisk', 0, 0*PI); // Place visual marker to see the center of the fortress + let x = actualX + fortressRadius + styleIndex * buildableMapSize / wallStyleList.length; + let y = actualY + fortressRadius; + let type = "tiny"; + let style = wallStyleList[styleIndex]; + let orientation = styleIndex * Math.PI / 32; + + placeObject(x, y, "other/obelisk", playerID, orientation); + placeFortress(x, y, type, style, playerID, orientation); } -actualX = distToMapBorder; // Reset actualX -actualY += 2 * fortressRadius + 2 * distToOtherWalls; // Increase actualY for next wall placement method - -////////////////////////// -// Circular wall placement -////////////////////////// -// NOTE: Don't use bending wall elements like corners here! -var radius = min((mapSize - actualY - distToOtherWalls) / 3, (buildableMapSize / wallStyleList.length - distToOtherWalls) / 2); // The radius of wall circle -var centerY = actualY + radius; // Y coordinate of the center of the wall circle -var orientation = 0; // Where the wall circle will be open if maxAngle < 2*PI, see below. Otherwise where the first wall element will be placed + +// Prep for next set of walls +actualX = distToMapBorder; +actualY += 2 * fortressRadius + distToOtherWalls; + +/** + * 'Generic' fortress placement (iberian wall circuit code) + * + * The function used here is unusual in that the owner and style arguments + * are swapped. It is also unusual in that we do not supply an orientation. + * + * The x/y position in this case marks the center point of the fortress. + * To make it clearer, we add an obilisk as a visual marker. + * + * We also supply a radius value to dictate how wide the circuit of walls should be. + */ +var radius = Math.min((mapSize - actualY - distToOtherWalls) / 3, (buildableMapSize / wallStyleList.length - distToOtherWalls) / 2); for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) { - let centerX = actualX + radius + styleIndex * buildableMapSize/wallStyleList.length; // X coordinate of the center of the wall circle - let playerId = 0; // Player ID of the player owning the wall, 0 is Gaia, 1 is the first player (default blue), ... - let wallPart = ['tower', 'wall', 'house']; // List of wall elements the wall will be build of. Optional, default id ['wall'] - let style = wallStyleList[styleIndex]; // The wall's style like 'cart', 'iber', 'pers', 'rome', 'romeSiege' or 'palisades' - let maxAngle = PI/2 * (styleIndex%3 + 2); // How far the wall should circumvent the center - placeCircularWall(centerX, centerY, radius, wallPart, style, playerId, orientation, maxAngle); // Actually placing the wall - placeObject(centerX, centerY, 'other/obelisk', 0, 0*PI); // Place visual marker to see the center of the wall circle - orientation += PI/16; // Increasing orientation to see how rotation works (like for object placement) + let centerX = actualX + radius + styleIndex * buildableMapSize / wallStyleList.length; + let centerY = actualY + radius; + let style = wallStyleList[styleIndex]; + + placeObject(centerX, centerY, 'other/obelisk', playerID, 0); + placeGenericFortress(centerX, centerY, radius, playerID, style); } -actualX = distToMapBorder; // Reset actualX -actualY += 2 * radius + distToOtherWalls; // Increase actualY for next wall placement method - -/////////////////////////// -// Polygonal wall placement -/////////////////////////// -// NOTE: Don't use bending wall elements like corners here! -var radius = min((mapSize - actualY - distToOtherWalls) / 2, (buildableMapSize / wallStyleList.length - distToOtherWalls) / 2); // The radius of wall polygons -var centerY = actualY + radius; // Y coordinate of the center of the wall polygon -var orientation = 0; // Where the wall circle will be open if ???, see below. Otherwise where the first wall will be placed + +// Prep for next set of walls +actualX = distToMapBorder; +actualY += 2 * radius + distToOtherWalls; + +/** + * Circular wall placement + * + * It is possible with this function to draw complete circles, or arcs. + * Each side of the wall consists of the contents of the provided wall + * array, with the code calculating the number and angle of turns and + * sides automatically based on the calculated length of each side and + * the given radius. + * + * This function does not permit the use of bending wall elements. + * + * In this case, the x/y co-ordinates are the center point around which + * to draw the walls. To make this clearer, we add an obelisk as a visual + * marker. + * + * We also provide a radius to define the distance between the center + * point and the walls. + * + * For this function, orientation is the direction that the opening of an + * arc faces. If the wall is to be a complete circle, then this is used as + * the orientation of the first wall piece. + */ +radius = Math.min((mapSize - actualY - distToOtherWalls) / 3, (buildableMapSize / wallStyleList.length - distToOtherWalls) / 2); for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) { - let centerX = actualX + radius + styleIndex * buildableMapSize/wallStyleList.length; // X coordinate of the center of the wall circle - let playerId = 0; // Player ID of the player owning the wall, 0 is Gaia, 1 is the first player (default blue), ... - let cornerWallElement = 'tower'; // With wall element type will be uset for the corners of the polygon - let wallPart = ['wall', 'tower']; // List of wall elements the wall will be build of. Optional, default id ['wall'] - let style = wallStyleList[styleIndex]; // The wall's style like 'cart', 'iber', 'pers', 'rome', 'romeSiege' or 'palisades' - let numCorners = (styleIndex)%6 + 3; // How many corners the plogon will have - let skipFirstWall = true; // If the wall should be open towards orientation - placePolygonalWall(centerX, centerY, radius, wallPart, cornerWallElement, style, playerId, orientation, numCorners, skipFirstWall); - placeObject(centerX, centerY, 'other/obelisk', 0, 0*PI); // Place visual marker to see the center of the wall circle - orientation += PI/16; // Increasing orientation to see how rotation works (like for object placement) + let centerX = actualX + radius + styleIndex * buildableMapSize / wallStyleList.length; + let centerY = actualY + radius; + let wallPart = ['tower', 'medium', 'house']; + let style = wallStyleList[styleIndex]; + let orientation = styleIndex * Math.PI / 16; + + // maxAngle is how far the wall should circumscribe the center. + // If equal to Pi * 2, then the wall will be a full circle. + // If less than Pi * 2, then the wall will be an arc. + let maxAngle = Math.PI / 2 * (styleIndex % 3 + 2); + + placeObject(centerX, centerY, 'other/obelisk', playerID, orientation); + placeCircularWall(centerX, centerY, radius, wallPart, style, playerID, orientation, maxAngle); } -actualX = distToMapBorder; // Reset actualX -actualY += 2 * radius + distToOtherWalls; // Increase actualY for next wall placement method - -//////////////////////// -// Linear wall placement -//////////////////////// -// NOTE: Don't use bending wall elements like corners here! -var maxWallLength = (mapSize - actualY - distToMapBorder - distToOtherWalls); // Just for this maps design. How long the longest wall will be -var numWallsPerStyle = floor(buildableMapSize / distToOtherWalls / wallStyleList.length); // Just for this maps design. How many walls of the same style will be placed + +// Prep for next set of walls. +actualX = distToMapBorder; +actualY += 2 * radius + distToOtherWalls; + +/** + * Regular Polygonal wall placement + * + * This function draws a regular polygonal wall around a given point. All + * the sides follow the same pattern, and the (automatically calculated) + * angles at the corners are identical. We define how many corners we want. + * + * This function does not permit the use of bending wall elements. + * + * In this case, the x/y co-ordinates are the center point around which + * to draw the walls. To make this clearer, we add an obelisk as a visual + * marker. + * + * We also provide a radius to define the distance between the center + * point and the walls. + * + * After the usual array of wall elements to use, and before the style + * argument, we provide the name of a single wall element to use as a + * corner piece. + * + * In this function, orientation is the direction the first wall has its + * outward side facing or, if the `skipFirstWall` argument is true, the + * opening in the wall. + */ +radius = Math.min((mapSize - actualY - distToOtherWalls) / 2, (buildableMapSize / wallStyleList.length - distToOtherWalls) / 2); +for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) +{ + let centerX = actualX + radius + styleIndex * buildableMapSize / wallStyleList.length; + let centerY = actualY + radius; + let wallParts = ['medium', 'tower']; // Function default: ['long', 'tower'] + + // Which wall element to use for the corners of the polygon + let cornerWallElement = 'tower'; + + let style = wallStyleList[styleIndex]; + let orientation = styleIndex * Math.PI / 16; + + // How many corners the polygon should have: + let numCorners = styleIndex % 6 + 3; + + // If true, the first side will not be drawn, leaving the wall open. + let skipFirstWall = true; + + placeObject(centerX, centerY, 'other/obelisk', playerID, orientation); + placePolygonalWall(centerX, centerY, radius, wallParts, cornerWallElement, style, playerID, orientation, numCorners, skipFirstWall); +} + +// Prep for next set of walls. +actualX = distToMapBorder; +actualY += 2 * radius + distToOtherWalls; + +/** + * Irregular Polygonal wall placement + * + * This function draws an irregular polygonal wall around a given point. + * Each side of the wall is different, each element used selected at + * pesudo-random from an assortment. The angles at the corners also differ. + * We can control this randomness by changing the irregularity argument. + * + * This function does not permit the use of bending wall elements. + * + * In this case, the x/y co-ordinates are the center point around which + * to draw the walls. To make this clearer, we add an obelisk as a visual + * marker. + * + * We also provide a radius to define the distance between the center + * point and the walls. + * + * The usual array of wall elements is left out here, instead we provide + * the name of a single wall element to use as a corner piece. + * + * In this function, orientation is the direction the first wall has its + * outward side facing or, if the `skipFirstWall` argument is true, the + * opening in the wall. + * + * The very last argument is the collection of wallparts used to build + * the wall. It is not defined in this example (so as to use the defaults) + * as it is not easy to comprehend. + */ +radius = Math.min((mapSize - actualY - distToOtherWalls) / 2, (buildableMapSize / wallStyleList.length - distToOtherWalls) / 2); // The radius of wall polygons +for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) +{ + let centerX = actualX + radius + styleIndex * buildableMapSize / wallStyleList.length; + let centerY = actualY + radius; + + // Which wall element type will be used for the corners of the polygon. + let cornerWallElement = 'tower'; + + let style = wallStyleList[styleIndex]; + let orientation = styleIndex * Math.PI / 16; + + // How many corners the polygon will have + let numCorners = styleIndex % 6 + 3; + + // Irregularity of the polygon. + let irregularity = 0.5; + + // If true, the first side will not be drawn, leaving the wall open. + let skipFirstWall = true; + + placeObject(centerX, centerY, 'other/obelisk', playerID, orientation); + placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement, style, playerID, orientation, numCorners, irregularity, skipFirstWall); +} + +// Prep for next set of walls. +actualX = distToMapBorder; +actualY += 2 * radius + distToOtherWalls; + +/** + * Linear wall placement + * + * This function draws a straight wall between two given points. + * + * This function does not permit the use of bending wall elements. + * + * This function has no orientation parameter, the wall pieces are angled + * automatically. Remember: each piece is placed to the left of the + * previous piece. Thus, if the start point is at the right-hand side of + * the screen and the end point is at the left-hand side, the "outside" + * of the walls is facing the top of the screen. + */ +// Two vars, just for this map; firstly how long the longest wall will be. +var maxWallLength = (mapSize - actualY - distToMapBorder - distToOtherWalls); +// And secondly, how many walls of the same style will be placed. +var numWallsPerStyle = Math.floor(buildableMapSize / distToOtherWalls / wallStyleList.length); + for (let styleIndex = 0; styleIndex < wallStyleList.length; ++styleIndex) for (let wallIndex = 0; wallIndex < numWallsPerStyle; ++wallIndex) { - let startX = actualX + (styleIndex * numWallsPerStyle + wallIndex) * distToOtherWalls; // X coordinate the wall will start from - let startY = actualY; // Y coordinate the wall will start from - let endX = startX; // X coordinate the wall will end - let endY = actualY + (wallIndex + 1) * maxWallLength/numWallsPerStyle; // Y coordinate the wall will end - let playerId = 0; // Player ID of the player owning the wall, 0 is Gaia, 1 is the first player (default blue), ... - let wallPart = ['tower', 'wall']; // List of wall elements the wall will be build of - let style = wallStyleList[styleIndex]; // The wall's style like 'cart', 'iber', 'pers', 'rome', 'romeSiege' or 'palisades' - placeLinearWall(startX, startY, endX, endY, wallPart, style, playerId); // Actually placing the wall - // placeObject(startX, startY, 'other/obelisk', 0, 0*PI); // Place visual marker to see where exsactly the wall begins - // placeObject(endX, endY, 'other/obelisk', 0, 0*PI); // Place visual marker to see where exsactly the wall ends - } + // Start point. + let startX = actualX + (styleIndex * numWallsPerStyle + wallIndex) * buildableMapSize / wallStyleList.length / numWallsPerStyle; + let startY = actualY; + + // End point. + let endX = startX; + let endY = actualY + (wallIndex + 1) * maxWallLength / numWallsPerStyle; -actualX = distToMapBorder; // Reset actualX -actualY += maxWallLength + distToOtherWalls; // Increase actualY for next wall placement method + let wallPart = ['tower', 'medium']; + let style = wallStyleList[styleIndex]; + + placeLinearWall(startX, startY, endX, endY, wallPart, style, playerID); + } ExportMap(); Index: binaries/data/mods/public/maps/random/wall_demo.json =================================================================== --- binaries/data/mods/public/maps/random/wall_demo.json +++ binaries/data/mods/public/maps/random/wall_demo.json @@ -1,14 +1,15 @@ { "settings" : { "Name" : "Wall Demo", + "GameType": "endless", "Script" : "wall_demo.js", - "Description" : "A demonstration of wall placement methods/code in random maps. Very large map size is recommended.", + "Description" : "A demonstration of wall placement methods/code in random maps. Giant map size is recommended!", "BaseTerrain" : ["grass1"], "BaseHeight" : 0, "Keywords": ["demo"], "CircularMap" : false, - "TriggerScripts" : [ - "random/wall_demo_triggers.js" - ] + "TriggerScripts" : [ + "random/wall_demo_triggers.js" + ] } } Index: binaries/data/mods/public/maps/random/wild_lake.js =================================================================== --- binaries/data/mods/public/maps/random/wild_lake.js +++ binaries/data/mods/public/maps/random/wild_lake.js @@ -271,24 +271,46 @@ "autumn": { "building": "structures/gaul_farmstead", "animal": "gaia/fauna_horse" } }; -wallStyles.other.sheepIn = new WallElement("sheepIn", farmEntities[currentBiome()].animal, PI / 4, -1.5, 0.75, PI/2); -wallStyles.other.foodBin = new WallElement("foodBin", "gaia/special_treasure_food_bin", PI/2, 1.5); -wallStyles.other.sheep = new WallElement("sheep", farmEntities[currentBiome()].animal, 0, 0, 0.75); -wallStyles.other.farm = new WallElement("farm", farmEntities[currentBiome()].building, PI, 0, -3); +g_WallStyles.other = { + "overlap": 0, + "fence": readyWallElement("other/fence_long", "gaia"), + "fence_short": readyWallElement("other/fence_short", "gaia"), + "bench": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "entPath": "other/bench" }, + "foodBin": { "angle": Math.PI / 2, "length": 1.5, "indent": 0, "bend": 0, "entPath": "gaia/special_treasure_food_bin" }, + "animal": { "angle": 0, "length": 0, "indent": 0.75, "bend": 0, "entPath": farmEntities[currentBiome()].animal }, + "farmstead": { "angle": Math.PI, "length": 0, "indent": -3, "bend": 0, "entPath": farmEntities[currentBiome()].building } +}; + let fences = [ - new Fortress("fence", ["foodBin", "farm", "bench", "sheepIn", "fence", "sheepIn", "fence", "sheepIn", "fence"]), - new Fortress("fence", ["foodBin", "farm", "fence", "sheepIn", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", "fence"]), new Fortress("fence", [ - "foodBin", "farm", "cornerIn", "bench", "cornerOut", "fence_short", "sheepIn", "fence", "sheepIn", - "fence", "sheepIn", "fence_short", "sheep", "fence" + "foodBin", "farmstead", "bench", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "fence" + ]), + new Fortress("fence", [ + "foodBin", "farmstead", "fence", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "bench", "animal", "fence", + "turn_0.25", "animal", "turn_0.25", "fence" + ]), + new Fortress("fence", [ + "foodBin", "farmstead", "turn_0.5", "bench", "turn_-0.5", "fence_short", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence" ]), new Fortress("fence", [ - "foodBin", "farm", "cornerIn", "fence_short", "cornerOut", "bench", "sheepIn", "fence", "sheepIn", - "fence", "sheepIn", "fence_short", "sheep", "fence" + "foodBin", "farmstead", "turn_0.5", "fence_short", "turn_-0.5", "bench", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "fence", + "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence" ]), new Fortress("fence", [ - "foodBin", "farm", "fence", "sheepIn", "bench", "sheep", "fence", "sheepIn", - "fence_short", "sheep", "fence", "sheepIn", "fence_short", "sheep", "fence" + "foodBin", "farmstead", "fence", + "turn_0.25", "animal", "turn_0.25", "bench", "animal", "fence", + "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence", + "turn_0.25", "animal", "turn_0.25", "fence_short", "animal", "fence" ]) ]; let num = fences.length; Index: binaries/data/mods/public/simulation/components/GuiInterface.js =================================================================== --- binaries/data/mods/public/simulation/components/GuiInterface.js +++ binaries/data/mods/public/simulation/components/GuiInterface.js @@ -1254,7 +1254,10 @@ // Create cache entries for templates we haven't seen before for (let type in wallSet.templates) { - let tpl = wallSet.templates[type]; + if (type == "curves") + continue; + + let tpl = wallSet.templates[type].replace("{civ}", QueryPlayerIDInterface(player).civ); if (!(tpl in this.placementWallEntities)) { this.placementWallEntities[tpl] = { Index: binaries/data/mods/public/simulation/components/WallSet.js =================================================================== --- binaries/data/mods/public/simulation/components/WallSet.js +++ binaries/data/mods/public/simulation/components/WallSet.js @@ -21,6 +21,21 @@ "" + "" + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + "" + "" + "" + Index: binaries/data/mods/public/simulation/data/civs/athen.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/athen.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/brit.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/brit.json +++ binaries/data/mods/public/simulation/data/civs/brit.json @@ -88,6 +88,11 @@ "Special": "" } ], + "WallSets": + [ + "other/wallset_palisade", + "structures/brit_wallset_stone" + ], "StartEntities": [ { Index: binaries/data/mods/public/simulation/data/civs/cart.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/cart.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/gaul.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/gaul.json +++ binaries/data/mods/public/simulation/data/civs/gaul.json @@ -88,6 +88,11 @@ "Special": "" } ], + "WallSets": + [ + "other/wallset_palisade", + "structures/gaul_wallset_stone" + ], "StartEntities": [ { Index: binaries/data/mods/public/simulation/data/civs/iber.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/iber.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/mace.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/mace.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/maur.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/maur.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/pers.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/pers.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/ptol.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/ptol.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/rome.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/rome.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/sele.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/sele.json +++ 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": [ { Index: binaries/data/mods/public/simulation/data/civs/spart.json =================================================================== --- binaries/data/mods/public/simulation/data/civs/spart.json +++ 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": [ { Index: binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml =================================================================== --- binaries/data/mods/public/simulation/templates/other/wallset_palisade.xml +++ 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 Index: binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml =================================================================== --- binaries/data/mods/public/simulation/templates/structures/rome_wallset_siege.xml +++ 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