Skip to content
Permalink
Browse files
Implement the "/my color" command in JavaScript
  • Loading branch information
RussellLVP committed Jul 21, 2020
1 parent 5f3bce5 commit dd4ef834f2839f79a0428fdc0dc47b6afde73ca0
Showing 10 changed files with 179 additions and 13 deletions.
@@ -478,9 +478,14 @@
"NUWANI_PLAYER_BANNED_NOTICE": "{FF9900}You have been banned by %s for %d days{FFFFFF}: %s",
"NUWANI_PLAYER_KICKED_NOTICE": "{FF9900}You have been kicked by %s{FFFFFF}: %s",

"PLAYER_COMMANDS_INVALID_SPAWN_WEAPON" : "@error Sorry, id %d is not a valid spawn weapon.",
"PLAYER_COMMANDS_COLOR_INVALID_FORMAT": "@error Colors must be given as #RRGGBB color codes.",
"PLAYER_COMMANDS_COLOR_UPDATED_FYI": "{33AA33}FYI{FFFFFF}: %s (Id:%d) has updated your player color!",
"PLAYER_COMMANDS_COLOR_UPDATED_FYI_ADMIN": "%s (Id:%d) has updated the color of %s (Id:%d) to #%s.",
"PLAYER_COMMANDS_COLOR_UPDATED_OTHER": "@success The color of %s (Id:%d) has been updated!",
"PLAYER_COMMANDS_COLOR_UPDATED_SELF": "@success Your color has been updated!",
"PLAYER_COMMANDS_SPAWN_WEAPONS_ARMOUR": "@success Spawn armour has been bought.",
"PLAYER_COMMANDS_SPAWN_WEAPONS_INVALID_AMOUNT" : "@error Sorry, you can only have a multiplier of 1-100.",
"PLAYER_COMMANDS_SPAWN_WEAPONS_INVALID_WEAPON" : "@error Sorry, id %d is not a valid spawn weapon.",
"PLAYER_COMMANDS_SPAWN_WEAPONS_NOT_ENOUGH_MONEY": "@error Sorry, you need %$ to buy this.",
"PLAYER_COMMANDS_SPAWN_WEAPONS_TELEPORT" : "@error Sorry, you can't get weapons now because you %s.",
"PLAYER_COMMANDS_SPAWN_WEAPONS_TELEPORT_TARGET" : "@error Sorry, the player can't get weapons now because %s %s.",
@@ -80,7 +80,7 @@ Feature | Code Health | Description
[Leaderboard](leaderboard/) | ❓ 2020 | The `/leaderboard` command, and database routines to calculate it.
[Location](location/) | ❓ 2020 | -
[NuwaniCommands](nuwani_commands/) | ❓ 2020 | The `/nuwani` command, enabling Management to control the bot system.
[PlayerCommands](player_commands/) | 2020 | Provides commands for the player also useable for admins. E.g. to buy weapons.
[PlayerCommands](player_commands/) | 2020 | Provides commands for the player also useable for admins. E.g. to buy weapons.
[PlayerFavours](player_favours/) | ❓ 2020 | -
[PlayerSettings](player_settings/) | ❓ 2020 | -
[Playground](playground/) | ❓ 2020 | -
@@ -1,10 +1,9 @@
# Player Commands Feature
Players have a lot of different commands to their disposal. For example they can choose what color
they have, whether people can teleport to them or what spawn weapons they have.
Administrators can use the same commands for the players to change those options for a player
where needed.
they have, whether people can teleport to them or what spawn weapons they have. Administrators can
use the same commands for the players to change those options for a player where needed.

This feature implements the commands a player can use on himself and if the functionality for
This feature implements the commands a player can use on themselves and if the functionality for
administrators overrides is required implement those too.

## Registering new commands.
@@ -13,6 +12,6 @@ Every command should have it's own file and test and extend `player_command.js`.
with the `.js` (`.test.js` excluded) will be loaded dynamically if found in the `commands` folder.

