@@ -44,6 +44,12 @@ export class PunishmentCommands {
4444 . build ( PunishmentCommands . prototype . onScanReloadCommand . bind ( this ) )
4545 . parameters ( [ { name : 'player' , type : CommandBuilder . kTypePlayer } ] )
4646 . build ( PunishmentCommands . prototype . onScanCommand . bind ( this ) ) ;
47+
48+ // /scanall
49+ server . commandManager . buildCommand ( 'scanall' )
50+ . description ( 'Scans all players on the server for possible cheating.' )
51+ . restrict ( Player . LEVEL_MANAGEMENT )
52+ . build ( PunishmentCommands . prototype . onScanAllCommand . bind ( this ) ) ;
4753 }
4854
4955 // ---------------------------------------------------------------------------------------------
@@ -201,6 +207,74 @@ export class PunishmentCommands {
201207 await dialog . displayForPlayer ( player ) ;
202208 }
203209
210+ // Scans all players on the server for possible cheating. Will show a dialog of all in-game
211+ // human players, their SA-MP version and whether anything was detected.
212+ async onScanAllCommand ( player ) {
213+ const resultPromises = [ ] ;
214+
215+ // (1) Start scans for all human players connected to the server.
216+ for ( const target of server . playerManager ) {
217+ if ( target . isNonPlayerCharacter ( ) )
218+ continue ;
219+
220+ resultPromises . push ( this . sampcac_ ( ) . detect ( target ) ) ;
221+ }
222+
223+ player . sendMessage ( Message . PUNISHMENT_SCAN_ALL_STARTING , resultPromises . length ) ;
224+
225+ // (2) Wait until all the scans have completed. They may not be complete.
226+ const results = await Promise . all ( resultPromises ) ;
227+ const dialog = new Menu ( 'Scan results' , [
228+ 'Player' ,
229+ 'Version' ,
230+ 'Minimized' ,
231+ 'Result'
232+ ] , { pageSize : 50 /* just in the odd case that there's >50 people in-game... */ } ) ;
233+
234+ for ( const result of results ) {
235+ const nickname = format (
236+ '{%s}%s' , result . player . colors . currentColor . toHexRGB ( ) , result . player . name ) ;
237+
238+ let version = null ;
239+
240+ // (a) Format the SA-MP version used. SAMPCAC users will be highlighted in green, where
241+ // users for whom detections are supported will be yellow. All others will be red.
242+ if ( result . sampcacVersion )
243+ version = '{4CAF50}' + result . version ;
244+ else if ( result . supported )
245+ version = '{CDDC39}' + result . version ;
246+ else
247+ version = '{FF5722}' + result . version ;
248+
249+ const minimized = ! ! result . minimized ? '{FF5722}yes' : '{9E9E9E}no' ;
250+ const detections = [ ...result . detectors ] . filter ( detector => {
251+ return detector [ 1 ] === DetectorResults . kResultDetected ;
252+ } ) ;
253+
254+ let detectionResult = null ;
255+
256+ // (b) Format the detection result. There either are detections (red), no detections
257+ // (green), or an unknown result because scans are not supported (grey).
258+ if ( detections . length > 1 )
259+ detectionResult = `{FF5722}${ detectionResult . length } detections` ;
260+ else if ( detections . length === 1 )
261+ detectionResult = `{FF5722}1 detection` ;
262+ else if ( ! result . supported )
263+ detectionResult = `{9E9E9E}unknown` ;
264+ else
265+ detectionResult = `{4CAF50}no detections` ;
266+
267+ // (c) Add the data to the dialog. Selecting a particular player will act as if the
268+ // administrator executed the `/scan` command on them, for more details.
269+ dialog . addItem ( nickname , version , minimized , detectionResult , ( ) => {
270+ return this . onScanCommand ( player , result . player ) ;
271+ } ) ;
272+ }
273+
274+ // (3) Display the results dialog to the current |player|.
275+ dialog . displayForPlayer ( player ) ;
276+ }
277+
204278 // Called when someone wishes to reload the SAMPCAC definition file. Generally only needed when
205279 // changes have been made, but it's undesirable to reload the entire feature.
206280 onScanReloadCommand ( player ) {
0 commit comments