forked from mirrors/0ad
Implement walls on Jebel Barkal, beautify the map, improve the trigger script and add stables/cavalry attckers, refs #5040.
Display "Napata will attack in %(time)s!" to observers, string addition permitted by Itms. Mapgen: Use the EntitiesObstructionPlacer from04679a8e2b, the wall constraints and return values from8b2a7f26e0, so that there are no walls at the hill and to keep gates free. Add palms to the city blocks and guardians at the central path. Carve out temple areas using the EntitiesObstructionPlacer, rather than having half the temple submerged by the mountain. More treasures on the hill and in the city and allow mines on top of the hill. Triggerscript: Make units always patrol, since the attack-walk order runs out and then units accumulated after eradicating a player. This also fixes the inconsistency that units captured after that order ran out, but not before. Don't use the violent stance since this also allows capturing. Move attacker targetClasses to attacker balancing composition object, so that it can be fine-tuned for every attackergroup. Balancing: Place Stables which only spawns melee cavalry which only attack traders, women and siege engines. Nerf fortresses by not exclusively spawning champions (temples still do). This was SVN commit r21556.
This commit is contained in:
@@ -72,26 +72,36 @@ const oBlemmyeCamp = "structures/kush_blemmye_camp";
|
||||
const oNubaVillage = "structures/kush_nuba_village";
|
||||
const oCivicCenter = "structures/kush_civil_centre";
|
||||
const oBarracks = "structures/kush_barracks";
|
||||
const oStable = "structures/kush_stable";
|
||||
const oElephantStables = "structures/kush_elephant_stables";
|
||||
const oWallMedium = "structures/kush_wall_medium";
|
||||
const oWallGate = "structures/kush_wall_gate";
|
||||
const oWallTower = "structures/kush_wall_tower";
|
||||
const oKushCitizenArcher = "units/kush_infantry_archer_e";
|
||||
const oKushChampionArcher = "units/kush_champion_infantry";
|
||||
const oKushChampions = [
|
||||
oKushChampionArcher,
|
||||
"units/kush_champion_infantry_amun",
|
||||
"units/kush_champion_infantry_apedemak"
|
||||
];
|
||||
const oPtolSiege = ["units/ptol_mechanical_siege_lithobolos_unpacked", "units/ptol_mechanical_siege_polybolos_unpacked"];
|
||||
const oTriggerPointPath = "trigger/trigger_point_A";
|
||||
const oTriggerPointCityPath = "trigger/trigger_point_A";
|
||||
const oTriggerPointAttackerPatrol = "trigger/trigger_point_B";
|
||||
|
||||
const aRock = actorTemplate("geology/stone_savanna_med");
|
||||
const aHandcart = actorTemplate("props/special/eyecandy/handcart_1");
|
||||
const aPlotFence = actorTemplate("props/special/common/plot_fence");
|
||||
const aStatueKush = actorTemplate("props/special/eyecandy/statues_kush");
|
||||
const aStatues = [
|
||||
"props/structures/kushites/statue_lion",
|
||||
"props/structures/kushites/statue_ram"
|
||||
"props/structures/kushites/statue_pedestal_rectangular",
|
||||
"props/structures/kushites/statue_pedestal_rectangular_lion"
|
||||
].map(actorTemplate);
|
||||
const aBushesFertileLand = [
|
||||
...new Array(3).fill("props/flora/shrub_spikes"),
|
||||
...new Array(3).fill("props/flora/ferns"),
|
||||
"props/flora/shrub_tropic_plant_a",
|
||||
"props/flora/shrub_tropic_plant_b",
|
||||
"props/flora/shrub_tropic_plant_flower",
|
||||
"props/flora/ferns",
|
||||
"props/flora/foliagebush",
|
||||
"props/flora/bush",
|
||||
"props/flora/bush_medit_la",
|
||||
@@ -148,14 +158,18 @@ const clPath = g_Map.createTileClass();
|
||||
const clPathStatues = g_Map.createTileClass();
|
||||
const clPathCrossing = g_Map.createTileClass();
|
||||
const clStatue = g_Map.createTileClass();
|
||||
const clTriggerPointPath = g_Map.createTileClass();
|
||||
const clWall = g_Map.createTileClass();
|
||||
const clGate = g_Map.createTileClass();
|
||||
const clTriggerPointCityPath = g_Map.createTileClass();
|
||||
const clTriggerPointMap = g_Map.createTileClass();
|
||||
const clSoldier = g_Map.createTileClass();
|
||||
|
||||
const clTower = g_Map.createTileClass();
|
||||
const clFortress = g_Map.createTileClass();
|
||||
const clTemple = g_Map.createTileClass();
|
||||
const clPyramid = g_Map.createTileClass();
|
||||
const clHouse = g_Map.createTileClass();
|
||||
const clBlacksmith = g_Map.createTileClass();
|
||||
const clStable = g_Map.createTileClass();
|
||||
const clElephantStables = g_Map.createTileClass();
|
||||
const clCivicCenter = g_Map.createTileClass();
|
||||
const clBarracks = g_Map.createTileClass();
|
||||
@@ -165,13 +179,15 @@ const clMarket = g_Map.createTileClass();
|
||||
|
||||
const riverAngle = Math.PI * 0.05;
|
||||
|
||||
const hillRadius = scaleByMapSize(40, 140);
|
||||
const hillRadius = scaleByMapSize(40, 120);
|
||||
const positionPyramids = new Vector2D(fractionToTiles(0.15), fractionToTiles(0.75));
|
||||
|
||||
const pathWidth = 4;
|
||||
const pathWidthCenter = 10;
|
||||
const pathWidthSecondary = 6;
|
||||
|
||||
const placeNapataWall = true;
|
||||
|
||||
const layoutFertileLandTextures = [
|
||||
{
|
||||
"left": fractionToTiles(0),
|
||||
@@ -223,7 +239,8 @@ var layoutKushTemples = [
|
||||
*/
|
||||
const layoutKushCity = [
|
||||
{
|
||||
"templateName": "uncapturable|" + oHouse
|
||||
"templateName": "uncapturable|" + oHouse,
|
||||
"painters": new TileClassPainter(clHouse)
|
||||
},
|
||||
{
|
||||
"templateName": "uncapturable|" + oFortress,
|
||||
@@ -240,6 +257,11 @@ const layoutKushCity = [
|
||||
"constraints": avoidClasses(clElephantStables, 10),
|
||||
"painters": new TileClassPainter(clElephantStables)
|
||||
},
|
||||
{
|
||||
"templateName": "uncapturable|" + oStable,
|
||||
"constraints": avoidClasses(clStable, 20),
|
||||
"painters": new TileClassPainter(clStable)
|
||||
},
|
||||
{
|
||||
"templateName": "uncapturable|" + oBarracks,
|
||||
"constraints": avoidClasses(clBarracks, 12),
|
||||
@@ -271,6 +293,15 @@ const layoutKushCity = [
|
||||
"painters": new TileClassPainter(clBlemmyeCamp)
|
||||
}
|
||||
];
|
||||
|
||||
g_WallStyles.napata = {
|
||||
"short": readyWallElement("uncapturable|" + oWallMedium),
|
||||
"medium": readyWallElement("uncapturable|" + oWallMedium),
|
||||
"tower": readyWallElement("uncapturable|" + oWallTower),
|
||||
"gate": readyWallElement("uncapturable|" + oWallGate),
|
||||
"overlap": 0.05
|
||||
};
|
||||
|
||||
Engine.SetProgress(10);
|
||||
|
||||
g_Map.log("Loading hill heightmap");
|
||||
@@ -295,6 +326,7 @@ const heightHill = heightDesert + heightScale(4);
|
||||
const heightHilltop = heightHill + heightScale(90);
|
||||
const heightHillArchers = (heightHilltop + heightHill) / 2;
|
||||
const heightOffsetPath = heightScale(-2.5);
|
||||
const heightOffsetWalls = heightScale(2.5);
|
||||
|
||||
g_Map.log("Flattening land");
|
||||
createArea(
|
||||
@@ -352,9 +384,12 @@ const playerPosition = playerPlacementCustomAngle(
|
||||
mapCenter,
|
||||
i => Math.PI * (-0.42 / numPlayers * (i + i % 2) - (i % 2) / 2))[0];
|
||||
|
||||
g_Map.log("Marking player positions");
|
||||
for (let position of playerPosition)
|
||||
addCivicCenterAreaToClass(position, clPlayer);
|
||||
if (!isNomad())
|
||||
{
|
||||
g_Map.log("Marking player positions");
|
||||
for (let position of playerPosition)
|
||||
addCivicCenterAreaToClass(position, clPlayer);
|
||||
}
|
||||
|
||||
g_Map.log("Marking water");
|
||||
createArea(
|
||||
@@ -482,17 +517,6 @@ const areaHilltop = createArea(
|
||||
new SlopeConstraint(-Infinity, 2)
|
||||
]);
|
||||
|
||||
g_Map.log("Painting cliffs");
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
[
|
||||
new TerrainPainter(tHillCliff),
|
||||
new TileClassPainter(clCliff)
|
||||
],
|
||||
[
|
||||
stayClasses(clHill, 0),
|
||||
new SlopeConstraint(2, Infinity)
|
||||
]);
|
||||
Engine.SetProgress(50);
|
||||
|
||||
for (let i = 0; i < numPlayers; ++i)
|
||||
@@ -535,7 +559,7 @@ for (let i = 0; i < numPlayers; ++i)
|
||||
[
|
||||
{
|
||||
"template": oWoodTreasure,
|
||||
"count": isDesert ? 3 : 0
|
||||
"count": isDesert ? 4 : 0
|
||||
},
|
||||
{
|
||||
"template": oStoneTreasure,
|
||||
@@ -580,9 +604,10 @@ Engine.SetProgress(60);
|
||||
// The city is a circle segment of this maximum size
|
||||
g_Map.log("Computing city grid");
|
||||
var gridCenter = new Vector2D(0, fractionToTiles(0.3)).rotate(-riverAngle).add(mapCenter).round();
|
||||
var gridMaxAngle = scaleByMapSize(Math.PI / 4, Math.PI);
|
||||
var gridMaxAngle = scaleByMapSize(Math.PI / 3, Math.PI);
|
||||
var gridStartAngle = -Math.PI / 2 -gridMaxAngle / 2 + riverAngle;
|
||||
var gridRadius = y => hillRadius + scaleByMapSize(10, 25) * y;
|
||||
var gridRadius = y => hillRadius + 18 * y;
|
||||
|
||||
var gridPointsX = layoutKushTemples.length;
|
||||
var gridPointsY = Math.floor(scaleByMapSize(2, 4));
|
||||
var gridPointXCenter = Math.floor(gridPointsX / 2);
|
||||
@@ -592,9 +617,10 @@ var gridPointYCenter = Math.floor(gridPointsY / 2);
|
||||
var cityGridPosition = [];
|
||||
var cityGridAngle = [];
|
||||
for (let y = 0; y < gridPointsY; ++y)
|
||||
[cityGridPosition[y], cityGridAngle[y]] = distributePointsOnCircularSegment(gridPointsX, gridMaxAngle * (gridPointsX + 1) / gridPointsX, gridStartAngle, gridRadius(y), gridCenter);
|
||||
[cityGridPosition[y], cityGridAngle[y]] = distributePointsOnCircularSegment(
|
||||
gridPointsX, gridMaxAngle * (gridPointsX + 1) / gridPointsX, gridStartAngle, gridRadius(y), gridCenter);
|
||||
|
||||
g_Map.log("Marking path points");
|
||||
g_Map.log("Marking city path crossings");
|
||||
for (let y in cityGridPosition)
|
||||
for (let x in cityGridPosition[y])
|
||||
{
|
||||
@@ -607,7 +633,7 @@ for (let y in cityGridPosition)
|
||||
]);
|
||||
}
|
||||
|
||||
g_Map.log("Marking horizontal paths");
|
||||
g_Map.log("Marking horizontal city paths");
|
||||
for (let y = 0; y < gridPointsY; ++y)
|
||||
for (let x = 1; x < gridPointsX; ++x)
|
||||
{
|
||||
@@ -617,7 +643,7 @@ for (let y = 0; y < gridPointsY; ++y)
|
||||
new TileClassPainter(clPath));
|
||||
}
|
||||
|
||||
g_Map.log("Marking vertical paths");
|
||||
g_Map.log("Marking vertical city paths");
|
||||
for (let y = 1; y < gridPointsY; ++y)
|
||||
for (let x = 0; x < gridPointsX; ++x)
|
||||
{
|
||||
@@ -635,26 +661,76 @@ for (let y = 1; y < gridPointsY; ++y)
|
||||
Engine.SetProgress(70);
|
||||
|
||||
g_Map.log("Placing kushite temples");
|
||||
var entitiesTemples = [];
|
||||
for (let i = 0; i < layoutKushTemples.length; ++i)
|
||||
{
|
||||
let x = i + (gridPointsX - layoutKushTemples.length) / 2;
|
||||
let templePosition = Vector2D.add(cityGridPosition[0][x], layoutKushTemples[i].pathOffset.rotate(-Math.PI / 2 - cityGridAngle[0][x]));
|
||||
g_Map.placeEntityPassable(layoutKushTemples[i].template, 0, templePosition, cityGridAngle[0][x]);
|
||||
clTemple.add(templePosition.round());
|
||||
entitiesTemples.push(g_Map.placeEntityPassable(layoutKushTemples[i].template, 0, templePosition, cityGridAngle[0][x]));
|
||||
}
|
||||
|
||||
g_Map.log("Marking temple area");
|
||||
createArea(
|
||||
new EntitiesObstructionPlacer(entitiesTemples, 0, Infinity),
|
||||
new TileClassPainter(clTemple));
|
||||
|
||||
g_Map.log("Smoothing temple ground");
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new ElevationBlendingPainter(heightDesert, 0.8),
|
||||
new NearTileClassConstraint(clTemple, 0));
|
||||
|
||||
g_Map.log("Painting cliffs");
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
[
|
||||
new TerrainPainter(tHillCliff),
|
||||
new TileClassPainter(clCliff)
|
||||
],
|
||||
[
|
||||
stayClasses(clHill, 0),
|
||||
new SlopeConstraint(2, Infinity)
|
||||
]);
|
||||
|
||||
g_Map.log("Painting temple ground");
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TerrainPainter(tPathWild),
|
||||
[
|
||||
new NearTileClassConstraint(clTemple, 1),
|
||||
avoidClasses(clPath, 0, clCliff, 0)
|
||||
]);
|
||||
|
||||
g_Map.log("Placing lion statues in the central path");
|
||||
var statueCount = scaleByMapSize(10, 40);
|
||||
var centralPathStart = cityGridPosition[0][gridPointXCenter];
|
||||
var centralPathLength = centralPathStart.distanceTo(cityGridPosition[gridPointsY - 1][gridPointXCenter]);
|
||||
var centralPathAngle = cityGridAngle[0][gridPointXCenter];
|
||||
for (let i = 0; i < 2; ++i)
|
||||
for (let stat = 0; stat < statueCount; ++stat)
|
||||
{
|
||||
let start = new Vector2D(0, pathWidthCenter * 3/4 * (i - 0.5)).rotate(-cityGridAngle[0][gridPointXCenter]).add(cityGridPosition[0][gridPointXCenter]);
|
||||
let position = new Vector2D(cityGridPosition[gridPointsY - 1][gridPointXCenter].distanceTo(cityGridPosition[0][gridPointXCenter]), 0).mult(stat / statueCount).rotate(-cityGridAngle[0][gridPointXCenter]).add(start).add(new Vector2D(0.5, 0.5));
|
||||
let start = new Vector2D(0, pathWidthCenter * 3/4 * (i - 0.5)).rotate(centralPathAngle).add(centralPathStart);
|
||||
let position = new Vector2D(centralPathLength, 0).mult(stat / statueCount).rotate(-centralPathAngle).add(start).add(new Vector2D(0.5, 0.5));
|
||||
|
||||
if (!avoidClasses(clPathCrossing, 2).allows(position))
|
||||
continue;
|
||||
|
||||
g_Map.placeEntityPassable(pickRandom(aStatues), 0, position, cityGridAngle[0][gridPointXCenter] - Math.PI * (i - 0.5));
|
||||
g_Map.placeEntityPassable(pickRandom(aStatues), 0, position, centralPathAngle - Math.PI * (i + 0.5));
|
||||
clPathStatues.add(position.round());
|
||||
}
|
||||
|
||||
g_Map.log("Placing guardian infantry in the central path");
|
||||
var centralChampionsCount = scaleByMapSize(2, 40);
|
||||
for (let i = 0; i < 2; ++i)
|
||||
for (let champ = 0; champ < centralChampionsCount; ++champ)
|
||||
{
|
||||
let start = new Vector2D(0, pathWidthCenter * 1/2 * (i - 0.5)).rotate(-centralPathAngle).add(centralPathStart);
|
||||
let position = new Vector2D(centralPathLength, 0).mult(champ / centralChampionsCount).rotate(-centralPathAngle).add(start).add(new Vector2D(0.5, 0.5));
|
||||
|
||||
if (!avoidClasses(clPathCrossing, 2).allows(position))
|
||||
continue;
|
||||
|
||||
g_Map.placeEntityPassable(pickRandom(oKushChampions), 0, position, centralPathAngle - Math.PI * (i - 0.5));
|
||||
clPathStatues.add(position.round());
|
||||
}
|
||||
|
||||
@@ -665,7 +741,7 @@ for (let x of [gridPointXCenter - 1, gridPointXCenter + 1])
|
||||
clPathStatues.add(cityGridPosition[gridPointYCenter][x]);
|
||||
}
|
||||
|
||||
g_Map.log("Painting paths");
|
||||
g_Map.log("Painting city paths");
|
||||
var areaPaths = createArea(
|
||||
new MapBoundsPlacer(),
|
||||
[
|
||||
@@ -674,11 +750,11 @@ var areaPaths = createArea(
|
||||
],
|
||||
stayClasses(clPath, 0));
|
||||
|
||||
g_Map.log("Placing triggerpoints on paths");
|
||||
g_Map.log("Placing triggerpoints on city paths");
|
||||
createObjectGroupsByAreas(
|
||||
new SimpleGroup([new SimpleObject(oTriggerPointPath, 1, 1, 0, 0)], true, clTriggerPointPath),
|
||||
new SimpleGroup([new SimpleObject(oTriggerPointCityPath, 1, 1, 0, 0)], true, clTriggerPointCityPath),
|
||||
0,
|
||||
[avoidClasses(clTriggerPointPath, 8), stayClasses(clPathCrossing, 2)],
|
||||
[avoidClasses(clTriggerPointCityPath, 8), stayClasses(clPathCrossing, 2)],
|
||||
scaleByMapSize(20, 100),
|
||||
30,
|
||||
[areaPaths]);
|
||||
@@ -695,25 +771,121 @@ for (let y = 1; y < gridPointsY; ++y)
|
||||
],
|
||||
new StaticConstraint(avoidClasses(clPath, 0)));
|
||||
|
||||
g_Map.log("Marking path palm area");
|
||||
var areaPathPalms = createArea(
|
||||
new MapBoundsPlacer(),
|
||||
undefined,
|
||||
new StaticConstraint([
|
||||
new NearTileClassConstraint(clPath, 1),
|
||||
avoidClasses(clPath, 0, clTemple, 10, clPyramid, 20, clPathCrossing, 1)
|
||||
]));
|
||||
if (placeNapataWall)
|
||||
{
|
||||
g_Map.log("Placing front walls");
|
||||
let wallGridMaxAngleSummand = Math.PI / 32;
|
||||
let wallGridStartAngle = gridStartAngle - wallGridMaxAngleSummand / 2;
|
||||
let wallGridRadiusFront = gridRadius(gridPointsY - 1) + pathWidth - 1;
|
||||
let wallGridMaxAngleFront = (gridMaxAngle + wallGridMaxAngleSummand) * gridPointsX / gridPointsX - wallGridMaxAngleSummand / 2;
|
||||
let entitiesWalls = placeCircularWall(
|
||||
gridCenter,
|
||||
wallGridRadiusFront,
|
||||
["tower", "short", "tower", "gate", "tower", "medium", "tower", "short"],
|
||||
"napata",
|
||||
0,
|
||||
wallGridStartAngle,
|
||||
wallGridMaxAngleFront,
|
||||
0,
|
||||
true,
|
||||
0);
|
||||
|
||||
g_Map.log("Placing path palms");
|
||||
g_Map.log("Placing side and back walls");
|
||||
let wallGridRadiusBack = hillRadius - scaleByMapSize(15, 25);
|
||||
let wallGridMaxAngleBack = (gridMaxAngle + wallGridMaxAngleSummand) * (gridPointsX + 1) / gridPointsX;
|
||||
let wallGridPositionFront = distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle, wallGridRadiusFront, gridCenter)[0];
|
||||
let wallGridPositionBack = distributePointsOnCircularSegment(gridPointsX, wallGridMaxAngleBack, wallGridStartAngle, wallGridRadiusBack, gridCenter)[0];
|
||||
let wallGridPosition = [wallGridPositionFront[0], ...wallGridPositionBack, wallGridPositionFront[wallGridPositionFront.length - 1]];
|
||||
for (let x = 1; x < wallGridPosition.length; ++x)
|
||||
entitiesWalls = entitiesWalls.concat(
|
||||
placeLinearWall(
|
||||
wallGridPosition[x - 1],
|
||||
wallGridPosition[x],
|
||||
["tower", "gate", "tower", "short", "tower", "short", "tower"],
|
||||
"napata",
|
||||
0,
|
||||
false,
|
||||
avoidClasses(clHill, 0, clTemple, 0)));
|
||||
|
||||
g_Map.log("Marking walls");
|
||||
createArea(
|
||||
new EntitiesObstructionPlacer(entitiesWalls, 0, Infinity),
|
||||
new TileClassPainter(clWall));
|
||||
|
||||
g_Map.log("Marking gates");
|
||||
let entitiesGates = entitiesWalls.filter(entity => entity.templateName.endsWith(oWallGate));
|
||||
createArea(
|
||||
new EntitiesObstructionPlacer(entitiesGates, 0, Infinity),
|
||||
new TileClassPainter(clGate));
|
||||
|
||||
g_Map.log("Painting wall terrain");
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
[
|
||||
new SmoothElevationPainter(ELEVATION_MODIFY, heightOffsetWalls, 2),
|
||||
new TerrainPainter(tPathWild)
|
||||
],
|
||||
[
|
||||
new NearTileClassConstraint(clWall, 1),
|
||||
avoidClasses(clCliff, 0)
|
||||
]);
|
||||
}
|
||||
Engine.SetProgress(70);
|
||||
|
||||
g_Map.log("Marking city palm area");
|
||||
var areaCityPalms =
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
undefined,
|
||||
new StaticConstraint([
|
||||
new NearTileClassConstraint(clPath, 1),
|
||||
avoidClasses(
|
||||
clPath, 0,
|
||||
clPyramid, 20,
|
||||
clTemple, 3,
|
||||
clWall, 3,
|
||||
clTower, 1,
|
||||
clFortress, 1,
|
||||
clPyramid, 1,
|
||||
clHouse, 1,
|
||||
clBlacksmith, 1,
|
||||
clElephantStables, 1,
|
||||
clStable, 1,
|
||||
clCivicCenter, 1,
|
||||
clBarracks, 1,
|
||||
clBlemmyeCamp, 1,
|
||||
clNubaVillage, 1,
|
||||
clMarket, 1)
|
||||
]));
|
||||
|
||||
g_Map.log("Placing city palms");
|
||||
createObjectGroupsByAreas(
|
||||
new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
|
||||
0,
|
||||
avoidClasses(clForest, 2, clCity, 0),
|
||||
scaleByMapSize(100, 400),
|
||||
10,
|
||||
[areaPathPalms]);
|
||||
avoidClasses(clForest, 2),
|
||||
scaleByMapSize(40, 400),
|
||||
15,
|
||||
[areaCityPalms]);
|
||||
|
||||
createBumps(new StaticConstraint(avoidClasses(clPlayer, 6, clCity, 0, clWater, 2, clHill, 0, clPath, 0, clTemple, 8, clPyramid, 8)), scaleByMapSize(30, 300), 1, 8, 4, 0, 3);
|
||||
g_Map.log("Marking wall palm area");
|
||||
var areaWallPalms = createArea(
|
||||
new MapBoundsPlacer(),
|
||||
undefined,
|
||||
new StaticConstraint([
|
||||
new NearTileClassConstraint(clWall, 2),
|
||||
avoidClasses(clPath, 1, clWall, 1, clGate, 3, clTemple, 2, clHill, 6)
|
||||
]));
|
||||
|
||||
g_Map.log("Placing city palms");
|
||||
createObjectGroupsByAreas(
|
||||
new SimpleGroup([new SimpleObject(oPalmPath, 1, 1, 0, 0)], true, clForest),
|
||||
0,
|
||||
avoidClasses(clForest, 2),
|
||||
scaleByMapSize(40, 200),
|
||||
50,
|
||||
[areaWallPalms]);
|
||||
|
||||
createBumps(new StaticConstraint(avoidClasses(clPlayer, 6, clCity, 0, clWater, 2, clHill, 0, clPath, 0, clTemple, 4, clPyramid, 8)), scaleByMapSize(30, 300), 1, 8, 4, 0, 3);
|
||||
Engine.SetProgress(75);
|
||||
|
||||
g_Map.log("Setting up common constraints");
|
||||
@@ -722,7 +894,7 @@ const stayFertileLand = new StaticConstraint(stayClasses(clFertileLand, 0));
|
||||
const nearWater = new NearTileClassConstraint(clWater, 3);
|
||||
var avoidCollisions = new AndConstraint(
|
||||
[
|
||||
new StaticConstraint(avoidClasses(clCliff, 0, clHill, 0, clPlayer, 15, clWater, 1, clPath, 2, clTemple, 12, clPyramid, 7, clCity, 4)),
|
||||
new StaticConstraint(avoidClasses(clCliff, 0, clHill, 0, clPlayer, 15, clWater, 1, clPath, 2, clTemple, 4, clPyramid, 7, clCity, 4, clWall, 4, clGate, 8)),
|
||||
avoidClasses(clForest, 1, clRock, 4, clMetal, 4, clFood, 6, clSoldier, 1, clTreasure, 1)
|
||||
]);
|
||||
|
||||
@@ -736,9 +908,11 @@ createForests(
|
||||
clForest,
|
||||
scaleByMapSize(250, 2000));
|
||||
|
||||
const avoidCollisionsMines = new StaticConstraint(avoidClasses(
|
||||
clWater, 4, clHill, 0, clFertileLand, 10, clCliff, 4, clCity, 4,
|
||||
clPlayer, 20, clForest, 4, clPyramid, 6, clTemple, 12, clPath, 4));
|
||||
const avoidCollisionsMines = new StaticConstraint([
|
||||
isNomad() ? new NullConstraint() : avoidClasses(clFertileLand, 10),
|
||||
avoidClasses(
|
||||
clWater, 4, clCliff, 4, clCity, 4,
|
||||
clPlayer, 20, clForest, 4, clPyramid, 6, clTemple, 4, clPath, 4, clGate, 8)]);
|
||||
|
||||
g_Map.log("Creating stone mines");
|
||||
createMines(
|
||||
@@ -748,7 +922,7 @@ createMines(
|
||||
],
|
||||
[avoidCollisionsMines, avoidClasses(clRock, 10)],
|
||||
clRock,
|
||||
scaleByMapSize(6, 24));
|
||||
scaleByMapSize(8, 26));
|
||||
|
||||
g_Map.log("Creating metal mines");
|
||||
createMines(
|
||||
@@ -758,7 +932,15 @@ createMines(
|
||||
],
|
||||
[avoidCollisionsMines, avoidClasses(clMetal, 10, clRock, 5)],
|
||||
clMetal,
|
||||
scaleByMapSize(6, 24));
|
||||
scaleByMapSize(8, 26));
|
||||
|
||||
g_Map.log("Placing triggerpoints for attackers");
|
||||
createObjectGroups(
|
||||
new SimpleGroup([new SimpleObject(oTriggerPointAttackerPatrol, 1, 1, 0, 0)], true, clTriggerPointMap),
|
||||
0,
|
||||
[avoidClasses(clCity, 8, clCliff, 4, clHill, 4, clWater, 0, clWall, 2, clForest, 1, clRock, 4, clMetal, 4, clTriggerPointMap, 15)],
|
||||
scaleByMapSize(20, 100),
|
||||
30);
|
||||
|
||||
g_Map.log("Creating berries");
|
||||
createObjectGroupsByAreas(
|
||||
@@ -925,18 +1107,21 @@ createObjectGroupsByAreas(
|
||||
new SimpleGroup([new RandomObject(oTreasuresHill, 1, 1, 2, 2)], true, clTreasure),
|
||||
0,
|
||||
new StaticConstraint([avoidClasses(clCliff, 1, clTreasure, 1)]),
|
||||
scaleByMapSize(3, 10),
|
||||
scaleByMapSize(8, 35),
|
||||
250,
|
||||
[areaHilltop]);
|
||||
|
||||
g_Map.log("Placing treasures in the city");
|
||||
var pathBorderConstraint = [new StaticConstraint([new NearTileClassConstraint(clCity, 1)]), avoidClasses(clTreasure, 2, clStatue, 10, clPathStatues, 4)];
|
||||
var pathBorderConstraint = [
|
||||
new StaticConstraint([new NearTileClassConstraint(clCity, 1)]),
|
||||
avoidClasses(clTreasure, 2, clStatue, 10, clPathStatues, 4, clWall, 2, clForest, 1)
|
||||
];
|
||||
createObjectGroupsByAreas(
|
||||
new SimpleGroup([new RandomObject(oTreasuresCity, 1, 1, 2, 2)], true, clTreasure),
|
||||
new SimpleGroup([new RandomObject(oTreasuresCity, 1, 1, 0, 2)], true, clTreasure),
|
||||
0,
|
||||
pathBorderConstraint,
|
||||
scaleByMapSize(2, 40),
|
||||
250,
|
||||
scaleByMapSize(2, 60),
|
||||
500,
|
||||
[areaPaths]);
|
||||
|
||||
g_Map.log("Placing handcarts on the paths");
|
||||
|
||||
@@ -14,8 +14,6 @@ const dryRun = false;
|
||||
*/
|
||||
const showDebugLog = false;
|
||||
|
||||
// TODO: harass attackers
|
||||
|
||||
var jebelBarkal_rank = "Advanced";
|
||||
|
||||
/**
|
||||
@@ -29,11 +27,13 @@ var jebelBarkal_templateClasses = deepfreeze({
|
||||
"champion_infantry_melee": "Champion+Infantry+Melee",
|
||||
"champion_infantry_ranged": "Champion+Infantry+Ranged",
|
||||
"champion_cavalry": "Champion+Cavalry",
|
||||
"champion_cavalry_melee": "Champion+Cavalry+Melee",
|
||||
"citizenSoldiers": "CitizenSoldier",
|
||||
"citizenSoldier_infantry": "CitizenSoldier+Infantry",
|
||||
"citizenSoldier_infantry_melee": "CitizenSoldier+Infantry+Melee",
|
||||
"citizenSoldier_infantry_ranged": "CitizenSoldier+Infantry+Ranged",
|
||||
"citizenSoldier_cavalry": "CitizenSoldier+Cavalry",
|
||||
"citizenSoldier_cavalry_melee": "CitizenSoldier+Cavalry+Melee",
|
||||
"healers": "Healer",
|
||||
"females": "FemaleCitizen"
|
||||
});
|
||||
@@ -71,7 +71,7 @@ var scaleByMapSize = (min, max) => min + (max - min) * (TriggerHelper.GetMapSize
|
||||
var jebelBarkal_cityPatrolGroup_count = time => scaleByTime(time, 3, scaleByMapSize(3, 10));
|
||||
var jebelBarkal_cityPatrolGroup_interval = time => scaleByTime(time, 5, 3);
|
||||
var jebelBarkal_cityPatrolGroup_balancing = {
|
||||
"buildingClasses": ["Wonder", "Temple", "CivCentre", "Fortress", "Barracks", "Embassy"],
|
||||
"buildingClasses": ["Wonder", "Temple", "CivCentre", "Fortress", "Barracks+!Stables", "Embassy"],
|
||||
"unitCount": time => Math.min(20, scaleByTime(time, 10, 45)),
|
||||
"unitComposition": (time, heroes) => [
|
||||
{
|
||||
@@ -90,7 +90,8 @@ var jebelBarkal_cityPatrolGroup_balancing = {
|
||||
"templates": jebelBarkal_templates.citizenSoldier_infantry_ranged,
|
||||
"frequency": scaleByTime(time, 3, 0)
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Unit+!Ship"
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -98,20 +99,20 @@ var jebelBarkal_cityPatrolGroup_balancing = {
|
||||
*/
|
||||
var jebelBarkal_attackInterval = time => randFloat(5, 7);
|
||||
|
||||
/**
|
||||
* Frequently cavalry is spawned at few buildings to harass the enemy trade.
|
||||
*/
|
||||
var jebelBarkal_harassInterval = time => randFloat(6, 8);
|
||||
|
||||
/**
|
||||
* Assume gaia to be the native kushite player.
|
||||
*/
|
||||
var jebelBarkal_playerID = 0;
|
||||
|
||||
/**
|
||||
* Soldiers will patrol along the city grid.
|
||||
* City patrols soldiers will patrol along these triggerpoints on the crossings of the city paths.
|
||||
*/
|
||||
var jebelBarkal_triggerPointPath = "A";
|
||||
var jebelBarkal_CityPatrolGroup_triggerPointPath = "A";
|
||||
|
||||
/**
|
||||
* Attackers will patrol these points after having finished the attack-walk order.
|
||||
*/
|
||||
var jebelBarkal_AttackGroup_triggerPointPatrol = "B";
|
||||
|
||||
/**
|
||||
* Attacker groups approach these player buildings and attack enemies of the given classes encountered.
|
||||
@@ -119,14 +120,9 @@ var jebelBarkal_triggerPointPath = "A";
|
||||
var jebelBarkal_pathTargetClasses = "CivCentre Wonder";
|
||||
|
||||
/**
|
||||
* Soldiers and siege towers prefer these targets when attacking or patroling.
|
||||
* Number of points the attackers patrol.
|
||||
*/
|
||||
var jebelBarkal_soldierTargetClasses = "Unit+!Ship";
|
||||
|
||||
/**
|
||||
* Elephants focus these units when attacking.
|
||||
*/
|
||||
var jebelBarkal_elephantTargetClasses = "Defensive SiegeEngine";
|
||||
var jebelBarkal_patrolPointCount = 6;
|
||||
|
||||
/**
|
||||
* This defines which units are spawned and garrisoned at the gamestart per building.
|
||||
@@ -137,7 +133,7 @@ var jebelBarkal_buildingGarrison = [
|
||||
"unitTemplates": jebelBarkal_templates.champions
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["Barracks", "Embassy"],
|
||||
"buildingClasses": ["Barracks+!Stables", "Embassy"],
|
||||
"unitTemplates": [...jebelBarkal_templates.citizenSoldiers, ...jebelBarkal_templates.champions]
|
||||
},
|
||||
{
|
||||
@@ -149,7 +145,7 @@ var jebelBarkal_buildingGarrison = [
|
||||
"unitTemplates": jebelBarkal_templates.elephants
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["Stable"],
|
||||
"buildingClasses": ["Stables"],
|
||||
"unitTemplates": jebelBarkal_templates.champion_cavalry
|
||||
},
|
||||
{
|
||||
@@ -170,6 +166,7 @@ var jebelBarkal_buildingGarrison = [
|
||||
*/
|
||||
var jebelBarkal_attackerGroup_balancing = [
|
||||
{
|
||||
// This should be the most influential building
|
||||
"buildingClasses": ["Wonder"],
|
||||
"unitCount": time => scaleByTime(time, 0, 85),
|
||||
"unitComposition": (time, heroes) => [
|
||||
@@ -198,7 +195,8 @@ var jebelBarkal_attackerGroup_balancing = [
|
||||
"templates": jebelBarkal_templates.citizenSoldier_infantry_ranged,
|
||||
"frequency": scaleByTime(time, 1, 0)
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Unit+!Ship"
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["Fortress"],
|
||||
@@ -212,12 +210,18 @@ var jebelBarkal_attackerGroup_balancing = [
|
||||
{
|
||||
"templates": jebelBarkal_templates.champions,
|
||||
"frequency": scaleByTime(time, 0, 1)
|
||||
},
|
||||
{
|
||||
"templates": jebelBarkal_templates.citizenSoldiers,
|
||||
"frequency": scaleByTime(time, 1, 0)
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Unit+!Ship"
|
||||
},
|
||||
{
|
||||
// These should only train the strongest units
|
||||
"buildingClasses": ["Temple"],
|
||||
"unitCount": time => Math.min(45, scaleByTime(time, 0, 90)),
|
||||
"unitCount": time => Math.min(45, scaleByTime(time, -30, 90)),
|
||||
"unitComposition": (time, heroes) => [
|
||||
{
|
||||
"templates": jebelBarkal_templates.heroes,
|
||||
@@ -236,7 +240,8 @@ var jebelBarkal_attackerGroup_balancing = [
|
||||
"templates": jebelBarkal_templates.healers,
|
||||
"frequency": randFloat(0.05, 0.2)
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Unit+!Ship"
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["CivCentre"],
|
||||
@@ -255,32 +260,34 @@ var jebelBarkal_attackerGroup_balancing = [
|
||||
"templates": jebelBarkal_templates.citizenSoldiers,
|
||||
"frequency": scaleByTime(time, 1, 0)
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Unit+!Ship"
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["Stable"],
|
||||
"buildingClasses": ["Stables"],
|
||||
"unitCount": time => Math.min(30, scaleByTime(time, 0, 80)),
|
||||
"harasserSize": time => Math.min(20, scaleByTime(time, 0, 50)),
|
||||
"unitComposition": (time, heroes) => [
|
||||
{
|
||||
"templates": jebelBarkal_templates.citizenSoldier_cavalry,
|
||||
"templates": jebelBarkal_templates.citizenSoldier_cavalry_melee,
|
||||
"frequency": scaleByTime(time, 2, 0)
|
||||
},
|
||||
{
|
||||
"templates": jebelBarkal_templates.champion_cavalry,
|
||||
"templates": jebelBarkal_templates.champion_cavalry_melee,
|
||||
"frequency": scaleByTime(time, 0, 1)
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Trader+!Ship SiegeEngine FemaleCitizen"
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["Barracks", "Embassy"],
|
||||
"buildingClasses": ["Barracks+!Stables", "Embassy"],
|
||||
"unitCount": time => Math.min(35, scaleByTime(time, 0, 70)),
|
||||
"unitComposition": (time, heroes) => [
|
||||
{
|
||||
"templates": jebelBarkal_templates.citizenSoldier_infantry,
|
||||
"frequency": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Unit+!Ship"
|
||||
},
|
||||
{
|
||||
"buildingClasses": ["ElephantStables"],
|
||||
@@ -290,7 +297,8 @@ var jebelBarkal_attackerGroup_balancing = [
|
||||
"templates": jebelBarkal_templates.elephants,
|
||||
"frequency": 1
|
||||
}
|
||||
]
|
||||
],
|
||||
"targetClasses": "Defensive SiegeEngine Monument"
|
||||
}
|
||||
];
|
||||
|
||||
@@ -355,6 +363,8 @@ Trigger.prototype.JebelBarkal_SpawnCityPatrolGroups = function()
|
||||
let time = TriggerHelper.GetMinutes();
|
||||
let groupCount = Math.floor(Math.max(0, jebelBarkal_cityPatrolGroup_count(time)) - this.jebelBarkal_patrolingUnits.length);
|
||||
|
||||
this.debugLog("Spawning " + groupCount + " city patrol groups, " + this.jebelBarkal_patrolingUnits.length + " exist");
|
||||
|
||||
for (let i = 0; i < groupCount; ++i)
|
||||
{
|
||||
let spawnEnt = pickRandom(this.jebelBarkal_patrolGroupSpawnPoints);
|
||||
@@ -363,8 +373,7 @@ Trigger.prototype.JebelBarkal_SpawnCityPatrolGroups = function()
|
||||
jebelBarkal_cityPatrolGroup_balancing.unitComposition(time, this.jebelBarkal_heroes),
|
||||
jebelBarkal_cityPatrolGroup_balancing.unitCount(time));
|
||||
|
||||
this.debugLog("Spawning " + groupCount + " city patrol groups, " +
|
||||
this.jebelBarkal_patrolingUnits.length + " exist, templates:\n" + uneval(templateCounts));
|
||||
this.debugLog(uneval(templateCounts));
|
||||
|
||||
let groupEntities = this.JebelBarkal_SpawnTemplates(spawnEnt, templateCounts);
|
||||
|
||||
@@ -375,16 +384,16 @@ Trigger.prototype.JebelBarkal_SpawnCityPatrolGroups = function()
|
||||
|
||||
TriggerHelper.SetUnitFormation(jebelBarkal_playerID, groupEntities, pickRandom(jebelBarkal_formations));
|
||||
|
||||
for (let entTriggerPoint of this.GetTriggerPoints(jebelBarkal_triggerPointPath))
|
||||
for (let patrolTarget of shuffleArray(this.GetTriggerPoints(jebelBarkal_CityPatrolGroup_triggerPointPath)))
|
||||
{
|
||||
let pos = TriggerHelper.GetEntityPosition2D(entTriggerPoint);
|
||||
let pos = TriggerHelper.GetEntityPosition2D(patrolTarget);
|
||||
ProcessCommand(jebelBarkal_playerID, {
|
||||
"type": "patrol",
|
||||
"entities": groupEntities,
|
||||
"x": pos.x,
|
||||
"z": pos.y,
|
||||
"targetClasses": {
|
||||
"attack": jebelBarkal_soldierTargetClasses
|
||||
"attack": jebelBarkal_cityPatrolGroup_balancing.targetClasses
|
||||
},
|
||||
"queued": true,
|
||||
"allowCapture": false
|
||||
@@ -417,13 +426,16 @@ Trigger.prototype.JebelBarkal_SpawnTemplates = function(spawnEnt, templateCounts
|
||||
*/
|
||||
Trigger.prototype.JebelBarkal_SpawnAttackerGroups = function()
|
||||
{
|
||||
let time = TriggerHelper.GetMinutes();
|
||||
this.debugLog("Attacker wave");
|
||||
if (!this.jebelBarkal_attackerGroupSpawnPoints)
|
||||
return;
|
||||
|
||||
let targets = TriggerHelper.GetAllPlayersEntitiesByClass(jebelBarkal_pathTargetClasses);
|
||||
if (!targets.length)
|
||||
return;
|
||||
|
||||
let time = TriggerHelper.GetMinutes();
|
||||
this.debugLog("Attacker wave");
|
||||
|
||||
let spawnedAnything = false;
|
||||
for (let spawnEnt of this.jebelBarkal_attackerGroupSpawnPoints)
|
||||
{
|
||||
@@ -432,7 +444,7 @@ Trigger.prototype.JebelBarkal_SpawnAttackerGroups = function()
|
||||
|
||||
let unitCount = Math.round(spawnPointBalancing.unitCount(time));
|
||||
|
||||
if (!unitCount)
|
||||
if (unitCount <= 0)
|
||||
continue;
|
||||
|
||||
let templateCounts = TriggerHelper.BalancedTemplateComposition(spawnPointBalancing.unitComposition(time, this.jebelBarkal_heroes), unitCount);
|
||||
@@ -447,25 +459,29 @@ Trigger.prototype.JebelBarkal_SpawnAttackerGroups = function()
|
||||
|
||||
let isElephant = TriggerHelper.EntityMatchesClassList(groupEntities[0], "Elephant");
|
||||
|
||||
for (let ent of groupEntities)
|
||||
TriggerHelper.SetUnitStance(ent, isElephant ? "aggressive" : "violent");
|
||||
|
||||
if (!isElephant)
|
||||
TriggerHelper.SetUnitFormation(jebelBarkal_playerID, groupEntities, pickRandom(jebelBarkal_formations));
|
||||
|
||||
let targetPos = TriggerHelper.GetEntityPosition2D(pickRandom(targets));
|
||||
if (!targetPos)
|
||||
let target = pickRandom(targets);
|
||||
if (!target)
|
||||
continue;
|
||||
|
||||
ProcessCommand(jebelBarkal_playerID, {
|
||||
"type": "attack-walk",
|
||||
"entities": groupEntities,
|
||||
"x": targetPos.x,
|
||||
"z": targetPos.y,
|
||||
"targetClasses": isElephant ? jebelBarkal_elephantTargetClasses : jebelBarkal_soldierTargetClasses,
|
||||
"allowCapture": false,
|
||||
"queued": false
|
||||
});
|
||||
let patrolTargets = [target].concat(shuffleArray(this.GetTriggerPoints(jebelBarkal_AttackGroup_triggerPointPatrol))).slice(0, jebelBarkal_patrolPointCount);
|
||||
for (let patrolTarget of patrolTargets)
|
||||
{
|
||||
let pos = TriggerHelper.GetEntityPosition2D(patrolTarget);
|
||||
ProcessCommand(jebelBarkal_playerID, {
|
||||
"type": "patrol",
|
||||
"entities": groupEntities,
|
||||
"x": pos.x,
|
||||
"z": pos.y,
|
||||
"targetClasses": {
|
||||
"attack": spawnPointBalancing.targetClasses
|
||||
},
|
||||
"queued": true,
|
||||
"allowCapture": false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnedAnything)
|
||||
@@ -474,13 +490,20 @@ Trigger.prototype.JebelBarkal_SpawnAttackerGroups = function()
|
||||
"translateMessage": true
|
||||
});
|
||||
|
||||
this.DoAfterDelay(jebelBarkal_attackInterval(TriggerHelper.GetMinutes()) * 60 * 1000, "JebelBarkal_SpawnAttackerGroups", {});
|
||||
let nextAttack = jebelBarkal_attackInterval(TriggerHelper.GetMinutes()) * 60 * 1000;
|
||||
|
||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).AddTimeNotification({
|
||||
"message": markForTranslation("Napata will attack in %(time)s!"),
|
||||
"players": [-1, 0],
|
||||
"translateMessage": true
|
||||
}, nextAttack);
|
||||
|
||||
this.DoAfterDelay(nextAttack, "JebelBarkal_SpawnAttackerGroups", {});
|
||||
};
|
||||
|
||||
/**
|
||||
* Keep track of heroes, so that each of them remains unique.
|
||||
* Keep track of spawn points, as only there units should be spawned.
|
||||
*
|
||||
*/
|
||||
Trigger.prototype.JebelBarkal_OwnershipChange = function(data)
|
||||
{
|
||||
|
||||
-1
@@ -17,7 +17,6 @@ EntitiesObstructionPlacer.prototype.place = function(constraint)
|
||||
{
|
||||
let halfObstructionSize = getObstructionSize(entity.templateName, this.margin).div(2);
|
||||
|
||||
// Place the entity if all points are within the boundaries and don't collide with the other entities
|
||||
let obstructionCorners = [
|
||||
new Vector2D(-halfObstructionSize.x, -halfObstructionSize.y),
|
||||
new Vector2D(-halfObstructionSize.x, +halfObstructionSize.y),
|
||||
|
||||
+1
@@ -7,6 +7,7 @@
|
||||
<GenericName>Stables</GenericName>
|
||||
<Tooltip>Train citizen-soldier cavalry. Research training improvements.</Tooltip>
|
||||
<Icon>structures/barracks.png</Icon>
|
||||
<Classes datatype="tokens">Stables</Classes>
|
||||
</Identity>
|
||||
<ProductionQueue>
|
||||
<BatchTimeModifier>0.8</BatchTimeModifier>
|
||||
|
||||
Reference in New Issue
Block a user