teeworlds
Version:
Library for (ingame) teeworlds bots.
406 lines • 16.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwMapParser = exports.TilemapLayerType = exports.TileFlags = exports.TileId = void 0;
const zlib_1 = require("zlib");
var TileId;
(function (TileId) {
TileId[TileId["AIR"] = 0] = "AIR";
TileId[TileId["SOLID"] = 1] = "SOLID";
TileId[TileId["DEATH"] = 2] = "DEATH";
TileId[TileId["NOHOOK"] = 3] = "NOHOOK";
TileId[TileId["NOLASER"] = 4] = "NOLASER";
TileId[TileId["THROUGH_CUT"] = 5] = "THROUGH_CUT";
TileId[TileId["THROUGH"] = 6] = "THROUGH";
TileId[TileId["JUMP"] = 7] = "JUMP";
TileId[TileId["FREEZE"] = 9] = "FREEZE";
TileId[TileId["TELEINEVIL"] = 10] = "TELEINEVIL";
TileId[TileId["UNFREEZE"] = 11] = "UNFREEZE";
TileId[TileId["DFREEZE"] = 12] = "DFREEZE";
TileId[TileId["DUNFREEZE"] = 13] = "DUNFREEZE";
TileId[TileId["TELEINWEAPON"] = 14] = "TELEINWEAPON";
TileId[TileId["TELEINHOOK"] = 15] = "TELEINHOOK";
TileId[TileId["WALLJUMP"] = 16] = "WALLJUMP";
TileId[TileId["EHOOK_ENABLE"] = 17] = "EHOOK_ENABLE";
TileId[TileId["EHOOK_DISABLE"] = 18] = "EHOOK_DISABLE";
TileId[TileId["HIT_ENABLE"] = 19] = "HIT_ENABLE";
TileId[TileId["HIT_DISABLE"] = 20] = "HIT_DISABLE";
TileId[TileId["SOLO_ENABLE"] = 21] = "SOLO_ENABLE";
TileId[TileId["SOLO_DISABLE"] = 22] = "SOLO_DISABLE";
})(TileId || (exports.TileId = TileId = {}));
var TileFlags;
(function (TileFlags) {
TileFlags[TileFlags["FLIP_X"] = 1] = "FLIP_X";
TileFlags[TileFlags["FLIP_Y"] = 2] = "FLIP_Y";
TileFlags[TileFlags["OPAQUE"] = 4] = "OPAQUE";
TileFlags[TileFlags["ROTATE"] = 8] = "ROTATE";
})(TileFlags || (exports.TileFlags = TileFlags = {}));
var TilemapLayerType;
(function (TilemapLayerType) {
TilemapLayerType[TilemapLayerType["TILES"] = 0] = "TILES";
TilemapLayerType[TilemapLayerType["GAME"] = 1] = "GAME";
TilemapLayerType[TilemapLayerType["TELE"] = 2] = "TELE";
TilemapLayerType[TilemapLayerType["SPEEDUP"] = 4] = "SPEEDUP";
TilemapLayerType[TilemapLayerType["FRONT"] = 8] = "FRONT";
TilemapLayerType[TilemapLayerType["SWITCH"] = 16] = "SWITCH";
TilemapLayerType[TilemapLayerType["TUNE"] = 32] = "TUNE";
})(TilemapLayerType || (exports.TilemapLayerType = TilemapLayerType = {}));
const ITEM_TYPE_GROUPS = 4;
const ITEM_TYPE_LAYERS = 5;
class TwMapParser {
static parse(buf) {
return this.parse_map(this.parse_datafile(buf));
}
static get_tile(map, x, y) {
var _a, _b, _c;
return (_c = (_b = (_a = map.game_layer) === null || _a === void 0 ? void 0 : _a.tiles) === null || _b === void 0 ? void 0 : _b[y]) === null || _c === void 0 ? void 0 : _c[x];
}
static is_solid(tile) { return (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.SOLID; }
static is_nohook(tile) { return (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.NOHOOK || (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.THROUGH_CUT; }
static is_freeze(tile) { return (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.FREEZE; }
static is_dfreeze(tile) { return (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.DFREEZE; }
static is_hookthrough(tile) { return (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.THROUGH || (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.THROUGH_CUT; }
static is_death(tile) { return (tile === null || tile === void 0 ? void 0 : tile.id) === TileId.DEATH; }
static is_air(tile) { return !tile || tile.id === TileId.AIR; }
static tile_name(id) {
var _a;
return (_a = TILE_NAMES[id]) !== null && _a !== void 0 ? _a : `unknown(${id})`;
}
static parse_datafile(buf) {
let pos = 0;
const r32 = () => { const v = buf.readInt32LE(pos); pos += 4; return v; };
const magic = buf.slice(0, 4).toString("ascii");
if (magic !== "DATA" && magic !== "ATAD")
throw new Error(`Invalid map magic: "${magic}"`);
pos += 4;
const version = r32();
if (version !== 3 && version !== 4)
throw new Error(`Unsupported datafile version: ${version}`);
r32();
r32();
const num_item_types = r32();
const num_items = r32();
const num_data = r32();
const item_block_size = r32();
const data_block_size = r32();
const item_types = [];
for (let i = 0; i < num_item_types; i++) {
item_types.push({ type_id: r32(), start: r32(), num: r32() });
}
const item_offsets = [];
for (let i = 0; i < num_items; i++)
item_offsets.push(r32());
const data_offsets = [];
for (let i = 0; i < num_data; i++)
data_offsets.push(r32());
if (version === 4) {
for (let i = 0; i < num_data; i++)
r32();
}
const items_start = pos;
const items_by_type = new Map();
for (const it of item_types) {
const arr = [];
for (let i = 0; i < it.num; i++) {
const off = items_start + item_offsets[it.start + i];
const id = buf.readUInt16LE(off);
const type_id = buf.readUInt16LE(off + 2);
const size = buf.readInt32LE(off + 4);
const item_data = [];
for (let j = 0; j < size / 4; j++) {
item_data.push(buf.readInt32LE(off + 8 + j * 4));
}
arr.push({ type_id, id, item_data });
}
items_by_type.set(it.type_id, arr);
}
const data_start = items_start + item_block_size;
const raw_data = buf.slice(data_start, data_start + data_block_size);
const data_items = [];
for (let i = 0; i < num_data; i++) {
const off = data_offsets[i];
const next_off = i + 1 < num_data ? data_offsets[i + 1] : data_block_size;
const chunk = raw_data.slice(off, next_off);
try {
data_items.push((0, zlib_1.inflateSync)(chunk));
}
catch (_a) {
data_items.push(chunk);
}
}
return { version, items: items_by_type, data_items };
}
static parse_map(df) {
var _a, _b;
const result = {
datafile_version: df.version,
groups: [],
};
const group_items = (_a = df.items.get(ITEM_TYPE_GROUPS)) !== null && _a !== void 0 ? _a : [];
const layer_items = (_b = df.items.get(ITEM_TYPE_LAYERS)) !== null && _b !== void 0 ? _b : [];
for (const group_item of group_items) {
const d = group_item.item_data;
if (d.length < 7)
continue;
const group_version = d[0];
const start_layer = d[5];
const num_layers = d[6];
const clipping = group_version >= 2 && d.length >= 8 ? d[7] !== 0 : false;
const name = group_version >= 3 && d.length >= 15
? this.decode_i32_string([d[12], d[13], d[14]])
: "";
const group = {
name,
x_offset: d[1],
y_offset: d[2],
x_parallax: d[3],
y_parallax: d[4],
clipping,
layers: [],
};
for (let li = start_layer; li < start_layer + num_layers; li++) {
const layer_item = layer_items[li];
if (!layer_item)
continue;
const ld = layer_item.item_data;
if (ld.length < 7 || ld[1] !== 2)
continue;
const layer_type = ld[6];
if (![0, 1, 2, 4, 8, 16, 32].includes(layer_type))
continue;
const tilemap_version = ld[3];
const width = ld[4];
const height = ld[5];
const detail = (ld[2] & 1) !== 0;
const layer_name = tilemap_version >= 3 && ld.length >= 18
? this.decode_i32_string([ld[15], ld[16], ld[17]])
: "";
const data_idx = this.get_data_index(layer_type, tilemap_version);
if (ld.length <= data_idx)
continue;
const raw_idx = ld[data_idx];
if (raw_idx < 0 || raw_idx >= df.data_items.length)
continue;
const raw_buf = df.data_items[raw_idx];
if (!raw_buf)
continue;
const compressed = (layer_type === TilemapLayerType.GAME || layer_type === TilemapLayerType.TILES) && tilemap_version >= 4;
const tile_layer = {
width,
height,
layer_type,
name: layer_name,
detail,
};
switch (layer_type) {
case TilemapLayerType.GAME:
case TilemapLayerType.FRONT:
case TilemapLayerType.TILES:
tile_layer.tiles = this.parse_tiles(raw_buf, width, height, compressed);
break;
case TilemapLayerType.TELE:
tile_layer.tele_tiles = this.parse_tele_tiles(raw_buf, width, height);
break;
case TilemapLayerType.SPEEDUP:
tile_layer.speedup_tiles = this.parse_speedup_tiles(raw_buf, width, height);
break;
case TilemapLayerType.SWITCH:
tile_layer.switch_tiles = this.parse_switch_tiles(raw_buf, width, height);
break;
case TilemapLayerType.TUNE:
tile_layer.tune_tiles = this.parse_tune_tiles(raw_buf, width, height);
break;
}
group.layers.push(tile_layer);
if (layer_type === TilemapLayerType.GAME && !result.game_layer)
result.game_layer = tile_layer;
if (layer_type === TilemapLayerType.FRONT && !result.front_layer)
result.front_layer = tile_layer;
if (layer_type === TilemapLayerType.TELE && !result.tele_layer)
result.tele_layer = tile_layer;
}
result.groups.push(group);
}
return result;
}
static parse_tiles(buf, width, height, compressed) {
const total = width * height;
const flat = [];
if (compressed) {
let p = 0;
while (flat.length < total && p + 4 <= buf.length) {
const id = buf.readUInt8(p);
const flags = buf.readUInt8(p + 1);
const skip = buf.readUInt8(p + 2);
p += 4;
for (let s = 0; s <= skip && flat.length < total; s++) {
flat.push({ id, flags });
}
}
}
else {
for (let i = 0; i + 4 <= buf.length && flat.length < total; i += 4) {
flat.push({
id: buf.readUInt8(i),
flags: buf.readUInt8(i + 1),
});
}
}
return this.flat_to_2d(flat, width, height);
}
static parse_tele_tiles(buf, width, height) {
const flat = [];
for (let i = 0; i + 2 <= buf.length && flat.length < width * height; i += 2) {
flat.push({
number: buf.readUInt8(i),
id: buf.readUInt8(i + 1),
});
}
return this.flat_to_2d(flat, width, height);
}
static parse_speedup_tiles(buf, width, height) {
const total = width * height;
const byte_per = total > 0 ? Math.floor(buf.length / total) : 6;
let src = buf;
if (byte_per === 4 && total > 0) {
const upgraded = Buffer.alloc(total * 6, 0);
for (let n = 0; n < total; n++) {
upgraded[n * 6 + 0] = buf[n * 4 + 0];
upgraded[n * 6 + 2] = 28;
upgraded[n * 6 + 4] = buf[n * 4 + 2];
upgraded[n * 6 + 5] = buf[n * 4 + 3];
}
src = upgraded;
}
const actual_bpt = byte_per === 4 ? 6 : byte_per;
const flat = [];
for (let n = 0; n < total; n++) {
const i = n * actual_bpt;
flat.push({
force: src.readUInt8(i),
max_speed: actual_bpt >= 2 ? src.readUInt8(i + 1) : 0,
id: actual_bpt >= 3 ? src.readUInt8(i + 2) : 0,
angle: actual_bpt >= 6 ? src.readInt16LE(i + 4) : 0,
});
}
return this.flat_to_2d(flat, width, height);
}
static parse_switch_tiles(buf, width, height) {
const total = width * height;
const byte_per = total > 0 ? Math.floor(buf.length / total) : 4;
let src = buf;
if ((byte_per === 2 || byte_per === 3) && total > 0) {
const upgraded = Buffer.alloc(total * 4, 0);
for (let n = 0; n < total; n++) {
upgraded[n * 4 + 0] = buf[n * byte_per + 0];
upgraded[n * 4 + 1] = buf[n * byte_per + 1];
if (byte_per === 3) {
upgraded[n * 4 + 2] = buf[n * byte_per + 2];
}
}
src = upgraded;
}
const actual_bpt = (byte_per === 2 || byte_per === 3) ? 4 : byte_per;
const flat = [];
for (let n = 0; n < total; n++) {
const i = n * actual_bpt;
flat.push({
number: src.readUInt8(i),
id: actual_bpt >= 2 ? src.readUInt8(i + 1) : 0,
flags: actual_bpt >= 3 ? src.readUInt8(i + 2) : 0,
delay: actual_bpt >= 4 ? src.readUInt8(i + 3) : 0,
});
}
return this.flat_to_2d(flat, width, height);
}
static parse_tune_tiles(buf, width, height) {
const flat = [];
for (let i = 0; i + 2 <= buf.length && flat.length < width * height; i += 2) {
flat.push({
number: buf.readUInt8(i),
id: buf.readUInt8(i + 1),
});
}
return this.flat_to_2d(flat, width, height);
}
static get_data_index(layer_type, tilemap_version) {
let idx;
switch (layer_type) {
case TilemapLayerType.GAME:
case TilemapLayerType.TILES:
idx = 14;
break;
case TilemapLayerType.TELE:
idx = 18;
break;
case TilemapLayerType.SPEEDUP:
idx = 19;
break;
case TilemapLayerType.FRONT:
idx = 20;
break;
case TilemapLayerType.SWITCH:
idx = 21;
break;
case TilemapLayerType.TUNE:
idx = 22;
break;
default: return 14;
}
if (tilemap_version < 3 && idx > 14)
idx -= 3;
return idx;
}
static decode_i32_string(ints) {
const bytes = [];
for (const v of ints) {
bytes.push((v >>> 24) & 0xff);
bytes.push((v >>> 16) & 0xff);
bytes.push((v >>> 8) & 0xff);
bytes.push(v & 0xff);
}
if (bytes.every(b => b === 0))
return "";
bytes.pop();
for (let i = 0; i < bytes.length; i++) {
bytes[i] = (bytes[i] + 128) & 0xff;
}
while (bytes.length > 0 && bytes[bytes.length - 1] === 0) {
bytes.pop();
}
return Buffer.from(bytes).toString("utf8");
}
static flat_to_2d(flat, width, height) {
const result = [];
for (let y = 0; y < height; y++) {
result.push(flat.slice(y * width, y * width + width));
}
return result;
}
}
exports.TwMapParser = TwMapParser;
const TILE_NAMES = {
0: "air",
1: "solid (hookable)",
2: "death",
3: "nohook",
4: "nolaser",
5: "through_cut (nohook + hookthrough)",
6: "through (hookthrough)",
7: "jump",
9: "freeze",
10: "tele_in_evil",
11: "unfreeze",
12: "deep_freeze",
13: "deep_unfreeze",
14: "tele_in_weapon",
15: "tele_in_hook",
16: "walljump",
17: "ehook_enable",
18: "ehook_disable",
19: "hit_enable",
20: "hit_disable",
21: "solo_enable",
22: "solo_disable",
};
exports.default = TwMapParser;
//# sourceMappingURL=twmap_parser.js.map