Changeset View
Standalone View
binaries/data/mods/public/maps/random/rmgen/wall_builder.js
//////////////////////////////////////////////////////////////////// | /** | ||||
// This file contains functionality to place walls on random maps // | * @file Contains functionality to place walls on random maps. | ||||
elexis: @file if you want, we have it in some other rmgen/ files | |||||
//////////////////////////////////////////////////////////////////// | */ | ||||
// To do: | /** | ||||
// Check if all wall placement methods work with wall elements with entity === undefined (some still might raise errors in that case) | * Set some globals for this module. | ||||
Done Inline ActionsTODO lists should be in the bugtracker elexis: TODO lists should be in the bugtracker | |||||
Not Done Inline ActionsAll todos in this file have been stripped out and moved to #4866. s0600204: All //todos// in this file have been stripped out and moved to #4866. | |||||
// 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 | var g_WallStyles = loadWallsetsFromCivData(); | ||||
Not Done Inline ActionsProbably ok to keep it here. (Just wondering we might want to move the declaration to the maps like we do with other constants and then pass this around all the time... maybe better to keep it here.) elexis: Probably ok to keep it here. (Just wondering we might want to move the declaration to the maps… | |||||
// 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 | var g_FortressTypes = createDefaultFortressTypes(); | ||||
Done Inline ActionsSame comment as g_FortressTypeKeys. I'd be happy to get rid of all calls to this one. elexis: Same comment as `g_FortressTypeKeys`. I'd be happy to get rid of all calls to this one. | |||||
// Perhaps swap angle and width in WallElement class(?) definition | |||||
// Adjust argument order to be always the same: | /** | ||||
Done Inline ActionsFortressKeys unused and don't really see the need to keep it in a second global (https://en.wikipedia.org/wiki/Single_source_of_truth thing) elexis: FortressKeys unused and don't really see the need to keep it in a second global (https://en. | |||||
// Coordinates (center/start/target) | * Fetches wallsets from {civ}.json files, and then uses them to load | ||||
// Wall element arguments (wall/wallPart/fortressType/cornerElement) | * basic wall elements. | ||||
// playerId (optional, default is 0/gaia) | */ | ||||
// wallStyle (optional, default is the players civ/"palisades for gaia") | function loadWallsetsFromCivData() | ||||
// angle/orientation (optional, default is 0) | { | ||||
// other (all optional) arguments especially those hard to define (wallPartsAssortment, maybe make an own function for it) | let wallsets = {}; | ||||
// Some arguments don't clearly match to this concept: | for (let civ in g_CivData) | ||||
// endWithFirst (wall or other) | { | ||||
Done Inline ActionsPut this into a function (exclamation mark) elexis: Put this into a function (exclamation mark)
Possibly `g_Foo = initFoo()` (likely still depends… | |||||
// skipFirstWall (wall or other) | let civInfo = g_CivData[civ]; | ||||
// gateOccurence (wall or other) | if (!civInfo.WallSets) | ||||
// numCorners (wall or other) | continue; | ||||
// skipFirstWall (wall or other) | |||||
// maxAngle (angle or other) | for (let path of civInfo.WallSets) | ||||
// maxBendOff (angle or other, unused ATM!!!) | { | ||||
// irregularity | // File naming conventions: | ||||
// maxTrys | // - other/wallset_{style} | ||||
// Add treasures to wall style "others" | // - structures/{civ}_wallset_{style} | ||||
Not Done Inline Actions(would be less maintenance to have it agnostic of filenames somehow) elexis: (would be less maintenance to have it agnostic of filenames somehow) | |||||
// Adjust documentation | let style = basename(path).split("_"); | ||||
// Perhaps rename "endLeft" to "start" and "endRight" to "end" | style = style[0] == "wallset" ? style[1] : style[0] + "_" + style[2]; | ||||
// ?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? | if (!wallsets[style]) | ||||
// ?Think of something to enable splitting walls into two walls so more complex walls can be build and roads can have branches/crossroads? | wallsets[style] = loadWallset(Engine.GetTemplate(path), civ); | ||||
// ?Readjust placement angle for wall elements with bending when used in linear/circular walls by their bending? | } | ||||
} | |||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | return wallsets; | ||||
// 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) | function loadWallset(wallsetPath, civ) | ||||
// The course of the wall will be changed by corners (bending != 0) and so the "inside"/"outside" direction | { | ||||
// | let newWallset = { "curves": [] }; | ||||
// type Descriptive string, example: "wallLong". NOTE: Not really needed. Mainly for custom wall elements and to get the wall element type in code | let wallsetData = GetTemplateDataHelper(wallsetPath).wallSet; | ||||
// entity Optional. Template name string of the entity to be placed, example: "structures/cart_wall_long". Default is undefined (No entity placed) | |||||
Done Inline Actionsnot needed (most uses can do for...in, or x[y]), elexis: not needed (most uses can do for...in, or x[y]),
nor advisable to put that in global context… | |||||
// angle Optional. The angle (float) added to place the entity so "outside" is right when the wall element is placed unrotated. Default is 0 | for (let element in wallsetData.templates) | ||||
// width Optional. How far this wall element lengthens the wall (float), if unrotated the Y space needed. Default is 0 | if (element == "curves") | ||||
// indent Optional. The lateral indentation of the entity, drawn "inside" (positive values) or pushed "outside" (negative values). Default is 0 | for (let filename of wallsetData.templates.curves) | ||||
Done Inline Actionsen-US (most prominently Armor, Color) elexis: `en-US` (most prominently Armor, Color) | |||||
// bending Optional. How the course of the wall is changed after this element, positive is bending "in"/left/counter clockwise (like entity placement) | newWallset.curves.push(readyWallElement(filename, civ)); | ||||
Done Inline Actions.tiny elexis: .tiny | |||||
// NOTE: Bending is not supported by all placement functions (see there) | else | ||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | newWallset[element] = readyWallElement(wallsetData.templates[element], civ); | ||||
function WallElement(type, entity, angle, width, indent, bending) | |||||
newWallset.overlap = wallsetData.minTowerOverlap * newWallset.tower.length; | |||||
return newWallset; | |||||
Not Done Inline ActionsPerhaps a JSON file would be handier to edit for modders elexis: Perhaps a JSON file would be handier to edit for modders | |||||
} | |||||
/** | |||||
* 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 | |||||
Not Done Inline Actions(startsWith somewhere maybe?) elexis: (`startsWith` somewhere maybe?) | |||||
* 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] | |||||
*/ | |||||
Not Done Inline Actionswhy the @? elexis: why the @? | |||||
Not Done Inline ActionsSo it's clear that it's not a wall element? Same reason we have @parent in templates? s0600204: So it's clear that it's not a wall element? Same reason we have `@parent` in templates? | |||||
function Fortress(type, wall=[], centerToFirstElement=undefined) | |||||
{ | { | ||||
this.type = type; | this.type = type; | ||||
Done Inline Actionsput that in a function. procedural code becomes unreadable quickly elexis: put that in a function. procedural code becomes unreadable quickly | |||||
// Default wall element type documentation: | this.wall = wall; | ||||
// Lengthening straight blocking (mainly left/right symmetric) wall elements (Walls and wall fortifications) | this.centerToFirstElement = centerToFirstElement; | ||||
// "wall" A blocking straight wall element that mainly lengthens the wall, self-explanatory | } | ||||
// "wallShort" self-explanatory | |||||
// "wallLong" self-explanatory | function createDefaultFortressTypes() | ||||
// "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 | let defaultFortresses = {}; | ||||
// 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 | * Define some basic default fortress types. | ||||
Done Inline Actionslogic in a library goes to functions elexis: logic in a library goes to functions | |||||
// "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 | let addFortress = (type, walls) => defaultFortresses[type] = { "wall": walls.concat(walls, walls, walls) }; | ||||
// Bending wall elements (Wall corners) | addFortress("tiny", ["gate", "tower", "short", "cornerIn", "short", "tower"]); | ||||
Done Inline ActionsSo use deepfreeze on the data that shouldn't be modified to get errors if some code unintentionally modifies stuff and remove the comment elexis: So use `deepfreeze` on the data that shouldn't be modified to get errors if some code… | |||||
Not Done Inline ActionsAnachronistic request phrased rudely. But done, where applicable. s0600204: Anachronistic request phrased rudely.
But done, where applicable. | |||||
// "cornerIn" A wall element bending the wall by PI/2 "inside" (left, +, see above), example: wall tower, palisade curve | addFortress("small", ["gate", "tower", "medium", "cornerIn", "medium", "tower"]); | ||||
// "cornerOut" A wall element bending the wall by PI/2 "outside" (right, -, see above), example: wall tower, palisade curve | addFortress("medium", ["gate", "tower", "long", "cornerIn", "long", "tower"]); | ||||
// "cornerHalfIn" A wall element bending the wall by PI/4 "inside" (left, +, see above), example: wall tower, palisade curve. NOTE: Not yet implemented | addFortress("normal", ["gate", "tower", "medium", "cornerIn", "medium", "cornerOut", "medium", "cornerIn", "medium", "tower"]); | ||||
// "cornerHalfOut" A wall element bending the wall by PI/4 "outside" (right, -, see above), example: wall tower, palisade curve. NOTE: Not yet implemented | addFortress("large", ["gate", "tower", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "tower"]); | ||||
Not Done Inline ActionsWhy do we even have this prototype if this only saves 3 variables elexis: Why do we even have this prototype if this only saves 3 variables | |||||
Not Done Inline ActionsDunno. It appears centerToFirstElement is calculated by the relevant placement function if it is passed undefined, and the other two can be used in a key: value arrangement. Kinda beyond the scope of this ticket to fix, though. s0600204: Dunno.
It appears `centerToFirstElement` is calculated by the relevant placement function if… | |||||
Not Done Inline Actions(That said, providing a centerToFirstElement argument (which doesn't happen anywhere, at the time of writing this) could ostensibly offset the fortress so it's not actually centred around the [x, z] spot it's placed. Although there are probably better ways of achieving the same goal.) s0600204: (That said, providing a `centerToFirstElement` argument (which doesn't happen anywhere, at the… | |||||
Not Done Inline ActionsThe function used when centerToFirstElement is just ONE of many possible ways to do it (basically the center of mass if all wall elements "weigh" the same) but definitely not always a very good one. FeXoR: The function used when centerToFirstElement is just ONE of many possible ways to do it… | |||||
// Zero length straight indented (mainly left/right symmetric) wall elements (Outposts/watchtowers and non-defensive base structures) | addFortress("veryLarge", ["gate", "tower", "medium", "cornerIn", "medium", "cornerOut", "long", "cornerIn", "long", "cornerOut", "medium", "cornerIn", "medium", "tower"]); | ||||
// "outpost" A zero-length wall element without bending far indented so it stands outside the wall, example: outpost, defence tower, watchtower | addFortress("giant", ["gate", "tower", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "cornerOut", "long", "cornerIn", "long", "tower"]); | ||||
// "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; | * Define some fortresses based on those above, but designed for use | ||||
this.angle = angle !== undefined ? angle : 0; | * with the "palisades" wallset. | ||||
this.width = width !== undefined ? width : 0; | */ | ||||
this.indent = indent !== undefined ? indent : 0; | for (let fortressType in defaultFortresses) | ||||
this.bending = bending !== undefined ? bending : 0; | { | ||||
} | const fillTowersBetween = ["short", "medium", "long", "start", "end", "cornerIn", "cornerOut"]; | ||||
Done Inline Actionsfor (let fortressType in defaultFortresses) elexis: `for (let fortressType in defaultFortresses)` | |||||
const newKey = fortressType + "Palisades"; | |||||
Not Done Inline ActionsTLDR elexis: TLDR | |||||
Not Done Inline ActionsBut better than no comment at all. I'm a programmer, not an author. s0600204: But better than no comment at all.
I'm a programmer, not an author. | |||||
Not Done Inline Actions2-3 sentence comments that concisely state what the function does are ideal to digest for the reader. elexis: 2-3 sentence comments that concisely state what the function does are ideal to digest for the… | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | const oldWall = defaultFortresses[fortressType].wall; | ||||
// Fortress class definition | |||||
// | defaultFortresses[newKey] = { "wall": [] }; | ||||
// 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) | |||||
{ | |||||
this.type = type; // Only usefull to get the type of the actual fortress | |||||
this.wall = wall !== undefined ? wall : []; | |||||
this.centerToFirstElement = undefined; | |||||
} | |||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
// 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) | |||||
{ | |||||
let civ = style; | |||||
if (style == "rome_siege") | |||||
civ = "rome"; | |||||
wallStyles[style] = { | |||||
// Default wall elements | |||||
"tower": new WallElement("tower", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), | |||||
"endLeft": new WallElement("endLeft", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), // Same as tower. To be compatible with palisades... | |||||
"endRight": new WallElement("endRight", "structures/" + style + "_wall_tower", PI, wallScaleByType[style]), // Same as tower. To be compatible with palisades... | |||||
"cornerIn": new WallElement("cornerIn", "structures/" + style + "_wall_tower", 5/4*PI, 0, 0.35 * wallScaleByType[style], PI/2), // 2^0.5 / 4 ~= 0.35 ~= 1/3 | |||||
"cornerOut": new WallElement("cornerOut", "structures/" + style + "_wall_tower", 3/4*PI, 0.71 * wallScaleByType[style], 0, -PI/2), // 2^0.5 / 2 ~= 0.71 ~= 2/3 | |||||
"wallShort": new WallElement("wallShort", "structures/" + style + "_wall_short", 0, 2*wallScaleByType[style]), | |||||
"wall": new WallElement("wall", "structures/" + style + "_wall_medium", 0, 4*wallScaleByType[style]), | |||||
"wallMedium": new WallElement("wall", "structures/" + style + "_wall_medium", 0, 4*wallScaleByType[style]), | |||||
"wallLong": new WallElement("wallLong", "structures/" + style + "_wall_long", 0, 6*wallScaleByType[style]), | |||||
// Gate and entrance wall elements | |||||
"gate": new WallElement("gate", "structures/" + style + "_wall_gate", PI, 6*wallScaleByType[style]), | |||||
"entry": new WallElement("entry", undefined, 0, 6*wallScaleByType[style]), | |||||
"entryTower": new WallElement("entryTower", "structures/" + civ + "_defense_tower", PI, 6*wallScaleByType[style], -4*wallScaleByType[style]), | |||||
"entryFort": new WallElement("entryFort", "structures/" + civ + "_fortress", 0, 8*wallScaleByType[style], 6*wallScaleByType[style]), | |||||
// Defensive wall elements with 0 width outside the wall | |||||
"outpost": new WallElement("outpost", "structures/" + civ + "_outpost", PI, 0, -4*wallScaleByType[style]), | |||||
"defenseTower": new WallElement("defenseTower", "structures/" + civ + "_defense_tower", PI, 0, -4*wallScaleByType[style]), | |||||
// Base buildings wall elements with 0 width inside the wall | |||||
"barracks": new WallElement("barracks", "structures/" + civ + "_barracks", PI, 0, 4.5*wallScaleByType[style]), | |||||
"civilCentre": new WallElement("civilCentre", "structures/" + civ + "_civil_centre", PI, 0, 4.5*wallScaleByType[style]), | |||||
"farmstead": new WallElement("farmstead", "structures/" + civ + "_farmstead", PI, 0, 4.5*wallScaleByType[style]), | |||||
"field": new WallElement("field", "structures/" + civ + "_field", PI, 0, 4.5*wallScaleByType[style]), | |||||
"fortress": new WallElement("fortress", "structures/" + civ + "_fortress", PI, 0, 4.5*wallScaleByType[style]), | |||||
"house": new WallElement("house", "structures/" + civ + "_house", PI, 0, 4.5*wallScaleByType[style]), | |||||
"market": new WallElement("market", "structures/" + civ + "_market", PI, 0, 4.5*wallScaleByType[style]), | |||||
"storehouse": new WallElement("storehouse", "structures/" + civ + "_storehouse", PI, 0, 4.5*wallScaleByType[style]), | |||||
"temple": new WallElement("temple", "structures/" + civ + "_temple", PI, 0, 4.5*wallScaleByType[style]), | |||||
// Generic space/gap wall elements | |||||
"space1": new WallElement("space1", undefined, 0, 1*wallScaleByType[style]), | |||||
"space2": new WallElement("space2", undefined, 0, 2*wallScaleByType[style]), | |||||
"space3": new WallElement("space3", undefined, 0, 3*wallScaleByType[style]), | |||||
"space4": new WallElement("space4", undefined, 0, 4*wallScaleByType[style]) | |||||
}; | |||||
} | |||||
// Add wall fortresses for all generic styles | |||||
wallStyles.athen.wallFort = new WallElement("wallFort", "structures/athen_fortress", 2*PI/2, 5.1, 1.9); | |||||
wallStyles.brit.wallFort = new WallElement("wallFort", "structures/brit_fortress", PI, 2.8); | |||||
wallStyles.cart.wallFort = new WallElement("wallFort", "structures/cart_fortress", PI, 5.1, 1.6); | |||||
wallStyles.gaul.wallFort = new WallElement("wallFort", "structures/gaul_fortress", PI, 4.2, 1.5); | |||||
wallStyles.iber.wallFort = new WallElement("wallFort", "structures/iber_fortress", PI, 5, 0.2); | |||||
wallStyles.mace.wallFort = new WallElement("wallFort", "structures/mace_fortress", 2*PI/2, 5.1, 1.9); | |||||
wallStyles.maur.wallFort = new WallElement("wallFort", "structures/maur_fortress", PI, 5.5); | |||||
wallStyles.pers.wallFort = new WallElement("wallFort", "structures/pers_fortress", PI, 5.6, 1.9); | |||||
wallStyles.ptol.wallFort = new WallElement("wallFort", "structures/ptol_fortress", 2*PI/2, 5.1, 1.9); | |||||
wallStyles.rome.wallFort = new WallElement("wallFort", "structures/rome_fortress", PI, 6.3, 2.1); | |||||
wallStyles.sele.wallFort = new WallElement("wallFort", "structures/sele_fortress", 2*PI/2, 5.1, 1.9); | |||||
wallStyles.spart.wallFort = new WallElement("wallFort", "structures/spart_fortress", 2*PI/2, 5.1, 1.9); | |||||
// Adjust "rome_siege" style | |||||
wallStyles.rome_siege.wallFort = new WallElement("wallFort", "structures/rome_army_camp", PI, 7.2, 2); | |||||
wallStyles.rome_siege.entryFort = new WallElement("entryFort", "structures/rome_army_camp", PI, 12, 7); | |||||
wallStyles.rome_siege.house = new WallElement("house", "structures/rome_tent", PI, 0, 4); | |||||
// Add special wall styles not well to implement generic (and to show how custom styles can be added) | |||||
wallScaleByType.palisades = 0.55; | |||||
let gate = new WallElement("gate", "other/palisades_rocks_gate", PI, 3.6); | |||||
wallStyles.palisades = { | |||||
"wall": new WallElement("wall", "other/palisades_rocks_medium", 0, 2.3), | |||||
"wallMedium": new WallElement("wall", "other/palisades_rocks_medium", 0, 2.3), | |||||
"wallLong": new WallElement("wall", "other/palisades_rocks_long", 0, 3.5), | |||||
"wallShort": new WallElement("wall", "other/palisades_rocks_short", 0, 1.2), | |||||
"tower": new WallElement("tower", "other/palisades_rocks_tower", -PI/2, 0.7), | |||||
"wallFort": new WallElement("wallFort", "other/palisades_rocks_fort", PI, 1.7), | |||||
"gate": gate, | |||||
"entry": new WallElement("entry", undefined, gate.angle, gate.width), | |||||
"entryTower": new WallElement("entryTower", "other/palisades_rocks_watchtower", 0, gate.width, -3), | |||||
"entryFort": new WallElement("entryFort", "other/palisades_rocks_fort", PI, 6, 3), | |||||
"cornerIn": new WallElement("cornerIn", "other/palisades_rocks_curve", 3*PI/4, 2.1, 0.7, PI/2), | |||||
"cornerOut": new WallElement("cornerOut", "other/palisades_rocks_curve", 5*PI/4, 2.1, -0.7, -PI/2), | |||||
"outpost": new WallElement("outpost", "other/palisades_rocks_outpost", PI, 0, -2), | |||||
"house": new WallElement("house", "other/celt_hut", PI, 0, 5), | |||||
"barracks": new WallElement("barracks", "structures/gaul_tavern", PI, 0, 5), | |||||
"endRight": new WallElement("endRight", "other/palisades_rocks_end", -PI/2, 0.2), | |||||
"endLeft": new WallElement("endLeft", "other/palisades_rocks_end", PI/2, 0.2) | |||||
}; | |||||
// NOTE: This is not a wall style in the common sense. Use with care! | |||||
wallStyles.road = { | |||||
"short": new WallElement("road", "actor|props/special/eyecandy/road_temperate_short.xml", PI/2, 4.5), | |||||
"long": new WallElement("road", "actor|props/special/eyecandy/road_temperate_long.xml", PI/2, 9.5), | |||||
// Correct width by -2*indent to fit xStraicht/corner | |||||
"cornerLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_corner.xml", -PI/2, 4.5-2*1.25, 1.25, PI/2), | |||||
"cornerRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_corner.xml", 0, 4.5-2*1.25, -1.25, -PI/2), | |||||
"curveLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_curve_small.xml", -PI/2, 4.5+2*0.2, -0.2, PI/2), | |||||
"curveRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_curve_small.xml", 0, 4.5+2*0.2, 0.2, -PI/2), | |||||
"start": new WallElement("road", "actor|props/special/eyecandy/road_temperate_end.xml", PI/2, 2), | |||||
"end": new WallElement("road", "actor|props/special/eyecandy/road_temperate_end.xml", -PI/2, 2), | |||||
"xStraight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5), | |||||
"xLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5, 0, PI/2), | |||||
"xRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_x.xml", 0, 4.5, 0, -PI/2), | |||||
"tLeft": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_T.xml", PI, 4.5, 1.25), | |||||
"tRight": new WallElement("road", "actor|props/special/eyecandy/road_temperate_intersect_T.xml", 0, 4.5, -1.25), | |||||
}; | |||||
// NOTE: This is not a wall style in the common sense. Use with care! | |||||
wallStyles.other = { | |||||
"fence": new WallElement("fence", "other/fence_long", -PI/2, 3.1), | |||||
"fence_medium": new WallElement("fence", "other/fence_long", -PI/2, 3.1), | |||||
"fence_short": new WallElement("fence_short", "other/fence_short", -PI/2, 1.5), | |||||
"fence_stone": new WallElement("fence_stone", "other/fence_stone", -PI/2, 2.5), | |||||
"palisade": new WallElement("palisade", "other/palisades_rocks_short", 0, 1.2), | |||||
"column": new WallElement("column", "other/column_doric", 0, 1), | |||||
"obelisk": new WallElement("obelisk", "other/obelisk", 0, 2), | |||||
"spike": new WallElement("spike", "other/palisades_angle_spike", -PI/2, 1), | |||||
"bench": new WallElement("bench", "other/bench", PI/2, 1.5), | |||||
"benchForTable": new WallElement("benchForTable", "other/bench", 0, 0.5), | |||||
"table": new WallElement("table", "other/table_rectangle", 0, 1), | |||||
"table_square": new WallElement("table_square", "other/table_square", PI/2, 1), | |||||
"flag": new WallElement("flag", "special/rallypoint", PI, 1), | |||||
"standing_stone": new WallElement("standing_stone", "gaia/special_ruins_standing_stone", PI, 1), | |||||
"settlement": new WallElement("settlement", "gaia/special_settlement", PI, 6), | |||||
"gap": new WallElement("gap", undefined, 0, 2), | |||||
"gapSmall": new WallElement("gapSmall", undefined, 0, 1), | |||||
"gapLarge": new WallElement("gapLarge", undefined, 0, 4), | |||||
"cornerIn": new WallElement("cornerIn", undefined, 0, 0, 0, PI/2), | |||||
"cornerOut": new WallElement("cornerOut", undefined, 0, 0, 0, -PI/2) | |||||
}; | |||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
// fortressTypes data structure for some default fortress types | |||||
// | |||||
// A fortress type is just an instance of the Fortress class with actually something in it | |||||
// fortressTypes holds all the fortresses within an associative array with a descriptive string as key (e.g. matching the map size) | |||||
// Examples: "tiny", "veryLarge" | |||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |||||
var fortressTypes = {}; | |||||
{ | |||||
let wallParts = { | |||||
"tiny": ["gate", "tower", "wallShort", "cornerIn", "wallShort", "tower"], | |||||
"small": ["gate", "tower", "wall", "cornerIn", "wall", "tower"], | |||||
"medium": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "tower"], | |||||
"normal": ["gate", "tower", "wall", "cornerIn", "wall", | |||||
"cornerOut", "wall", "cornerIn", "wall", "tower"], | |||||
"large": ["gate", "tower", "wallLong", "cornerIn", "wallLong", | |||||
"cornerOut", "wallLong", "cornerIn", "wallLong", "tower"], | |||||
"veryLarge": ["gate", "tower", "wall", "cornerIn", "wall", "cornerOut", "wallLong", | |||||
"cornerIn", "wallLong", "cornerOut", "wall", "cornerIn", "wall", "tower"], | |||||
"giant": ["gate", "tower", "wallLong", "cornerIn", "wallLong", "cornerOut", "wallLong", | |||||
"cornerIn", "wallLong", "cornerOut", "wallLong", "cornerIn", "wallLong", "tower"] | |||||
}; | |||||
for (let type in wallParts) | |||||
{ | |||||
fortressTypes[type] = new Fortress(type); | |||||
let wp = wallParts[type]; | |||||
fortressTypes[type].wall = wp.concat(wp, wp, wp); | |||||
} | |||||
} | |||||
// Setup some better looking semi default fortresses for "palisades" style | |||||
for (let type in fortressTypes) | |||||
{ | |||||
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) | for (let j = 0; j < oldWall.length; ++j) | ||||
{ | { | ||||
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) | defaultFortresses[newKey].wall.push(oldWall[j]); | ||||
if (j+1 < oldWall.length) | |||||
if (fillTowersBetween.indexOf(oldWall[j]) > -1 && fillTowersBetween.indexOf(oldWall[j+1]) > -1) // ... > -1 means "exists" here | if (j + 1 < oldWall.length && | ||||
fortressTypes[newKey].wall.push("tower"); | fillTowersBetween.indexOf(oldWall[j]) != -1 && | ||||
fillTowersBetween.indexOf(oldWall[j + 1]) != -1) | |||||
{ | |||||
defaultFortresses[newKey].wall.push("tower"); | |||||
} | |||||
} | } | ||||
} | } | ||||
Done Inline Actionselement = outpost || element = tower? elexis: element = outpost || element = tower? | |||||
// Setup some balanced (to civ type fortresses) semi default fortresses for "palisades" style | return defaultFortresses; | ||||
// TODO | } | ||||
// Add some "fortress types" for roads (will only work with style "road") | /** | ||||
{ | * Define some helper functions | ||||
// ["start", "short", "xRight", "xLeft", "cornerLeft", "xStraight", "long", "xLeft", "xRight", "cornerRight", "tRight", "tLeft", "xRight", "xLeft", "curveLeft", "xStraight", "curveRight", "end"]; | */ | ||||
Done Inline Actionsat least the code says so elexis: at least the code says so | |||||
let roadTypes = { | |||||
"road01": ["short", "curveLeft", "short", "curveLeft", "short", "curveLeft", "short", "curveLeft"], | /** | ||||
"road02": ["short", "cornerLeft", "short", "cornerLeft", "short", "cornerLeft", "short", "cornerLeft"], | * Get a wall element of a style. | ||||
"road03": ["xStraight", "curveLeft", "xStraight", "curveLeft", "xStraight", "curveLeft", "xStraight", "curveLeft"], | * | ||||
"road04": ["start", "curveLeft", "tRight", "cornerLeft", "tRight", "curveRight", "short", "xRight", "curveLeft", "xRight", "short", "cornerLeft", "tRight", "short", | * Valid elements: | ||||
"curveLeft", "short", "tRight", "cornerLeft", "short", "xRight", "curveLeft", "xRight", "short", "curveRight", "tRight", "cornerLeft", "tRight", "curveLeft", "end"], | * long, medium, short, start, end, cornerIn, cornerOut, tower, fort, gate, entry, entryTower, entryFort | ||||
"road05": ["start", "tLeft", "short", "xRight", | * | ||||
"curveLeft", "xRight", "tRight", "cornerLeft", "tRight", | * Dynamic elements: | ||||
"curveLeft", "short", "tRight", "cornerLeft", "xRight", | * `gap_{x}` returns a non-blocking gap of length `x` meters. | ||||
Done Inline ActionsSounds like potential for complexity reduction elexis: Sounds like potential for complexity reduction | |||||
"cornerLeft", "xRight", "short", "tRight", "curveLeft", "end"], | * `turn_{x}` returns a zero-length turn of angle `x` radians. | ||||
Not Done Inline ActionsResults in things like turn_0.25 for a eighth-of-a-turn, and turn_0.5 for a quarter. Considered briefly using degrees (turn_45 and turn_90 look better), but figured that would get confusing as all other angle measurements are in radians. s0600204: Results in things like `turn_0.25` for a eighth-of-a-turn, and `turn_0.5` for a quarter. | |||||
}; | * | ||||
* Any other arbitrary string passed will be attempted to be used as: `structures/{civ}_{arbitrary_string}`. | |||||
Done Inline Actionsgapx returns a gap of the length x? elexis: `gapx` returns a gap of the length `x`? | |||||
Done Inline ActionsA wall element that leaves a gap (no entity) of the given width is returned, yes ;p FeXoR: A wall element that leaves a gap (no entity) of the given width is returned, yes ;p
So… | |||||
for (let type in roadTypes) | * | ||||
fortressTypes[type] = new Fortress(type, roadTypes[type]); | * @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. | |||||
Not Done Inline Actions-> turnin and turnout return a 90 degree turn? (Perhaps the code would be easier to read, maintain and document if those were separate functions.) elexis: -> `turnin` and `turnout` return a 90 degree turn?
(Perhaps the code would be easier to read… | |||||
Not Done Inline ActionsYes. And since a wall arrangement has an inside and an outside (e.g. for closed walls and for gates and towers with droppoints/visible entrances) there are two versions: In and Out. FeXoR: Yes. And since a wall arrangement has an inside and an outside (e.g. for closed walls and for… | |||||
/////////////////////////////// | */ | ||||
// Define some helper functions | function getWallElement(element, style) | ||||
/////////////////////////////// | { | ||||
style = validateStyle(style); | |||||
Done Inline ActionsThe sentence can be deleted, no? It just states that {civ} is replaced with the civ of the player? (else replace "normal structure" with its definition) elexis: The sentence can be deleted, no? It just states that {civ} is replaced with the civ of the… | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | if (g_WallStyles[style][element]) | ||||
Done Inline Actions(the indentation speaks for itself I guess) elexis: (the indentation speaks for itself I guess) | |||||
// getWallAlignment | return g_WallStyles[style][element]; | ||||
// | |||||
// Returns a list of objects containing all information to place all the wall elements entities with placeObject (but the player ID) | // Attempt to derive any unknown elements. | ||||
// Placing the first wall element at startX/startY placed with an angle given by orientation | // Defaults to a wall tower piece | ||||
// An alignment can be used to get the "center" of a "wall" (more likely used for fortresses) with getCenterToFirstElement | const quarterBend = Math.PI / 2; | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | let wallset = g_WallStyles[style]; | ||||
function getWallAlignment(startX, startY, wall, style, orientation) | let civ = style.split("_")[0]; | ||||
{ | let ret = wallset.tower ? clone(wallset.tower) : { "angle": 0, "bend": 0, "length": 0, "indent": 0 }; | ||||
// Graciously handle arguments | |||||
if (wall === undefined) | switch (element) | ||||
wall = []; | |||||
if (!wallStyles.hasOwnProperty(style)) | |||||
{ | { | ||||
warn("Function getWallAlignment: Unknown style: " + style + ' (falling back to "athen")'); | case "cornerIn": | ||||
style = "athen"; | if (wallset.curves) | ||||
for (let curve of wallset.curves) | |||||
if (curve.bend == quarterBend) | |||||
ret = curve; | |||||
if (ret.bend != quarterBend) | |||||
{ | |||||
ret.angle += Math.PI / 4; | |||||
Done Inline Actions(maybe / 4, / 2 in all cases) elexis: (maybe / 4, / 2 in all cases) | |||||
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; | |||||
} | } | ||||
orientation = orientation || 0; | |||||
if (ret.bend != quarterBend) | |||||
{ | |||||
ret.angle -= Math.PI / 4; | |||||
ret.indent = -ret.length / 4; | |||||
ret.length = 0; | |||||
} | |||||
ret.bend = -Math.PI / 2; | |||||
break; | |||||
case "entry": | |||||
ret.templateName = undefined; | |||||
ret.length = wallset.gate.length; | |||||
break; | |||||
case "entryTower": | |||||
ret.templateName = g_CivData[civ] ? "structures/" + civ + "_defense_tower" : "other/palisades_rocks_watchtower"; | |||||
ret.indent = ret.length * -3; | |||||
ret.length = wallset.gate.length; | |||||
break; | |||||
case "entryFort": | |||||
ret = clone(wallset.fort); | |||||
ret.angle -= Math.PI; | |||||
ret.length *= 1.5; | |||||
ret.indent = ret.length; | |||||
break; | |||||
case "start": | |||||
if (wallset.end) | |||||
{ | |||||
ret = clone(wallset.end); | |||||
ret.angle += Math.PI; | |||||
} | |||||
break; | |||||
case "end": | |||||
if (wallset.end) | |||||
ret = wallset.end; | |||||
break; | |||||
default: | |||||
if (element.startsWith("gap_")) | |||||
{ | |||||
Done Inline ActionsCiv always defined now and FALLBACK nuked, rP20512 elexis: Civ always defined now and FALLBACK nuked, rP20512 | |||||
ret.templateName = undefined; | |||||
ret.angle = 0; | |||||
ret.length = +element.slice("gap_".length); | |||||
} | |||||
else if (element.startsWith("turn_")) | |||||
{ | |||||
ret.templateName = undefined; | |||||
ret.bend = +element.slice("turn_".length) * Math.PI; | |||||
Done Inline Actionselement.substr("turn_".length)? (same above) (bit odd to parse a number from a name rather than having it in a template property) elexis: `element.substr("turn_".length)`? (same above) (bit odd to parse a number from a name rather… | |||||
Not Done Inline ActionsI thought the same ^^ FeXoR: I thought the same ^^ | |||||
ret.length = 0; | |||||
} | |||||
else | |||||
{ | |||||
if (!g_CivData[civ]) | |||||
civ = Object.keys(g_CivData)[0]; | |||||
Not Done Inline ActionsWould be nice to avoid fallbacks elexis: Would be nice to avoid fallbacks | |||||
Not Done Inline ActionsAnd yet we need this one to support incorporating structures (such as houses) with the palisade wallset. (Or we could emit a warning that you can't use structures/{civ}_{structure} with wallsets (like palisades) that don't have an intrinsic or native civ. That would cause the wall demo map to display said warnings, however.) (Or we could return a blank wall element, which FeXoR might not be happy about.) s0600204: And yet we need this one to support incorporating structures (such as houses) with the palisade… | |||||
Not Done Inline ActionsAbsolutely correct, I won't like not being able to place civ dependent structures within a palisade wall. FeXoR: Absolutely correct, I won't like not being able to place civ dependent structures within a… | |||||
Not Done Inline Actionss0600204 so you're saying if we fix the palisade template and give each civ a civ-specific palisade (like fatherbushido planned with Nesico) we can remove this fallback? elexis: s0600204 so you're saying if we fix the palisade template and give each civ a civ-specific… | |||||
Not Done Inline Actions
(Can't seem to find that discussion) If every civ had a structures/{civ}_wallset_palisade, then yes. structures/athen_wallset_palisade.xml <?xml version="1.0" encoding="utf-8"?> <Entity parent="other/wallset_palisade"> <Identity> <Civ>athen</Civ> </Identity> </Entity> It would require any map that wanted to use it to specify something like brit_palisade or maur_palisade rather than just palisade. (Looking at Danubius, there.) Unless that map did g_WallStyles["palisade"] = loadWallset("other/palisade_wallset"); manually itself, which kinda puts us back to square one, esp. if that map then tried including arbitrary structures through this function (rather than pre-defining them). s0600204: > [...] planned with Nescio [...]
(Can't seem to find that discussion)
If every civ had a… | |||||
let templateName = "structures/" + civ + "_" + element; | |||||
if (Engine.TemplateExists(templateName)) | |||||
{ | |||||
ret.indent = ret.length * (element == "outpost" || element.endsWith("_tower") ? -3 : 3.5); | |||||
ret.templateName = templateName; | |||||
ret.length = 0; | |||||
} | |||||
else | |||||
warn("Unrecognised wall element: '" + element + "' (" + style + "). Defaulting to " + (wallset.tower ? "'tower'." : "a blank element.")); | |||||
} | |||||
} | |||||
// Cache to save having to calculate this element again. | |||||
g_WallStyles[style][element] = deepfreeze(ret); | |||||
Done Inline ActionsThanks! elexis: Thanks! | |||||
return ret; | |||||
} | |||||
/** | |||||
Done Inline ActionsRelevant: rP20396 s0600204: Relevant: rP20396 | |||||
* Prepare a wall element for inclusion in a style. | |||||
Done Inline Actions-() elexis: -() | |||||
* | |||||
* @param {string} path - The template path to read values from | |||||
*/ | |||||
function readyWallElement(path, civCode) | |||||
{ | |||||
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({ | |||||
"templateName": path, | |||||
"angle": template.wallPiece ? template.wallPiece.angle : Math.PI, | |||||
"length": length / TERRAIN_TILE_SIZE, | |||||
"indent": template.wallPiece ? template.wallPiece.indent / TERRAIN_TILE_SIZE : 0, | |||||
"bend": template.wallPiece ? template.wallPiece.bend : 0 | |||||
}); | |||||
} | |||||
/** | |||||
* 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 alignment = []; | ||||
let wallX = startX; | let wallX = startX; | ||||
let wallY = startY; | let wallY = startY; | ||||
for (let i = 0; i < wall.length; ++i) | for (let i = 0; i < wall.length; ++i) | ||||
{ | { | ||||
let element = wallStyles[style][wall[i]]; | let element = getWallElement(wall[i], style); | ||||
if (element === undefined && i == 0) | if (!element && i == 0) | ||||
Done Inline Actionsuneval elexis: uneval | |||||
warn("No valid wall element: " + wall[i]); | { | ||||
warn("Not a valid wall element: style = " + style + ", wall[" + i + "] = " + wall[i] + "; " + uneval(element)); | |||||
continue; | |||||
} | |||||
// Indentation | // Indentation | ||||
let placeX = wallX - element.indent * cos(orientation); | let placeX = wallX - element.indent * Math.cos(orientation); | ||||
let placeY = wallY - element.indent * sin(orientation); | let placeY = wallY - element.indent * Math.sin(orientation); | ||||
// Add wall elements entity placement arguments to the alignment | // Add wall elements entity placement arguments to the alignment | ||||
alignment.push({ | alignment.push({ | ||||
"x": placeX, | "x": placeX, | ||||
"y": placeY, | "y": placeY, | ||||
"entity": element.entity, | "templateName": element.templateName, | ||||
"angle": orientation + element.angle | "angle": orientation + element.angle | ||||
}); | }); | ||||
// Preset vars for the next wall element | // Preset vars for the next wall element | ||||
if (i+1 < wall.length) | if (i + 1 < wall.length) | ||||
{ | { | ||||
orientation += element.bending; | orientation += element.bend; | ||||
let nextElement = wallStyles[style][wall[i+1]]; | let nextElement = getWallElement(wall[i + 1], style); | ||||
if (nextElement === undefined) | if (!nextElement) | ||||
warn("No valid wall element: " + wall[i+1]); | { | ||||
let distance = (element.width + nextElement.width)/2; | 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 | // Corrections for elements with indent AND bending | ||||
let indent = element.indent; | let indent = element.indent; | ||||
let bending = element.bending; | let bend = element.bend; | ||||
if (bending !== 0 && indent !== 0) | if (bend != 0 && indent != 0) | ||||
{ | { | ||||
// Indent correction to adjust distance | // Indent correction to adjust distance | ||||
distance += indent*sin(bending); | distance += indent * Math.sin(bend); | ||||
Done Inline ActionsMath added in other places but not here elexis: Math added in other places but not here | |||||
// Indent correction to normalize indentation | // Indent correction to normalize indentation | ||||
wallX += indent * cos(orientation); | wallX += indent * Math.cos(orientation); | ||||
wallY += indent * sin(orientation); | wallY += indent * Math.sin(orientation); | ||||
} | } | ||||
// Set the next coordinates of the next element in the wall without indentation adjustment | // Set the next coordinates of the next element in the wall without indentation adjustment | ||||
wallX -= distance * sin(orientation); | wallX -= distance * Math.sin(orientation); | ||||
wallY += distance * cos(orientation); | wallY += distance * Math.cos(orientation); | ||||
} | } | ||||
} | } | ||||
return alignment; | return alignment; | ||||
} | } | ||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// getCenterToFirstElement | * Center calculation works like getting the center of mass assuming all wall elements have the same "weight" | ||||
// | * | ||||
// 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 | ||||
// | * | ||||
// It returns the vector from the center to the first wall element | * @param {number} alignment | ||||
// Used to get centerToFirstElement of fortresses by default | * @returns {object} Vector from the center of the set of aligned wallpieces to the first wall element. | ||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | */ | ||||
function getCenterToFirstElement(alignment) | function getCenterToFirstElement(alignment) | ||||
{ | { | ||||
let centerToFirstElement = { "x": 0, "y": 0 }; | 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.x -= align.x / alignment.length; | ||||
centerToFirstElement.y -= alignment[i].y/alignment.length; | centerToFirstElement.y -= align.y / alignment.length; | ||||
} | } | ||||
Done Inline ActionsWhen already fixing the whitespace for (let align of alginment) elexis: When already fixing the whitespace `for (let align of alginment)` | |||||
return centerToFirstElement; | return centerToFirstElement; | ||||
} | } | ||||
////////////////////////////////////////////////////////////////// | /** | ||||
// getWallLength | * Does not support bending wall elements like corners. | ||||
Done Inline ActionsThere is no gain from reading getWallLength and Note elexis: There is no gain from reading `getWallLength` and `Note` | |||||
// | * | ||||
// NOTE: Does not support bending wall elements like corners! | * @param {string} style | ||||
// e.g. used by placeIrregularPolygonalWall | * @param {array} wall | ||||
////////////////////////////////////////////////////////////////// | * @returns {number} The sum length (in terrain cells, not meters) of the provided wall. | ||||
function getWallLength(wall, style) | */ | ||||
{ | 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 = validateStyle(style); | ||||
Done Inline ActionsThere should be a good reason to add defaults elexis: There should be a good reason to add defaults | |||||
style = "athen"; | |||||
} | |||||
let length = 0; | let length = 0; | ||||
for (let i = 0; i < wall.length; ++i) | let overlap = g_WallStyles[style].overlap; | ||||
length += wallStyles[style][wall[i]].width; | for (let element of wall) | ||||
length += getWallElement(element, style).length - overlap; | |||||
return length; | return length; | ||||
} | } | ||||
///////////////////////////////////////////// | /** | ||||
// Define the different wall placer functions | * Makes sure the style exists and, if not, provides a fallback. | ||||
///////////////////////////////////////////// | * | ||||
* @param {string} style | |||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | * @param {number} [playerId] | ||||
Done Inline ActionsName too short and ambiguous for a library elexis: Name too short and ambiguous for a library | |||||
// placeWall | * @returns {string} Valid style. | ||||
// | */ | ||||
// Places a wall with wall elements attached to another like determined by WallElement properties. | function validateStyle(style, playerId = 0) | ||||
Not Done Inline ActionsProbably better to make playerID mandatory. elexis: Probably better to make playerID mandatory.
Why is the playerID irrelevant when called from… | |||||
Not Done Inline ActionsBecause getWallElement just returns the data of an element, it doesn't place it (for any player). FeXoR: Because getWallElement just returns the data of an element, it doesn't place it (for any… | |||||
Not Done Inline ActionsA function called validate should test if something is correct. elexis: A function called validate should test if something is correct.
A function that gets something… | |||||
// | { | ||||
// startX, startY Where the first wall element should be placed | if (!style || !g_WallStyles[style]) | ||||
Done Inline Actions!g_WallStyle[style]? same for similar checks elexis: `!g_WallStyle[style]`? same for similar checks | |||||
// 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) | |||||
{ | |||||
// Graciously handle arguments | |||||
if (wall === undefined) | |||||
wall = []; | |||||
playerId = playerId || 0; | |||||
if (!wallStyles.hasOwnProperty(style)) | |||||
{ | { | ||||
if (playerId == 0) | if (playerId == 0) | ||||
style = style || "palisades"; | return Object.keys(g_WallStyles)[0]; | ||||
Done Inline Actionsno else after return elexis: no else after return | |||||
else | |||||
style = getCivCode(playerId); | style = getCivCode(playerId) + "_stone"; | ||||
Done Inline ActionsrP20513 removed all the -1 +1 fun elexis: rP20513 removed all the -1 +1 fun | |||||
return !g_WallStyles[style] ? Object.keys(g_WallStyles)[0] : style; | |||||
} | |||||
return style; | |||||
Not Done Inline ActionsIf we are commenting on functions and their arguments, we should use JS doc. elexis: If we are commenting on functions and their arguments, we should use JS doc.
If we don't have… | |||||
Not Done Inline ActionsMaybe I'm just assuming that someone is actually going to get a JSdoc parser or auto-documenter or something up and running at some point. Anecdotally, I've been spending some time recently looking into something that required me to look at the auto-generated documentation for the ICU project. With that, you get the comment and the function name, and that's it. Sure, there's a link to a page where you can see the header file (stripped of all comment blocks), but if you want to see the underlying code that actually comprises the function, one has to go very much out of one's way to locate it. (I mean, in our code, we generally have a 1:1 relation between header files and the source code, and they're kept in the same folder; but over in the ICU project, one .h file might contain the descriptions for functions defined across several different .c files, and the .c files are split across two or three directories somewhere else in the repository's directory structure.) My point is, that sure it's obvious to us here and now that playerId is numeric and that the return is a string. But we have the luxury of having the code in front of us. But if and when someone finally does get an auto-documenter going, then those people reading the output will most likely only be able to see, much like on our current Doxygen instance, the block comment and the function name. It won't necessarily be "obvious" to them. s0600204: Maybe I'm just assuming that someone is actually going to get a JSdoc parser or auto-documenter… | |||||
Not Done Inline ActionsThe important point of having comments in a defined format to me is to avoid made-up formats that mutually conflict with each other, allowing the reader to look up things in doubt instead of having to guess what might have been meant. I share Philips point of view, it is very unlikely that someone will understand what the code is doing if they don't have the code at hand while reading the documentation. If we would support that, then we would have to describe the complete behavior of the code with all its edge cases - which is likely much longer than the code itself. From 2015-08-31-QuakeNet-#0ad-dev.log:
I guess were writing a public API, still prefer to keep it short & simple and avoid stating the obvious. elexis: The important point of having comments in a defined format to me is to avoid made-up formats… | |||||
} | } | ||||
orientation = orientation || 0; | |||||
// Get wall alignment | /** | ||||
* 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); | |||||
let AM = getWallAlignment(startX, startY, wall, style, orientation); | let AM = getWallAlignment(startX, startY, wall, style, orientation); | ||||
// Place the wall | |||||
for (let iWall = 0; iWall < wall.length; ++iWall) | for (let iWall = 0; iWall < wall.length; ++iWall) | ||||
{ | if (AM[iWall].templateName) | ||||
let entity = AM[iWall].entity; | placeObject(AM[iWall].x, AM[iWall].y, AM[iWall].templateName, playerId, AM[iWall].angle); | ||||
if (entity !== undefined) | |||||
placeObject(AM[iWall].x, AM[iWall].y, entity, playerId, AM[iWall].angle); | |||||
} | |||||
} | } | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placeCustomFortress | * Places an abitrarily designed "fortress" (closed loop of wall elements) | ||||
// | * centered around a given point. | ||||
// 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) | * The fortress wall should always start with the main entrance (like | ||||
// | * "entry" or "gate") to get the orientation correct. | ||||
// 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 | * @param {number} centerX | ||||
// playerId Optional. Number of the player the wall will be placed for. Default is 0 (gaia) | * @param {number} centerY | ||||
// orientation Optional. Angle the first wall element (should be a gate or entrance) is placed. Default is BUILDING_ORIENTATION | * @param {object} [fortress] - If not provided, defaults to the predefined "medium" fortress type. | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | * @param {string} [style] - Wall style string. | ||||
function placeCustomFortress(centerX, centerY, fortress, style, playerId = 0, orientation = BUILDING_ORIENTATION) | * @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 | ||||
// Graciously handle arguments | */ | ||||
fortress = fortress || fortressTypes.medium; | function placeCustomFortress(centerX, centerY, fortress, style, playerId = 0, orientation = 0) | ||||
if (!wallStyles.hasOwnProperty(style)) | |||||
{ | { | ||||
if (playerId == 0) | fortress = fortress || g_FortressTypes.medium; | ||||
style = style || "palisades"; | style = validateStyle(style, playerId); | ||||
else | |||||
style = getCivCode(playerId); | |||||
} | |||||
// Calculate center if fortress.centerToFirstElement is undefined (default) | // Calculate center if fortress.centerToFirstElement is undefined (default) | ||||
let centerToFirstElement = fortress.centerToFirstElement; | let centerToFirstElement = fortress.centerToFirstElement; | ||||
if (centerToFirstElement === undefined) | if (centerToFirstElement === undefined) | ||||
centerToFirstElement = getCenterToFirstElement(getWallAlignment(0, 0, fortress.wall, style)); | centerToFirstElement = getCenterToFirstElement(getWallAlignment(0, 0, fortress.wall, style)); | ||||
// Placing the fortress wall | // Placing the fortress wall | ||||
let startX = centerX + centerToFirstElement.x * cos(orientation) - centerToFirstElement.y * sin(orientation); | let startX = centerX + centerToFirstElement.x * Math.cos(orientation) - centerToFirstElement.y * Math.sin(orientation); | ||||
let startY = centerY + centerToFirstElement.y * cos(orientation) + centerToFirstElement.x * sin(orientation); | let startY = centerY + centerToFirstElement.y * Math.cos(orientation) + centerToFirstElement.x * Math.sin(orientation); | ||||
placeWall(startX, startY, fortress.wall, style, playerId, orientation); | placeWall(startX, startY, fortress.wall, style, playerId, orientation); | ||||
} | } | ||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placeFortress | * Places a predefined fortress centered around the provided point. | ||||
// | * | ||||
// Like placeCustomFortress just it takes type (a fortress type string, has to be in fortressTypes) instead of an instance of Fortress | * @see Fortress | ||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | * | ||||
function placeFortress(centerX, centerY, type, style, playerId, orientation) | * @param {string} [type] - Predefined fortress type, as used as a key in g_FortressTypes. | ||||
{ | */ | ||||
// Graciously handle arguments | function placeFortress(centerX, centerY, type = "medium", style, playerId = 0, orientation = 0) | ||||
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 | // Call placeCustomFortress with the given arguments | ||||
placeCustomFortress(centerX, centerY, fortressTypes[type], style, playerId, orientation); | placeCustomFortress(centerX, centerY, g_FortressTypes[type], style, playerId, orientation); | ||||
Done Inline ActionsI don't see an information in the 6 lines above that isn't in the one line below elexis: I don't see an information in the 6 lines above that isn't in the one line below | |||||
} | } | ||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placeLinearWall | * Places a straight wall from a given point to another, using the provided | ||||
// | * wall parts repeatedly. | ||||
// Places a straight wall from a given coordinate to an other repeatedly using the wall parts. | * | ||||
// | * Note: Any "bending" wall pieces passed will be complained about. | ||||
// 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) | * @param {number} startX - Approximate start point of the wall. | ||||
// wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["tower", "wallLong"] | * @param {number} startY - Approximate start point of the wall. | ||||
// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia | * @param {number} targetX - Approximate end point of the wall. | ||||
// playerId Optional. Integer number of the player. Default is 0 (gaia) | * @param {number} targetY - Approximate end point of the wall. | ||||
// endWithFirst Optional. A boolean value. If true the 1st wall element in the wallPart array will finalize the wall. Default is true | * @param {array} [wallPart=["tower", "long"]] | ||||
// | * @param {number} [playerId] | ||||
// TODO: Maybe add angle offset for more generic looking? | * @param {boolean} [endWithFirst] - If true, the first wall element will also be the last. | ||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | */ | ||||
function placeLinearWall(startX, startY, targetX, targetY, wallPart, style, playerId, endWithFirst) | 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) | wallPart = wallPart || ["tower", "long"]; | ||||
style = style || "palisades"; | style = validateStyle(style, playerId); | ||||
else | |||||
style = getCivCode(playerId); | |||||
} | |||||
endWithFirst = typeof endWithFirst == "undefined" ? true : endWithFirst; | |||||
// Check arguments | // Check arguments | ||||
for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex) | for (let element of wallPart) | ||||
{ | if (getWallElement(element, style).bend != 0) | ||||
Done Inline ActionsI saw bend !== 0 above, so this one should also have to check explicitly or both should use non-explicit checks elexis: I saw `bend !== 0` above, so this one should also have to check explicitly or both should use… | |||||
let bending = wallStyles[style][wallPart[elementIndex]].bending; | warn("placeLinearWall : Bending is not supported by this function, but the following bending wall element was used: " + element); | ||||
if (bending != 0) | |||||
warn("Bending is not supported by placeLinearWall but a bending wall element is used: " + wallPart[elementIndex] + " -> wallStyles[style][wallPart[elementIndex]].entity"); | |||||
} | |||||
// Setup number of wall parts | // Setup number of wall parts | ||||
let totalLength = Math.euclidDistance2D(startX, startY, targetX, targetY); | let totalLength = Math.euclidDistance2D(startX, startY, targetX, targetY); | ||||
let wallPartLength = 0; | let wallPartLength = getWallLength(style, wallPart); | ||||
for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex) | let numParts = Math.ceil(totalLength / wallPartLength); | ||||
wallPartLength += wallStyles[style][wallPart[elementIndex]].width; | |||||
let numParts = 0; | |||||
if (endWithFirst) | if (endWithFirst) | ||||
numParts = ceil((totalLength - wallStyles[style][wallPart[0]].width) / wallPartLength); | numParts = Math.ceil((totalLength - getWallElement(wallPart[0], style).length) / wallPartLength); | ||||
else | |||||
numParts = ceil(totalLength / wallPartLength); | |||||
// Setup scale factor | // Setup scale factor | ||||
let scaleFactor = 1; | let scaleFactor = totalLength / (numParts * wallPartLength); | ||||
if (endWithFirst) | if (endWithFirst) | ||||
scaleFactor = totalLength / (numParts * wallPartLength + wallStyles[style][wallPart[0]].width); | scaleFactor = totalLength / (numParts * wallPartLength + getWallElement(wallPart[0], style).length); | ||||
else | |||||
scaleFactor = totalLength / (numParts * wallPartLength); | |||||
// Setup angle | // Setup angle | ||||
let wallAngle = getAngle(startX, startY, targetX, targetY); // NOTE: function "getAngle()" is about to be changed... | 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 | // Place wall entities | ||||
let x = startX; | let x = startX; | ||||
let y = startY; | let y = startY; | ||||
let overlap = g_WallStyles[style].overlap; | |||||
for (let partIndex = 0; partIndex < numParts; ++partIndex) | for (let partIndex = 0; partIndex < numParts; ++partIndex) | ||||
{ | { | ||||
for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex) | for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex) | ||||
{ | { | ||||
let wallEle = wallStyles[style][wallPart[elementIndex]]; | let wallEle = getWallElement(wallPart[elementIndex], style); | ||||
// Width correction | let wallLength = (wallEle.length - overlap) / 2; | ||||
x += scaleFactor * wallEle.width/2 * cos(wallAngle); | let distX = scaleFactor * wallLength * Math.cos(wallAngle); | ||||
y += scaleFactor * wallEle.width/2 * sin(wallAngle); | let distY = scaleFactor * wallLength * Math.sin(wallAngle); | ||||
// Length correction | |||||
x += distX; | |||||
y += distY; | |||||
// Indent correction | // Indent correction | ||||
let placeX = x - wallEle.indent * sin(wallAngle); | let placeX = x - wallEle.indent * Math.sin(wallAngle); | ||||
let placeY = y + wallEle.indent * cos(wallAngle); | let placeY = y + wallEle.indent * Math.cos(wallAngle); | ||||
// Placement | // Placement | ||||
let entity = wallEle.entity; | if (wallEle.templateName) | ||||
if (entity !== undefined) | placeObject(placeX, placeY, wallEle.templateName, playerId, placeAngle + wallEle.angle); | ||||
Done Inline Actionsif (entPath)? same below elexis: `if (entPath)`? same below | |||||
placeObject(placeX, placeY, entity, playerId, placeAngle + wallEle.angle); | |||||
x += scaleFactor * wallEle.width/2 * cos(wallAngle); | // Prep for next object | ||||
y += scaleFactor * wallEle.width/2 * sin(wallAngle); | x += distX; | ||||
y += distY; | |||||
} | } | ||||
} | } | ||||
if (endWithFirst) | if (endWithFirst) | ||||
{ | { | ||||
let wallEle = wallStyles[style][wallPart[0]]; | let wallEle = getWallElement(wallPart[0], style); | ||||
x += scaleFactor * wallEle.width/2 * cos(wallAngle); | let wallLength = (wallEle.length - overlap) / 2; | ||||
y += scaleFactor * wallEle.width/2 * sin(wallAngle); | x += scaleFactor * wallLength * Math.cos(wallAngle); | ||||
let entity = wallEle.entity; | y += scaleFactor * wallLength * Math.sin(wallAngle); | ||||
if (entity !== undefined) | if (wallEle.templateName) | ||||
placeObject(x, y, entity, playerId, placeAngle + wallEle.angle); | placeObject(x, y, wallEle.templateName, playerId, placeAngle + wallEle.angle); | ||||
} | } | ||||
} | } | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placeCircularWall | * Places a (semi-)circular wall of repeated wall elements around a central | ||||
// | * point at a given radius. | ||||
// 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 wall does not have to be closed, and can be left open in the form | ||||
// The orientation then determines where this open part faces (0 means right like unrotated building's drop-points) | * 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 | ||||
// centerX/Y Coordinates of the circle's center | * building's drop-point. | ||||
// 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"] | * Note: Any "bending" wall pieces passed will be complained about. | ||||
// 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) | * @param {number} centerX - Center of the circle or arc. | ||||
// orientation Optional. Where the open part of the (circular) arc should face (if maxAngle is < 2*PI). Default is 0 | * @param {number} centerY - Center of the circle or arc. | ||||
// maxAngle Optional. How far the wall should circumvent the center. Default is 2*PI (full circle) | * @param (number} radius - Approximate radius of the circle. (Given the maxBendOff argument) | ||||
// 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 | * @param {array} [wallPart] | ||||
// maxBendOff Optional. How irregular the circle should be. 0 means regular circle, PI/2 means very irregular. Default is 0 (regular circle) | * @param {string} [style] | ||||
// | * @param {number} [playerId] | ||||
// NOTE: Don't use wall elements with bending like corners! | * @param {number} [orientation] - Where the open part of the arc should face, if applicable. | ||||
// TODO: Perhaps add eccentricity and maxBendOff functionality (untill now an unused argument) | * @param {number} [maxAngle] - How far the wall should circumscribe the center. Default is Pi * 2 (for a full circle). | ||||
// TODO: Perhaps add functionality for spirals | * @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, orientation, maxAngle, endWithFirst, maxBendOff) | */ | ||||
{ | 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) | wallPart = wallPart || ["tower", "long"]; | ||||
style = style || "palisades"; | style = validateStyle(style, playerId); | ||||
else | |||||
style = getCivCode(playerId); | |||||
} | |||||
orientation = orientation || 0; | |||||
maxAngle = maxAngle || 2*PI; | |||||
if (endWithFirst === undefined) | if (endWithFirst === undefined) | ||||
endWithFirst = maxAngle < 2*PI - 0.001; // Can this be done better? | endWithFirst = maxAngle < Math.PI * 2 - 0.001; // Can this be done better? | ||||
Done Inline Actions(If we're touching lines with TWO_PI I'd propose to replace it with 2 * Math.PI in the sense of deleting unneeded constants, thus also removing the discussion on how these unneeded variables should be named) elexis: (If we're touching lines with `TWO_PI` I'd propose to replace it with `2 * Math.PI` in the… | |||||
Not Done Inline Actions(Would be nice to find out if this TODO and the epsilon can be killed) elexis: (Would be nice to find out if this TODO and the epsilon can be killed) | |||||
Not Done Inline ActionsSadly not. The float errors might sum up and if they do so that the sum us smaller than 2Pi there will be wall elements placed twice. FeXoR: Sadly not. The float errors might sum up and if they do so that the sum us smaller than 2Pi… | |||||
maxBendOff = maxBendOff || 0; | |||||
// Check arguments | // Check arguments | ||||
if (maxBendOff > PI/2 || maxBendOff < 0) | if (maxBendOff > Math.PI / 2 || maxBendOff < 0) | ||||
warn("placeCircularWall maxBendOff sould satisfy 0 < maxBendOff < PI/2 (~1.5) but it is: " + maxBendOff); | warn("placeCircularWall : maxBendOff should satisfy 0 < maxBendOff < PI/2 (~1.5rad) but it is: " + maxBendOff); | ||||
for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex) | |||||
{ | for (let element of wallPart) | ||||
let bending = wallStyles[style][wallPart[elementIndex]].bending; | if (getWallElement(element, style).bend != 0) | ||||
if (bending != 0) | warn("placeCircularWall : Bending is not supported by this function, but the following bending wall element was used: " + element); | ||||
warn("Bending is not supported by placeCircularWall but a bending wall element is used: " + wallPart[elementIndex]); | |||||
} | |||||
// Setup number of wall parts | // Setup number of wall parts | ||||
let totalLength = maxAngle * radius; | let totalLength = maxAngle * radius; | ||||
let wallPartLength = 0; | let wallPartLength = getWallLength(style, wallPart); | ||||
for (let elementIndex = 0; elementIndex < wallPart.length; ++elementIndex) | let numParts = Math.ceil(totalLength / wallPartLength); | ||||
wallPartLength += wallStyles[style][wallPart[elementIndex]].width; | |||||
let numParts = 0; | |||||
if (endWithFirst) | if (endWithFirst) | ||||
numParts = ceil((totalLength - wallStyles[style][wallPart[0]].width) / wallPartLength); | numParts = Math.ceil((totalLength - getWallElement(wallPart[0], style).length) / wallPartLength); | ||||
else | |||||
numParts = ceil(totalLength / wallPartLength); | |||||
// Setup scale factor | // Setup scale factor | ||||
let scaleFactor = 1; | let scaleFactor = totalLength / (numParts * wallPartLength); | ||||
if (endWithFirst) | if (endWithFirst) | ||||
scaleFactor = totalLength / (numParts * wallPartLength + wallStyles[style][wallPart[0]].width); | scaleFactor = totalLength / (numParts * wallPartLength + getWallElement(wallPart[0], style).length); | ||||
else | |||||
scaleFactor = totalLength / (numParts * wallPartLength); | |||||
// Place wall entities | // Place wall entities | ||||
let actualAngle = orientation + (2*PI - maxAngle) / 2; | let actualAngle = orientation + (Math.PI * 2 - maxAngle) / 2; | ||||
let x = centerX + radius*cos(actualAngle); | let x = centerX + radius * Math.cos(actualAngle); | ||||
let y = centerY + radius*sin(actualAngle); | let y = centerY + radius * Math.sin(actualAngle); | ||||
let overlap = g_WallStyles[style].overlap; | |||||
for (let partIndex = 0; partIndex < numParts; ++partIndex) | 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 | // Width correction | ||||
let addAngle = scaleFactor * wallEle.width / radius; | let addAngle = scaleFactor * (wallEle.length - overlap) / radius; | ||||
let targetX = centerX + radius * cos(actualAngle + addAngle); | let targetX = centerX + radius * Math.cos(actualAngle + addAngle); | ||||
let targetY = centerY + radius * sin(actualAngle + addAngle); | let targetY = centerY + radius * Math.sin(actualAngle + addAngle); | ||||
let placeX = x + (targetX - x)/2; | let placeX = x + (targetX - x) / 2; | ||||
let placeY = y + (targetY - y)/2; | let placeY = y + (targetY - y) / 2; | ||||
let placeAngle = actualAngle + addAngle/2; | let placeAngle = actualAngle + addAngle / 2; | ||||
Not Done Inline Actions(The entire function sounds a lot like it might be nicer with Vector instances. target = center.add.rotateAround or something. Can be done by volunteers of a different revision afterwards. The function arguments would then also be Vectors. Eventually the entire rmgen code would use vectors, hiding all trigonometric functions and linear algebra details. (#4845)) elexis: (The entire function sounds a lot like it might be nicer with `Vector` instances. target =… | |||||
Not Done Inline ActionsSounds good. I'll leave it for someone else to tackle, though, as most definitely out of scope for this particular revision. s0600204: Sounds good.
I'll leave it for someone else to tackle, though, as most definitely out of scope… | |||||
// Indent correction | // Indent correction | ||||
placeX -= wallEle.indent * cos(placeAngle); | placeX -= wallEle.indent * Math.cos(placeAngle); | ||||
placeY -= wallEle.indent * sin(placeAngle); | placeY -= wallEle.indent * Math.sin(placeAngle); | ||||
// Placement | // Placement | ||||
let entity = wallEle.entity; | if (wallEle.templateName) | ||||
if (entity !== undefined) | placeObject(placeX, placeY, wallEle.templateName, playerId, placeAngle + wallEle.angle); | ||||
placeObject(placeX, placeY, entity, playerId, placeAngle + wallEle.angle); | |||||
// Prepare for the next wall element | // Prepare for the next wall element | ||||
actualAngle += addAngle; | actualAngle += addAngle; | ||||
x = centerX + radius*cos(actualAngle); | x = centerX + radius * Math.cos(actualAngle); | ||||
y = centerY + radius*sin(actualAngle); | y = centerY + radius * Math.sin(actualAngle); | ||||
} | } | ||||
if (endWithFirst) | if (endWithFirst) | ||||
{ | { | ||||
let wallEle = wallStyles[style][wallPart[0]]; | let wallEle = getWallElement(wallPart[0], style); | ||||
let addAngle = scaleFactor * wallEle.width / radius; | let addAngle = scaleFactor * wallEle.length / radius; | ||||
let targetX = centerX + radius * cos(actualAngle + addAngle); | let targetX = centerX + radius * Math.cos(actualAngle + addAngle); | ||||
let targetY = centerY + radius * sin(actualAngle + addAngle); | let targetY = centerY + radius * Math.sin(actualAngle + addAngle); | ||||
let placeX = x + (targetX - x)/2; | let placeX = x + (targetX - x) / 2; | ||||
let placeY = y + (targetY - y)/2; | let placeY = y + (targetY - y) / 2; | ||||
let placeAngle = actualAngle + addAngle/2; | let placeAngle = actualAngle + addAngle / 2; | ||||
placeObject(placeX, placeY, wallEle.entity, playerId, placeAngle + wallEle.angle); | placeObject(placeX, placeY, wallEle.templateName, playerId, placeAngle + wallEle.angle); | ||||
Not Done Inline Actions(The addition of Math and whitespace fixes could be committed separately too, but sounds like a lot of rebase work, no need to) elexis: (The addition of `Math` and whitespace fixes could be committed separately too, but sounds like… | |||||
} | } | ||||
} | } | ||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placePolygonalWall | * Places a polygonal wall of repeated wall elements around a central | ||||
// | * point at a given radius. | ||||
// Place a polygonal wall of repeated wall elements given in the argument wallPart around centerX/centerY with the given radius | * | ||||
// | * Note: Any "bending" wall pieces passed will be ignored. | ||||
// centerX/Y Coordinates of the polygon's center | * | ||||
// radius How wide the circle should be in which the polygon fits | * @param {number} centerX | ||||
// wallPart Optional. An array of NON-BENDING wall element type strings. Default is ["wallLong", "tower"] | * @param {number} centerY | ||||
// cornerWallElement Optional. Wall element to be placed at the polygon's corners. Default is "tower" | * @param {number} radius | ||||
// style Optional. Wall style string. Default is the civ of the given player, "palisades" for gaia | * @param {array} [wallPart] | ||||
// playerId Optional. Integer number of the player. Default is 0 (gaia) | * @param {string} [cornerWallElement] - Wall element to be placed at the polygon's corners. | ||||
// orientation Optional. Angle from the center to the first linear wall part placed. Default is 0 (towards positive X/right) | * @param {string} [style] | ||||
// numCorners Optional. How many corners the polygon will have. Default is 8 (matching a civ centers territory) | * @param {number} [playerId] | ||||
// skipFirstWall Optional. Boolean. If the first linear wall part will be left opened as entrance. Default is true | * @param {number} [orientation] - Direction the first wall piece or opening in the wall faces. | ||||
// | * @param {number} [numCorners] - How many corners the polygon will have. | ||||
// NOTE: Don't use wall elements with bending like corners! | * @param {boolean} [skipFirstWall] - If the first linear wall part will be left opened as entrance. | ||||
// TODO: Replace skipFirstWall with firstWallPart to enable gate/defended entrance placement | */ | ||||
// TODO: Check some arguments | function placePolygonalWall(centerX, centerY, radius, wallPart, cornerWallElement = "tower", style, playerId = 0, orientation = 0, numCorners = 8, skipFirstWall = true) | ||||
// 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) | |||||
{ | { | ||||
// Setup optional arguments to the default | wallPart = wallPart || ["long", "tower"]; | ||||
wallPart = wallPart || ["wallLong", "tower"]; | style = validateStyle(style, playerId); | ||||
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; | |||||
// Setup angles | // Setup angles | ||||
let angleAdd = 2*PI/numCorners; | let angleAdd = Math.PI * 2 / numCorners; | ||||
let angleStart = orientation - angleAdd/2; | let angleStart = orientation - angleAdd / 2; | ||||
// Setup corners | // Setup corners | ||||
let corners = []; | let corners = []; | ||||
for (let i = 0; i < numCorners; ++i) | 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 | // Place Corners and walls | ||||
for (let i = 0; i < numCorners; ++i) | for (let i = 0; i < numCorners; ++i) | ||||
{ | { | ||||
let angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY); | let angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY); | ||||
placeObject(corners[i][0], corners[i][1], wallStyles[style][cornerWallElement].entity, playerId, angleToCorner); | placeObject(corners[i][0], corners[i][1], getWallElement(cornerWallElement, style).templateName, playerId, angleToCorner); | ||||
if (!skipFirstWall || i != 0) | 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( | placeLinearWall( | ||||
// Adjustment to the corner element width (approximately) | // Adjustment to the corner element width (approximately) | ||||
corners[i][0] + wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAdd/2), // startX | corners[i][0] + cornerX, // startX | ||||
corners[i][1] - wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAdd/2), // startY | corners[i][1] - cornerY, // startY | ||||
corners[(i+1)%numCorners][0] - wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAdd/2), // targetX | corners[targetCorner][0] - cornerX, // targetX | ||||
corners[(i+1)%numCorners][1] + wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAdd/2), // targetY | corners[targetCorner][1] + cornerY, // targetY | ||||
wallPart, style, playerId); | wallPart, style, playerId); | ||||
} | } | ||||
} | } | ||||
} | |||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placeIrregularPolygonalWall | * Places an irregular polygonal wall consisting of parts semi-randomly | ||||
// | * chosen from a provided assortment, built around a central point at a | ||||
// Place an irregular polygonal wall of some wall parts to choose from around centerX/centerY with the given radius | * given radius. | ||||
// | * | ||||
// centerX/Y Coordinates of the polygon's center | * Note: Any "bending" wall pieces passed will be ... I'm not sure. TODO: test what happens! | ||||
// 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" | * Note: The wallPartsAssortment is last because it's the hardest to set. | ||||
// 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) | * @param {number} centerX | ||||
// orientation Optional. Angle from the center to the first linear wall part placed. Default is 0 (towards positive X/right) | * @param {number} centerY | ||||
// numCorners Optional. How many corners the polygon will have. Default is 8 (matching a civ centers territory) | * @param {number} radius | ||||
// irregularity Optional. How irregular the polygon will be. 0 means regular, 1 means VERY irregular. Default is 0.5 | * @param {string} [cornerWallElement] - Wall element to be placed at the polygon's corners. | ||||
// skipFirstWall Optional. Boolean. If the first linear wall part will be left opened as entrance. Default is true | * @param {string} [style] | ||||
// wallPartsAssortment Optional. An array of wall part arrays to choose from for each linear wall connecting the corners. Default is hard to describe ^^ | * @param {number} [playerId] | ||||
// | * @param {number} [orientation] - Direction the first wallpiece or opening in the wall faces. | ||||
// NOTE: wallPartsAssortment is put to the end because it's hardest to set | * @param {number} [numCorners] - How many corners the polygon will have. | ||||
// NOTE: Don't use wall elements with bending like corners! | * @param {number} [irregularity] - How irregular the polygon will be. 0 = regular, 1 = VERY irregular. | ||||
// TODO: Replace skipFirstWall with firstWallPart to enable gate/defended entrance placement | * @param {boolean} [skipFirstWall] - If true, the first linear wall part will be left open as an entrance. | ||||
// TODO: Check some arguments | * @param {array} [wallPartsAssortment] - An array of wall part arrays to choose from for each linear wall connecting the corners. | ||||
// TODO: Perhaps add eccentricity | */ | ||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | function placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement = "tower", style, playerId = 0, orientation = 0, numCorners, irregularity = 0.5, skipFirstWall = false, wallPartsAssortment) | ||||
function placeIrregularPolygonalWall(centerX, centerY, radius, cornerWallElement, style, playerId, orientation, numCorners, irregularity, skipFirstWall, wallPartsAssortment) | |||||
{ | |||||
// Setup optional arguments | |||||
playerId = playerId || 0; | |||||
if (!wallStyles.hasOwnProperty(style)) | |||||
{ | { | ||||
if (playerId == 0) | style = validateStyle(style, playerId); | ||||
style = style || "palisades"; | numCorners = numCorners || randIntInclusive(5, 7); | ||||
Done Inline ActionsrandInt was replaced by randIntInclusive and randIntExclusive by bb last release elexis: `randInt` was replaced by `randIntInclusive` and `randIntExclusive` by bb last release | |||||
else | |||||
style = getCivCode(playerId); | |||||
} | |||||
Done Inline Actionsspaces around operators elexis: spaces around operators | |||||
// Generating a generic wall part assortment with each wall part including 1 gate lengthened by walls and towers | // 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... | // 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 centeredWallPart = ["gate"]; | ||||
let extandingWallPartAssortment = [["tower", "wallLong"], ["tower", "wall"]]; | let extendingWallPartAssortment = [["tower", "long"], ["tower", "medium"]]; | ||||
defaultWallPartsAssortment.push(centeredWallPart); | defaultWallPartsAssortment.push(centeredWallPart); | ||||
for (let i = 0; i < extandingWallPartAssortment.length; ++i) | for (let assortment of extendingWallPartAssortment) | ||||
Done Inline ActionsI expect that the reverse shouldn't break the iterator if we use a for of loop here elexis: I expect that the reverse shouldn't break the iterator if we use a `for of` loop here | |||||
{ | { | ||||
let wallPart = centeredWallPart; | let wallPart = centeredWallPart; | ||||
for (let j = 0; j < radius; ++j) | for (let j = 0; j < radius; ++j) | ||||
{ | { | ||||
if (j%2 == 0) | if (j % 2 == 0) | ||||
wallPart = wallPart.concat(extandingWallPartAssortment[i]); | wallPart = wallPart.concat(assortment); | ||||
else | else | ||||
{ | { | ||||
extandingWallPartAssortment[i].reverse(); | assortment.reverse(); | ||||
wallPart = extandingWallPartAssortment[i].concat(wallPart); | wallPart = assortment.concat(wallPart); | ||||
extandingWallPartAssortment[i].reverse(); | assortment.reverse(); | ||||
} | } | ||||
defaultWallPartsAssortment.push(wallPart); | defaultWallPartsAssortment.push(wallPart); | ||||
} | } | ||||
} | } | ||||
// Setup optional arguments to the default | // Setup optional arguments to the default | ||||
wallPartsAssortment = wallPartsAssortment || defaultWallPartsAssortment; | 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 | // Setup angles | ||||
let angleToCover = 2*PI; | let angleToCover = Math.PI * 2; | ||||
let angleAddList = []; | let angleAddList = []; | ||||
for (let i = 0; i < numCorners; ++i) | for (let i = 0; i < numCorners; ++i) | ||||
{ | { | ||||
// Randomize covered angles. Variety scales down with raising angle though... | // 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]; | angleToCover -= angleAddList[angleAddList.length - 1]; | ||||
} | } | ||||
// Setup corners | // Setup corners | ||||
let corners = []; | let corners = []; | ||||
let angleActual = orientation - angleAddList[0]/2; | let angleActual = orientation - angleAddList[0] / 2; | ||||
for (let i = 0; i < numCorners; ++i) | 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) | if (i < numCorners - 1) | ||||
angleActual += angleAddList[i+1]; | angleActual += angleAddList[i + 1]; | ||||
} | } | ||||
// Setup best wall parts for the different walls (a bit confusing naming...) | // Setup best wall parts for the different walls (a bit confusing naming...) | ||||
let wallPartLengths = []; | let wallPartLengths = []; | ||||
let maxWallPartLength = 0; | let maxWallPartLength = 0; | ||||
for (let partIndex = 0; partIndex < wallPartsAssortment.length; ++partIndex) | for (let wallPart of wallPartsAssortment) | ||||
{ | { | ||||
let length = wallPartLengths[partIndex]; | let length = getWallLength(style, wallPart); | ||||
wallPartLengths.push(getWallLength(wallPartsAssortment[partIndex], style)); | wallPartLengths.push(length); | ||||
if (length > maxWallPartLength) | if (length > maxWallPartLength) | ||||
maxWallPartLength = length; | 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! | 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) | for (let i = 0; i < numCorners; ++i) | ||||
{ | { | ||||
let bestWallPart = []; // This is a simpel wall part not a wallPartsAssortment! | let bestWallPart = []; // This is a simple wall part not a wallPartsAssortment! | ||||
let bestWallLength = 99999999; | let bestWallLength = Infinity; | ||||
Done Inline ActionsInfinity. if MAX_VALUE actually makes it into the engine in some coordinate it would fail equally. elexis: `Infinity`. if `MAX_VALUE` actually makes it into the engine in some coordinate it would fail… | |||||
Done Inline ActionsThanks elexis: Thanks | |||||
// NOTE: This is not exactly like the length the wall will be in the end. Has to be tweaked... | let targetCorner = (i + 1) % numCorners; | ||||
let wallLength = Math.euclidDistance2D(corners[i][0], corners[i][1], corners[(i + 1) % numCorners][0], corners[(i + 1) % numCorners][1]); | // NOTE: This is not quite the length the wall will be in the end. Has to be tweaked... | ||||
let numWallParts = ceil(wallLength/maxWallPartLength); | 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) | for (let partIndex = 0; partIndex < wallPartsAssortment.length; ++partIndex) | ||||
{ | { | ||||
let linearWallLength = numWallParts*wallPartLengths[partIndex]; | let linearWallLength = numWallParts * wallPartLengths[partIndex]; | ||||
if (linearWallLength < bestWallLength && linearWallLength > wallLength) | if (linearWallLength < bestWallLength && linearWallLength > wallLength) | ||||
{ | { | ||||
bestWallPart = wallPartsAssortment[partIndex]; | bestWallPart = wallPartsAssortment[partIndex]; | ||||
bestWallLength = linearWallLength; | bestWallLength = linearWallLength; | ||||
} | } | ||||
} | } | ||||
wallPartList.push(bestWallPart); | wallPartList.push(bestWallPart); | ||||
} | } | ||||
// Place Corners and walls | // Place Corners and walls | ||||
for (let i = 0; i < numCorners; ++i) | for (let i = 0; i < numCorners; ++i) | ||||
{ | { | ||||
let angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY); | let angleToCorner = getAngle(corners[i][0], corners[i][1], centerX, centerY); | ||||
placeObject(corners[i][0], corners[i][1], wallStyles[style][cornerWallElement].entity, playerId, angleToCorner); | placeObject(corners[i][0], corners[i][1], getWallElement(cornerWallElement, style).templateName, playerId, angleToCorner); | ||||
if (!skipFirstWall || i != 0) | 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( | placeLinearWall( | ||||
// Adjustment to the corner element width (approximately) | // Adjustment to the corner element width (approximately) | ||||
corners[i][0] + wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAddList[i]/2), // startX | corners[i][0] + cornerLength * Math.sin(startAngle), // startX | ||||
corners[i][1] - wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAddList[i]/2), // startY | corners[i][1] - cornerLength * Math.cos(startAngle), // startY | ||||
corners[(i+1)%numCorners][0] - wallStyles[style][cornerWallElement].width/2 * sin(angleToCorner + angleAddList[(i+1)%numCorners]/2), // targetX | corners[targetCorner][0] - cornerLength * Math.sin(targetAngle), // targetX | ||||
corners[(i+1)%numCorners][1] + wallStyles[style][cornerWallElement].width/2 * cos(angleToCorner + angleAddList[(i+1)%numCorners]/2), // targetY | corners[targetCorner][1] + cornerLength * Math.cos(targetAngle), // targetY | ||||
wallPartList[i], style, playerId, false); | wallPartList[i], style, playerId, false); | ||||
} | } | ||||
} | } | ||||
} | |||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | /** | ||||
// placeGenericFortress | * Places a generic fortress with towers at the edges connected with long | ||||
// | * walls and gates, positioned around a central point at a given radius. | ||||
// 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 | * The difference between this and the other two Fortress placement functions | ||||
// | * is that those place a predefined fortress, regardless of terrain type. | ||||
// centerX/Y The approximate center coordinates of the fortress | * This function attempts to intelligently place a wall circuit around | ||||
// radius The approximate radius of the wall to be placed | * the central point taking into account terrain and other obstacles. | ||||
// 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 | * This is the default Iberian civ bonus starting wall. | ||||
// 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 | * @param {number} centerX - The approximate center coordinates of the fortress | ||||
// maxTrys Optional. How often the function tries to find a better fitting shape at max. Default is 100 | * @param {number} centerY - The approximate center coordinates of the fortress | ||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | * @param {number} [radius] - The approximate radius of the wall to be placed. | ||||
function placeGenericFortress(centerX, centerY, radius, playerId, style, irregularity, gateOccurence, maxTrys) | * @param {number} [playerId] | ||||
{ | * @param {string} [style] | ||||
// Setup optional arguments | * @param {number} [irregularity] - 0 = circle, 1 = very spiky | ||||
radius = radius || 20; | * @param {number} [gateOccurence] - Integer number, every n-th walls will be a gate instead. | ||||
playerId = playerId || 0; | * @param {number} [maxTrys] - How often the function tries to find a better fitting shape. | ||||
if (!wallStyles.hasOwnProperty(style)) | */ | ||||
function placeGenericFortress(centerX, centerY, radius = 20, playerId = 0, style, irregularity = 0.5, gateOccurence = 3, maxTrys = 100) | |||||
{ | { | ||||
if (playerId == 0) | style = validateStyle(style, playerId); | ||||
style = style || "palisades"; | |||||
else | |||||
style = getCivCode(playerId); | |||||
} | |||||
irregularity = irregularity || 1/2; | |||||
gateOccurence = gateOccurence || 3; | |||||
maxTrys = maxTrys || 100; | |||||
// Setup some vars | // Setup some vars | ||||
let startAngle = randFloat(0, 2*PI); | let startAngle = randFloat(0, Math.PI * 2); | ||||
let actualOffX = radius*cos(startAngle); | let actualOffX = radius * Math.cos(startAngle); | ||||
let actualOffY = radius*sin(startAngle); | let actualOffY = radius * Math.sin(startAngle); | ||||
let actualAngle = 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 | // Searching for a well fitting point derivation | ||||
let tries = 0; | let tries = 0; | ||||
let bestPointDerivation = undefined; | let bestPointDerivation; | ||||
let minOverlap = 1000; | let minOverlap = 1000; | ||||
let overlap = undefined; | let overlap; | ||||
while (tries < maxTrys && minOverlap > wallStyles[style].tower.width / 10) | while (tries < maxTrys && minOverlap > g_WallStyles[style].overlap) | ||||
{ | { | ||||
let pointDerivation = []; | let pointDerivation = []; | ||||
let distanceToTarget = 1000; | let distanceToTarget = 1000; | ||||
let targetReached = false; | let targetReached = false; | ||||
while (!targetReached) | while (!targetReached) | ||||
{ | { | ||||
let indent = randFloat(-irregularity*pointDistance, irregularity*pointDistance); | let indent = randFloat(-irregularity * pointDistance, irregularity * pointDistance); | ||||
let tmpAngle = getAngle(actualOffX, actualOffY, | let tmpAngle = getAngle(actualOffX, actualOffY, | ||||
(radius + indent)*cos(actualAngle + (pointDistance / radius)), | (radius + indent) * Math.cos(actualAngle + pointDistance / radius), | ||||
(radius + indent)*sin(actualAngle + (pointDistance / radius))); | (radius + indent) * Math.sin(actualAngle + pointDistance / radius) | ||||
actualOffX += pointDistance*cos(tmpAngle); | ); | ||||
actualOffY += pointDistance*sin(tmpAngle); | actualOffX += pointDistance * Math.cos(tmpAngle); | ||||
actualOffY += pointDistance * Math.sin(tmpAngle); | |||||
actualAngle = getAngle(0, 0, actualOffX, actualOffY); | actualAngle = getAngle(0, 0, actualOffX, actualOffY); | ||||
pointDerivation.push([actualOffX, actualOffY]); | pointDerivation.push([actualOffX, actualOffY]); | ||||
distanceToTarget = Math.euclidDistance2D(actualOffX, actualOffY, ...pointDerivation[0]); | distanceToTarget = Math.euclidDistance2D(actualOffX, actualOffY, ...pointDerivation[0]); | ||||
let numPoints = pointDerivation.length; | let numPoints = pointDerivation.length; | ||||
if (numPoints > 3 && distanceToTarget < pointDistance) // Could be done better... | if (numPoints > 3 && distanceToTarget < pointDistance) // Could be done better... | ||||
{ | { | ||||
targetReached = true; | targetReached = true; | ||||
overlap = pointDistance - Math.euclidDistance2D(...pointDerivation[numPoints - 1], ...pointDerivation[0]); | overlap = pointDistance - Math.euclidDistance2D(...pointDerivation[numPoints - 1], ...pointDerivation[0]); | ||||
if (overlap < minOverlap) | if (overlap < minOverlap) | ||||
{ | { | ||||
minOverlap = overlap; | minOverlap = overlap; | ||||
bestPointDerivation = pointDerivation; | bestPointDerivation = pointDerivation; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
+tries; | ++tries; | ||||
} | } | ||||
log("placeGenericFortress: Reduced overlap to " + minOverlap + " after " + tries + " tries"); | log("placeGenericFortress: Reduced overlap to " + minOverlap + " after " + tries + " tries"); | ||||
// Place wall | // Place wall | ||||
for (let pointIndex = 0; pointIndex < bestPointDerivation.length; ++pointIndex) | for (let pointIndex = 0; pointIndex < bestPointDerivation.length; ++pointIndex) | ||||
{ | { | ||||
let startX = centerX + bestPointDerivation[pointIndex][0]; | let startX = centerX + bestPointDerivation[pointIndex][0]; | ||||
let startY = centerY + bestPointDerivation[pointIndex][1]; | let startY = centerY + bestPointDerivation[pointIndex][1]; | ||||
let targetX = centerX + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][0]; | let targetX = centerX + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][0]; | ||||
let targetY = centerY + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][1]; | let targetY = centerY + bestPointDerivation[(pointIndex + 1) % bestPointDerivation.length][1]; | ||||
let angle = getAngle(startX, startY, targetX, targetY); | let angle = getAngle(startX, startY, targetX, targetY); | ||||
let wallElement = "wallLong"; | |||||
if ((pointIndex + 1) % gateOccurence == 0) | let element = (pointIndex + 1) % gateOccurence == 0 ? "gate" : "long"; | ||||
wallElement = "gate"; | element = getWallElement(element, style); | ||||
let entity = wallStyles[style][wallElement].entity; | if (element.templateName) | ||||
if (entity) | { | ||||
let dist = Math.euclidDistance2D(startX, startY, targetX, targetY) / 2; | |||||
placeObject( | placeObject( | ||||
startX + (Math.euclidDistance2D(startX, startY, targetX, targetY) / 2) * Math.cos(angle), | startX + dist * Math.cos(angle), // placeX | ||||
startY + (Math.euclidDistance2D(startX, startY, targetX, targetY) / 2) * Math.sin(angle), | startY + dist * Math.sin(angle), // placeY | ||||
entity, | element.templateName, playerId, angle - Math.PI / 2 + element.angle | ||||
playerId, | ); | ||||
angle - Math.PI / 2 + wallStyles[style][wallElement].angle); | } | ||||
// Place tower | // Place tower | ||||
startX = centerX + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][0]; | startX = centerX + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][0]; | ||||
startY = centerY + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][1]; | startY = centerY + bestPointDerivation[(pointIndex + bestPointDerivation.length - 1) % bestPointDerivation.length][1]; | ||||
angle = getAngle(startX, startY, targetX, targetY); | angle = getAngle(startX, startY, targetX, targetY); | ||||
let tower = getWallElement("tower", style); | |||||
placeObject( | placeObject( | ||||
centerX + bestPointDerivation[pointIndex][0], | centerX + bestPointDerivation[pointIndex][0], | ||||
centerY + bestPointDerivation[pointIndex][1], | centerY + bestPointDerivation[pointIndex][1], | ||||
wallStyles[style].tower.entity, | tower.templateName, playerId, angle - Math.PI / 2 + tower.angle | ||||
playerId, | ); | ||||
angle - PI/2 + wallStyles[style].tower.angle); | |||||
} | } | ||||
} | } |
@file if you want, we have it in some other rmgen/ files