Skip to content
Permalink
Browse files
Begin moving colour management to JavaScript
  • Loading branch information
RussellLVP committed Jul 20, 2020
1 parent b953584 commit 190dbe77202ba9d8b6abba20f21f8d760e4c331a
Showing 12 changed files with 558 additions and 8 deletions.
@@ -33,12 +33,13 @@ other foundational features, without circular dependencies.

Feature | Code Health | Description
-------------------------------------|-------------|------------------------------------------------------------------
[AccountProvider](account_provider/) | ✔ 2020 | Provides the `Player.account` supplement, loads and saves account data.
[AccountProvider](account_provider/) | ✔ 2020 | Provides the `Player.prototype.account` supplement, loads and saves account data.
[Communication](communication/) | ✔ 2020 | Provides communication abilities, chat, spam and message filters.
[Finance](finance/) | ✔ 2020 | Manages player's cash, bank account balances, tax and loans.
[Limits](limits/) | ✔ 2020 | Centrally decides on limitations for various features, e.g. teleportation.
[Nuwani](nuwani/) | ✔ 2020 | Provides our ability to interact with IRC and Discord.
[PlayerStats](player_stats/) | ✔ 2020 | Provides the `Player.stats` supplement, tracks player metrics.
[PlayerColors](player_colors/) | ✔ 2020 | Provides the `Player.prototype.colors` supplement and color management.
[PlayerStats](player_stats/) | ✔ 2020 | Provides the `Player.prototype.stats` supplement, tracks player metrics.
[SAMPCAC](sampcac/) | ✔ 2020 | Integration with the SAMPCAC anti cheat system, optional for players.
[Settings](settings/) | ✔ 2020 | Manages persistent settings on the server.

