Skip to content

Commit dd4ef83

Browse files
committed
Implement the "/my color" command in JavaScript
1 parent 5f3bce5 commit dd4ef83

10 files changed

+179
-13
lines changed

data/messages.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -478,9 +478,14 @@
478478
"NUWANI_PLAYER_BANNED_NOTICE": "{FF9900}You have been banned by %s for %d days{FFFFFF}: %s",
479479
"NUWANI_PLAYER_KICKED_NOTICE": "{FF9900}You have been kicked by %s{FFFFFF}: %s",
480480

481-
"PLAYER_COMMANDS_INVALID_SPAWN_WEAPON" : "@error Sorry, id %d is not a valid spawn weapon.",
481+
"PLAYER_COMMANDS_COLOR_INVALID_FORMAT": "@error Colors must be given as #RRGGBB color codes.",
482+
"PLAYER_COMMANDS_COLOR_UPDATED_FYI": "{33AA33}FYI{FFFFFF}: %s (Id:%d) has updated your player color!",
483+
"PLAYER_COMMANDS_COLOR_UPDATED_FYI_ADMIN": "%s (Id:%d) has updated the color of %s (Id:%d) to #%s.",
484+
"PLAYER_COMMANDS_COLOR_UPDATED_OTHER": "@success The color of %s (Id:%d) has been updated!",
485+
"PLAYER_COMMANDS_COLOR_UPDATED_SELF": "@success Your color has been updated!",
482486
"PLAYER_COMMANDS_SPAWN_WEAPONS_ARMOUR": "@success Spawn armour has been bought.",
483487
"PLAYER_COMMANDS_SPAWN_WEAPONS_INVALID_AMOUNT" : "@error Sorry, you can only have a multiplier of 1-100.",
488+
"PLAYER_COMMANDS_SPAWN_WEAPONS_INVALID_WEAPON" : "@error Sorry, id %d is not a valid spawn weapon.",
484489
"PLAYER_COMMANDS_SPAWN_WEAPONS_NOT_ENOUGH_MONEY": "@error Sorry, you need %$ to buy this.",
485490
"PLAYER_COMMANDS_SPAWN_WEAPONS_TELEPORT" : "@error Sorry, you can't get weapons now because you %s.",
486491
"PLAYER_COMMANDS_SPAWN_WEAPONS_TELEPORT_TARGET" : "@error Sorry, the player can't get weapons now because %s %s.",

javascript/features/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Feature | Code Health | Description
8080
[Leaderboard](leaderboard/) | ❓ 2020 | The `/leaderboard` command, and database routines to calculate it.
8181
[Location](location/) | ❓ 2020 | -
8282
[NuwaniCommands](nuwani_commands/) | ❓ 2020 | The `/nuwani` command, enabling Management to control the bot system.
83-
[PlayerCommands](player_commands/) | 2020 | Provides commands for the player also useable for admins. E.g. to buy weapons.
83+
[PlayerCommands](player_commands/) | 2020 | Provides commands for the player also useable for admins. E.g. to buy weapons.
8484
[PlayerFavours](player_favours/) | ❓ 2020 | -
8585
[PlayerSettings](player_settings/) | ❓ 2020 | -
8686
[Playground](playground/) | ❓ 2020 | -
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
# Player Commands Feature
22
Players have a lot of different commands to their disposal. For example they can choose what color
3-
they have, whether people can teleport to them or what spawn weapons they have.
4-
Administrators can use the same commands for the players to change those options for a player
5-
where needed.
3+
they have, whether people can teleport to them or what spawn weapons they have. Administrators can
4+
use the same commands for the players to change those options for a player where needed.
65

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

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