## TODO:
There's still a big amount of player commands in the PAWN code. We call them by PawnInvoking the
There's still a big amount of player commands in the Pawn code. We call them by PawnInvoking the
`OnPlayerCommandText` if the specific command is not registered in JavaScript. Those commands should
move towards the JS code base.
move towards the JS code base.
@@ -0,0 +1,50 @@
// Copyright 2020 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.

import { Color } from 'base/color.js';
import { CommandBuilder } from 'components/command_manager/command_builder.js';
import { PlayerCommand } from 'features/player_commands/player_command.js';

// Implements the "/my color" command, which is available to VIPs who wish to change their color.
// Administrators further have the ability to execute this command on other players.
export class ColorCommand extends PlayerCommand {
get name() { return 'color'; }
get parameters() {
return [
{ name: 'color', type: CommandBuilder.WORD_PARAMETER, optional: true }
];
}

// This command is only available to VIPs for now.
get requireVip() { return true; }

// Called when a player executes the "/my color" or "/p [player] color" command. Optionally a
// RGB color can be given in |color|, which is only available for administrators.
async execute(player, target, color) {
if (color && !color.match(/^#[0-9a-f]{6}$/i)) {
player.sendMessage(Message.PLAYER_COMMANDS_COLOR_INVALID_FORMAT);
return;
}

// Sanitizes the color, either from HEX or by showing a color picker.
color = color ? Color.fromHex(color.substring(1))
: await this.playerColors().displayColorPickerForPlayer(player);

if (!color)
return; // the |player| has aborted out of the colour selection flow

target.colors.customColor = color;

if (player === target) {
player.sendMessage(Message.PLAYER_COMMANDS_COLOR_UPDATED_SELF);
} else {
this.announce().announceToAdministrators(
Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI_ADMIN, player.name, player.id,
target.name, target.id, color.toHexRGB());

player.sendMessage(Message.PLAYER_COMMANDS_COLOR_UPDATED_OTHER, target.name, target.id);
target.sendMessage(Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI, player.name, player.id);
}
}
}
@@ -0,0 +1,85 @@
// Copyright 2020 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.

describe('ColorCommand', (it, beforeEach) => {
let gunther = null;
let russell = null;

beforeEach(async () => {
const feature = server.featureManager.loadFeature('player_commands');

gunther = server.playerManager.getById(/* Gunther= */ 0);
russell = server.playerManager.getById(/* Russell= */ 1);
russell.level = Player.LEVEL_ADMINISTRATOR;

await feature.registry_.initialize();
await russell.identify();
});

it('should enable players to change their colors', async (assert) => {
// (1) Non-VIP players get an error message when changing their colour.
assert.isTrue(await gunther.issueCommand('/my color'));

assert.equal(gunther.messages.length, 1);
assert.equal(gunther.messages[0], Message.PLAYER_COMMANDS_REQUIRES_VIP);

assert.isTrue(await russell.issueCommand('/p Russell color'));

assert.equal(russell.messages.length, 1);
assert.equal(russell.messages[0], Message.PLAYER_COMMANDS_REQUIRES_VIP);

gunther.setVip(true);
russell.setVip(true);

// (2) It's possible for players to change their own colour.
{
const currentColor = russell.color;

assert.isTrue(await russell.issueCommand('/my color'));
assert.notDeepEqual(russell.color, currentColor);

assert.equal(russell.messages.length, 2);
assert.equal(russell.messages[1], Message.PLAYER_COMMANDS_COLOR_UPDATED_SELF);
}

// (3) It's possible for administrators to change other player's colours.
{
const currentColor = gunther.color;

assert.isTrue(await russell.issueCommand('/p gunth color'));
assert.notDeepEqual(gunther.color, currentColor);

assert.equal(russell.messages.length, 4);
assert.includes(
russell.messages[2],
Message.format(Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI_ADMIN, russell.name,
russell.id, gunther.name, gunther.id, 'FFFF00'));

assert.equal(
russell.messages[3],
Message.format(Message.PLAYER_COMMANDS_COLOR_UPDATED_OTHER, gunther.name,
gunther.id));

assert.equal(gunther.messages.length, 2);
assert.equal(
gunther.messages[1],
Message.format(Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI, russell.name,
russell.id));
}

// (4) A warning message is shown when trying to update to an invalid color.
assert.isTrue(await russell.issueCommand('/my color bananarama'));

assert.equal(russell.messages.length, 5);
assert.equal(russell.messages[4], Message.PLAYER_COMMANDS_COLOR_INVALID_FORMAT);

// (5) Administrators can update colours to specific RGB values.
{
const color = 'FFFF00';

assert.isTrue(await russell.issueCommand('/my color #' + color));
assert.equal(russell.color.toHexRGB(), color);
}
});
});
@@ -39,7 +39,7 @@ export class SpawnWeapons extends PlayerCommand {
}

if (!WeaponData.hasSpawnWeapon(weapon)) {
player.sendMessage(Message.PLAYER_COMMANDS_INVALID_SPAWN_WEAPON, weapon);
player.sendMessage(Message.PLAYER_COMMANDS_SPAWN_WEAPONS_INVALID_WEAPON, weapon);
return;
}

@@ -8,17 +8,20 @@ export class PlayerCommand {
#announce_ = null;
#finance_ = null;
#limits_ = null;
#playerColors_ = null;

constructor(announce, finance, limits) {
constructor(announce, finance, limits, playerColors) {
this.#announce_ = announce;
this.#finance_ = finance;
this.#limits_ = limits;
this.#playerColors_ = playerColors;
}

// Gets read-only access to the dependencies available to commands.
get announce() { return this.#announce_; }
get finance() { return this.#finance_; }
get limits() { return this.#limits_; }
get playerColors() { return this.#playerColors_; }

// ---------------------------------------------------------------------------------------------
// Required API to be implemented by individual commands.
@@ -45,7 +48,7 @@ export class PlayerCommand {
get playerLevel() { return Player.LEVEL_PLAYER; }

// Gets whether players have to be VIP in order to be able to execute this command.
get playerVip() { return false; }
get requireVip() { return false; }

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

@@ -57,5 +60,6 @@ export class PlayerCommand {
this.#announce_ = null;
this.#finance_ = null;
this.#limits_ = null;
this.#playerColors_ = null;
}
}
@@ -74,7 +74,7 @@ export class PlayerCommandRegistry {

// Bail out if the |player| is using the command on themselves, it requires VIP access and
// they don't have VIP access. Administrators cannot cheat this way either.
if (self === player && command.playerVip && !player.isVip()) {
if (self === player && command.requireVip && !player.isVip()) {
player.sendMessage(Message.PLAYER_COMMANDS_REQUIRES_VIP);
return;
}
@@ -0,0 +1,22 @@
// Copyright 2020 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.

describe('PlayerCommandRegistry', (it, beforeEach) => {
let registry = null;

beforeEach(async () => {
const feature = server.featureManager.loadFeature('player_commands');

registry = feature.registry_;

// If this line throws an exception, then there's an import error with one of the commands
// that's being dynamically imported. Read the exception message, and off you go.
await registry.initialize();
});

it('should expose the /my and /p commands to the server', assert => {
assert.isTrue(server.commandManager.hasCommand('my'));
assert.isTrue(server.commandManager.hasCommand('p'));
});
});
@@ -18,10 +18,11 @@ export default class PlayerCommands extends Feature {
const announce = this.defineDependency('announce');
const finance = this.defineDependency('finance');
const limits = this.defineDependency('limits');
const playerColors = this.defineDependency('player_colors');

// The PlayerCommandRegistry loads and keeps track of the individual available commands,
// which will be loaded from files through a globbing pattern.
this.registry_ = new PlayerCommandRegistry(announce, finance, limits);
this.registry_ = new PlayerCommandRegistry(announce, finance, limits, playerColors);

// Initialize immediately when running the production server, otherwise lazily.
if (!server.isTest())

0 comments on commit dd4ef83

Please sign in to comment.