forked from mirrors/0ad
Ambush bluffs rework.
Remove ugly large circle patterns around the playerbase on Ambush, fixes #4993. To ensure passability, create ramps from the playerbase to the bluffs. Change the circular player avoidance to a ChainPlacer generating more heterogenous pattern. Use vectors in rmgen2 bluffs creation and simplify equations, refs #4992. Don't turn inaccessible bluffs to plateaus but don't place them until it is certain they are passable. Increase minimum distance from the playerbase to the mapcenter by picking different distance values per playerbase pattern in g_PlayerbaseTypes. Attempt to improve bluffs performance by avoiding bluffIgnore by 0 instead of bluff by 12, refs #5011. Implement AdjacentToAreaConstraint and deleteTerrainEntity. Delete createBoundingBox and use getBoundingBox, refs #4947, #4805. Delete fadeToGround and nextToFeature and use conventional createArea calls with the SmoothingPainter of02fe3ef3e3, refs #5027. Paint bluff cliffs slightly more accurately using the SlopeConstraint from24388f781e, refs #5004. This was SVN commit r21300.
This commit is contained in:
@@ -8,28 +8,31 @@ setSelectedBiome();
|
||||
var heightLand = 2;
|
||||
|
||||
var g_Map = new RandomMap(heightLand, g_Terrains.mainTerrain);
|
||||
var mapCenter = g_Map.getCenter();
|
||||
var mapSize = g_Map.getSize();
|
||||
|
||||
initTileClasses();
|
||||
|
||||
initTileClasses(["bluffsPassage", "nomadArea"]);
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TileClassPainter(g_TileClasses.land));
|
||||
|
||||
Engine.SetProgress(10);
|
||||
|
||||
const playerbasePattern = randomStartingPositionPattern(getTeamsArray());
|
||||
createBasesByPattern(playerbasePattern.setup, playerbasePattern.distance, playerbasePattern.separation, randomAngle());
|
||||
var playerIDs;
|
||||
var playerPosition;
|
||||
if (!isNomad())
|
||||
{
|
||||
let playerbasePattern = randomStartingPositionPattern(getTeamsArray());
|
||||
[playerIDs, playerPosition] = createBasesByPattern(playerbasePattern.setup, playerbasePattern.distance, playerbasePattern.groupedDistance, randomAngle());
|
||||
markPlayerAvoidanceArea(playerPosition, defaultPlayerBaseRadius());
|
||||
}
|
||||
Engine.SetProgress(20);
|
||||
|
||||
addElements([
|
||||
{
|
||||
"func": addBluffs,
|
||||
"baseHeight": heightLand,
|
||||
"avoid": [
|
||||
g_TileClasses.bluff, 12,
|
||||
g_TileClasses.hill, 5,
|
||||
g_TileClasses.player, 35
|
||||
],
|
||||
"avoid": [g_TileClasses.bluffIgnore, 0],
|
||||
"sizes": ["normal", "big", "huge"],
|
||||
"mixes": ["same"],
|
||||
"amounts": ["tons"]
|
||||
@@ -48,11 +51,15 @@ addElements([
|
||||
]);
|
||||
Engine.SetProgress(30);
|
||||
|
||||
if (!isNomad())
|
||||
createBluffsPassages(playerPosition);
|
||||
|
||||
addElements([
|
||||
{
|
||||
"func": addLayeredPatches,
|
||||
"avoid": [
|
||||
g_TileClasses.bluff, 2,
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.dirt, 5,
|
||||
g_TileClasses.forest, 2,
|
||||
g_TileClasses.mountain, 2,
|
||||
@@ -67,6 +74,7 @@ addElements([
|
||||
"func": addDecoration,
|
||||
"avoid": [
|
||||
g_TileClasses.bluff, 2,
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.forest, 2,
|
||||
g_TileClasses.mountain, 2,
|
||||
g_TileClasses.player, 12,
|
||||
@@ -83,6 +91,7 @@ addElements(shuffleArray([
|
||||
{
|
||||
"func": addMetal,
|
||||
"avoid": [
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.berries, 5,
|
||||
g_TileClasses.forest, 3,
|
||||
g_TileClasses.mountain, 2,
|
||||
@@ -99,6 +108,7 @@ addElements(shuffleArray([
|
||||
{
|
||||
"func": addStone,
|
||||
"avoid": [
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.berries, 5,
|
||||
g_TileClasses.forest, 3,
|
||||
g_TileClasses.mountain, 2,
|
||||
@@ -116,6 +126,7 @@ addElements(shuffleArray([
|
||||
{
|
||||
"func": addForests,
|
||||
"avoid": [
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.forest, 6,
|
||||
g_TileClasses.metal, 3,
|
||||
g_TileClasses.mountain, 5,
|
||||
@@ -132,6 +143,7 @@ addElements(shuffleArray([
|
||||
{
|
||||
"func": addForests,
|
||||
"avoid": [
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.bluff, 10,
|
||||
g_TileClasses.forest, 10,
|
||||
g_TileClasses.metal, 3,
|
||||
@@ -152,6 +164,7 @@ addElements(shuffleArray([
|
||||
"func": addBerries,
|
||||
"avoid": [
|
||||
g_TileClasses.bluff, 5,
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.forest, 5,
|
||||
g_TileClasses.metal, 10,
|
||||
g_TileClasses.mountain, 2,
|
||||
@@ -167,6 +180,7 @@ addElements(shuffleArray([
|
||||
"func": addAnimals,
|
||||
"avoid": [
|
||||
g_TileClasses.bluff, 5,
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.forest, 2,
|
||||
g_TileClasses.metal, 2,
|
||||
g_TileClasses.mountain, 1,
|
||||
@@ -176,13 +190,14 @@ addElements(shuffleArray([
|
||||
],
|
||||
"sizes": ["small"],
|
||||
"mixes": ["similar"],
|
||||
"amounts": ["normal", "many"]
|
||||
"amounts": ["few"]
|
||||
},
|
||||
{
|
||||
"func": addStragglerTrees,
|
||||
"avoid": [
|
||||
g_TileClasses.berries, 5,
|
||||
g_TileClasses.bluff, 5,
|
||||
g_TileClasses.bluffsPassage, 4,
|
||||
g_TileClasses.forest, 7,
|
||||
g_TileClasses.metal, 2,
|
||||
g_TileClasses.mountain, 1,
|
||||
@@ -197,15 +212,26 @@ addElements(shuffleArray([
|
||||
]));
|
||||
Engine.SetProgress(90);
|
||||
|
||||
placePlayersNomad(
|
||||
g_TileClasses.player,
|
||||
avoidClasses(
|
||||
g_TileClasses.bluff, 4,
|
||||
g_TileClasses.water, 4,
|
||||
g_TileClasses.forest, 1,
|
||||
g_TileClasses.metal, 4,
|
||||
g_TileClasses.rock, 4,
|
||||
g_TileClasses.mountain, 4,
|
||||
g_TileClasses.animals, 2));
|
||||
if (isNomad())
|
||||
{
|
||||
g_Map.log("Preventing units to be spawned at the map border");
|
||||
createArea(
|
||||
new DiskPlacer(mapSize / 2 - scaleByMapSize(15, 35), mapCenter),
|
||||
new TileClassPainter(g_TileClasses.nomadArea));
|
||||
|
||||
placePlayersNomad(
|
||||
g_TileClasses.player,
|
||||
[
|
||||
stayClasses(g_TileClasses.nomadArea, 0),
|
||||
avoidClasses(
|
||||
g_TileClasses.bluff, 2,
|
||||
g_TileClasses.water, 4,
|
||||
g_TileClasses.forest, 1,
|
||||
g_TileClasses.metal, 4,
|
||||
g_TileClasses.rock, 4,
|
||||
g_TileClasses.mountain, 4,
|
||||
g_TileClasses.animals, 2)
|
||||
]);
|
||||
}
|
||||
|
||||
g_Map.ExportMap();
|
||||
|
||||
@@ -20,7 +20,7 @@ createArea(
|
||||
Engine.SetProgress(20);
|
||||
|
||||
const playerbasePattern = randomStartingPositionPattern(getTeamsArray());
|
||||
createBasesByPattern(playerbasePattern.setup, playerbasePattern.distance, playerbasePattern.separation, randomAngle());
|
||||
createBasesByPattern(playerbasePattern.setup, playerbasePattern.distance, playerbasePattern.groupedDistance, randomAngle());
|
||||
Engine.SetProgress(40);
|
||||
|
||||
var features = [
|
||||
|
||||
@@ -532,6 +532,7 @@ function createTributaryRivers(riverAngle, riverCount, riverWidth, heightRiverbe
|
||||
* @property {number} [tileClass] - Marks the passage with this tile class.
|
||||
* @property {string} [terrain] - Texture to be painted on the passage area.
|
||||
* @property {string} [edgeTerrain] - Texture to be painted on the borders of the passage.
|
||||
* @returns {Area}
|
||||
*/
|
||||
function createPassage(args)
|
||||
{
|
||||
@@ -543,6 +544,7 @@ function createPassage(args)
|
||||
let passageVec = Vector2D.sub(args.end, args.start);
|
||||
let widthDirection = passageVec.perpendicular().normalize();
|
||||
let lengthStep = 1 / (2 * passageVec.length());
|
||||
let points = [];
|
||||
|
||||
for (let lengthFraction = 0; lengthFraction <= 1; lengthFraction += lengthStep)
|
||||
{
|
||||
@@ -558,6 +560,8 @@ function createPassage(args)
|
||||
args.maxHeight !== undefined && g_Map.getHeight(location) > args.maxHeight)
|
||||
continue;
|
||||
|
||||
points.push(location);
|
||||
|
||||
let smoothDistance = args.smoothWidth + Math.abs(stepWidth) - halfPassageWidth;
|
||||
|
||||
g_Map.setHeight(
|
||||
@@ -575,6 +579,8 @@ function createPassage(args)
|
||||
createTerrain(args.terrain).place(location);
|
||||
}
|
||||
}
|
||||
|
||||
return new Area(points);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,21 @@ StayAreasConstraint.prototype.allows = function(position)
|
||||
return this.areas.some(area => area.contains(position));
|
||||
};
|
||||
|
||||
/**
|
||||
* The StayAreasConstraint is met if the point is adjacent to one of the given Areas and not contained by that Area.
|
||||
*/
|
||||
function AdjacentToAreaConstraint(areas)
|
||||
{
|
||||
this.areas = areas;
|
||||
}
|
||||
|
||||
AdjacentToAreaConstraint.prototype.allows = function(position)
|
||||
{
|
||||
return this.areas.some(area =>
|
||||
!area.contains(position) &&
|
||||
g_Map.getAdjacentPoints(position).some(adjacentPosition => area.contains(adjacentPosition)));
|
||||
};
|
||||
|
||||
/**
|
||||
* The AvoidAreasConstraint is met if none of the given Areas contain the point.
|
||||
*/
|
||||
|
||||
@@ -310,6 +310,15 @@ RandomMap.prototype.setTerrainEntity = function(templateName, playerID, position
|
||||
new Entity(this.getEntityID(), templateName, playerID, position, orientation);
|
||||
};
|
||||
|
||||
RandomMap.prototype.deleteTerrainEntity = function(position)
|
||||
{
|
||||
let tilePosition = position.clone().floor();
|
||||
if (!this.validTilePassable(tilePosition))
|
||||
throw new Error("setTerrainEntity: invalid tile position " + uneval(position));
|
||||
|
||||
this.terrainEntities[tilePosition.x][tilePosition.y] = undefined;
|
||||
};
|
||||
|
||||
RandomMap.prototype.createTileClass = function()
|
||||
{
|
||||
return new TileClass(this.size);
|
||||
|
||||
@@ -6,6 +6,72 @@ var g_Props = {
|
||||
"skeleton": "actor|props/special/eyecandy/skeleton.xml",
|
||||
};
|
||||
|
||||
/**
|
||||
* Prevent circular patterns around the CC by marking a random chain of circles there to be ignored by bluffs.
|
||||
*/
|
||||
function markPlayerAvoidanceArea(playerPosition, radius)
|
||||
{
|
||||
for (let position of playerPosition)
|
||||
createArea(
|
||||
new ChainPlacer(3, 6, scaleByMapSize(25, 60), Infinity, position, radius),
|
||||
new TileClassPainter(g_TileClasses.bluffIgnore),
|
||||
undefined,
|
||||
scaleByMapSize(7, 14));
|
||||
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TileClassPainter(g_TileClasses.bluffIgnore),
|
||||
new NearTileClassConstraint(g_TileClasses.baseResource, 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Paints a ramp from the given positions to t
|
||||
* Bluffs might surround playerbases either entirely or unfairly.
|
||||
*/
|
||||
function createBluffsPassages(playerPosition)
|
||||
{
|
||||
g_Map.log("Creating passages towards the center");
|
||||
for (let position of playerPosition)
|
||||
{
|
||||
let successful = true;
|
||||
for (let tryCount = 0; tryCount < 80; ++tryCount)
|
||||
{
|
||||
let angle = position.angleTo(g_Map.getCenter()) + randFloat(-1, 1) * Math.PI / 2;
|
||||
let start = Vector2D.add(position, new Vector2D(defaultPlayerBaseRadius() * 0.7, 0).rotate(angle).perpendicular());
|
||||
let end = Vector2D.add(position, new Vector2D(defaultPlayerBaseRadius() * randFloat(1.7, 2), 0).rotate(angle).perpendicular());
|
||||
|
||||
if (!(new AndConstraint(avoidClasses(g_TileClasses.forest, 0), stayClasses(g_TileClasses.bluff, 16)).allows(end)))
|
||||
continue;
|
||||
|
||||
if ((g_Map.getHeight(end.clone().floor()) - g_Map.getHeight(start.clone().floor())) / start.distanceTo(end) > 1.5)
|
||||
continue;
|
||||
|
||||
let area = createPassage({
|
||||
"start": start,
|
||||
"end": end,
|
||||
"startWidth": scaleByMapSize(10, 20),
|
||||
"endWidth": scaleByMapSize(10, 14),
|
||||
"smoothWidth": 3,
|
||||
"terrain": g_Terrains.mainTerrain,
|
||||
"tileClass": g_TileClasses.bluffsPassage
|
||||
});
|
||||
|
||||
for (let point of area.getPoints())
|
||||
g_Map.deleteTerrainEntity(point);
|
||||
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TerrainPainter(g_Terrains.cliff),
|
||||
[
|
||||
new StayAreasConstraint([area]),
|
||||
new SlopeConstraint(2, Infinity)
|
||||
]);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create bluffs, i.e. a slope hill reachable from ground level.
|
||||
* Fill it with wood, mines, animals and decoratives.
|
||||
@@ -20,7 +86,12 @@ function addBluffs(constraint, size, deviation, fill, baseHeight)
|
||||
{
|
||||
g_Map.log("Creating bluffs");
|
||||
|
||||
var constrastTerrain = g_Terrains.tier2Terrain;
|
||||
let elevation = 30;
|
||||
|
||||
// Percent of the length of the bluff determining the entrance area
|
||||
let margin = 0.08;
|
||||
|
||||
let constrastTerrain = g_Terrains.tier2Terrain;
|
||||
|
||||
if (currentBiome() == "generic/tropic")
|
||||
constrastTerrain = g_Terrains.dirt;
|
||||
@@ -28,100 +99,86 @@ function addBluffs(constraint, size, deviation, fill, baseHeight)
|
||||
if (currentBiome() == "generic/autumn")
|
||||
constrastTerrain = g_Terrains.tier3Terrain;
|
||||
|
||||
var count = fill * 15;
|
||||
var minSize = 5;
|
||||
var maxSize = 7;
|
||||
var elevation = 30;
|
||||
var spread = 100;
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
for (let i = 0; i < fill * 15; ++i)
|
||||
{
|
||||
var offset = getRandomDeviation(size, deviation);
|
||||
let bluffDeviation = getRandomDeviation(size, deviation);
|
||||
|
||||
var rendered = createAreas(
|
||||
new ChainPlacer(Math.floor(minSize * offset), Math.floor(maxSize * offset), Math.floor(spread * offset), 0.5),
|
||||
[
|
||||
new LayeredPainter([g_Terrains.cliff, g_Terrains.mainTerrain, constrastTerrain], [2, 3]),
|
||||
new SmoothElevationPainter(ELEVATION_MODIFY, Math.floor(elevation * offset), 2),
|
||||
new TileClassPainter(g_TileClasses.bluff)
|
||||
],
|
||||
// Pick a random bluff location and shape
|
||||
let areasBluff = createAreas(
|
||||
new ChainPlacer(5 * bluffDeviation, 7 * bluffDeviation, 100 * bluffDeviation, 0.5),
|
||||
undefined,
|
||||
constraint,
|
||||
1);
|
||||
|
||||
// Find the bounding box of the bluff
|
||||
if (rendered[0] === undefined)
|
||||
if (!areasBluff.length)
|
||||
continue;
|
||||
|
||||
var points = rendered[0].getPoints();
|
||||
|
||||
var corners = findCorners(points);
|
||||
|
||||
// Seed an array the size of the bounding box
|
||||
var bb = createBoundingBox(points, corners);
|
||||
|
||||
// Get a random starting position for the baseline and the endline
|
||||
var angle = randIntInclusive(0, 3);
|
||||
var opAngle = angle - 2;
|
||||
if (angle < 2)
|
||||
opAngle = angle + 2;
|
||||
let angle = randIntInclusive(0, 3);
|
||||
let opposingAngle = (angle + 2) % 4;
|
||||
|
||||
// Find the edges of the bluff
|
||||
var baseLine;
|
||||
var endLine;
|
||||
let baseLine;
|
||||
let endLine;
|
||||
|
||||
// If we can't access the bluff, try different angles
|
||||
var retries = 0;
|
||||
var bluffCat = 2;
|
||||
while (bluffCat != 0 && retries < 5)
|
||||
let retries = 0;
|
||||
let bluffPassable = false;
|
||||
while (!bluffPassable && retries++ < 4)
|
||||
{
|
||||
baseLine = findClearLine(bb, corners, angle, baseHeight);
|
||||
endLine = findClearLine(bb, corners, opAngle, baseHeight);
|
||||
baseLine = findClearLine(areasBluff[0], angle);
|
||||
endLine = findClearLine(areasBluff[0], opposingAngle);
|
||||
bluffPassable = isBluffPassable(areasBluff[0], baseLine, endLine);
|
||||
|
||||
bluffCat = unreachableBluff(bb, corners, baseLine, endLine);
|
||||
++angle;
|
||||
if (angle > 3)
|
||||
angle = 0;
|
||||
|
||||
opAngle = angle - 2;
|
||||
if (angle < 2)
|
||||
opAngle = angle + 2;
|
||||
|
||||
++retries;
|
||||
angle = (angle + 1) % 4;
|
||||
opposingAngle = (angle + 2) % 4;
|
||||
}
|
||||
|
||||
// Inaccessible, turn it into a plateau
|
||||
if (bluffCat > 0)
|
||||
{
|
||||
removeBluff(points);
|
||||
if (!bluffPassable)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create an entrance area by using a small margin
|
||||
var margin = 0.08;
|
||||
var ground = createTerrain(g_Terrains.mainTerrain);
|
||||
var slopeLength = (1 - margin) * Math.euclidDistance2D(baseLine.midX, baseLine.midZ, endLine.midX, endLine.midZ);
|
||||
// Paint bluff texture and elevation
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
[
|
||||
new LayeredPainter([g_Terrains.mainTerrain, constrastTerrain], [5]),
|
||||
new SmoothElevationPainter(ELEVATION_MODIFY, elevation * bluffDeviation, 2),
|
||||
new TileClassPainter(g_TileClasses.bluff)
|
||||
],
|
||||
new StayAreasConstraint(areasBluff));
|
||||
|
||||
let slopeLength = (1 - margin) * Vector2D.average([baseLine.start, baseLine.end]).distanceTo(Vector2D.average([endLine.start, endLine.end]));
|
||||
|
||||
// Adjust the height of each point in the bluff
|
||||
for (let point of points)
|
||||
for (let point of areasBluff[0].getPoints())
|
||||
{
|
||||
var dist = Math.abs(distanceOfPointFromLine(
|
||||
new Vector2D(baseLine.x1, baseLine.z1),
|
||||
new Vector2D(baseLine.x2, baseLine.z2),
|
||||
point));
|
||||
|
||||
var curHeight = g_Map.getHeight(point);
|
||||
var newHeight = curHeight - curHeight * (dist / slopeLength) - 2;
|
||||
|
||||
newHeight = Math.max(newHeight, endLine.height);
|
||||
|
||||
if (newHeight <= endLine.height + 2 && g_Map.validTile(point) && g_Map.getTexture(point).indexOf('cliff') != -1)
|
||||
ground.place(point);
|
||||
|
||||
g_Map.setHeight(point, newHeight);
|
||||
let dist = Math.abs(distanceOfPointFromLine(baseLine.start, baseLine.end, point));
|
||||
g_Map.setHeight(point, Math.max(g_Map.getHeight(point) * (1 - dist / slopeLength) - 2, baseHeight));
|
||||
}
|
||||
|
||||
// Smooth out the ground around the bluff
|
||||
fadeToGround(bb, corners.minX, corners.minZ, endLine.height);
|
||||
// Flatten all points adjacent to but not on the bluff
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
[
|
||||
new SmoothingPainter(1, 1, 1),
|
||||
new TerrainPainter(g_Terrains.mainTerrain)
|
||||
],
|
||||
new AdjacentToAreaConstraint(areasBluff));
|
||||
|
||||
// Paint cliffs
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TerrainPainter(g_Terrains.cliff),
|
||||
[
|
||||
new StayAreasConstraint(areasBluff),
|
||||
new SlopeConstraint(2, Infinity)
|
||||
]);
|
||||
|
||||
// Performance improvement
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TileClassPainter(g_TileClasses.bluffIgnore),
|
||||
new NearTileClassConstraint(g_TileClasses.bluff, 8));
|
||||
}
|
||||
|
||||
addElements([
|
||||
@@ -432,6 +489,11 @@ function addHills(constraint, size, deviation, fill)
|
||||
"maxElevation": 12,
|
||||
"steepness": 1.5
|
||||
});
|
||||
|
||||
createArea(
|
||||
new MapBoundsPlacer(),
|
||||
new TileClassPainter(g_TileClasses.bluffIgnore),
|
||||
new NearTileClassConstraint(g_TileClasses.hill, 6));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -942,48 +1004,41 @@ function addStragglerTrees(constraint, size, deviation, fill)
|
||||
}
|
||||
}
|
||||
|
||||
///////////
|
||||
// Terrain Helpers
|
||||
///////////
|
||||
|
||||
/**
|
||||
* Determine if the endline of the bluff is within the tilemap.
|
||||
*
|
||||
* @returns {Number} 0 if the bluff is reachable, otherwise a positive number
|
||||
*/
|
||||
function unreachableBluff(bb, corners, baseLine, endLine)
|
||||
function isBluffPassable(bluffArea, baseLine, endLine)
|
||||
{
|
||||
// If we couldn't find a slope line
|
||||
if (typeof baseLine.midX === "undefined" || typeof endLine.midX === "undefined")
|
||||
return 1;
|
||||
if (!baseLine ||
|
||||
!endLine ||
|
||||
!g_Map.validTilePassable(endLine.start) &&
|
||||
!g_Map.validTilePassable(endLine.end))
|
||||
return false;
|
||||
|
||||
// If the end points aren't on the tilemap
|
||||
if (!g_Map.validTile(new Vector2D(endLine.x1, endLine.z1)) && !g_Map.validTile(new Vector2D(endLine.x2, endLine.z2)))
|
||||
return 2;
|
||||
|
||||
var minTilesInGroup = 1;
|
||||
var insideBluff = false;
|
||||
var outsideBluff = false;
|
||||
let minTilesInGroup = 2;
|
||||
let insideBluff = false;
|
||||
let outsideBluff = false;
|
||||
|
||||
// If there aren't enough points in each row
|
||||
for (var x = 0; x < bb.length; ++x)
|
||||
let corners = getBoundingBox(bluffArea.getPoints());
|
||||
for (let x = corners.min.x; x <= corners.max.x; ++x)
|
||||
{
|
||||
var count = 0;
|
||||
for (var z = 0; z < bb[x].length; ++z)
|
||||
let count = 0;
|
||||
for (let y = corners.min.y; y <= corners.max.y; ++y)
|
||||
{
|
||||
if (!bb[x][z].isFeature)
|
||||
let pos = new Vector2D(x, y);
|
||||
if (!bluffArea.contains(pos))
|
||||
continue;
|
||||
|
||||
var valid = g_Map.validTile(new Vector2D(x + corners.minX, z + corners.minZ));
|
||||
|
||||
let valid = g_Map.validTilePassable(pos);
|
||||
if (valid)
|
||||
++count;
|
||||
|
||||
if (!insideBluff && valid)
|
||||
if (valid)
|
||||
insideBluff = true;
|
||||
|
||||
if (outsideBluff && valid)
|
||||
return 3;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're expecting the end of the bluff
|
||||
@@ -991,28 +1046,28 @@ function unreachableBluff(bb, corners, baseLine, endLine)
|
||||
outsideBluff = true;
|
||||
}
|
||||
|
||||
var insideBluff = false;
|
||||
var outsideBluff = false;
|
||||
insideBluff = false;
|
||||
outsideBluff = false;
|
||||
|
||||
// If there aren't enough points in each column
|
||||
for (var z = 0; z < bb[0].length; ++z)
|
||||
for (let y = corners.min.y; y <= corners.max.y; ++y)
|
||||
{
|
||||
var count = 0;
|
||||
for (var x = 0; x < bb.length; ++x)
|
||||
let count = 0;
|
||||
for (let x = corners.min.x; x <= corners.max.x; ++x)
|
||||
{
|
||||
if (!bb[x][z].isFeature)
|
||||
let pos = new Vector2D(x, y);
|
||||
if (!bluffArea.contains(pos))
|
||||
continue;
|
||||
|
||||
var valid = g_Map.validTile(new Vector2D(x + corners.minX, z + corners.minZ));
|
||||
|
||||
let valid = g_Map.validTilePassable(pos.add(corners.min));
|
||||
if (valid)
|
||||
++count;
|
||||
|
||||
if (!insideBluff && valid)
|
||||
if (valid)
|
||||
insideBluff = true;
|
||||
|
||||
if (outsideBluff && valid)
|
||||
return 3;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We're expecting the end of the bluff
|
||||
@@ -1020,186 +1075,72 @@ function unreachableBluff(bb, corners, baseLine, endLine)
|
||||
outsideBluff = true;
|
||||
}
|
||||
|
||||
// Bluff is reachable
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the bluff class and turn it into a plateau.
|
||||
* Find a 45 degree line that does not intersect with the bluff.
|
||||
*/
|
||||
function removeBluff(points)
|
||||
function findClearLine(bluffArea, angle)
|
||||
{
|
||||
g_Map.log("Replacing bluff with plateau");
|
||||
let corners = getBoundingBox(bluffArea.getPoints());
|
||||
|
||||
for (let point of points)
|
||||
g_TileClasses.mountain.add(point);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array of points the fill a bounding box around a terrain feature.
|
||||
*/
|
||||
function createBoundingBox(points, corners)
|
||||
{
|
||||
var bb = [];
|
||||
var width = corners.maxX - corners.minX + 1;
|
||||
var length = corners.maxZ - corners.minZ + 1;
|
||||
for (var w = 0; w < width; ++w)
|
||||
{
|
||||
bb[w] = [];
|
||||
for (let l = 0; l < length; ++l)
|
||||
bb[w][l] = {
|
||||
"height": g_Map.getHeight(new Vector2D(w + corners.minX, l + corners.minZ)),
|
||||
"isFeature": false
|
||||
};
|
||||
}
|
||||
|
||||
// Define the coordinates that represent the bluff
|
||||
for (let point of points)
|
||||
bb[point.x - corners.minX][point.y - corners.minZ].isFeature = true;
|
||||
|
||||
return bb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flattens the ground touching a terrain feature.
|
||||
*/
|
||||
function fadeToGround(bb, minX, minZ, elevation)
|
||||
{
|
||||
var ground = createTerrain(g_Terrains.mainTerrain);
|
||||
for (var x = 0; x < bb.length; ++x)
|
||||
for (var z = 0; z < bb[x].length; ++z)
|
||||
{
|
||||
if (!bb[x][z].isFeature && nextToFeature(bb, x, z))
|
||||
{
|
||||
let position = new Vector2D(x + minX, z + minZ);
|
||||
g_Map.setHeight(position, g_Map.getAverageHeight(position));
|
||||
ground.place(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a 45 degree line in a bounding box that does not intersect any terrain feature.
|
||||
*/
|
||||
function findClearLine(bb, corners, angle, baseHeight)
|
||||
{
|
||||
// Angle - 0: northwest; 1: northeast; 2: southeast; 3: southwest
|
||||
var z = corners.maxZ;
|
||||
var xOffset = -1;
|
||||
var zOffset = -1;
|
||||
|
||||
switch(angle)
|
||||
let offset;
|
||||
let y;
|
||||
switch (angle)
|
||||
{
|
||||
case 0:
|
||||
offset = new Vector2D(-1, -1);
|
||||
y = corners.max.y;
|
||||
break;
|
||||
case 1:
|
||||
xOffset = 1;
|
||||
offset = new Vector2D(1, -1);
|
||||
y = corners.max.y;
|
||||
break;
|
||||
case 2:
|
||||
xOffset = 1;
|
||||
zOffset = 1;
|
||||
z = corners.minZ;
|
||||
offset = new Vector2D(1, 1);
|
||||
y = corners.min.y;
|
||||
break;
|
||||
case 3:
|
||||
zOffset = 1;
|
||||
z = corners.minZ;
|
||||
offset = new Vector2D(-1, 1);
|
||||
y = corners.min.y;
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unknown angle " + angle);
|
||||
}
|
||||
|
||||
var clearLine = {};
|
||||
|
||||
for (var x = corners.minX; x <= corners.maxX; ++x)
|
||||
let clearLine;
|
||||
for (let x = corners.min.x; x <= corners.max.x; ++x)
|
||||
{
|
||||
let position2 = new Vector2D(x, z);
|
||||
let start = new Vector2D(x, y);
|
||||
|
||||
var clear = true;
|
||||
let intersectsBluff = false;
|
||||
let end = start.clone();
|
||||
|
||||
while (position2.x >= corners.minX && position2.x <= corners.maxX && position2.y >= corners.minZ && position2.y <= corners.maxZ)
|
||||
while (end.x >= corners.min.x && end.x <= corners.max.x && end.y >= corners.min.y && end.y <= corners.max.y)
|
||||
{
|
||||
var bp = bb[position2.x - corners.minX][position2.y - corners.minZ];
|
||||
if (bp.isFeature && g_Map.validTile(position2))
|
||||
if (bluffArea.contains(end) && g_Map.validTilePassable(end))
|
||||
{
|
||||
clear = false;
|
||||
intersectsBluff = true;
|
||||
break;
|
||||
}
|
||||
|
||||
position2.add(new Vector2D(xOffset, zOffset));
|
||||
end.add(offset);
|
||||
}
|
||||
|
||||
if (clear)
|
||||
{
|
||||
var lastX = position2.x - xOffset;
|
||||
var lastZ = position2.y - zOffset;
|
||||
var midX = Math.floor((x + lastX) / 2);
|
||||
var midZ = Math.floor((z + lastZ) / 2);
|
||||
if (!intersectsBluff)
|
||||
clearLine = {
|
||||
"x1": x,
|
||||
"z1": z,
|
||||
"x2": lastX,
|
||||
"z2": lastZ,
|
||||
"midX": midX,
|
||||
"midZ": midZ,
|
||||
"height": baseHeight
|
||||
"start": start,
|
||||
"end": end.sub(offset)
|
||||
};
|
||||
}
|
||||
|
||||
if (clear && (angle == 1 || angle == 2))
|
||||
break;
|
||||
|
||||
if (!clear && (angle == 0 || angle == 3))
|
||||
if (intersectsBluff ? (angle == 0 || angle == 3) : (angle == 1 || angle == 2))
|
||||
break;
|
||||
}
|
||||
|
||||
return clearLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the corners of a bounding box.
|
||||
*/
|
||||
function findCorners(points)
|
||||
{
|
||||
// Find the bounding box of the terrain feature
|
||||
var mapSize = g_Map.getSize();
|
||||
var minX = mapSize + 1;
|
||||
var minZ = mapSize + 1;
|
||||
var maxX = -1;
|
||||
var maxZ = -1;
|
||||
|
||||
for (let point of points)
|
||||
{
|
||||
minX = Math.min(point.x, minX);
|
||||
minZ = Math.min(point.y, minZ);
|
||||
|
||||
maxX = Math.max(point.x, maxX);
|
||||
maxZ = Math.max(point.y, maxZ);
|
||||
}
|
||||
|
||||
return {
|
||||
"minX": minX,
|
||||
"minZ": minZ,
|
||||
"maxX": maxX,
|
||||
"maxZ": maxZ
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a point in a bounding box array is next to a terrain feature.
|
||||
*/
|
||||
function nextToFeature(bb, x, z)
|
||||
{
|
||||
for (var xOffset = -1; xOffset <= 1; ++xOffset)
|
||||
for (var zOffset = -1; zOffset <= 1; ++zOffset)
|
||||
{
|
||||
var thisX = x + xOffset;
|
||||
var thisZ = z + zOffset;
|
||||
if (thisX < 0 || thisX >= bb.length || thisZ < 0 || thisZ >= bb[x].length || thisX == 0 && thisZ == 0)
|
||||
continue;
|
||||
|
||||
if (bb[thisX][thisZ].isFeature)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a number within a random deviation of a base number.
|
||||
*/
|
||||
|
||||
@@ -31,7 +31,7 @@ var g_DefaultTileClasses = [
|
||||
"baseResource",
|
||||
"berries",
|
||||
"bluff",
|
||||
"bluffSlope",
|
||||
"bluffIgnore", // performance improvement
|
||||
"dirt",
|
||||
"fish",
|
||||
"food",
|
||||
@@ -58,21 +58,29 @@ var g_PlayerbaseTypes = {
|
||||
"line": {
|
||||
"available": () => g_Map.getSize() >= 384 && getTeamsArray().length >= 2 && getNumPlayers() >= 4,
|
||||
"getPosition": (distance, groupedDistance, startAngle) => placeLine(getTeamsArray(), distance, groupedDistance, startAngle),
|
||||
"distance": fractionToTiles(randFloat(0.2, 0.35)),
|
||||
"groupedDistance": fractionToTiles(randFloat(0.05, 0.1)),
|
||||
"walls": false
|
||||
},
|
||||
"radial": {
|
||||
"available": () => true,
|
||||
"getPosition": (distance, groupedDistance, startAngle) => playerPlacementCircle(distance, startAngle),
|
||||
"distance": fractionToTiles(randFloat(0.25, 0.35)),
|
||||
"groupedDistance": fractionToTiles(randFloat(0.07, 0.1)),
|
||||
"walls": true
|
||||
},
|
||||
"random": {
|
||||
"available": () => g_Map.getSize() >= 256 && (getTeamsArray().length >= 3 || getNumPlayers() > 4),
|
||||
"getPosition": (distance, groupedDistance, startAngle) => playerPlacementRandom(sortAllPlayers()) || playerPlacementCircle(distance, startAngle),
|
||||
"distance": fractionToTiles(randFloat(0.25, 0.35)),
|
||||
"groupedDistance": fractionToTiles(randFloat(0.07, 0.1)),
|
||||
"walls": true
|
||||
},
|
||||
"stronghold": {
|
||||
"available": () => g_Map.getSize() >= 256 && getTeamsArray().length >= 2 && getNumPlayers() >= 4,
|
||||
"getPosition": (distance, groupedDistance, startAngle) => placeStronghold(getTeamsArray(), distance, groupedDistance, startAngle),
|
||||
"distance": fractionToTiles(randFloat(0.2, 0.35)),
|
||||
"groupedDistance": fractionToTiles(randFloat(0.07, 0.1)),
|
||||
"walls": false
|
||||
}
|
||||
};
|
||||
@@ -237,10 +245,11 @@ function getTeamsArray()
|
||||
*/
|
||||
function randomStartingPositionPattern(teamsArray)
|
||||
{
|
||||
let type = pickRandom(Object.keys(g_PlayerbaseTypes).filter(type => g_PlayerbaseTypes[type].available()));
|
||||
return {
|
||||
"setup": pickRandom(Object.keys(g_PlayerbaseTypes).filter(type => g_PlayerbaseTypes[type].available())),
|
||||
"distance": fractionToTiles(randFloat(0.2, 0.35)),
|
||||
"separation": fractionToTiles(randFloat(0.05, 0.1))
|
||||
"setup": type,
|
||||
"distance": g_PlayerbaseTypes[type].distance,
|
||||
"groupedDistance": g_PlayerbaseTypes[type].groupedDistance
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ createArea(
|
||||
|
||||
Engine.SetProgress(20);
|
||||
|
||||
createBasesByPattern("stronghold", fractionToTiles(randFloat(0.2, 0.35)), fractionToTiles(randFloat(0.05, 0.1)), randomAngle());
|
||||
const [playerIDs, playerPosition] = createBasesByPattern("stronghold", fractionToTiles(randFloat(0.2, 0.35)), fractionToTiles(randFloat(0.05, 0.1)), randomAngle());
|
||||
markPlayerAvoidanceArea(playerPosition, defaultPlayerBaseRadius());
|
||||
|
||||
Engine.SetProgress(30);
|
||||
|
||||
addElements(shuffleArray([
|
||||
@@ -97,6 +99,10 @@ addElements(shuffleArray([
|
||||
"amounts": g_AllAmounts
|
||||
}
|
||||
]));
|
||||
|
||||
if (!isNomad())
|
||||
createBluffsPassages(playerPosition);
|
||||
|
||||
Engine.SetProgress(60);
|
||||
|
||||
addElements([
|
||||
|
||||
Reference in New Issue
Block a user