Skip to content

Commit 5f17e44

Browse files
committed
games: Revamp conditional commands
1 parent 26ac52e commit 5f17e44

1 file changed

Lines changed: 119 additions & 126 deletions

File tree

src/ps/commands/games/core.tsx

Lines changed: 119 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { ChatError } from '@/utils/chatError';
1010
import type { NoTranslate, ToTranslate, TranslationFn } from '@/i18n/types';
1111
import type { CommonGame } from '@/ps/games/game';
1212
import type { BaseModEntry } from '@/ps/games/mods';
13-
import type { PSCommand } from '@/types/chat';
13+
import type { PSCommand, PSCommandChild } from '@/types/chat';
1414
import type { Room } from 'ps-client';
1515
import type { HTMLopts } from 'ps-client/classes/common';
1616

@@ -29,6 +29,11 @@ type SearchContext =
2929

3030
type RoomContext = { room: Room; $T: TranslationFn };
3131

32+
function conditionalCommand(condition: unknown, ...subcommands: PSCommandChild[]): PSCommand['children'] {
33+
if (!condition) return {};
34+
return Object.fromEntries(subcommands.map(subcommand => [subcommand.name, subcommand]));
35+
}
36+
3237
export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]): PSCommand => {
3338
const gameId = _gameId as keyof Games;
3439

@@ -213,23 +218,19 @@ export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]):
213218
}
214219
},
215220
},
216-
...(Game.meta.autostart === false
217-
? ({
218-
start: {
219-
name: 'start',
220-
aliases: ['s', 'go', 'g'],
221-
help: 'Starts a game if it does not have an auto-start.',
222-
syntax: 'CMD [id]',
223-
perms: Symbol.for('games.create'),
224-
async run({ message, arg, $T }): Promise<void> {
225-
const { game } = getGame(arg, { action: 'start', user: message.author.id }, { room: message.target, $T });
226-
if (!game.startable()) throw new ChatError($T('GAME.CANNOT_START'));
227-
game.start();
228-
game.closeSignups(false);
229-
},
230-
},
231-
} satisfies PSCommand['children'])
232-
: {}),
221+
...conditionalCommand(Game.meta.autostart === false, {
222+
name: 'start',
223+
aliases: ['s', 'go', 'g'],
224+
help: 'Starts a game if it does not have an auto-start.',
225+
syntax: 'CMD [id]',
226+
perms: Symbol.for('games.create'),
227+
async run({ message, arg, $T }): Promise<void> {
228+
const { game } = getGame(arg, { action: 'start', user: message.author.id }, { room: message.target, $T });
229+
if (!game.startable()) throw new ChatError($T('GAME.CANNOT_START'));
230+
game.start();
231+
game.closeSignups(false);
232+
},
233+
}),
233234
reaction: {
234235
name: 'reaction',
235236
aliases: ['x', '!!'],
@@ -241,17 +242,16 @@ export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]):
241242
game.action(message.author, ctx, true);
242243
},
243244
},
244-
audience: {
245+
...conditionalCommand('external' in Game.instance.prototype, {
245246
name: 'audience',
246247
help: 'Allows an audience member to perform an action.',
247248
syntax: 'CMD [id], [move]',
248249
async run({ message, arg, $T }) {
249-
if (!('external' in Game.instance.prototype)) throw new ChatError($T('GAME.COMMAND_NOT_ENABLED'));
250250
const { game, ctx } = getGame(arg, { action: 'audience', user: message.author.id }, { room: message.target, $T });
251251
if (!game.external) throw new ChatError($T('CMD_NOT_FOUND'));
252252
game.external(message.author, ctx);
253253
},
254-
},
254+
}),
255255
end: {
256256
name: 'end',
257257
aliases: ['e'],
@@ -267,14 +267,13 @@ export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]):
267267
game.end('force');
268268
},
269269
},
270-
substitute: {
270+
...conditionalCommand(Game.meta.players === 'many', {
271271
name: 'substitute',
272272
aliases: ['sub', 'swap'],
273273
help: 'Replaces an inactive player with an active one.',
274274
perms: Symbol.for('games.manage'),
275275
syntax: 'CMD #id, [user1], [user2]',
276276
async run({ message, arg, $T }) {
277-
if (Game.meta.players === 'single') throw new ChatError($T('GAME.COMMAND_NOT_ENABLED', { game: Game.meta.name }));
278277
const { game, ctx } = getGame(arg, { action: 'sub' }, { room: message.target, $T });
279278
const users = ctx.split(',').map(toId);
280279
const outUser = users.find(user => Object.values(game.players).some(player => player.id === user));
@@ -287,7 +286,7 @@ export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]):
287286
if (replace.data) message.reply(replace.data);
288287
game.update();
289288
},
290-
},
289+
}),
291290
forfeit: {
292291
name: 'forfeit',
293292
aliases: ['f', 'ff', 'leave', 'l'],
@@ -383,66 +382,61 @@ export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]):
383382
message.reply(`/closehtmlpage ${message.author.id}, ${game.id}` as NoTranslate);
384383
},
385384
},
386-
...(Game.meta.mods
387-
? ({
388-
mod: {
389-
name: 'mod',
390-
aliases: ['#'],
391-
help: 'Modifies a given game.',
392-
perms: Symbol.for('games.create'),
393-
syntax: 'CMD [game ref], [mod]',
394-
async run({ message, arg, $T }) {
395-
const { game, ctx } = getGame(arg, { action: 'mod', user: message.author.id }, { room: message.target, $T });
396-
if (!game.moddable?.() || !game.applyMod) throw new ChatError($T('GAME.CANNOT_MOD'));
397-
const mod = parseMod(ctx, Game.meta.mods!.list, Game.meta.mods!.data);
398-
if (!mod) throw new ChatError($T('GAME.MOD_NOT_FOUND', { mod: ctx }));
399-
const applied = game.applyMod(mod);
400-
if (applied.success) message.reply(applied.data);
401-
},
402-
},
403-
mods: {
404-
name: 'mods',
405-
aliases: ['modslist', 'listmods', 'modoptions'],
406-
help: `Lists the mods available for ${Game.meta.name}.`,
407-
syntax: 'CMD',
408-
async run({ broadcastHTML }) {
409-
const mods = Game.meta.mods!;
410-
broadcastHTML(
411-
<div>
412-
{Object.values(mods.data)
413-
.filter((mod): mod is BaseModEntry => !!mod)
414-
.map(mod => (
415-
<details>
416-
<summary>
417-
<b>{mod.name}</b>
418-
{mod.aliases?.length ? ` (${mod.aliases.join('/')})` : null}
419-
</summary>
420-
{mod.desc}
421-
</details>
422-
))
423-
.space(<br />)}
424-
</div>
425-
);
426-
},
427-
},
428-
} satisfies PSCommand['children'])
429-
: {}),
430-
...(Game.meta.themes
431-
? ({
432-
theme: {
433-
name: 'theme',
434-
aliases: ['t'],
435-
help: "Customizes a game's theme.",
436-
perms: Symbol.for('games.create'),
437-
syntax: 'CMD [game ref], [theme name]',
438-
async run({ message, arg, $T }) {
439-
const { game, ctx } = getGame(arg, { action: 'any' }, { room: message.target, $T });
440-
const result = game.setTheme(ctx);
441-
message.reply(result);
442-
},
443-
},
444-
} satisfies PSCommand['children'])
445-
: {}),
385+
...conditionalCommand(
386+
Game.meta.mods,
387+
{
388+
name: 'mod',
389+
aliases: ['#'],
390+
help: 'Modifies a given game.',
391+
perms: Symbol.for('games.create'),
392+
syntax: 'CMD [game ref], [mod]',
393+
async run({ message, arg, $T }) {
394+
const { game, ctx } = getGame(arg, { action: 'mod', user: message.author.id }, { room: message.target, $T });
395+
if (!game.moddable?.() || !game.applyMod) throw new ChatError($T('GAME.CANNOT_MOD'));
396+
const mod = parseMod(ctx, Game.meta.mods!.list, Game.meta.mods!.data);
397+
if (!mod) throw new ChatError($T('GAME.MOD_NOT_FOUND', { mod: ctx }));
398+
const applied = game.applyMod(mod);
399+
if (applied.success) message.reply(applied.data);
400+
},
401+
},
402+
{
403+
name: 'mods',
404+
aliases: ['modslist', 'listmods', 'modoptions'],
405+
help: `Lists the mods available for ${Game.meta.name}.`,
406+
syntax: 'CMD',
407+
async run({ broadcastHTML }) {
408+
const mods = Game.meta.mods!;
409+
broadcastHTML(
410+
<div>
411+
{Object.values(mods.data)
412+
.filter((mod): mod is BaseModEntry => !!mod)
413+
.map(mod => (
414+
<details>
415+
<summary>
416+
<b>{mod.name}</b>
417+
{mod.aliases?.length ? ` (${mod.aliases.join('/')})` : null}
418+
</summary>
419+
{mod.desc}
420+
</details>
421+
))
422+
.space(<br />)}
423+
</div>
424+
);
425+
},
426+
}
427+
),
428+
...conditionalCommand(Game.meta.themes, {
429+
name: 'theme',
430+
aliases: ['t'],
431+
help: "Customizes a game's theme.",
432+
perms: Symbol.for('games.create'),
433+
syntax: 'CMD [game ref], [theme name]',
434+
async run({ message, arg, $T }) {
435+
const { game, ctx } = getGame(arg, { action: 'any' }, { room: message.target, $T });
436+
const result = game.setTheme(ctx);
437+
message.reply(result);
438+
},
439+
}),
446440
menu: {
447441
name: 'menu',
448442
aliases: ['m', 'list'],
@@ -471,48 +465,47 @@ export const command: PSCommand[] = Object.entries(Games).map(([_gameId, Game]):
471465
message.reply($T('GAME.STASHED', { id: game.id }));
472466
},
473467
},
474-
...(Game.meta.players === 'many'
475-
? {
476-
backups: {
477-
name: 'backups',
478-
aliases: ['bu', 'b'],
479-
help: 'Shows a list of currently available backups.',
480-
perms: Symbol.for('games.create'),
481-
syntax: 'CMD',
482-
async run({ message }) {
483-
const HTML = renderBackups(message.target, Game.meta);
484-
message.sendHTML(HTML, { name: `${gameId}-backups` });
485-
},
486-
},
487-
restore: {
488-
name: 'restore',
489-
aliases: ['r', 'unstash', 'unyeet'],
490-
help: 'Restores a game from stash/backups.',
491-
perms: Symbol.for('games.create'),
492-
syntax: 'CMD [id]',
493-
async run({ message, arg, $T }) {
494-
const id = arg.trim().toUpperCase();
495-
if (!/^#\w+$/.test(id)) throw new ChatError($T('GAME.INVALID_INPUT'));
496-
if (PSGames[gameId]?.[id]) throw new ChatError($T('GAME.IN_PROGRESS'));
497-
const lookup = gameCache.get(id);
498-
if (lookup.room !== message.target.roomid) throw new ChatError($T('WRONG_ROOM'));
499-
if (lookup.game !== gameId) throw new ChatError($T('GAME.RESTORING_WRONG_TYPE'));
500-
const game = new Game.instance({
501-
id: lookup.id,
502-
meta: Game.meta,
503-
room: message.target,
504-
$T,
505-
by: message.author,
506-
backup: lookup.backup,
507-
args: [],
508-
});
509-
message.reply($T('GAME.RESTORED', { id: game.id }));
510-
if (game.started) game.update();
511-
else game.signups();
512-
},
513-
},
514-
}
515-
: {}),
468+
...conditionalCommand(
469+
Game.meta.players === 'many',
470+
{
471+
name: 'backups',
472+
aliases: ['bu', 'b'],
473+
help: 'Shows a list of currently available backups.',
474+
perms: Symbol.for('games.create'),
475+
syntax: 'CMD',
476+
async run({ message }) {
477+
const HTML = renderBackups(message.target, Game.meta);
478+
message.sendHTML(HTML, { name: `${gameId}-backups` });
479+
},
480+
},
481+
{
482+
name: 'restore',
483+
aliases: ['r', 'unstash', 'unyeet'],
484+
help: 'Restores a game from stash/backups.',
485+
perms: Symbol.for('games.create'),
486+
syntax: 'CMD [id]',
487+
async run({ message, arg, $T }) {
488+
const id = arg.trim().toUpperCase();
489+
if (!/^#\w+$/.test(id)) throw new ChatError($T('GAME.INVALID_INPUT'));
490+
if (PSGames[gameId]?.[id]) throw new ChatError($T('GAME.IN_PROGRESS'));
491+
const lookup = gameCache.get(id);
492+
if (lookup.room !== message.target.roomid) throw new ChatError($T('WRONG_ROOM'));
493+
if (lookup.game !== gameId) throw new ChatError($T('GAME.RESTORING_WRONG_TYPE'));
494+
const game = new Game.instance({
495+
id: lookup.id,
496+
meta: Game.meta,
497+
room: message.target,
498+
$T,
499+
by: message.author,
500+
backup: lookup.backup,
501+
args: [],
502+
});
503+
message.reply($T('GAME.RESTORED', { id: game.id }));
504+
if (game.started) game.update();
505+
else game.signups();
506+
},
507+
}
508+
),
516509
},
517510
};
518511
});

0 commit comments

Comments
 (0)