Landscape
Tile Map
Landscape files (aka Tile maps) are loaded from the cache from index #4 or "map archive". Landscape files are compressed with GZip, so you have to decompress them before you can use them.
Here is a sample of a refactored #317 revision client reading a region's landscape file:
for (int plane = 0; plane < 4; plane++) {
for (int x = 0; x < 64; x++) {
for (int y = 0; y < 64; y++) {
while (true) { // now we're loading data for tile [plane][x][y]
int data = tileMapBuffer.getUnsignedByte();
if (data == 0) {
if (plane == 0) {
vertexHeights[0][x][y] = -calculateVertexHeight(932731 + x + xOffset, 556238 + y + yOffset) * 8;
}
else {
vertexHeights[plane][x][y] = vertexHeights[plane - 1][x][y] - 240;
}
break;
}
else if (data == 1) {
int height = tileMapBuffer.getUnsignedByte();
if (height == 1) {
height = 0;
}
if (plane == 0) {
vertexHeights[0][x][y] = -height * 8;
}
else {
vertexHeights[plane][x][y] = vertexHeights[plane - 1][x][y] - height * 8;
}
break;
}
else if (data <= 49) {
overlayFloorIds[plane][x][y] = tileMapBuffer.getByte();
overlayClippingPaths[plane][x][y] = (byte) ((data - 2) / 4);
overlayRotations[plane][x][y] = (byte) (data - 2 + shapeOffset & 0x3); // shapeoffset can be set as a parameter
}
else if (data <= 81) {
renderRuleFlags[plane][x][y] = (byte) (data - 49); // 0x1 = clipped, 0x2 = bridge, 0x4 = roof, 0x8 = ??
}
else {
underlayFloorIds[plane][x][y] = (byte) (data - 81);
}
}
}
}
}
Dynamic Terrain Level
In order to add some dynamic height to the world, a random noise generation function is used to calculate heights of tile vertices given an input of x and y tile coordinates for all tiles on plane 0 but can be overridden per-tile using opcode 1 when encoding the tile.
The parameters for the noise generator are seeded with constant integers to ensure all calculations are the same between clients and servers.
Collision
For getting the tile/object collision map loaded correctly, the tile map must be loaded first. The reason for this is because of the bridge setting flag (0x2). If you don't load the tile map first then when you load the object map, objects on height 1 won't be clipped on height 0 even though when you're on height 0 you can walk over the bridge (which tiles and objects are declared on height 1). Bridges only work on height level 1 in 317s. Tiles with the clipping flag (0x1) on height level 0 that ALSO have a bridge above them on height level 1 are ignored so as to only take clipping data from the bridge tiles/objects and not what is below it.
We can observe this being true in this sample code block which handles collision flags on the server-side:
for (int plane = 0; plane < 4; plane++) {
for (int x = 0; x < 104; x++) {
for (int y = 0; y < 104; y++) {
if ((renderRuleFlags[plane][x][y] & 0x1) == 1) { // clipping flag
int originalPlane = plane; // let's just say plane = 0;
if ((renderRuleFlags[1][x][y] & 0x2) == 2) { // if same tile on plane = 1 has bridge flag
originalPlane--; // originalPlane = -1;
}
if (originalPlane >= 0) { // ignoring height level 0's clipping flag if bridge is on height level 1
collisionMaps[originalPlane].markBlocked(x, y);
}
}
}
}
}
Tile Clip Paths
Originally documented by Maxi. There are 12 floor tile clipping paths. They can be rotated 4 ways, resulting in 48 different possible shapes. They are used to partially cut parts of the tile texture and then the tiles its underlay will be visible.
Each floor tile has a setting on how its texture needs to be cut, these are the above clipping paths. To give you a good idea of this here is a picture with modified tile clipping paths: