Skip to content

Commit d9f1dc3

Browse files
committed
Add the ability to scan players
1 parent e76cdfe commit d9f1dc3

File tree

4 files changed

+124
-2
lines changed

4 files changed

+124
-2
lines changed

data/messages.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,8 @@
492492
"POSITIONING_FORWARD": "@success I like to move it, move it (forward)!",
493493

494494
"PUNISHMENT_ADMIN_UNBAN": "%d (Id:%d) has unbanned %s: %s",
495+
"PUNISHMENT_SCAN_ERROR_NPC": "@error It's not possible to scan non-player characters.",
496+
"PUNISHMENT_SCAN_STARTING": "{CCCCCC}* Started scanning %s (Id:%d), this could take several seconds...",
495497

496498
"RACE_ERROR_NO_RACES_AVAILABLE": "@error Sorry, there are no races available right now.",
497499
"RACE_ERROR_INVALID_RACE_ID": "@error Sorry, that is not a known race. Type /race for an overview!",

javascript/features/punishments/punishment_commands.js

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import { BanDatabase } from 'features/punishments/ban_database.js';
66
import { CommandBuilder } from 'components/command_manager/command_builder.js';
7+
import { DetectorResults } from 'features/sampcac/detector_results.js';
78
import { Menu } from 'components/menu/menu.js';
89
import { Question } from 'components/dialogs/question.js';
910

@@ -14,21 +15,43 @@ import { formatDate } from 'base/time.js';
1415
// Contains a series of commands that may be used by in-game administrators to inspect and manage
1516
// kicks and bans on the server. Note that a player's history can be seen with `/account` already.
1617
export class PunishmentCommands {
18+
announce_ = null;
1719
database_ = null;
20+
playground_ = null;
21+
sampcac_ = null;
1822
settings_ = null;
1923

20-
constructor(database, announce, settings) {
24+
constructor(database, announce, playground, sampcac, settings) {
2125
this.database_ = database;
2226
this.announce_ = announce;
27+
this.sampcac_ = sampcac;
2328
this.settings_ = settings;
2429

30+
this.playground_ = playground;
31+
this.playground_.addReloadObserver(this, () => this.registerTrackedCommands());
32+
33+
this.registerTrackedCommands();
34+
2535
// /lastbans [limit=10]
2636
server.commandManager.buildCommand('lastbans')
2737
.restrict(Player.LEVEL_ADMINISTRATOR)
2838
.parameters([{ name: 'limit', type: CommandBuilder.NUMBER_PARAMETER, defaultValue: 10 }])
2939
.build(PunishmentCommands.prototype.onLastBansCommand.bind(this));
40+
41+
// /scan [player]
42+
server.commandManager.buildCommand('scan')
43+
.restrict(player => this.playground_().canAccessCommand(player, 'scan'))
44+
.parameters([{ name: 'player', type: CommandBuilder.PLAYER_PARAMETER }])
45+
.build(PunishmentCommands.prototype.onScanCommand.bind(this));
3046
}
3147

48+
// Registers the commands that have to be known by the Playground feature for access tracking.
49+
registerTrackedCommands() {
50+
this.playground_().registerCommand('scan', Player.LEVEL_MANAGEMENT);
51+
}
52+
53+
// ---------------------------------------------------------------------------------------------
54+
3255
// /lastbans
3356
//
3457
// Displays the most recent bans issued on the server to the administrator. Clicking on one of
@@ -110,7 +133,68 @@ export class PunishmentCommands {
110133
});
111134
}
112135

