Skip to content

Commit

Permalink
Report illegal vehicle entry to administrators
Browse files Browse the repository at this point in the history
  • Loading branch information
RussellLVP committed Oct 20, 2016
1 parent 3cb3438 commit c5e8fe2
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 0 deletions.
2 changes: 2 additions & 0 deletions data/messages.json
@@ -1,6 +1,8 @@
{
"TEST_MESSAGE": "Hello, world!",

"ABUSE_ANNOUNCE_DETECTED": "%s (Id: %d) has just abused %s (%s time).",

"ANNOUNCE_ADMINISTRATORS": "{FFFF00}Admin notice{FFFFFF}: %s",
"ANNOUNCE_ALL": "{80CBC4}*** %s",
"ANNOUNCE_MINIGAME": "{CCD782}Sign up for the {838F31}%s{CCD782}! Type {838F31}%s{CCD782} to join.",
Expand Down
12 changes: 12 additions & 0 deletions javascript/features/abuse/abuse.js
Expand Up @@ -4,6 +4,7 @@

const AbuseConstants = require('features/abuse/abuse_constants.js');
const AbuseMitigator = require('features/abuse/abuse_mitigator.js');
const AbuseMonitor = require('features/abuse/abuse_monitor.js');
const AbuseNatives = require('features/abuse/abuse_natives.js');
const DamageManager = require('features/abuse/damage_manager.js');
const Feature = require('components/feature_manager/feature.js');
Expand All @@ -17,10 +18,15 @@ class Abuse extends Feature {
constructor() {
super();

// The announce feature enables abuse to be reported to administrators.
this.announce_ = this.defineDependency('announce');

// The settings for the Abuse system are configurable at runtime.
this.settings_ = this.defineDependency('settings');

this.mitigator_ = new AbuseMitigator();
this.monitor_ = new AbuseMonitor(this.announce_, this.settings_);

this.damageManager_ = new DamageManager(this.mitigator_, this.settings_);

this.natives_ = new AbuseNatives(this);
Expand Down Expand Up @@ -112,6 +118,12 @@ class Abuse extends Feature {
this.natives_.dispose();
this.natives_ = null;

this.damageManager_.dispose();
this.damageManager_ = null;

this.monitor_.dispose();
this.monitor_ = null;

this.mitigator_.dispose();
this.mitigator_ = null;
}
Expand Down
87 changes: 87 additions & 0 deletions javascript/features/abuse/abuse_monitor.js
@@ -0,0 +1,87 @@
// Copyright 2016 Las Venturas Playground. All rights reserved.
// Use of this source code is governed by the MIT license, a copy of which can
// be found in the LICENSE file.

// The abuse monitor keeps an eye out for players who may abuse something on Las Venturas Playground
// and informs administrators of the event when something has been detected.
//
// The abuse monitor is able to detect the following kinds of abuse:
// 1) Illegal vehicle entry: entering a vehicle that is locked for the player.
//
class AbuseMonitor {
constructor(announce, settings) {
this.announce_ = announce;
this.settings_ = settings;

this.detected_ = new Map([
[ AbuseMonitor.TYPE_ILLEGAL_VEHICLE_ENTRY, new WeakMap() ]
]);

server.playerManager.addObserver(this);
}

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

// Returns the maximum number of times to report a particular warning about a particular player.
getReportLimit() { return this.settings_().getValue('abuse/warning_report_limit'); }

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

// Reports that the |player| has been detected for abusing |type|. Administrators will receive
// a configurable amount of warnings for the |player, type| tuple as well.
reportAbuse(player, type) {
const incidents = (this.detected_.get(type).get(player) || 0) + 1;
if (incidents <= this.getReportLimit()) {
const incidentDescription = this.getTypeDescription(type);
const incidentOrdinal = incidents.toOrdinalString();

this.announce_().announceToAdministrators(
Message.ABUSE_ANNOUNCE_DETECTED, player.name, player.id, incidentDescription,
incidentOrdinal);
}

// Do keep track of the new number of incidents for the |player|.
this.detected_.get(type).set(player, incidents);
}

// Returns the number of times the |player| has been caught for the various kinds of abuse.
getPlayerStatistics(player) {
// Utility function that returns the number of times |player| has been caught for |type|.
const createEntryForType = type =>
[ this.getTypeDescription(type), this.detected_.get(type).get(player) || 0 ];

return new Map([
createEntryForType(AbuseMonitor.TYPE_ILLEGAL_VEHICLE_ENTRY)
]);
}

// Gets the textual description for the abuse |type|.
getTypeDescription(type) {
switch (type) {
case AbuseMonitor.TYPE_ILLEGAL_VEHICLE_ENTRY:
return 'illegal vehicle entry';
default:
throw new Error('Unknown abuse type given: ' + type);
}
}

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

// Called when the |player| enters the |vehicle|. Will report them for abuse when the vehicle
// was locked for them, and entry therefore shouldn't have been possible.
onPlayerEnterVehicle(player, vehicle) {
if (vehicle.isLockedForPlayer(player))
this.reportAbuse(player, AbuseMonitor.TYPE_ILLEGAL_VEHICLE_ENTRY);
}

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

dispose() {
server.playerManager.removeObserver(this);
}
}

// The different sorts of abuse that can be detected by the monitor.
AbuseMonitor.TYPE_ILLEGAL_VEHICLE_ENTRY = 0;

exports = AbuseMonitor;
101 changes: 101 additions & 0 deletions javascript/features/abuse/abuse_monitor.test.js
@@ -0,0 +1,101 @@
// Copyright 2016 Las Venturas Playground. All rights reserved.
// Use of this source code is governed by the MIT license, a copy of which can
// be found in the LICENSE file.

const AbuseMonitor = require('features/abuse/abuse_monitor.js');

describe('AbuseMonitor', (it, beforeEach) => {
let monitor = null;
let settings = null;

beforeEach(() => {
monitor = server.featureManager.loadFeature('abuse').monitor_;
settings = server.featureManager.loadFeature('settings');
});

it('should be able to detect and report illegal vehicle entry', assert => {
const gunther = server.playerManager.getById(0 /* Gunther */);
const russell = server.playerManager.getById(1 /* Russell */);

russell.identify();
russell.level = Player.LEVEL_ADMINISTRATOR;

const vehicle = server.vehicleManager.createVehicle({
modelId: 441,
position: new Vector(200, 300, 50)
});

vehicle.lockForPlayer(gunther);

// (1) Make sure that the abuse can be detected.
{
assert.isTrue(vehicle.isLockedForPlayer(gunther));

// Force |gunther| in the |vehicle|. This roughly matches how cheats enter vehicles.
gunther.enterVehicle(vehicle);

// Make sure that |russell| has received a warning about the incident.
assert.equal(russell.messages.length, 1);
assert.isTrue(
russell.messages[0].includes(
Message.format(Message.ABUSE_ANNOUNCE_DETECTED, gunther.name, gunther.id,
'illegal vehicle entry', '1st')));

// Make sure that the incident has been reported in |gunther|'s statistics.
const statistics = monitor.getPlayerStatistics(gunther);
assert.equal(statistics.get('illegal vehicle entry'), 1);
}

// (2) Make sure that the warnings setting will be respected.
{
settings.setValue('abuse/warning_report_limit', 10);
assert.equal(monitor.getReportLimit(), 10);

// Incidents should be reported up to the 10th time.
for (let i = 2; i <= 10; ++i) {
gunther.enterVehicle(vehicle);
assert.equal(russell.messages.length, i);
}

// The next incidents should not be reported to administrators anymore.
assert.equal(russell.messages.length, 10);

gunther.enterVehicle(vehicle);
gunther.enterVehicle(vehicle);
gunther.enterVehicle(vehicle);
gunther.enterVehicle(vehicle);

assert.equal(russell.messages.length, 10);

const statistics = monitor.getPlayerStatistics(gunther);
assert.equal(statistics.get('illegal vehicle entry'), 14);
}

});

it('should gather and have names for all sorts of abuse', assert => {
const gunther = server.playerManager.getById(0 /* Gunther */);
const types = new Set();

for (const name of Object.getOwnPropertyNames(AbuseMonitor)) {
if (name.startsWith('TYPE_'))
types.add(AbuseMonitor[name]);
}

assert.isAbove(types.size, 0);

// (1) Verify that all types have a description.
{
for (const type of types)
assert.equal(typeof monitor.getTypeDescription(type), 'string');
}

// (2) Verify that all types are included in the statistics.
{
const statistics = monitor.getPlayerStatistics(gunther);

for (const type of types)
assert.isTrue(statistics.has(monitor.getTypeDescription(type)));
}
});
});
2 changes: 2 additions & 0 deletions javascript/features/settings/setting_list.js
Expand Up @@ -15,4 +15,6 @@ exports = [

new Setting('abuse', 'teleportation_admin_override', Setting.TYPE_BOOLEAN, true, 'Should administrators override teleportation restrictions?'),
new Setting('abuse', 'teleportation_throttle_time', Setting.TYPE_NUMBER, 180, 'Minimum number of seconds between teleporting twice.'),

new Setting('abuse', 'warning_report_limit', Setting.TYPE_NUMBER, 3, 'Number of types to report a specific abuse type for a player.'),
];
1 change: 1 addition & 0 deletions javascript/main.js
Expand Up @@ -8,6 +8,7 @@ const TestRunner = require('base/test/test_runner.js');
// Import global objects.
require('base/color.js');
require('base/message.js');
require('base/number_util.js');
require('base/string_util.js');
require('base/time.js');
require('base/vector.js');
Expand Down
4 changes: 4 additions & 0 deletions javascript/mock_server.js
Expand Up @@ -21,6 +21,8 @@ const MockTextLabel = require('entities/test/mock_text_label.js');
const MockVehicle = require('entities/test/mock_vehicle.js');

const Abuse = require('features/abuse/abuse.js');
const Communication = require('features/communication/communication.js');
const MockAnnounce = require('features/announce/test/mock_announce.js');
const Settings = require('features/settings/settings.js');
const Streamer = require('features/streamer/streamer.js');

Expand All @@ -46,6 +48,8 @@ class MockServer {
// Register features whose production versions are suitable for testing.
this.featureManager_.registerFeaturesForTests({
abuse: Abuse,
announce: MockAnnounce, // TODO: Move functionality to |communication|. See #309.
communication: Communication,
settings: Settings,
streamer: Streamer
});
Expand Down

0 comments on commit c5e8fe2

Please sign in to comment.