@@ -79,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 | -
@@ -32,6 +32,9 @@ export default class Communication extends Feature {
// features, as communication is a cricial part of the server.
this.markFoundational();

// Depend on the PlayerColors feature, which provides a supplement we style text with.
this.defineDependency('player_colors');

// Depend on Nuwani for being able to distribute communication to non-game destinations.
const nuwani = this.defineDependency('nuwani');

@@ -4,6 +4,8 @@

import { AdministratorChannel } from 'features/communication/channels/administrator_channel.js';

import { format } from 'base/format.js';

describe('CommunicationManager', (it, beforeEach, afterEach) => {
let gunther = null;
let manager = null;
@@ -70,6 +72,11 @@ describe('CommunicationManager', (it, beforeEach, afterEach) => {
});

it('should allow players to mention other players in public chat', async (assert) => {
function colorForPlayer(player) {
return format(
'{%06X}@%s{FFFFFF}', player.colors.currentColor.toNumberRGB(), player.name);
}

// (1) Verify that all nicknames can be matched
const nicknameTestCasePlayerId = 42;
const nicknameTestCases = [
@@ -88,7 +95,7 @@ describe('CommunicationManager', (it, beforeEach, afterEach) => {

await gunther.issueMessage(`Hey @${testCase}, how are you?`);
assert.equal(gunther.messages.length, 1);
assert.includes(gunther.messages.pop(), `{FFFFFF}@${testCase}{FFFFFF}`);
assert.includes(gunther.messages.pop(), colorForPlayer(testPlayer));

assert.equal(testPlayer.soundIdForTesting, 1058);

@@ -107,27 +114,27 @@ describe('CommunicationManager', (it, beforeEach, afterEach) => {

await gunther.issueMessage(testCase);
assert.equal(gunther.messages.length, 1);
assert.includes(gunther.messages.pop(), `{FFFFFF}@Russell{FFFFFF}`);
assert.includes(gunther.messages.pop(), colorForPlayer(russell));

await server.clock.advance(10 * 1000); // get past the spam filter
}

// (3) Verify that players cannot mention themselves, or invalid players.
await gunther.issueMessage('Hey @Gunther, how are you?');
assert.equal(gunther.messages.length, 1);
assert.doesNotInclude(gunther.messages.pop(), `{FFFFFF}@Gunther{FFFFFF}`);
assert.doesNotInclude(gunther.messages.pop(), `@Gunther{FFFFFF}`);

// (4) Verify that it doesn't catch cases that aren't mentions.
await gunther.issueMessage('info@domain.com');
assert.equal(gunther.messages.length, 1);
assert.doesNotInclude(gunther.messages.pop(), `{FFFFFF}info@domain.com{FFFFFF}`);

// (5) Verify that it's got the ability to use the mentioned player's colour.
russell.color = Color.fromRGB(50, 150, 250);
russell.colors.customColor = Color.fromRGB(50, 150, 250);

await gunther.issueMessage('Heya @Russell, how are you?');
assert.equal(gunther.messages.length, 1);
assert.includes(gunther.messages.pop(), `{3296FA}@Russell{FFFFFF}`);
assert.includes(gunther.messages.pop(), colorForPlayer(russell));
});

it('should allow delegates to intercept received messages', async (assert) => {
@@ -0,0 +1,40 @@
# Player Colors
Determining which colour will be assigned to a player is not an entirely straightforward task. It's
decided based on a hierarchy of needs:

1. Base colour assigned based on their ID,
1. Base colour assigned based on their level, when applicable,
1. Custom colour that the player is able to pick themselves,
1. Custom colour based on the gang that the player is part of, if they choose to use this,
1. Custom colour applied based on their activity, e.g. while playing a minigame.

Some of these have additional complexities, for example because players can decide that they don't
want to use their gang's colour at all.

## Updating a player's color
Features are free to update colours through the `Player.prototype.colors` supplement, which has been
implemented by the [PlayerColorsSupplement](player_colors_supplement.js) class. Examples include:

### Usage for Games
```javascript
class MyGame extends Game {
async onPlayerAdded(player) {
player.colors.gameColor = Color.fromRGBA(255, 0, 0, 170);
}
async onPlayerRemoved(player) {
player.colors.gameColor = null;
}
}
```

### Usage from Pawn
```
stock StartGame() {
SetPlayerGameColor(playerId, COLOR_RED);
}
stock StopGame() {
ReleasePlayerGameColor(playerId);
}
```
@@ -0,0 +1,218 @@
// 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';

// Default alpha channel for player colors.
export const kDefaultAlpha = 170;

// Array of the default colors that will be assigned to players based on their ID. When Player IDs
// are seen that exceed the number of colours in this array, it will loop around instead.
const kDefaultColors = [
/* 0 */ Color.fromRGBA(47, 200, 39, kDefaultAlpha),
/* 1 */ Color.fromRGBA(15, 217, 250, kDefaultAlpha),
/* 2 */ Color.fromRGBA(220, 20, 60, kDefaultAlpha),
/* 3 */ Color.fromRGBA(227, 172, 18, kDefaultAlpha),
/* 4 */ Color.fromRGBA(119, 136, 153, kDefaultAlpha),
/* 5 */ Color.fromRGBA(0, 248, 83, kDefaultAlpha),
/* 6 */ Color.fromRGBA(101, 173, 235, kDefaultAlpha),
/* 7 */ Color.fromRGBA(255, 20, 147, kDefaultAlpha),
/* 8 */ Color.fromRGBA(244, 164, 96, kDefaultAlpha),
/* 9 */ Color.fromRGBA(238, 130, 238, kDefaultAlpha),
/* 10 */ Color.fromRGBA(220, 222, 61, kDefaultAlpha),
/* 11 */ Color.fromRGBA(165, 94, 44, kDefaultAlpha),
/* 12 */ Color.fromRGBA(130, 157, 199, kDefaultAlpha),
/* 13 */ Color.fromRGBA(4, 149, 205, kDefaultAlpha),
/* 14 */ Color.fromRGBA(20, 255, 127, kDefaultAlpha),
/* 15 */ Color.fromRGBA(203, 126, 211, kDefaultAlpha),
/* 16 */ Color.fromRGBA(201, 80, 84, kDefaultAlpha),
/* 17 */ Color.fromRGBA(250, 251, 113, kDefaultAlpha),
/* 18 */ Color.fromRGBA(36, 124, 27, kDefaultAlpha),
/* 19 */ Color.fromRGBA(241, 50, 50, kDefaultAlpha),
/* 20 */ Color.fromRGBA(250, 36, 204, kDefaultAlpha),
/* 21 */ Color.fromRGBA(179, 107, 114, kDefaultAlpha),
/* 22 */ Color.fromRGBA(67, 128, 216, kDefaultAlpha),
/* 23 */ Color.fromRGBA(255, 146, 73, kDefaultAlpha),
/* 24 */ Color.fromRGBA(255, 153, 194, kDefaultAlpha),
/* 25 */ Color.fromRGBA(255, 39, 39, kDefaultAlpha),
/* 26 */ Color.fromRGBA(137, 82, 235, kDefaultAlpha),
/* 27 */ Color.fromRGBA(70, 126, 64, kDefaultAlpha),
/* 28 */ Color.fromRGBA(175, 175, 175, kDefaultAlpha),
/* 29 */ Color.fromRGBA(255, 68, 169, kDefaultAlpha),
/* 30 */ Color.fromRGBA(193, 247, 236, kDefaultAlpha),
/* 31 */ Color.fromRGBA(78, 255, 0, kDefaultAlpha),
/* 32 */ Color.fromRGBA(0, 219, 255, kDefaultAlpha),
/* 33 */ Color.fromRGBA(219, 54, 250, kDefaultAlpha),
/* 34 */ Color.fromRGBA(218, 120, 37, kDefaultAlpha),
/* 35 */ Color.fromRGBA(214, 66, 96, kDefaultAlpha),
/* 36 */ Color.fromRGBA(56, 75, 202, kDefaultAlpha),
/* 37 */ Color.fromRGBA(210, 235, 27, kDefaultAlpha),
/* 38 */ Color.fromRGBA(172, 55, 110, kDefaultAlpha),
/* 39 */ Color.fromRGBA(184, 166, 107, kDefaultAlpha),
/* 40 */ Color.fromRGBA(47, 200, 39, kDefaultAlpha),
/* 41 */ Color.fromRGBA(15, 217, 250, kDefaultAlpha),
/* 42 */ Color.fromRGBA(220, 20, 60, kDefaultAlpha),
/* 43 */ Color.fromRGBA(227, 172, 18, kDefaultAlpha),
/* 44 */ Color.fromRGBA(119, 136, 153, kDefaultAlpha),
/* 45 */ Color.fromRGBA(0, 248, 83, kDefaultAlpha),
/* 46 */ Color.fromRGBA(101, 173, 235, kDefaultAlpha),
/* 47 */ Color.fromRGBA(255, 20, 147, kDefaultAlpha),
/* 48 */ Color.fromRGBA(244, 164, 96, kDefaultAlpha),
/* 49 */ Color.fromRGBA(238, 130, 238, kDefaultAlpha),
/* 50 */ Color.fromRGBA(220, 222, 61, kDefaultAlpha),
/* 51 */ Color.fromRGBA(165, 94, 44, kDefaultAlpha),
/* 52 */ Color.fromRGBA(130, 157, 199, kDefaultAlpha),
/* 53 */ Color.fromRGBA(4, 149, 205, kDefaultAlpha),
/* 54 */ Color.fromRGBA(20, 255, 127, kDefaultAlpha),
/* 55 */ Color.fromRGBA(203, 126, 211, kDefaultAlpha),
/* 56 */ Color.fromRGBA(201, 80, 84, kDefaultAlpha),
/* 57 */ Color.fromRGBA(250, 251, 113, kDefaultAlpha),
/* 58 */ Color.fromRGBA(36, 124, 27, kDefaultAlpha),
/* 59 */ Color.fromRGBA(241, 50, 50, kDefaultAlpha),
/* 60 */ Color.fromRGBA(250, 36, 204, kDefaultAlpha),
/* 61 */ Color.fromRGBA(179, 107, 114, kDefaultAlpha),
/* 62 */ Color.fromRGBA(67, 128, 216, kDefaultAlpha),
/* 63 */ Color.fromRGBA(255, 146, 73, kDefaultAlpha),
/* 64 */ Color.fromRGBA(255, 153, 194, kDefaultAlpha),
/* 65 */ Color.fromRGBA(255, 39, 39, kDefaultAlpha),
/* 66 */ Color.fromRGBA(137, 82, 235, kDefaultAlpha),
/* 67 */ Color.fromRGBA(70, 126, 64, kDefaultAlpha),
/* 68 */ Color.fromRGBA(175, 175, 175, kDefaultAlpha),
/* 69 */ Color.fromRGBA(255, 68, 169, kDefaultAlpha),
/* 70 */ Color.fromRGBA(193, 247, 236, kDefaultAlpha),
/* 71 */ Color.fromRGBA(78, 255, 0, kDefaultAlpha),
/* 72 */ Color.fromRGBA(0, 219, 255, kDefaultAlpha),
/* 73 */ Color.fromRGBA(219, 54, 250, kDefaultAlpha),
/* 74 */ Color.fromRGBA(218, 120, 37, kDefaultAlpha),
/* 75 */ Color.fromRGBA(214, 66, 96, kDefaultAlpha),
/* 76 */ Color.fromRGBA(56, 75, 202, kDefaultAlpha),
/* 77 */ Color.fromRGBA(210, 235, 27, kDefaultAlpha),
/* 78 */ Color.fromRGBA(172, 55, 110, kDefaultAlpha),
/* 79 */ Color.fromRGBA(184, 166, 107, kDefaultAlpha),
/* 80 */ Color.fromRGBA(47, 200, 39, kDefaultAlpha),
/* 81 */ Color.fromRGBA(15, 217, 250, kDefaultAlpha),
/* 82 */ Color.fromRGBA(220, 20, 60, kDefaultAlpha),
/* 83 */ Color.fromRGBA(227, 172, 18, kDefaultAlpha),
/* 84 */ Color.fromRGBA(119, 136, 153, kDefaultAlpha),
/* 85 */ Color.fromRGBA(0, 248, 83, kDefaultAlpha),
/* 86 */ Color.fromRGBA(101, 173, 235, kDefaultAlpha),
/* 87 */ Color.fromRGBA(255, 20, 147, kDefaultAlpha),
/* 88 */ Color.fromRGBA(244, 164, 96, kDefaultAlpha),
/* 89 */ Color.fromRGBA(238, 130, 238, kDefaultAlpha),
/* 90 */ Color.fromRGBA(220, 222, 61, kDefaultAlpha),
/* 91 */ Color.fromRGBA(165, 94, 44, kDefaultAlpha),
/* 92 */ Color.fromRGBA(130, 157, 199, kDefaultAlpha),
/* 93 */ Color.fromRGBA(4, 149, 205, kDefaultAlpha),
/* 94 */ Color.fromRGBA(20, 255, 127, kDefaultAlpha),
/* 95 */ Color.fromRGBA(203, 126, 211, kDefaultAlpha),
/* 96 */ Color.fromRGBA(201, 80, 84, kDefaultAlpha),
/* 97 */ Color.fromRGBA(250, 251, 113, kDefaultAlpha),
/* 98 */ Color.fromRGBA(36, 124, 27, kDefaultAlpha),
/* 99 */ Color.fromRGBA(241, 50, 50, kDefaultAlpha),
/* 100 */ Color.fromRGBA(250, 36, 204, kDefaultAlpha),
/* 101 */ Color.fromRGBA(179, 107, 114, kDefaultAlpha),
/* 102 */ Color.fromRGBA(67, 128, 216, kDefaultAlpha),
/* 103 */ Color.fromRGBA(255, 146, 73, kDefaultAlpha),
/* 104 */ Color.fromRGBA(255, 153, 194, kDefaultAlpha),
/* 105 */ Color.fromRGBA(255, 39, 39, kDefaultAlpha),
/* 106 */ Color.fromRGBA(137, 82, 235, kDefaultAlpha),
/* 107 */ Color.fromRGBA(70, 126, 64, kDefaultAlpha),
/* 108 */ Color.fromRGBA(175, 175, 175, kDefaultAlpha),
/* 109 */ Color.fromRGBA(255, 68, 169, kDefaultAlpha),
/* 110 */ Color.fromRGBA(193, 247, 236, kDefaultAlpha),
/* 111 */ Color.fromRGBA(78, 255, 0, kDefaultAlpha),
/* 112 */ Color.fromRGBA(0, 219, 255, kDefaultAlpha),
/* 113 */ Color.fromRGBA(219, 54, 250, kDefaultAlpha),
/* 114 */ Color.fromRGBA(218, 120, 37, kDefaultAlpha),
/* 115 */ Color.fromRGBA(214, 66, 96, kDefaultAlpha),
/* 116 */ Color.fromRGBA(56, 75, 202, kDefaultAlpha),
/* 117 */ Color.fromRGBA(210, 235, 27, kDefaultAlpha),
/* 118 */ Color.fromRGBA(172, 55, 110, kDefaultAlpha),
/* 119 */ Color.fromRGBA(184, 166, 107, kDefaultAlpha),
/* 120 */ Color.fromRGBA(47, 200, 39, kDefaultAlpha),
/* 121 */ Color.fromRGBA(15, 217, 250, kDefaultAlpha),
/* 122 */ Color.fromRGBA(220, 20, 60, kDefaultAlpha),
/* 123 */ Color.fromRGBA(227, 172, 18, kDefaultAlpha),
/* 124 */ Color.fromRGBA(119, 136, 153, kDefaultAlpha),
/* 125 */ Color.fromRGBA(0, 248, 83, kDefaultAlpha),
/* 126 */ Color.fromRGBA(101, 173, 235, kDefaultAlpha),
/* 127 */ Color.fromRGBA(255, 20, 147, kDefaultAlpha),
/* 128 */ Color.fromRGBA(244, 164, 96, kDefaultAlpha),
/* 129 */ Color.fromRGBA(238, 130, 238, kDefaultAlpha),
/* 130 */ Color.fromRGBA(220, 222, 61, kDefaultAlpha),
/* 131 */ Color.fromRGBA(165, 94, 44, kDefaultAlpha),
/* 132 */ Color.fromRGBA(130, 157, 199, kDefaultAlpha),
/* 133 */ Color.fromRGBA(4, 149, 205, kDefaultAlpha),
/* 134 */ Color.fromRGBA(20, 255, 127, kDefaultAlpha),
/* 135 */ Color.fromRGBA(203, 126, 211, kDefaultAlpha),
/* 136 */ Color.fromRGBA(201, 80, 84, kDefaultAlpha),
/* 137 */ Color.fromRGBA(250, 251, 113, kDefaultAlpha),
/* 138 */ Color.fromRGBA(36, 124, 27, kDefaultAlpha),
/* 139 */ Color.fromRGBA(241, 50, 50, kDefaultAlpha),
/* 140 */ Color.fromRGBA(250, 36, 204, kDefaultAlpha),
/* 141 */ Color.fromRGBA(179, 107, 114, kDefaultAlpha),
/* 142 */ Color.fromRGBA(67, 128, 216, kDefaultAlpha),
/* 143 */ Color.fromRGBA(255, 146, 73, kDefaultAlpha),
/* 144 */ Color.fromRGBA(255, 153, 194, kDefaultAlpha),
/* 145 */ Color.fromRGBA(255, 39, 39, kDefaultAlpha),
/* 146 */ Color.fromRGBA(137, 82, 235, kDefaultAlpha),
/* 147 */ Color.fromRGBA(70, 126, 64, kDefaultAlpha),
/* 148 */ Color.fromRGBA(175, 175, 175, kDefaultAlpha),
/* 149 */ Color.fromRGBA(255, 68, 169, kDefaultAlpha),
/* 150 */ Color.fromRGBA(193, 247, 236, kDefaultAlpha),
/* 151 */ Color.fromRGBA(78, 255, 0, kDefaultAlpha),
/* 152 */ Color.fromRGBA(0, 219, 255, kDefaultAlpha),
/* 153 */ Color.fromRGBA(219, 54, 250, kDefaultAlpha),
/* 154 */ Color.fromRGBA(218, 120, 37, kDefaultAlpha),
/* 155 */ Color.fromRGBA(214, 66, 96, kDefaultAlpha),
/* 156 */ Color.fromRGBA(56, 75, 202, kDefaultAlpha),
/* 157 */ Color.fromRGBA(210, 235, 27, kDefaultAlpha),
/* 158 */ Color.fromRGBA(172, 55, 110, kDefaultAlpha),
/* 159 */ Color.fromRGBA(184, 166, 107, kDefaultAlpha),
/* 160 */ Color.fromRGBA(47, 200, 39, kDefaultAlpha),
/* 161 */ Color.fromRGBA(15, 217, 250, kDefaultAlpha),
/* 162 */ Color.fromRGBA(220, 20, 60, kDefaultAlpha),
/* 163 */ Color.fromRGBA(227, 172, 18, kDefaultAlpha),
/* 164 */ Color.fromRGBA(119, 136, 153, kDefaultAlpha),
/* 165 */ Color.fromRGBA(0, 248, 83, kDefaultAlpha),
/* 166 */ Color.fromRGBA(101, 173, 235, kDefaultAlpha),
/* 167 */ Color.fromRGBA(255, 20, 147, kDefaultAlpha),
/* 168 */ Color.fromRGBA(244, 164, 96, kDefaultAlpha),
/* 169 */ Color.fromRGBA(238, 130, 238, kDefaultAlpha),
/* 170 */ Color.fromRGBA(220, 222, 61, kDefaultAlpha),
/* 171 */ Color.fromRGBA(165, 94, 44, kDefaultAlpha),
/* 172 */ Color.fromRGBA(130, 157, 199, kDefaultAlpha),
/* 173 */ Color.fromRGBA(4, 149, 205, kDefaultAlpha),
/* 174 */ Color.fromRGBA(20, 255, 127, kDefaultAlpha),
/* 175 */ Color.fromRGBA(203, 126, 211, kDefaultAlpha),
/* 176 */ Color.fromRGBA(201, 80, 84, kDefaultAlpha),
/* 177 */ Color.fromRGBA(250, 251, 113, kDefaultAlpha),
/* 178 */ Color.fromRGBA(36, 124, 27, kDefaultAlpha),
/* 179 */ Color.fromRGBA(241, 50, 50, kDefaultAlpha),
/* 180 */ Color.fromRGBA(250, 36, 204, kDefaultAlpha),
/* 181 */ Color.fromRGBA(179, 107, 114, kDefaultAlpha),
/* 182 */ Color.fromRGBA(67, 128, 216, kDefaultAlpha),
/* 183 */ Color.fromRGBA(255, 146, 73, kDefaultAlpha),
/* 184 */ Color.fromRGBA(255, 153, 194, kDefaultAlpha),
/* 185 */ Color.fromRGBA(255, 39, 39, kDefaultAlpha),
/* 186 */ Color.fromRGBA(137, 82, 235, kDefaultAlpha),
/* 187 */ Color.fromRGBA(70, 126, 64, kDefaultAlpha),
/* 188 */ Color.fromRGBA(175, 175, 175, kDefaultAlpha),
/* 189 */ Color.fromRGBA(255, 68, 169, kDefaultAlpha),
/* 190 */ Color.fromRGBA(193, 247, 236, kDefaultAlpha),
/* 191 */ Color.fromRGBA(78, 255, 0, kDefaultAlpha),
/* 192 */ Color.fromRGBA(0, 219, 255, kDefaultAlpha),
/* 193 */ Color.fromRGBA(219, 54, 250, kDefaultAlpha),
/* 194 */ Color.fromRGBA(218, 120, 37, kDefaultAlpha),
/* 195 */ Color.fromRGBA(214, 66, 96, kDefaultAlpha),
/* 196 */ Color.fromRGBA(56, 75, 202, kDefaultAlpha),
/* 197 */ Color.fromRGBA(210, 235, 27, kDefaultAlpha),
/* 198 */ Color.fromRGBA(172, 55, 110, kDefaultAlpha),
/* 199 */ Color.fromRGBA(184, 166, 107, kDefaultAlpha)
];

// Returns the default color for the given |player|.
export function getDefaultColorForPlayer(player) {
return kDefaultColors[player.id % kDefaultColors.length];
}

0 comments on commit 190dbe7

Please sign in to comment.