You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
5.7 KiB
JavaScript
193 lines
5.7 KiB
JavaScript
const MAP_WIDTH = 40;
|
|
const MAP_HEIGHT = 40;
|
|
const TILE_SIZE = 100;
|
|
const DIR = FileInfo.path(__filename);
|
|
|
|
const EXPORT_EMPTY = 100;
|
|
const EMPTY_IDS = new Set([100, 200, 500, 1000, 2000, 3000, 4000]);
|
|
|
|
const do_layer = (map, name, str, ts_name, has_empty) =>
|
|
{
|
|
const ts_file = `${DIR}/tileset_${ts_name}.tsj`;
|
|
// Using the read method on the tileset format will not mark the tileset as
|
|
// external, so if the user saves the map, it will have a copy of the tileset.
|
|
// tiled.open will properly mark it as external, but it also 1) opens a new
|
|
// tab for the tileset, 2) litters recent files. Closing it at the end solves
|
|
// 1), but not 2).
|
|
//const ts = tiled.tilesetFormat('json').read(ts_file);
|
|
const ts = tiled.open(ts_file);
|
|
try
|
|
{
|
|
map.addTileset(ts);
|
|
|
|
const layer = new TileLayer(name);
|
|
layer.width = MAP_WIDTH;
|
|
layer.height = MAP_HEIGHT;
|
|
const edit = layer.edit();
|
|
map.addLayer(layer);
|
|
|
|
for (let y = 0; y < MAP_HEIGHT; ++y)
|
|
for (let x = 0; x < MAP_WIDTH; ++x)
|
|
{
|
|
const id = str.charCodeAt(y*MAP_WIDTH + x);
|
|
if (has_empty && EMPTY_IDS.has(id)) continue;
|
|
edit.setTile(x, y, ts.tile(id));
|
|
}
|
|
|
|
edit.apply();
|
|
}
|
|
finally { tiled.close(ts); }
|
|
};
|
|
|
|
const read = (fn) =>
|
|
{
|
|
const file = new TextFile(fn, TextFile.ReadOnly);
|
|
const content = file.readAll();
|
|
file.close();
|
|
|
|
const json = JSON.parse(LZString.decompressFromBase64(content));
|
|
// {Type: 'Always'/'Hybrid', Tiles: '...', Objects: '...'}
|
|
const map = new TileMap();
|
|
map.setSize(MAP_WIDTH, MAP_HEIGHT);
|
|
map.setTileSize(TILE_SIZE, TILE_SIZE);
|
|
map.setProperty('is_hybrid', json.Type == 'Hybrid');
|
|
do_layer(map, 'Tiles', json.Tiles, 'tile', false);
|
|
do_layer(map, 'Objects', json.Objects, 'object', true);
|
|
|
|
return map;
|
|
};
|
|
|
|
const assert = (expr, err) =>
|
|
{
|
|
if (expr) return;
|
|
throw new Error(err);
|
|
};
|
|
|
|
const get_layer = (map, name) =>
|
|
{
|
|
for (const l of map.layers)
|
|
if (l.name === name)
|
|
return l;
|
|
throw Error(`Missing layer "${name}"`);
|
|
};
|
|
|
|
const write_layer = (map, name) =>
|
|
{
|
|
const layer = get_layer(map, name);
|
|
const codes = [];
|
|
for (let y = 0; y < MAP_HEIGHT; ++y)
|
|
for (let x = 0; x < MAP_WIDTH; ++x)
|
|
{
|
|
const tile = layer.tileAt(x, y);
|
|
if (tile == null)
|
|
codes.push(EXPORT_EMPTY);
|
|
else
|
|
{
|
|
assert(tile.tileset.name == name,
|
|
`Bad tileset ${tile.tileset.name} on layer ${name} at ${x}, ${y}`);
|
|
codes.push(tile.id);
|
|
}
|
|
}
|
|
return String.fromCharCode(...codes);
|
|
};
|
|
|
|
const write = (map, fn) =>
|
|
{
|
|
assert(map.width == MAP_WIDTH && map.height == MAP_HEIGHT, 'Invalid map size');
|
|
assert(!map.infinite, 'Map is infinite');
|
|
assert(map.layerCount == 2, 'Invalid layer count');
|
|
|
|
const json = {
|
|
Type: map.property('is_hybrid') ? 'Hybrid' : 'Always',
|
|
Tiles: write_layer(map, 'Tiles'),
|
|
Objects: write_layer(map, 'Objects'),
|
|
};
|
|
const text = LZString.compressToBase64(JSON.stringify(json));
|
|
|
|
const file = new TextFile(fn, TextFile.WriteOnly);
|
|
file.writeLine(text);
|
|
file.commit();
|
|
};
|
|
|
|
tiled.registerMapFormat(
|
|
'bondageclub', { name: 'BondageClub map', extension: 'txt', read, write });
|
|
|
|
const validate_layer = (check, layer) =>
|
|
{
|
|
check(layer.width == MAP_WIDTH && layer.height == MAP_HEIGHT,
|
|
`Layer ${layer.name} is not ${MAP_WIDTH}x${MAP_HEIGHT}`);
|
|
const is_objects = layer.name === 'Objects';
|
|
|
|
for (let y = 0; y < layer.height; ++y)
|
|
for (let x = 0; x < layer.width; ++x)
|
|
{
|
|
const tile = layer.tileAt(x, y);
|
|
if (tile == null)
|
|
check(is_objects, `Empty tile on layer ${layer.name} at ${x}, ${y}`);
|
|
else
|
|
{
|
|
check(tile.tileset.name == layer.name,
|
|
`Bad tileset ${tile.tileset.name} on layer ${layer.name} at ${x}, ${y}`);
|
|
}
|
|
}
|
|
};
|
|
|
|
const validate = () =>
|
|
{
|
|
let was_error = false;
|
|
const map = tiled.activeAsset;
|
|
const check = (expr, err, func) =>
|
|
{
|
|
if (expr) return;
|
|
was_error = true;
|
|
tiled.warn(err, func);
|
|
};
|
|
|
|
check(!map.infinite, 'Map is infinite');
|
|
check(map.width == MAP_WIDTH && map.height == MAP_HEIGHT,
|
|
`Map not ${MAP_WIDTH}x${MAP_HEIGHT}`);
|
|
check(map.layerCount == 2, 'Map layer count is not 2');
|
|
|
|
const layers = {};
|
|
for (const l of map.layers)
|
|
{
|
|
layers[l.name] = l;
|
|
if (l.name === 'Objects' || l.name === 'Tiles')
|
|
validate_layer(check, l);
|
|
else
|
|
check(false, `Layer "${l.name}" has invalid name`);
|
|
}
|
|
|
|
if (layers.Objects && layers.Tiles &&
|
|
layers.Objects.width === layers.Tiles.width &&
|
|
layers.Objects.height === layers.Objects.height)
|
|
for (let y = 0; y < layers.Objects.height; ++y)
|
|
for (let x = 0; x < layers.Objects.width; ++x)
|
|
{
|
|
const obj = layers.Objects.tileAt(x, y);
|
|
if (obj == null) continue;
|
|
const tile = layers.Tiles.tileAt(x, y);
|
|
if (tile == null) continue; // error'd in validate_layer
|
|
|
|
const obj_type = obj.property('type');
|
|
const tile_type = tile.property('type');
|
|
// validation logic from ChatRoomMapViewUpdateFlag
|
|
check(obj_type !== 'floor' || tile_type === 'floor',
|
|
`Floor item on not floor at ${x}, ${y}`);
|
|
check(obj_type !== 'wall' || tile_type === 'wall',
|
|
`Wall item on not wall at ${x}, ${y}`);
|
|
if (obj_type === 'door')
|
|
{
|
|
check(tile_type === 'wall', `Door on not wall at ${x}, ${y}`);
|
|
tile_below = layers.Tiles.tileAt(x, y+1);
|
|
check(tile_below == null || tile_below.property('type') !== 'wall',
|
|
`Place below door is wall at ${x}, ${y}`);
|
|
}
|
|
}
|
|
|
|
if (was_error) tiled.alert('Validation failed, check issues for details');
|
|
};
|
|
const act = tiled.registerAction('bc_validate', validate);
|
|
act.text = 'Validate BC map';
|
|
tiled.extendMenu('Map', [{action: 'bc_validate', before: 'MapProperties'}]);
|