136+
// Initializes a SAMPCAC scan on the given |target|, and displays a dialog to the |player| when
137+
// the scan has been completed. This could take several seconds.
138+
async onScanCommand(player, target) {
139+
if (target.isNonPlayerCharacter()) {
140+
player.sendMessage(Message.PUNISHMENT_SCAN_ERROR_NPC);
141+
return;
142+
}
143+
144+
player.sendMessage(Message.PUNISHMENT_SCAN_STARTING, target.name, target.id);
145+
146+
const results = await this.sampcac_().detect(target);
147+
const dialog = new Menu('Scan results', [ 'Detector', 'Result' ]);
148+
149+
// (1) Add all the meta-data fields to the |dialog|.
150+
dialog.addItem('SA-MP Version', results.version);
151+
dialog.addItem('SAMPCAC Version', results.sampcacVersion || '{9E9E9E}none');
152+
dialog.addItem('SAMPCAC HwID', results.sampcacHardwareId || '{9E9E9E}none');
153+
dialog.addItem('Minimized', results.minimized ? '{FF5722}yes' : '{4CAF50}no');
154+
155+
// (2) Add each of the detectors to the |dialog|, if any have been loaded. They have to be
156+
// sorted prior to being added, as they've been stored in arbitrary order.
157+
if (results.detectors.size > 0) {
158+
dialog.addItem('----------', '----------');
159+
160+
const detectors = [ ...results.detectors ].sort((lhs, rhs) => {
161+
return lhs[0].localeCompare(rhs[0]);
162+
});
163+
164+
for (const [ name, result ] of detectors) {
165+
let resultLabel = '{BDBDBD}undeterminable';
166+
167+
switch (result) {
168+
case DetectorResults.kResultUnavailable:
169+
resultLabel = '{9E9E9E}unavailable';
170+
break;
171+
172+
case DetectorResults.kResultClean:
173+
resultLabel = '{4CAF50}not detected';
174+
break;
175+
176+
case DetectorResults.kResultDetected:
177+
resultLabel = '{FF5722}detected';
178+
break;
179+
}
180+
181+
dialog.addItem(name, resultLabel);
182+
}
183+
}
184+
185+
// (3) Display the |dialog| to the |player|, and call it a day.
186+
await dialog.displayForPlayer(player);
187+
}
188+
189+
// ---------------------------------------------------------------------------------------------
190+
113191
dispose() {
114192
server.commandManager.removeCommand('lastbans');
193+
server.commandManager.removeCommand('scan');
194+
195+
this.playground_().unregisterCommand('scan');
196+
197+
this.playground_.removeReloadObserver(this);
198+
this.playground_ = null;
115199
}
116200
}

javascript/features/punishments/punishment_commands.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,33 @@ describe('PunishmentCommands', (it, beforeEach) => {
9999
Message.format(Message.PUNISHMENT_ADMIN_UNBAN, gunther.name, gunther.id,
100100
'[BB]Joe', 'reason'));
101101
});
102+
103+
it('should be able to run memory scans against a particular player', async (assert) => {
104+
const russell = server.playerManager.getById(/* Russell= */ 1);
105+
106+
gunther.level = Player.LEVEL_MANAGEMENT;
107+
russell.setIsNonPlayerCharacterForTesting(true);
108+
109+
// (1) It's not possible to start scans for non-player characters.
110+
assert.isTrue(await gunther.issueCommand('/scan Russell'));
111+
assert.equal(gunther.messages.length, 1);
112+
assert.equal(gunther.messages[0], Message.PUNISHMENT_SCAN_ERROR_NPC);
113+
114+
// (2) Scans should return after a sensible amount of time, and explain that to the player.
115+
gunther.respondToDialog({ response: 0 /* dismiss */ });
116+
117+
const [ commandResult, advanceResult ] = await Promise.all([
118+
gunther.issueCommand('/scan Gunther'),
119+
server.clock.advance(30 * 1000 /* some excessively long amount of time*/),
120+
]);
121+
122+
assert.isTrue(commandResult);
123+
assert.equal(gunther.messages.length, 2);
124+
assert.equal(
125+
gunther.messages[1],
126+
Message.format(Message.PUNISHMENT_SCAN_STARTING, gunther.name, gunther.id));
127+
128+
assert.includes(gunther.lastDialog, 'SAMPCAC Version');
129+
assert.includes(gunther.lastDialog, gunther.version);
130+
});
102131
});

javascript/features/punishments/punishments.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ export default class Punishments extends Feature {
2828
this.nuwani_ = this.defineDependency('nuwani');
2929
this.nuwani_.addReloadObserver(this, () => this.initializeNuwaniCommands());
3030

31+
// Used to control access to certain commands that haven't fully launched yet.
32+
const playground = this.defineDependency('playground');
33+
34+
// Depends on SAMPCAC to be able to run memory scans against a particular player.
35+
const sampcac = this.defineDependency('sampcac');
36+
3137
// Controls certain settings for the in-game commands.
3238
const settings = this.defineDependency('settings');
3339

@@ -37,7 +43,8 @@ export default class Punishments extends Feature {
3743
: new BanDatabase();
3844

3945
// Provides the in-game commands related to punishing players.
40-
this.commands_ = new PunishmentCommands(this.database_, this.announce_, settings);
46+
this.commands_ = new PunishmentCommands(
47+
this.database_, this.announce_, playground, sampcac, settings);
4148

4249
this.initializeNuwaniCommands();
4350
}

0 commit comments

Comments
 (0)