Add support for TW large tilesets, real MS tilesets, better tileset detection, and an attempted fix for CC1 thin wall tiles
This commit is contained in:
1 parent
3e7390ffc0
commit
51bc3dfe83
6 files changed
+743
-206
No files matched your search
@@ -1,13 +1,14 @@
|
||||
import { readFile, stat } from 'fs/promises';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { argv, exit, stderr, stdout } from 'process';
|
||||
|
||||
import { compat_flags_for_ruleset } from '../defs.js';
|
||||
import { Level } from '../game.js';
|
||||
import * as format_c2g from '../format-c2g.js';
|
||||
import * as format_dat from '../format-dat.js';
|
||||
import * as format_tws from '../format-tws.js';
|
||||
import * as util from '../util.js';
|
||||
|
||||
import { argv, exit, stderr, stdout } from 'process';
|
||||
import { opendir, readFile, stat } from 'fs/promises';
|
||||
import { performance } from 'perf_hooks';
|
||||
import { LocalDirectorySource } from './lib.js';
|
||||
|
||||
// TODO arguments:
|
||||
// - custom pack to test, possibly its solutions, possibly its ruleset (or default to steam-strict/lynx)
|
||||
@@ -17,39 +18,6 @@ import { performance } from 'perf_hooks';
|
||||
// - support for xfails somehow?
|
||||
// TODO use this for a test suite
|
||||
|
||||
export class LocalDirectorySource extends util.FileSource {
|
||||
constructor(root) {
|
||||
super();
|
||||
this.root = root;
|
||||
this.files = {};
|
||||
this._loaded_promise = this._scan_dir('/');
|
||||
}
|
||||
|
||||
async _scan_dir(path) {
|
||||
let dir = await opendir(this.root + path);
|
||||
for await (let dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
await this._scan_dir(path + dirent.name + '/');
|
||||
}
|
||||
else {
|
||||
let filepath = path + dirent.name;
|
||||
this.files[filepath.toLowerCase()] = filepath;
|
||||
if (this.files.size > 2000)
|
||||
throw `way, way too many files in local directory source ${this.root}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async get(path) {
|
||||
let realpath = this.files[path.toLowerCase()];
|
||||
if (realpath) {
|
||||
return (await readFile(this.root + realpath)).buffer;
|
||||
}
|
||||
else {
|
||||
throw new Error(`No such file: ${path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pad(s, n) {
|
||||
return s.substring(0, n).padEnd(n, " ");
|
||||
@@ -330,7 +298,7 @@ may be run with different compat modes.
|
||||
don't support built-in replays, this must be a TWS file
|
||||
-l level range to play back; either 'all' or a string like '1-4,10'
|
||||
-f force the next argument to be interpreted as a file path, if for
|
||||
some perverse reason you have a level file named '-c'
|
||||
some perverse reason you have a level file named '-c'
|
||||
-h, --help ignore other arguments and show this message
|
||||
|
||||
Supports the same filetypes as Lexy's Labyrinth: DAT/CCL, C2M, or a directory
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { opendir, readFile } from 'fs/promises';
|
||||
|
||||
import canvas from 'canvas';
|
||||
|
||||
import CanvasRenderer from '../renderer-canvas.js';
|
||||
import * as util from '../util.js';
|
||||
|
||||
export class NodeCanvasRenderer extends CanvasRenderer {
|
||||
static make_canvas(w, h) {
|
||||
return canvas.createCanvas(w, h);
|
||||
}
|
||||
}
|
||||
|
||||
export class LocalDirectorySource extends util.FileSource {
|
||||
constructor(root) {
|
||||
super();
|
||||
this.root = root;
|
||||
this.files = {};
|
||||
this._loaded_promise = this._scan_dir('/');
|
||||
}
|
||||
|
||||
async _scan_dir(path) {
|
||||
let dir = await opendir(this.root + path);
|
||||
for await (let dirent of dir) {
|
||||
if (dirent.isDirectory()) {
|
||||
await this._scan_dir(path + dirent.name + '/');
|
||||
}
|
||||
else {
|
||||
let filepath = path + dirent.name;
|
||||
this.files[filepath.toLowerCase()] = filepath;
|
||||
if (this.files.size > 2000)
|
||||
throw `way, way too many files in local directory source ${this.root}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async get(path) {
|
||||
let realpath = this.files[path.toLowerCase()];
|
||||
if (realpath) {
|
||||
return (await readFile(this.root + realpath)).buffer;
|
||||
}
|
||||
else {
|
||||
throw new Error(`No such file: ${path}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import * as process from 'process';
|
||||
|
||||
import canvas from 'canvas';
|
||||
import minimist from 'minimist';
|
||||
|
||||
import * as format_c2g from '../format-c2g.js';
|
||||
import { infer_tileset_from_image } from '../tileset.js';
|
||||
import { NodeCanvasRenderer } from './lib.js';
|
||||
|
||||
|
||||
const USAGE = `\
|
||||
Usage: render.mjs [OPTION]... LEVELFILE OUTFILE
|
||||
Renders the level contained in LEVELFILE to a PNG and saves it to OUTFILE.
|
||||
|
||||
Arguments:
|
||||
-t FILE path to a tileset to use
|
||||
-e render in editor mode: use the revealed forms of tiles and
|
||||
show facing directions
|
||||
-l NUM choose the level number to render, if LEVELFILE is a pack
|
||||
[default: 1]
|
||||
-r REGION specify the region to render; see below
|
||||
|
||||
REGION may be one of:
|
||||
initial an area the size of the level's viewport, centered on the
|
||||
player's initial position
|
||||
all the entire level
|
||||
WxH an area W by H, centered on the player's initial position
|
||||
...etc...
|
||||
`;
|
||||
async function main() {
|
||||
let args = minimist(process.argv.slice(2), {
|
||||
alias: {
|
||||
tileset: ['t'],
|
||||
},
|
||||
});
|
||||
// assert _.length is 2
|
||||
let [pack_path, dest_path] = args._;
|
||||
|
||||
// TODO i need a more consistent and coherent way to turn a path into a level pack, currently
|
||||
// this is only a single c2m
|
||||
let pack_data = await readFile(pack_path);
|
||||
let stored_level = format_c2g.parse_level(pack_data.buffer);
|
||||
|
||||
let img = await canvas.loadImage(args.tileset ?? 'tileset-lexy.png');
|
||||
let tileset = infer_tileset_from_image(img);
|
||||
let renderer = new NodeCanvasRenderer(tileset);
|
||||
renderer.set_level(stored_level);
|
||||
|
||||
let i = stored_level.linear_cells.findIndex(cell => cell.some(tile => tile && tile.type.is_real_player));
|
||||
if (i < 0) {
|
||||
console.log("???");
|
||||
process.stderr.write("error: no players in this level\n");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let [x, y] = stored_level.scalar_to_coords(i);
|
||||
let w = stored_level.viewport_size;
|
||||
let h = w;
|
||||
|
||||
// TODO this is probably duplicated from the renderer, and could also be reused in the editor
|
||||
// TODO handle a map smaller than the viewport
|
||||
let x0 = Math.max(0, x - w / 2);
|
||||
let y0 = Math.max(0, y - h / 2);
|
||||
renderer.draw_static_region(x0, y0, x0 + w, y0 + h);
|
||||
|
||||
await writeFile(dest_path, renderer.canvas.toBuffer());
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in new issue
Block a user