@@ -10,7 +10,7 @@ import { ChatError } from '@/utils/chatError';
1010import type { NoTranslate , ToTranslate , TranslationFn } from '@/i18n/types' ;
1111import type { CommonGame } from '@/ps/games/game' ;
1212import type { BaseModEntry } from '@/ps/games/mods' ;
13- import type { PSCommand } from '@/types/chat' ;
13+ import type { PSCommand , PSCommandChild } from '@/types/chat' ;
1414import type { Room } from 'ps-client' ;
1515import type { HTMLopts } from 'ps-client/classes/common' ;
1616
@@ -29,6 +29,11 @@ type SearchContext =
2929
3030type 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+
3237export 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