Skip to content

Commit

Permalink
Add the ability to scan players
Browse files Browse the repository at this point in the history
  • Loading branch information
RussellLVP committed Jul 21, 2020
1 parent e76cdfe commit d9f1dc3
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 2 deletions.
2 changes: 2 additions & 0 deletions data/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,8 @@
"POSITIONING_FORWARD": "@success I like to move it, move it (forward)!",

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

"RACE_ERROR_NO_RACES_AVAILABLE": "@error Sorry, there are no races available right now.",
"RACE_ERROR_INVALID_RACE_ID": "@error Sorry, that is not a known race. Type /race for an overview!",
Expand Down
86 changes: 85 additions & 1 deletion javascript/features/punishments/punishment_commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import { BanDatabase } from 'features/punishments/ban_database.js';
import { CommandBuilder } from 'components/command_manager/command_builder.js';
import { DetectorResults } from 'features/sampcac/detector_results.js';
import { Menu } from 'components/menu/menu.js';
import { Question } from 'components/dialogs/question.js';

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

constructor(database, announce, settings) {
constructor(database, announce, playground, sampcac, settings) {
this.database_ = database;
this.announce_ = announce;
this.sampcac_ = sampcac;
this.settings_ = settings;

this.playground_ = playground;
this.playground_.addReloadObserver(this, () => this.registerTrackedCommands());

this.registerTrackedCommands();

// /lastbans [limit=10]
server.commandManager.buildCommand('lastbans')
.restrict(Player.LEVEL_ADMINISTRATOR)
.parameters([{ name: 'limit', type: CommandBuilder.NUMBER_PARAMETER, defaultValue: 10 }])
.build(PunishmentCommands.prototype.onLastBansCommand.bind(this));

// /scan [player]
server.commandManager.buildCommand('scan')
.restrict(player => this.playground_().canAccessCommand(player, 'scan'))
.parameters([{ name: 'player', type: CommandBuilder.PLAYER_PARAMETER }])
.build(PunishmentCommands.prototype.onScanCommand.bind(this));
}

// Registers the commands that have to be known by the Playground feature for access tracking.
registerTrackedCommands() {
this.playground_().registerCommand('scan', Player.LEVEL_MANAGEMENT);
}

// ---------------------------------------------------------------------------------------------

// /lastbans
//
// Displays the most recent bans issued on the server to the administrator. Clicking on one of
Expand Down Expand Up @@ -110,7 +133,68 @@ export class PunishmentCommands {
});
}

// Initializes a SAMPCAC scan on the given |target|, and displays a dialog to the |player| when
// the scan has been completed. This could take several seconds.
async onScanCommand(player, target) {
if (target.isNonPlayerCharacter()) {
player.sendMessage(Message.PUNISHMENT_SCAN_ERROR_NPC);
return;
}

player.sendMessage(Message.PUNISHMENT_SCAN_STARTING, target.name, target.id);

const results = await this.sampcac_().detect(target);
const dialog = new Menu('Scan results', [ 'Detector', 'Result' ]);

// (1) Add all the meta-data fields to the |dialog|.
dialog.addItem('SA-MP Version', results.version);
dialog.addItem('SAMPCAC Version', results.sampcacVersion || '{9E9E9E}none');
dialog.addItem('SAMPCAC HwID', results.sampcacHardwareId || '{9E9E9E}none');
dialog.addItem('Minimized', results.minimized ? '{FF5722}yes' : '{4CAF50}no');

// (2) Add each of the detectors to the |dialog|, if any have been loaded. They have to be
// sorted prior to being added, as they've been stored in arbitrary order.
if (results.detectors.size > 0) {
dialog.addItem('----------', '----------');

const detectors = [ ...results.detectors ].sort((lhs, rhs) => {
return lhs[0].localeCompare(rhs[0]);
});

for (const [ name, result ] of detectors) {
let resultLabel = '{BDBDBD}undeterminable';

switch (result) {
case DetectorResults.kResultUnavailable:
resultLabel = '{9E9E9E}unavailable';
break;

case DetectorResults.kResultClean:
resultLabel = '{4CAF50}not detected';
break;

case DetectorResults.kResultDetected:
resultLabel = '{FF5722}detected';
break;
}

dialog.addItem(name, resultLabel);
}
}

// (3) Display the |dialog| to the |player|, and call it a day.
await dialog.displayForPlayer(player);
}

// ---------------------------------------------------------------------------------------------

dispose() {
server.commandManager.removeCommand('lastbans');
server.commandManager.removeCommand('scan');

this.playground_().unregisterCommand('scan');

this.playground_.removeReloadObserver(this);
this.playground_ = null;
}
}
29 changes: 29 additions & 0 deletions javascript/features/punishments/punishment_commands.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,33 @@ describe('PunishmentCommands', (it, beforeEach) => {
Message.format(Message.PUNISHMENT_ADMIN_UNBAN, gunther.name, gunther.id,
'[BB]Joe', 'reason'));
});

it('should be able to run memory scans against a particular player', async (assert) => {
const russell = server.playerManager.getById(/* Russell= */ 1);

gunther.level = Player.LEVEL_MANAGEMENT;
russell.setIsNonPlayerCharacterForTesting(true);

// (1) It's not possible to start scans for non-player characters.
assert.isTrue(await gunther.issueCommand('/scan Russell'));
assert.equal(gunther.messages.length, 1);
assert.equal(gunther.messages[0], Message.PUNISHMENT_SCAN_ERROR_NPC);

// (2) Scans should return after a sensible amount of time, and explain that to the player.
gunther.respondToDialog({ response: 0 /* dismiss */ });

const [ commandResult, advanceResult ] = await Promise.all([
gunther.issueCommand('/scan Gunther'),
server.clock.advance(30 * 1000 /* some excessively long amount of time*/),
]);

assert.isTrue(commandResult);
assert.equal(gunther.messages.length, 2);
assert.equal(
gunther.messages[1],
Message.format(Message.PUNISHMENT_SCAN_STARTING, gunther.name, gunther.id));

assert.includes(gunther.lastDialog, 'SAMPCAC Version');
assert.includes(gunther.lastDialog, gunther.version);
});
});
9 changes: 8 additions & 1 deletion javascript/features/punishments/punishments.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export default class Punishments extends Feature {
this.nuwani_ = this.defineDependency('nuwani');
this.nuwani_.addReloadObserver(this, () => this.initializeNuwaniCommands());

// Used to control access to certain commands that haven't fully launched yet.
const playground = this.defineDependency('playground');

// Depends on SAMPCAC to be able to run memory scans against a particular player.
const sampcac = this.defineDependency('sampcac');

// Controls certain settings for the in-game commands.
const settings = this.defineDependency('settings');

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

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

this.initializeNuwaniCommands();
}
Expand Down

0 comments on commit d9f1dc3

Please sign in to comment.