Files
tws-zombies-scripts/tools/check_commands.mjs
T

101 lines
5.3 KiB
JavaScript

// Exercise the actual common/leaderboard chat listeners with private reply shims.
// This does not emulate Plutonium's VM or native chat delivery.
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import vm from 'node:vm';
const root = new URL('../', import.meta.url);
let checks = 0;
function translated(source, name) {
const match = new RegExp(`^${name}\\(([^\\n]*)\\)\\s*\\n\\{`, 'm').exec(source);
assert(match, name);
const start = match.index + match[0].length;
let end = start, depth = 1;
while (depth) {
const ch = source[end++];
if (ch === '{') depth++;
if (ch === '}') depth--;
}
let body = source.slice(start, end - 1);
const locals = new Set([...body.matchAll(/(?<![\w.])(\w+)\s*=(?!=)/g)].map(m => m[1]));
const listener = name.endsWith('_listener');
if (listener) { locals.add('message'); locals.add('player'); }
for (const param of match[1].split(',').map(s => s.trim())) locals.delete(param);
body = body.replace(/level endon\([^;]*\);/g, '')
.replace(/level waittill\("say", message, player\);/g, '[message, player] = yield "say";')
.replace(/player show_help\(\);/g, 'help.push(player);')
.replace(/player thread lb_request\(true\);/g, 'records.push(player);')
.replace(/player iPrintLn\(/g, 'player.replies.push(')
.replace(/\.size\b/g, '.length');
return `function${listener ? '*' : ''} ${name}(${match[1]}) { var ${[...locals].join(', ') || 'unused'}; ${body} }`;
}
for (const game of ['t5', 't6']) {
const common = readFileSync(new URL(`${game}/scripts/tws_${game}_common.gsc`, root), 'utf8');
const leaderboard = readFileSync(new URL(`${game}/scripts/tws_${game}_leaderboard.gsc`, root), 'utf8');
const help = [], records = [], sender = { replies: [] }, other = { replies: [] };
const c = vm.createContext({
level: {}, help, records,
isDefined: value => value !== undefined && value !== null,
toLower: value => value.toLowerCase(),
strTok: value => value.split(/[ \t\r\n]+/).filter(Boolean),
getSubStr: (value, start, end) => value.slice(start, end),
spawnStruct: () => ({}),
});
for (const name of ['register_help_command', 'help_first_word', 'help_unknown_command', 'help_chat_listener'])
vm.runInContext(translated(common, name), c);
vm.runInContext(translated(leaderboard, 'lb_chat_listener'), c);
// Missing/disabled optional modules must not suppress unknown-command replies.
assert.equal(c.help_unknown_command('.record'), true);
assert.equal(c.help_unknown_command('.w 2000'), true);
c.register_help_command('.record', 'Custom record command');
assert.equal(c.help_unknown_command('.record'), false);
c.level.tws_help_commands = [];
checks += 3;
const chat = c.help_chat_listener(), lb = c.lb_chat_listener();
assert.equal(chat.next().value, 'say');
assert.equal(lb.next().value, 'say');
for (const file of ['common', 'afk', 'bank', 'hud', 'join', 'lock', 'leaderboard']) {
const source = readFileSync(new URL(`${game}/scripts/tws_${game}_${file}.gsc`, root), 'utf8');
for (const [, command, description] of source.matchAll(/register_help_command\("([^"]+)", "([^"]+)"\)/g))
c.register_help_command(command, description);
}
c.register_help_command('!custom <value>', 'Custom bang command');
const send = (message, player = sender) => {
sender.replies.length = 0; other.replies.length = 0; help.length = 0; records.length = 0;
assert.equal(chat.next([message, player]).value, 'say');
assert.equal(lb.next([message, player]).value, 'say');
};
for (const message of ['.unknown', '!unknown', '.', '!', '.return', '.records', '.helpful', '!help']) {
send(message);
assert.deepEqual(sender.replies, ['Unknown command. Type .help for the list'], `${game} ${message}`);
assert.deepEqual(other.replies, []);
assert.deepEqual(help, []); assert.deepEqual(records, []);
checks++;
}
for (const message of ['', 'hello', 'info', 'hello .unknown', ' .unknown', undefined,
'.w 2000', '.W ALL', '.d 500', '.w\t2000', '.afk', '.afk buy 10', '.lock', '.unlock', '!custom 5',
'.help extra', '.record extra']) {
send(message);
assert.deepEqual(sender.replies, [], `${game} ${message}`);
assert.deepEqual(help, []); assert.deepEqual(records, []);
checks++;
}
for (const message of ['.help', '.HELP', '.commands', '.info', '.INFO', '.command', '.COMMAND']) {
send(message, other);
assert.deepEqual(help, [other]); assert.deepEqual(records, []);
assert.deepEqual(sender.replies, []); assert.deepEqual(other.replies, []);
checks++;
}
for (const message of ['.leaders', '.LEADERS', '.record', '.RECORD']) {
send(message, other);
assert.deepEqual(records, [other]); assert.deepEqual(help, []);
assert.deepEqual(sender.replies, []); assert.deepEqual(other.replies, []);
checks++;
}
send('.unknown', null);
assert.deepEqual(sender.replies, []); assert.deepEqual(help, []); assert.deepEqual(records, []);
checks++;
}
console.log(`PASS ${checks} command discovery checks (both games). Native chat delivery still requires Plutonium.`);