1514
## TODO:
16-
There's still a big amount of player commands in the PAWN code. We call them by PawnInvoking the
15+
There's still a big amount of player commands in the Pawn code. We call them by PawnInvoking the
1716
`OnPlayerCommandText` if the specific command is not registered in JavaScript. Those commands should
18-
move towards the JS code base.
17+
move towards the JS code base.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2020 Las Venturas Playground. All rights reserved.
2+
// Use of this source code is governed by the MIT license, a copy of which can
3+
// be found in the LICENSE file.
4+
5+
import { Color } from 'base/color.js';
6+
import { CommandBuilder } from 'components/command_manager/command_builder.js';
7+
import { PlayerCommand } from 'features/player_commands/player_command.js';
8+
9+
// Implements the "/my color" command, which is available to VIPs who wish to change their color.
10+
// Administrators further have the ability to execute this command on other players.
11+
export class ColorCommand extends PlayerCommand {
12+
get name() { return 'color'; }
13+
get parameters() {
14+
return [
15+
{ name: 'color', type: CommandBuilder.WORD_PARAMETER, optional: true }
16+
];
17+
}
18+
19+
// This command is only available to VIPs for now.
20+
get requireVip() { return true; }
21+
22+
// Called when a player executes the "/my color" or "/p [player] color" command. Optionally a
23+
// RGB color can be given in |color|, which is only available for administrators.
24+
async execute(player, target, color) {
25+
if (color && !color.match(/^#[0-9a-f]{6}$/i)) {
26+
player.sendMessage(Message.PLAYER_COMMANDS_COLOR_INVALID_FORMAT);
27+
return;
28+
}
29+
30+
// Sanitizes the color, either from HEX or by showing a color picker.
31+
color = color ? Color.fromHex(color.substring(1))
32+
: await this.playerColors().displayColorPickerForPlayer(player);
33+
34+
if (!color)
35+
return; // the |player| has aborted out of the colour selection flow
36+
37+
target.colors.customColor = color;
38+
39+
if (player === target) {
40+
player.sendMessage(Message.PLAYER_COMMANDS_COLOR_UPDATED_SELF);
41+
} else {
42+
this.announce().announceToAdministrators(
43+
Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI_ADMIN, player.name, player.id,
44+
target.name, target.id, color.toHexRGB());
45+
46+
player.sendMessage(Message.PLAYER_COMMANDS_COLOR_UPDATED_OTHER, target.name, target.id);
47+
target.sendMessage(Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI, player.name, player.id);
48+
}
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright 2020 Las Venturas Playground. All rights reserved.
2+
// Use of this source code is governed by the MIT license, a copy of which can
3+
// be found in the LICENSE file.
4+
5+
describe('ColorCommand', (it, beforeEach) => {
6+
let gunther = null;
7+
let russell = null;
8+
9+
beforeEach(async () => {
10+
const feature = server.featureManager.loadFeature('player_commands');
11+
12+
gunther = server.playerManager.getById(/* Gunther= */ 0);
13+
russell = server.playerManager.getById(/* Russell= */ 1);
14+
russell.level = Player.LEVEL_ADMINISTRATOR;
15+
16+
await feature.registry_.initialize();
17+
await russell.identify();
18+
});
19+
20+
it('should enable players to change their colors', async (assert) => {
21+
// (1) Non-VIP players get an error message when changing their colour.
22+
assert.isTrue(await gunther.issueCommand('/my color'));
23+
24+
assert.equal(gunther.messages.length, 1);
25+
assert.equal(gunther.messages[0], Message.PLAYER_COMMANDS_REQUIRES_VIP);
26+
27+
assert.isTrue(await russell.issueCommand('/p Russell color'));
28+
29+
assert.equal(russell.messages.length, 1);
30+
assert.equal(russell.messages[0], Message.PLAYER_COMMANDS_REQUIRES_VIP);
31+
32+
gunther.setVip(true);
33+
russell.setVip(true);
34+
35+
// (2) It's possible for players to change their own colour.
36+
{
37+
const currentColor = russell.color;
38+
39+
assert.isTrue(await russell.issueCommand('/my color'));
40+
assert.notDeepEqual(russell.color, currentColor);
41+
42+
assert.equal(russell.messages.length, 2);
43+
assert.equal(russell.messages[1], Message.PLAYER_COMMANDS_COLOR_UPDATED_SELF);
44+
}
45+
46+
// (3) It's possible for administrators to change other player's colours.
47+
{
48+
const currentColor = gunther.color;
49+
50+
assert.isTrue(await russell.issueCommand('/p gunth color'));
51+
assert.notDeepEqual(gunther.color, currentColor);
52+
53+
assert.equal(russell.messages.length, 4);
54+
assert.includes(
55+
russell.messages[2],
56+
Message.format(Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI_ADMIN, russell.name,
57+
russell.id, gunther.name, gunther.id, 'FFFF00'));
58+
59+
assert.equal(
60+
russell.messages[3],
61+
Message.format(Message.PLAYER_COMMANDS_COLOR_UPDATED_OTHER, gunther.name,
62+
gunther.id));
63+
64+
assert.equal(gunther.messages.length, 2);
65+
assert.equal(
66+
gunther.messages[1],
67+
Message.format(Message.PLAYER_COMMANDS_COLOR_UPDATED_FYI, russell.name,
68+
russell.id));
69+
}
70+
71+
// (4) A warning message is shown when trying to update to an invalid color.
72+
assert.isTrue(await russell.issueCommand('/my color bananarama'));
73+
74+
assert.equal(russell.messages.length, 5);
75+
assert.equal(russell.messages[4], Message.PLAYER_COMMANDS_COLOR_INVALID_FORMAT);
76+
77+
// (5) Administrators can update colours to specific RGB values.
78+
{
79+
const color = 'FFFF00';
80+
81+
assert.isTrue(await russell.issueCommand('/my color #' + color));
82+
assert.equal(russell.color.toHexRGB(), color);
83+
}
84+
});
85+
});

javascript/features/player_commands/commands/spawn_weapons.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class SpawnWeapons extends PlayerCommand {
3939
}
4040

4141
if (!WeaponData.hasSpawnWeapon(weapon)) {
42-
player.sendMessage(Message.PLAYER_COMMANDS_INVALID_SPAWN_WEAPON, weapon);
42+
player.sendMessage(Message.PLAYER_COMMANDS_SPAWN_WEAPONS_INVALID_WEAPON, weapon);
4343
return;
4444
}
4545

javascript/features/player_commands/player_command.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ export class PlayerCommand {
88
#announce_ = null;
99
#finance_ = null;
1010
#limits_ = null;
11+
#playerColors_ = null;
1112

12-
constructor(announce, finance, limits) {
13+
constructor(announce, finance, limits, playerColors) {
1314
this.#announce_ = announce;
1415
this.#finance_ = finance;
1516
this.#limits_ = limits;
17+
this.#playerColors_ = playerColors;
1618
}
1719

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

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

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

5053
// ---------------------------------------------------------------------------------------------
5154

@@ -57,5 +60,6 @@ export class PlayerCommand {
5760
this.#announce_ = null;
5861
this.#finance_ = null;
5962
this.#limits_ = null;
63+
this.#playerColors_ = null;
6064
}
6165
}

javascript/features/player_commands/player_command_registry.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class PlayerCommandRegistry {
7474

7575
// Bail out if the |player| is using the command on themselves, it requires VIP access and
7676
// they don't have VIP access. Administrators cannot cheat this way either.
77-
if (self === player && command.playerVip && !player.isVip()) {
77+
if (self === player && command.requireVip && !player.isVip()) {
7878
player.sendMessage(Message.PLAYER_COMMANDS_REQUIRES_VIP);
7979
return;
8080
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2020 Las Venturas Playground. All rights reserved.
2+
// Use of this source code is governed by the MIT license, a copy of which can
3+
// be found in the LICENSE file.
4+
5+
describe('PlayerCommandRegistry', (it, beforeEach) => {
6+
let registry = null;
7+
8+
beforeEach(async () => {
9+
const feature = server.featureManager.loadFeature('player_commands');
10+
11+
registry = feature.registry_;
12+
13+
// If this line throws an exception, then there's an import error with one of the commands
14+
// that's being dynamically imported. Read the exception message, and off you go.
15+
await registry.initialize();
16+
});
17+
18+
it('should expose the /my and /p commands to the server', assert => {
19+
assert.isTrue(server.commandManager.hasCommand('my'));
20+
assert.isTrue(server.commandManager.hasCommand('p'));
21+
});
22+
});

javascript/features/player_commands/player_commands.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ export default class PlayerCommands extends Feature {
1818
const announce = this.defineDependency('announce');
1919
const finance = this.defineDependency('finance');
2020
const limits = this.defineDependency('limits');
21+
const playerColors = this.defineDependency('player_colors');
2122

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

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

0 commit comments

Comments
 (0)