Files
pixel.horse/src/ts/client/tileUtils.ts
T
Eliot Partridge caf70a864f Revert codestyle changes (#40)
* Revert "7f7efbb94bab8574e42d942ad82d414e007b2970"

Code style changes should probably be part of a PR
2019-08-30 10:53:14 -05:00

603 lines
20 KiB
TypeScript

import {
PaletteManager, Season, TileSets, Region, TileType, Camera, PaletteSpriteBatch, DrawOptions, WorldMap, IMap,
Sprite, MapType
} from '../common/interfaces';
import * as sprites from '../generated/sprites';
import { getRegionTile, getRegionElevation } from '../common/region';
import { getRegionGlobal } from '../common/worldMap';
import { tileWidth, tileHeight, tileElevation, WATER_FPS, REGION_SIZE, WATER_HEIGHT } from '../common/constants';
import { clamp, toInt, at, invalidEnumReturn } from '../common/utils';
import { isAreaVisible } from '../common/camera';
import { WHITE } from '../common/colors';
import { releasePalette } from '../graphics/paletteManager';
import { drawPixelText } from '../graphics/graphicsUtils';
import { toScreenX, toScreenY, toWorldZ } from '../common/positionUtils';
const TILE_COUNTS = [[0, 4], [2, 3], [4, 3], [6, 3], [8, 3], [13, 3], [14, 3], [47, 4]];
export const TILE_COUNT_MAP: number[] = [];
export const TILE_MAP_MAP: number[] = [];
TILE_COUNTS.forEach(([tile, count]) => {
while (TILE_COUNT_MAP.length < (tile + 1)) {
TILE_COUNT_MAP.push(1);
}
TILE_COUNT_MAP[tile] = count;
});
let tileIndex = 0;
for (let i = 0; i <= 47; i++) {
TILE_MAP_MAP.push(tileIndex);
tileIndex += TILE_COUNT_MAP[i];
}
// 1 | 2 | 4
// ----+----+----
// 8 | | 16
// ----+----+----
// 32 | 64 | 128
export const TILE_MAP = [
46, 46, 22, 22, 46, 46, 22, 22, 21, 21, // 0-9
17, 11, 21, 21, 17, 11, 19, 19, 18, 18, // 10-19
19, 19, 12, 12, 14, 14, 24, 28, 14, 14, // 20-29
30, 6, 46, 46, 22, 22, 46, 46, 22, 22, // 30-39
21, 21, 17, 11, 21, 21, 17, 11, 19, 19, // 40-49
18, 18, 19, 19, 12, 12, 14, 14, 24, 28, // 50-59
14, 14, 30, 6, 20, 20, 13, 13, 20, 20, // 60-69
13, 13, 16, 16, 23, 32, 16, 16, 23, 32, // 70-79
15, 15, 25, 25, 15, 15, 34, 34, 26, 26, // 80-89
45, 41, 26, 26, 42, 36, 20, 20, 13, 13, // 90-99
20, 20, 13, 13, 10, 10, 31, 4, 10, 10, // 100-109
31, 4, 15, 15, 25, 25, 15, 15, 34, 34, // 110-119
27, 27, 43, 37, 27, 27, 35, 5, 46, 46, // 120-129
22, 22, 46, 46, 22, 22, 21, 21, 17, 11, // 130-139
21, 21, 17, 11, 19, 19, 18, 18, 19, 19, // 140-149
12, 12, 14, 14, 24, 28, 14, 14, 30, 6, // 150-159
46, 46, 22, 22, 46, 46, 22, 22, 21, 21, // 160-169
17, 11, 21, 21, 17, 11, 19, 19, 18, 18, // 170-179
19, 19, 12, 12, 14, 14, 24, 28, 14, 14, // 180-189
30, 6, 20, 20, 13, 13, 20, 20, 13, 13, // 190-199
16, 16, 23, 32, 16, 16, 23, 32, 9, 9, // 200-209
33, 33, 9, 9, 8, 8, 29, 29, 44, 39, // 210-219
29, 29, 38, 7, 20, 20, 13, 13, 20, 20, // 220-229
13, 13, 10, 10, 31, 4, 10, 10, 31, 4, // 230-239
9, 9, 33, 33, 9, 9, 8, 8, 2, 2, // 240-249
40, 3, 2, 2, 1, 0 // 250-255
];
const enum TileTypeNumber {
None = 0,
Grass = 1,
Water = 2,
Wood = 3,
GrassNew = 4,
Water2 = 5,
Water3 = 6,
Water4 = 7,
Ice = 8,
SnowOnIce = 9,
Stone = 10,
Stone2 = 11,
Boat = 12,
}
const waterFrames: number[] = [
TileTypeNumber.Water, TileTypeNumber.Water2, TileTypeNumber.Water3, TileTypeNumber.Water4
];
export function updateTileSets(
paletteManager: PaletteManager, tileSets: TileSets | undefined, season: Season, mapType: MapType
) {
if (tileSets) {
tileSets.forEach(t => releasePalette(t.palette));
}
return createTileSets(paletteManager, season, mapType);
}
export function createTileSets(paletteManager: PaletteManager, season: Season, mapType: MapType): TileSets {
const isWinter = season === Season.Winter;
const isAutumn = season === Season.Autumn;
const isCave = mapType === MapType.Cave;
const grassTiles = isCave ? sprites.caveTiles : (isWinter ? sprites.snowTiles : sprites.grassTiles);
const grassPalette = grassTiles.palettes[isCave ? 0 : (isAutumn ? 1 : 0)];
const icePaletteIndex = isWinter ? 2 : (isAutumn ? 1 : 0);
const waterPaletteIndex = isCave ? 3 : (isWinter ? 2 : (isAutumn ? 1 : 0));
const waterPalette = sprites.waterTiles1.palettes[waterPaletteIndex];
// indexes equal to TileTypeNumber values
return [
{ // 0
sprites: [sprites.tile_none.color],
palette: paletteManager.addArray(sprites.tile_none.palettes![0]),
},
{ // 1
sprites: grassTiles.sprites,
palette: paletteManager.addArray(grassPalette),
},
{ // 2
sprites: sprites.waterTiles1.sprites,
palette: paletteManager.addArray(waterPalette),
},
{ // 3
sprites: sprites.woodTiles.sprites,
palette: paletteManager.addArray(sprites.woodTiles.palettes[0]),
},
{ // 4
sprites: sprites.grassTilesNew.sprites,
palette: paletteManager.addArray(sprites.grassTilesNew.palettes[0]),
},
// water frames
{ // 5
sprites: sprites.waterTiles2.sprites,
palette: paletteManager.addArray(waterPalette),
},
{ // 6
sprites: sprites.waterTiles3.sprites,
palette: paletteManager.addArray(waterPalette),
},
{ // 7
sprites: sprites.waterTiles4.sprites,
palette: paletteManager.addArray(waterPalette),
},
// ice
{ // 8
sprites: sprites.iceTiles.sprites,
palette: paletteManager.addArray(sprites.iceTiles.palettes[icePaletteIndex]),
},
// snow on ice
{ // 9
sprites: sprites.snowOnIceTiles.sprites,
palette: paletteManager.addArray(sprites.snowOnIceTiles.palettes[0]),
},
// stone
{ // 10
sprites: sprites.stoneTiles.sprites,
palette: paletteManager.addArray(sprites.stoneTiles.palettes[0]),
},
// stone 2
{ // 11
sprites: sprites.stone2Tiles.sprites,
palette: paletteManager.addArray(sprites.stone2Tiles.palettes[0]),
},
];
}
export function drawTiles(
batch: PaletteSpriteBatch, region: Region, camera: Camera, map: WorldMap, tileSets: TileSets, options: DrawOptions
) {
const { tileIndices } = region;
const { tileTime } = map;
const regionX = region.x * REGION_SIZE;
const regionY = region.y * REGION_SIZE;
if (isAreaVisible(camera, regionX * tileWidth, regionY * tileHeight, REGION_SIZE * tileWidth, REGION_SIZE * tileHeight)) {
const minX = clamp(Math.floor(camera.x / tileWidth - regionX), 0, REGION_SIZE);
const minY = clamp(Math.floor(camera.actualY / tileHeight - regionY), 0, REGION_SIZE);
const maxX = clamp(Math.ceil((camera.x + camera.w) / tileWidth - regionX), 0, REGION_SIZE);
const maxY = clamp(Math.ceil((camera.actualY + camera.h) / tileHeight - regionY), 0, REGION_SIZE);
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const tileIndex = tileIndices[x | (y << 3)];
if (tileIndex === -1) {
options.error(`Uninitialized tile index at (${x}, ${y}) ` +
`region: (${region.x}, ${region.y}, ${region.tilesDirty}, ${region.lastTileUpdate}) ` +
`now: ${performance.now()}`);
region.tilesDirty = true;
continue;
}
const tileTypeNumber = tileIndex >>> 8;
const isWater = tileTypeNumber === TileTypeNumber.Water || tileTypeNumber === TileTypeNumber.Boat;
const tileSpriteIndex = tileIndex & 0xff;
const tileSetIndex = isWater ? at(waterFrames, toInt(tileTime) % waterFrames.length)! : tileTypeNumber;
const tileSet = tileSets[tileSetIndex];
if (!tileSet) {
options.error(`Missing tileset: position: (${x}, ${y}) tile: (${getRegionTile(region, x, y)}) ` +
`info: (${tileIndex}, ${tileSetIndex}, ${tileTime}, ${tileSpriteIndex}, ${JSON.stringify(waterFrames)})`);
tileIndices[x | (y << 3)] = -1;
region.tilesDirty = true;
continue;
}
const rx = (x + regionX) * tileWidth;
const ry = (y + regionY) * tileHeight;
if (DEVELOPMENT && !tileSet.sprites[tileSpriteIndex]) {
console.error('Missing sprite', tileSetIndex, tileSpriteIndex);
}
batch.drawSprite(tileSet.sprites[tileSpriteIndex], WHITE, tileSet.palette, rx, ry);
}
}
}
}
export function drawTilesDebugInfo(batch: PaletteSpriteBatch, region: Region, camera: Camera, options: DrawOptions) {
const { tileIndices } = region;
const regionX = region.x * REGION_SIZE;
const regionY = region.y * REGION_SIZE;
if (isAreaVisible(camera, regionX * tileWidth, regionY * tileHeight, REGION_SIZE * tileWidth, REGION_SIZE * tileHeight)) {
const minX = clamp(Math.floor(camera.x / tileWidth - regionX), 0, REGION_SIZE);
const minY = clamp(Math.floor(camera.actualY / tileHeight - regionY), 0, REGION_SIZE);
const maxX = clamp(Math.ceil((camera.x + camera.w) / tileWidth - regionX), 0, REGION_SIZE);
const maxY = clamp(Math.ceil((camera.actualY + camera.h) / tileHeight - regionY), 0, REGION_SIZE);
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const tileIndex = tileIndices[x | (y << 3)];
if (tileIndex === -1) {
continue;
}
const tileTypeNumber = tileIndex >>> 8;
const tileSpriteIndex = tileIndex & 0xff;
const rx = (x + regionX) * tileWidth;
const ry = (y + regionY) * tileHeight;
if (options.tileIndices) {
drawPixelText(batch, rx + 2, ry + 2, 0x000000ff, `${tileTypeNumber}:${tileSpriteIndex}`);
drawPixelText(batch, rx + 2, ry + 2 + 7, 0x555555ff, `${region.tiles[x + REGION_SIZE * y]}`);
}
if (options.tileGrid) {
batch.drawRect(y !== 0 ? 0x00000011 : 0x00000022, rx, ry, tileWidth, 1);
batch.drawRect(x !== 0 ? 0x00000011 : 0x00000022, rx, ry + 1, 1, tileHeight - 1);
}
}
}
}
}
export function drawTilesNew(
batch: PaletteSpriteBatch, region: Region, camera: Camera, map: WorldMap, tileSets: TileSets, options: DrawOptions
) {
const regionX = region.x * REGION_SIZE;
const regionY = region.y * REGION_SIZE;
const TILE_COLOR = 0x666666ff;
const TILE_FRONT_COLOR = 0x5e5e5eff;
const OUTLINE_2_COLOR = 0xffffff22;
const OUTLINE_COLOR = 0x00000022;
if (isAreaVisible(camera, regionX * tileWidth, regionY * tileHeight, REGION_SIZE * tileWidth, REGION_SIZE * tileHeight)) {
const minX = clamp(Math.floor(camera.x / tileWidth - regionX), 0, REGION_SIZE);
const minY = clamp(Math.floor(camera.y / tileHeight - regionY), 0, REGION_SIZE);
const maxX = clamp(Math.ceil((camera.x + camera.w) / tileWidth - regionX), 0, REGION_SIZE);
const maxY = clamp(Math.ceil((camera.y + camera.h) / tileHeight - regionY), 0, REGION_SIZE);
for (let y = minY; y < maxY; y++) {
for (let x = minX; x < maxX; x++) {
const elevation = getRegionElevation(region, x, y);
const cliffTop = getRegionElevation(region, x, y - 1) < elevation;
const cliffLeft = getRegionElevation(region, x - 1, y) < elevation;
const cliffRight = getRegionElevation(region, x + 1, y) < elevation;
const cliffBottom = getRegionElevation(region, x, y + 1) < elevation;
const elevDiff = Math.max(0, elevation - getRegionElevation(region, x, y + 1));
const tx = (x + regionX) * tileWidth;
const ty = (y + regionY) * tileHeight - elevation * tileElevation;
batch.drawRect(TILE_COLOR, tx, ty, tileWidth, tileHeight);
if (elevation) {
batch.drawRect(TILE_FRONT_COLOR, tx, ty + tileHeight, tileWidth, elevDiff * tileElevation);
if (cliffLeft) {
batch.drawRect(OUTLINE_COLOR, tx, ty + tileHeight, 1, elevation * tileElevation);
}
if (cliffRight) {
batch.drawRect(OUTLINE_COLOR, tx + tileWidth - 1, ty + tileHeight, 1, elevation * tileElevation);
}
if (cliffBottom) {
batch.drawRect(OUTLINE_2_COLOR, tx, ty + tileHeight - 1, tileWidth, 1);
batch.drawRect(OUTLINE_2_COLOR, tx, (ty + tileHeight + elevDiff * tileElevation) - 1, tileWidth, 1);
}
}
if (cliffTop) {
batch.drawRect(OUTLINE_2_COLOR, tx, ty, tileWidth, 1);
}
if (cliffLeft) {
batch.drawRect(OUTLINE_2_COLOR, tx, ty, 1, tileHeight);
}
if (cliffRight) {
batch.drawRect(OUTLINE_COLOR, tx + tileWidth - 1, ty, 1, tileHeight);
}
if (cliffBottom) {
batch.drawRect(OUTLINE_COLOR, tx, ty + tileHeight - 1, tileWidth, 1);
}
if (options.gridLines) {
batch.drawRect(OUTLINE_COLOR, tx + tileWidth - 1, ty, 1, tileHeight);
batch.drawRect(OUTLINE_COLOR, tx, ty + tileHeight - 1, tileWidth - 1, 1);
// drawPixelText(batch, tx + 1, ty + 1, OUTLINE_COLOR, elevation.toString(10));
}
const rx = x + regionX;
const ry = y + regionY;
const tileIndex = region.tileIndices[x | (y << 3)];
// const tileTypeNumber = tileIndex >> 8;
const tileOffset = tileIndex & 0xff;
const grass = tileSets[3];
const baseX = (region.x * REGION_SIZE) | 0;
const baseY = (region.y * REGION_SIZE) | 0;
if (getTileNormal(region.tiles, baseX, baseY, x, y, map, TileType.None) === TileType.Grass) {
batch.drawSprite(grass.sprites[tileOffset], WHITE, grass.palette, rx * tileWidth, ry * tileHeight);
}
}
}
}
}
export function updateTileIndices(region: Region, map: IMap<Region | undefined>) {
for (let y = 0, i = 0; y < REGION_SIZE; y++) {
for (let x = 0; x < REGION_SIZE; x++ , i++) {
if (region.tileIndices[i] === -1) {
region.tileIndices[i] = getTileIndex(region, i, x, y, map);
}
}
}
region.tilesDirty = false;
region.lastTileUpdate = performance.now();
}
function tileTypeNumber(type: TileType) {
switch (type) {
case TileType.Water:
case TileType.WalkableWater:
return TileTypeNumber.Water;
case TileType.Wood:
return TileTypeNumber.Wood;
case TileType.Ice:
case TileType.WalkableIce:
return TileTypeNumber.Ice;
case TileType.SnowOnIce:
return TileTypeNumber.SnowOnIce;
case TileType.Stone:
return TileTypeNumber.Stone;
case TileType.Stone2:
return TileTypeNumber.Stone2;
case TileType.Boat:
return TileTypeNumber.Boat;
case TileType.Grass:
case TileType.Dirt:
case TileType.ElevatedDirt:
return TileTypeNumber.Grass;
case TileType.None:
case TileType.WallH:
case TileType.WallV:
return TileTypeNumber.None;
default:
return invalidEnumReturn(type, TileTypeNumber.None);
}
}
function normalizeTile(type: TileType, base: TileType) {
switch (type) {
case TileType.SnowOnIce:
return base === TileType.SnowOnIce ? type : TileType.Ice;
case TileType.WalkableIce:
return TileType.Ice;
case TileType.WalkableWater:
case TileType.Boat:
return TileType.Water;
case TileType.ElevatedDirt:
return TileType.Dirt;
default:
return type;
}
}
function normalizeTileBase(type: TileType) {
switch (type) {
case TileType.WalkableIce:
return TileType.Ice;
case TileType.WalkableWater:
case TileType.Boat:
return TileType.Water;
case TileType.ElevatedDirt:
return TileType.Dirt;
default:
return type;
}
}
function getTileNormal(
tiles: Uint8Array, baseX: number, baseY: number, x: number, y: number, map: IMap<Region | undefined>, base: TileType
) {
if (x >= 0 && y >= 0 && x < REGION_SIZE && y < REGION_SIZE) {
return normalizeTile(tiles[x | (y << 3)], base);
} else {
const mapX = clamp(x + baseX, 0, map.width - 1);
const mapY = clamp(y + baseY, 0, map.height - 1);
const region = getRegionGlobal(map, mapX, mapY);
if (region !== undefined) {
const regionX = mapX - region.x * REGION_SIZE;
const regionY = mapY - region.y * REGION_SIZE;
return normalizeTile(region.tiles[regionX | (regionY << 3)], base);
} else {
return TileType.None;
}
}
}
function getTileIndex(region: Region, index: number, x: number, y: number, map: IMap<Region | undefined>): number {
const tiles = region.tiles;
const type = tiles[x | (y << 3)] as TileType;
const tileType = tileTypeNumber(type);
let baseTileIndex = 0;
if (type === TileType.Dirt || type === TileType.ElevatedDirt) {
baseTileIndex = 47;
} else if (type !== TileType.None) {
let topLeft = 0, top = 0, topRight = 0, left = 0, right = 0, bottomLeft = 0, bottom = 0, bottomRight = 0;
if (x > 1 && y > 1 && x < (REGION_SIZE - 1) && y < (REGION_SIZE - 1)) {
topLeft = normalizeTile(tiles[(x - 1) | (y - 1) << 3], type);
top = normalizeTile(tiles[(x) | (y - 1) << 3], type);
topRight = normalizeTile(tiles[(x + 1) | (y - 1) << 3], type);
left = normalizeTile(tiles[(x - 1) | (y) << 3], type);
right = normalizeTile(tiles[(x + 1) | (y) << 3], type);
bottomLeft = normalizeTile(tiles[(x - 1) | (y + 1) << 3], type);
bottom = normalizeTile(tiles[(x) | (y + 1) << 3], type);
bottomRight = normalizeTile(tiles[(x + 1) | (y + 1) << 3], type);
} else {
const baseX = (region.x * REGION_SIZE) | 0;
const baseY = (region.y * REGION_SIZE) | 0;
topLeft = getTileNormal(tiles, baseX, baseY, x - 1, y - 1, map, type);
top = getTileNormal(tiles, baseX, baseY, x, y - 1, map, type);
topRight = getTileNormal(tiles, baseX, baseY, x + 1, y - 1, map, type);
left = getTileNormal(tiles, baseX, baseY, x - 1, y, map, type);
right = getTileNormal(tiles, baseX, baseY, x + 1, y, map, type);
bottomLeft = getTileNormal(tiles, baseX, baseY, x - 1, y + 1, map, type);
bottom = getTileNormal(tiles, baseX, baseY, x, y + 1, map, type);
bottomRight = getTileNormal(tiles, baseX, baseY, x + 1, y + 1, map, type);
}
const normalized = normalizeTileBase(type);
const index = 0
| ((topLeft === normalized) ? 1 : 0)
| ((top === normalized) ? 2 : 0)
| ((topRight === normalized) ? 4 : 0)
| ((left === normalized) ? 8 : 0)
| ((right === normalized) ? 16 : 0)
| ((bottomLeft === normalized) ? 32 : 0)
| ((bottom === normalized) ? 64 : 0)
| ((bottomRight === normalized) ? 128 : 0);
baseTileIndex = TILE_MAP[index];
}
const tileCount = type !== TileType.None ? TILE_COUNT_MAP[baseTileIndex] : 1;
const tileIndex = TILE_MAP_MAP[baseTileIndex] + (tileCount > 1 ? region.randoms[index] % tileCount : 0);
return (tileType << 8) | tileIndex;
}
const tileIndices = [
47, 47, 0, 0, 13, 19, 21, 20, 15, 16,
47, 47, 0, 0, 13, 13, 45, 22, 18, 17,
9, 2, 2, 2, 10, 14, 14, 14, 35, 36,
8, 5, null, 7, 4, 27, 26, 29, 37, 38,
8, null, 46, null, 4, 28, 24, 30, 39, 40,
8, 3, null, 1, 4, 23, 31, 32, 41, 42,
12, 6, 6, 6, 11, 25, 33, 34, 43, 44,
];
let tileHeightMaps = new Map<number, number[]>();
let tileHeightMapsInitialized = false;
function valueToHeight(value: number, bottom: number, top: number) {
return bottom + ((value / 255) * (top - bottom));
}
export function initializeTileHeightmaps() {
if (tileHeightMapsInitialized)
return;
function createTileHeightMaps(sprite: Sprite, tileType: TileTypeNumber, bottom: number, top: number) {
const sheetData = sprites.normalSpriteSheet.data!;
const tiles: number[][] = [];
for (let ty = 0; ty < 7; ty++) {
for (let tx = 0; tx < 10; tx++) {
const tile: number[] = [];
const baseX = tx * tileWidth + sprite.x;
const baseY = ty * tileHeight + sprite.y;
for (let y = 0, i = 0; y < tileHeight; y++) {
for (let x = 0; x < tileWidth; x++ , i++) {
const sx = baseX + x;
const sy = baseY + y;
const value = sheetData.data[(sx + sy * sheetData.width) * 4];
tile.push(valueToHeight(value, bottom, top));
}
}
tiles.push(tile);
}
}
tileHeightMapsInitialized = true;
const counts = new Uint8Array(100);
for (let i = 0; i < tileIndices.length; i++) {
const index = tileIndices[i];
if (index !== null) {
const key = (tileType << 8) | (TILE_MAP_MAP[index] + counts[index]);
tileHeightMaps.set(key, tiles[i]);
counts[index]++;
}
}
}
createTileHeightMaps(sprites.dirt_water_heightmap, TileTypeNumber.Water, -0.25, 0);
createTileHeightMaps(sprites.dirt_ice_heightmap, TileTypeNumber.Ice, -0.2, 0);
createTileHeightMaps(sprites.dirt_stone_cave_height_map, TileTypeNumber.Grass, 0.2, 0);
}
const waterHeight = WATER_HEIGHT.map(toWorldZ);
export function isInWater(tileIndex: number, x: number, y: number) {
const tileType = (tileIndex & 0xff00) >> 8;
if (tileType === TileTypeNumber.Water) {
const heightMaps = tileHeightMaps.get(tileIndex);
if (heightMaps !== undefined) {
const tx = clamp(toScreenX(x - Math.floor(x)), 0, tileWidth - 1) | 0;
const ty = clamp(toScreenY(y - Math.floor(y)), 0, tileHeight - 1) | 0;
return heightMaps[tx + ty * tileWidth] === -0.25;
}
}
return false;
}
export function getTileHeight(
tileType: TileType, tileIndex: number, x: number, y: number, gameTime: number, mapType: MapType
) {
const typeNumber = (tileIndex & 0xff00) >> 8;
if (
typeNumber === TileTypeNumber.Ice ||
typeNumber === TileTypeNumber.Water ||
(mapType === MapType.Cave && tileType === TileType.Grass)
) {
if (tileType !== TileType.WalkableWater && tileType !== TileType.WalkableIce) {
const heightMaps = tileHeightMaps.get(tileIndex);
if (heightMaps !== undefined) {
const tx = clamp(toScreenX(x - Math.floor(x)), 0, tileWidth - 1) | 0;
const ty = clamp(toScreenY(y - Math.floor(y)), 0, tileHeight - 1) | 0;
return heightMaps[tx + ty * tileWidth];
}
}
} else if (typeNumber === TileTypeNumber.SnowOnIce) {
return -0.2;
} else if (tileType === TileType.ElevatedDirt) {
return 0.5;
} else if (typeNumber === TileTypeNumber.Boat) {
const frame = ((gameTime / 1000) * WATER_FPS) | 0;
return waterHeight[frame % waterHeight.length];
}
return 0;
}