mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-07-04 05:55:47 -07:00
326 lines
7.9 KiB
JavaScript
326 lines
7.9 KiB
JavaScript
/**
|
|
* Class for holding map data and providing basic API to change it
|
|
*
|
|
* @param {int} [size] - Size of the map in tiles
|
|
* @param {float} [baseHeight] - Starting height of the map
|
|
*/
|
|
function Map(size, baseHeight)
|
|
{
|
|
// Size must be 0 to 1024, divisible by patches
|
|
this.size = size;
|
|
|
|
// Create 2D arrays for textures, object, and areas
|
|
this.texture = [];
|
|
this.terrainObjects = [];
|
|
this.area = [];
|
|
|
|
for (let i = 0; i < size; ++i)
|
|
{
|
|
// Texture IDs
|
|
this.texture[i] = new Uint16Array(size);
|
|
// Entities
|
|
this.terrainObjects[i] = [];
|
|
// Area IDs
|
|
this.area[i] = new Uint16Array(size);
|
|
|
|
for (let j = 0; j < size; ++j)
|
|
this.terrainObjects[i][j] = [];
|
|
}
|
|
|
|
// Create 2D array for heightmap
|
|
let mapSize = size;
|
|
if (!TILE_CENTERED_HEIGHT_MAP)
|
|
++mapSize;
|
|
|
|
this.height = [];
|
|
for (let i = 0; i < mapSize; ++i)
|
|
{
|
|
this.height[i] = new Float32Array(mapSize);
|
|
|
|
for (let j = 0; j < mapSize; ++j)
|
|
this.height[i][j] = baseHeight;
|
|
}
|
|
|
|
// Create name <-> id maps for textures
|
|
this.nameToID = {};
|
|
this.IDToName = [];
|
|
|
|
// Array of objects (entitys/actors)
|
|
this.objects = [];
|
|
// Array of integers
|
|
this.tileClasses = [];
|
|
|
|
this.areaID = 0;
|
|
|
|
// Starting entity ID, arbitrary number to leave some space for player entities
|
|
this.entityCount = 150;
|
|
}
|
|
|
|
Map.prototype.initTerrain = function(baseTerrain)
|
|
{
|
|
for (let i = 0; i < this.size; ++i)
|
|
for (let j = 0; j < this.size; ++j)
|
|
baseTerrain.place(i, j);
|
|
};
|
|
|
|
// Return ID of texture (by name)
|
|
Map.prototype.getTextureID = function(texture)
|
|
{
|
|
if (texture in this.nameToID)
|
|
return this.nameToID[texture];
|
|
|
|
// Add new texture
|
|
let id = this.IDToName.length;
|
|
this.nameToID[texture] = id;
|
|
this.IDToName[id] = texture;
|
|
|
|
return id;
|
|
};
|
|
|
|
// Return next free entity ID
|
|
Map.prototype.getEntityID = function()
|
|
{
|
|
return this.entityCount++;
|
|
};
|
|
|
|
// Check bounds on tile map
|
|
Map.prototype.validT = function(x, z, distance = 0)
|
|
{
|
|
if (g_MapSettings.CircularMap)
|
|
{
|
|
let halfSize = Math.floor(this.size / 2);
|
|
return Math.round(getDistance(x, z, halfSize, halfSize)) < halfSize - distance - 1;
|
|
}
|
|
else
|
|
return x >= distance && z >= distance && x < this.size - distance && z < this.size - distance;
|
|
};
|
|
|
|
// Check bounds on tile map
|
|
Map.prototype.inMapBounds = function(x, z)
|
|
{
|
|
return x >= 0 && z >= 0 && x < this.size && z < this.size;
|
|
};
|
|
|
|
// Check bounds on height map if TILE_CENTERED_HEIGHT_MAP==true then it's (size, size) otherwise (size + 1 by size + 1)
|
|
Map.prototype.validH = function(x, z)
|
|
{
|
|
if (x < 0 || z < 0)
|
|
return false;
|
|
if (TILE_CENTERED_HEIGHT_MAP)
|
|
return x < this.size && z < this.size;
|
|
return x <= this.size && z <= this.size;
|
|
};
|
|
|
|
// Check bounds on tile class
|
|
Map.prototype.validClass = function(c)
|
|
{
|
|
return c >= 0 && c < this.tileClasses.length;
|
|
};
|
|
|
|
Map.prototype.getTexture = function(x, z)
|
|
{
|
|
if (!this.validT(x, z))
|
|
throw "getTexture: invalid tile position (" + x + ", " + z + ")";
|
|
|
|
return this.IDToName[this.texture[x][z]];
|
|
};
|
|
|
|
Map.prototype.setTexture = function(x, z, texture)
|
|
{
|
|
if (!this.validT(x, z))
|
|
throw "setTexture: invalid tile position (" + x + ", " + z + ")";
|
|
|
|
this.texture[x][z] = this.getTextureID(texture);
|
|
};
|
|
|
|
Map.prototype.getHeight = function(x, z)
|
|
{
|
|
if (!this.validH(x, z))
|
|
throw "getHeight: invalid vertex position (" + x + ", " + z + ")";
|
|
|
|
return this.height[x][z];
|
|
};
|
|
|
|
Map.prototype.setHeight = function(x, z, height)
|
|
{
|
|
if (!this.validH(x, z))
|
|
throw "setHeight: invalid vertex position (" + x + ", " + z + ")";
|
|
|
|
this.height[x][z] = height;
|
|
};
|
|
|
|
Map.prototype.getTerrainObjects = function(x, z)
|
|
{
|
|
if (!this.validT(x, z))
|
|
throw "getTerrainObjects: invalid tile position (" + x + ", " + z + ")";
|
|
|
|
return this.terrainObjects[x][z];
|
|
};
|
|
|
|
Map.prototype.setTerrainObject = function(x, z, object)
|
|
{
|
|
if (!this.validT(x, z, 2))
|
|
throw "setTerrainObject: invalid tile position (" + x + ", " + z + ")";
|
|
|
|
this.terrainObjects[x][z] = object;
|
|
};
|
|
|
|
Map.prototype.placeTerrain = function(x, z, terrain)
|
|
{
|
|
terrain.place(x, z);
|
|
};
|
|
|
|
Map.prototype.addObject = function(obj)
|
|
{
|
|
this.objects.push(obj);
|
|
};
|
|
|
|
Map.prototype.createArea = function(placer, painter, constraint)
|
|
{
|
|
// Check for multiple painters
|
|
if (painter instanceof Array)
|
|
painter = new MultiPainter(painter);
|
|
|
|
if (constraint === undefined || constraint === null)
|
|
constraint = new NullConstraint();
|
|
else if (constraint instanceof Array)
|
|
// Check for multiple constraints
|
|
constraint = new AndConstraint(constraint);
|
|
|
|
let points = placer.place(constraint);
|
|
if (!points)
|
|
return undefined;
|
|
|
|
let newID = ++this.areaID;
|
|
let area = new Area(points, newID);
|
|
for (let p of points)
|
|
this.area[p.x][p.z] = newID;
|
|
|
|
painter.paint(area);
|
|
|
|
return area;
|
|
};
|
|
|
|
Map.prototype.createObjectGroup = function(placer, player, constraint)
|
|
{
|
|
// Check for null constraint
|
|
if (constraint === undefined || constraint === null)
|
|
constraint = new NullConstraint();
|
|
else if (constraint instanceof Array)
|
|
constraint = new AndConstraint(constraint);
|
|
|
|
return placer.place(player, constraint);
|
|
};
|
|
|
|
Map.prototype.createTileClass = function()
|
|
{
|
|
let newID = this.tileClasses.length;
|
|
this.tileClasses.push(new TileClass(this.size, newID));
|
|
|
|
return newID;
|
|
};
|
|
|
|
// Get height taking into account terrain curvature
|
|
Map.prototype.getExactHeight = function(x, z)
|
|
{
|
|
let xi = Math.min(Math.floor(x), this.size);
|
|
let zi = Math.min(Math.floor(z), this.size);
|
|
let xf = x - xi;
|
|
let zf = z - zi;
|
|
|
|
let h00 = this.height[xi][zi];
|
|
let h01 = this.height[xi][zi + 1];
|
|
let h10 = this.height[xi + 1][zi];
|
|
let h11 = this.height[xi + 1][zi + 1];
|
|
|
|
return (1 - zf) * ((1 - xf) * h00 + xf * h10) + zf * ((1 - xf) * h01 + xf * h11);
|
|
};
|
|
|
|
// Converts from the tile centered height map to the corner based height map, used when TILE_CENTERED_HEIGHT_MAP = true
|
|
Map.prototype.cornerHeight = function(x, z)
|
|
{
|
|
let count = 0;
|
|
let sumHeight = 0;
|
|
|
|
for (let dir of [[-1, -1], [-1, 0], [0, -1], [0, 0]])
|
|
if (this.validH(x + dir[0], z + dir[1]))
|
|
{
|
|
++count;
|
|
sumHeight += this.height[x + dir[0]][z + dir[1]];
|
|
}
|
|
|
|
if (count == 0)
|
|
return 0;
|
|
|
|
return sumHeight / count;
|
|
};
|
|
|
|
Map.prototype.getFullEntityList = function(rotateForMapExport = false)
|
|
{
|
|
// Change rotation from simple 2d to 3d befor giving to engine
|
|
if (rotateForMapExport)
|
|
for (let obj of this.objects)
|
|
obj.rotation.y = PI / 2 - obj.rotation.y;
|
|
|
|
// All non terrain objects
|
|
let entities = this.objects;
|
|
|
|
// Terrain objects e.g. trees
|
|
let size = this.size;
|
|
for (let x = 0; x < size; ++x)
|
|
for (let z = 0; z < size; ++z)
|
|
if (this.terrainObjects[x][z] !== undefined)
|
|
entities.push(this.terrainObjects[x][z]);
|
|
|
|
return entities;
|
|
};
|
|
|
|
Map.prototype.getMapData = function()
|
|
{
|
|
let data = {};
|
|
|
|
// Convert 2D heightmap array to flat array
|
|
// Flat because it's easier to handle by the engine
|
|
let mapSize = this.size + 1;
|
|
let height = new Uint16Array(mapSize * mapSize);
|
|
for (let x = 0; x < mapSize; ++x)
|
|
for (let z = 0; z < mapSize; ++z)
|
|
{
|
|
let currentHeight;
|
|
if (TILE_CENTERED_HEIGHT_MAP)
|
|
currentHeight = this.cornerHeight(x, z);
|
|
else
|
|
currentHeight = this.height[x][z];
|
|
|
|
// Correct height by SEA_LEVEL and prevent under/overflow in terrain data
|
|
height[z * mapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE)));
|
|
}
|
|
|
|
data.height = height;
|
|
data.seaLevel = SEA_LEVEL;
|
|
|
|
// Terrain, map width in tiles
|
|
data.size = this.size;
|
|
|
|
// Get array of textures used in this map
|
|
data.textureNames = this.IDToName;
|
|
|
|
// Entities
|
|
data.entities = this.getFullEntityList(true);
|
|
log("Number of entities: "+ data.entities.length);
|
|
|
|
// Convert 2D tile data to flat array
|
|
let tileIndex = new Uint16Array(this.size * this.size);
|
|
let tilePriority = new Uint16Array(this.size * this.size);
|
|
for (let x = 0; x < this.size; ++x)
|
|
for (let z = 0; z < this.size; ++z)
|
|
{
|
|
// TODO: For now just use the texture's index as priority, might want to do this another way
|
|
tileIndex[z * this.size + x] = this.texture[x][z];
|
|
tilePriority[z * this.size + x] = this.texture[x][z];
|
|
}
|
|
|
|
data.tileData = { "index": tileIndex, "priority": tilePriority };
|
|
|
|
return data;
|
|
};
|