Feature/custom colors and leaderboard#3950
Conversation
|
Holden seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
WalkthroughThis PR adds a three-mode color customization system (random/custom/team) via UserSettings, creates a ColorInput UI component for mode cycling and custom color picking, integrates it into PlayPage, applies the selected colors to PlayerView territory and border rendering, and enhances Leaderboard to display player betrayal counts and alliance membership in a separate stats view. ChangesColor Customization and Leaderboard Stats
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (1)
src/core/game/UserSettings.ts (1)
256-258: 💤 Low valueToggle behavior may be confusing with three-way state.
When
colorMode()is"team", callingtoggleCustomColorsEnabled()will switch to"custom"(not back to"random"). This asymmetric toggle behavior might surprise callers expecting a simple on/off switch.Since the comment describes this as a "legacy helper", this may be acceptable for backward compatibility. Consider documenting the exact behavior or deprecating in favor of direct
setColorMode()calls.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/core/game/UserSettings.ts` around lines 256 - 258, The helper toggleCustomColorsEnabled currently flips colorMode() between "custom" and "random" which yields surprising results when colorMode() is "team"; update toggleCustomColorsEnabled (or mark it deprecated) so its behavior is explicit: either (A) implement a true toggle that switches back to the previous non-"custom" mode (store previousMode when entering "custom" and restore it when leaving), or (B) change it to a clear on/off API (e.g., setCustomColorsEnabled(boolean) that sets "custom" or a chosen fallback like "random"), and add a short doc/deprecation comment on toggleCustomColorsEnabled explaining the chosen behavior and recommending callers use setColorMode() directly; reference the methods toggleCustomColorsEnabled, colorMode, setColorMode when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/client/ColorInput.ts`:
- Around line 51-53: Replace hardcoded user-facing strings in ColorInput.ts with
calls to translateText(): locate the branch that returns
"Custom"/"Team"/"Random" (the getter or method referencing this.mode) and change
each literal to translateText('colorMode.custom') /
translateText('colorMode.team') / translateText('colorMode.random') (or similar
keys), then add corresponding entries in resources/lang/en.json; apply the same
treatment for the other occurrences noted (lines around 68-69, 78, 83, 87, 99)
so every visible label uses translateText() rather than raw strings.
- Around line 91-102: The transparent color <input id="color-input-picker"> is
currently stretched over the whole control (class "w-full h-1" + style
"height:100%") and blocks clicks to the mode-cycle button; restrict its hit area
to only the color swatch (make it a small, absolutely positioned element over
the swatch rather than full width, or adjust CSS to remove full-width/100%
height) and ensure it uses the existing onColorChange handler. Also refactor the
modeLabel() function to call translateText() for user-facing strings and add
corresponding keys (e.g. "colorMode.custom", "colorMode.team",
"colorMode.random", "colorMode.pickTooltip", "colorMode.label") to
resources/lang/en.json so "Custom", "Team", "Random", "Pick your territory
color", and "Color Mode..." all come from translations.
In `@src/client/components/baseComponents/setting/SettingColor.ts`:
- Line 6: The default visible label on the SettingColor component is hardcoded
as "Color"; change the `@property`() label initializer in SettingColor (the label
property) to call translateText(...) instead (e.g.,
translateText('setting.color')) and add the matching key/value ("setting.color":
"Color") to resources/lang/en.json so all user-visible text is localized; use
the translateText function name and ensure the key follows existing naming
conventions.
In `@src/client/graphics/layers/Leaderboard.ts`:
- Line 329: The hardcoded UI strings in Leaderboard.ts must be replaced with
localized keys: change the ternary that renders ${this._viewState === "default"
? "Show Alliances" : "Show Stats"} to use
translateText("leaderboard.showAlliances") and
translateText("leaderboard.showStats") respectively (ensure translateText is
imported/available in the Leaderboard class), and add matching entries
"leaderboard.showAlliances" and "leaderboard.showStats" to
resources/lang/en.json so the translations are present.
- Line 238: The string "Betrayals" in Leaderboard.ts is hardcoded and must be
localized; replace the literal with translateText("leaderboard.betrayals") where
it appears (search for the "Betrayals" token in the Leaderboard component/render
method) and add a matching "leaderboard.betrayals": "Betrayals" entry to
resources/lang/en.json so the translateText lookup resolves.
- Line 245: The hardcoded "Alliances" string in Leaderboard.ts must be replaced
with translateText("leaderboard.alliances") and the corresponding key added to
resources/lang/en.json; update any JSX or template code that currently renders
"Alliances" to call translateText and ensure translateText is imported in
Leaderboard.ts (add import if missing), then add "leaderboard.alliances":
"Alliances" to resources/lang/en.json.
In `@src/core/game/GameView.ts`:
- Around line 268-304: Add unit tests for GameView's color selection branches:
cover when GameView.game.myClientID() === data.clientID vs not to exercise
userSettings.colorMode() === "custom" with team() === null (verify
_territoryColor uses userSettings.customPrimaryColor() and _borderColor uses
customSecondaryColor()) and team() !== null (verify team games use
defaultTerritoryColor and border uses maybeFocusedBorderColor), colorMode ===
"team" (verify _territoryColor forced to defaultTerritoryColor), and "random"
mode (verify fallback chain: cosmetics.color?.color ->
pattern?.colorPalette?.primaryColor -> defaultTerritoryColor, and secondaryColor
fallback for _borderColor). Instantiate GameView with controlled mocks/stubs for
game, userSettings, theme, pattern, cosmetics and assert _territoryColor,
_structureColors (via theme.structureColors), and _borderColor for each case.
In `@src/core/game/UserSettings.ts`:
- Around line 240-275: Add unit tests for the new color-mode helpers: cover
colorMode() validation by asserting unknown stored values fall back to "random"
and setColorMode()/colorMode() round-trip; verify default customPrimaryColor()
and customSecondaryColor() return "`#2196f3`" and "`#1565c0`" when nothing is
stored; test toggleCustomColorsEnabled() and customColorsEnabled() produce the
correct transitions between "custom" and "random"; and assert persistence by
simulating/localStorage (or the storage layer used by UserSettings) to ensure
values written by setColorMode, setCustomPrimaryColor, and
setCustomSecondaryColor are actually persisted and reloaded. Target tests at the
UserSettings class and the methods colorMode, setColorMode, customColorsEnabled,
toggleCustomColorsEnabled, customPrimaryColor, setCustomPrimaryColor,
customSecondaryColor, and setCustomSecondaryColor.
---
Nitpick comments:
In `@src/core/game/UserSettings.ts`:
- Around line 256-258: The helper toggleCustomColorsEnabled currently flips
colorMode() between "custom" and "random" which yields surprising results when
colorMode() is "team"; update toggleCustomColorsEnabled (or mark it deprecated)
so its behavior is explicit: either (A) implement a true toggle that switches
back to the previous non-"custom" mode (store previousMode when entering
"custom" and restore it when leaving), or (B) change it to a clear on/off API
(e.g., setCustomColorsEnabled(boolean) that sets "custom" or a chosen fallback
like "random"), and add a short doc/deprecation comment on
toggleCustomColorsEnabled explaining the chosen behavior and recommending
callers use setColorMode() directly; reference the methods
toggleCustomColorsEnabled, colorMode, setColorMode when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 36d07086-38ee-4b0a-9b58-f7875824a39e
📒 Files selected for processing (6)
src/client/ColorInput.tssrc/client/components/PlayPage.tssrc/client/components/baseComponents/setting/SettingColor.tssrc/client/graphics/layers/Leaderboard.tssrc/core/game/GameView.tssrc/core/game/UserSettings.ts
| if (this.mode === "custom") return "Custom"; | ||
| if (this.mode === "team") return "Team"; | ||
| return "Random"; |
There was a problem hiding this comment.
Localize new UI strings via translateText().
These user-facing labels/titles are hardcoded and bypass localization.
As per coding guidelines: src/client/**/*.{ts,tsx}: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.
Also applies to: 68-69, 78-78, 83-83, 87-87, 99-99
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/ColorInput.ts` around lines 51 - 53, Replace hardcoded user-facing
strings in ColorInput.ts with calls to translateText(): locate the branch that
returns "Custom"/"Team"/"Random" (the getter or method referencing this.mode)
and change each literal to translateText('colorMode.custom') /
translateText('colorMode.team') / translateText('colorMode.random') (or similar
keys), then add corresponding entries in resources/lang/en.json; apply the same
treatment for the other occurrences noted (lines around 68-69, 78, 83, 87, 99)
so every visible label uses translateText() rather than raw strings.
| <!-- Hidden native color picker — only shown in custom mode --> | ||
| ${this.mode === "custom" | ||
| ? html` | ||
| <input | ||
| type="color" | ||
| id="color-input-picker" | ||
| .value=${this.primaryColor} | ||
| @input=${this.onColorChange} | ||
| title="Pick your territory color" | ||
| class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer" | ||
| style="height:100%;top:0;" | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's read the ColorInput.ts file to see the full context
head -n 120 src/client/ColorInput.ts | tail -n +70Repository: openfrontio/OpenFrontIO
Length of output: 1459
🏁 Script executed:
# Check if the file uses translateText() function
rg "translateText" src/client/ColorInput.ts -A 2 -B 2Repository: openfrontio/OpenFrontIO
Length of output: 49
🏁 Script executed:
# Look for all user-visible strings in the file
rg -A 3 -B 3 'title=|placeholder=|textContent' src/client/ColorInput.tsRepository: openfrontio/OpenFrontIO
Length of output: 938
🏁 Script executed:
# Check the resources/lang/en.json for existing translations
head -n 50 resources/lang/en.jsonRepository: openfrontio/OpenFrontIO
Length of output: 1452
🏁 Script executed:
# Look for how mode cycling button is implemented and attached
rg -A 10 "onModeChange|modeButton|mode-cycle" src/client/ColorInput.tsRepository: openfrontio/OpenFrontIO
Length of output: 49
🏁 Script executed:
# Check the modeLabel() method to see if it returns localized text
rg -A 5 "modeLabel" src/client/ColorInput.tsRepository: openfrontio/OpenFrontIO
Length of output: 715
🏁 Script executed:
# Check how the file is structured - parent container for button and input
rg -B 15 "color-input-picker" src/client/ColorInput.ts | head -n 30Repository: openfrontio/OpenFrontIO
Length of output: 694
🏁 Script executed:
# Verify the exact parent container structure
ast-grep --pattern 'render() {
$$$
<button
id="color-input-btn"
$$$
>
$$$
</button>
$$$
}'Repository: openfrontio/OpenFrontIO
Length of output: 49
Color picker overlay blocks mode-cycle button and text lacks translations.
In custom mode, the transparent color input (w-full h-1 + height:100%) covers the button underneath, preventing clicks to cycle modes. Additionally, user-visible text ("Custom", "Team", "Random", "Pick your territory color", "Color Mode...") must use translateText() with entries in resources/lang/en.json per guidelines.
Suggested fix for overlay
- class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer"
- style="height:100%;top:0;"
+ class="absolute inset-x-0 top-1 mx-auto w-7 h-7 opacity-0 cursor-pointer"Also refactor modeLabel() to use translateText() and add translation keys to language file.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <!-- Hidden native color picker — only shown in custom mode --> | |
| ${this.mode === "custom" | |
| ? html` | |
| <input | |
| type="color" | |
| id="color-input-picker" | |
| .value=${this.primaryColor} | |
| @input=${this.onColorChange} | |
| title="Pick your territory color" | |
| class="absolute bottom-0 left-0 w-full h-1 opacity-0 cursor-pointer" | |
| style="height:100%;top:0;" | |
| /> | |
| ${this.mode === "custom" | |
| ? html` | |
| <input | |
| type="color" | |
| id="color-input-picker" | |
| .value=${this.primaryColor} | |
| `@input`=${this.onColorChange} | |
| title="Pick your territory color" | |
| class="absolute inset-x-0 top-1 mx-auto w-7 h-7 opacity-0 cursor-pointer" | |
| /> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/ColorInput.ts` around lines 91 - 102, The transparent color <input
id="color-input-picker"> is currently stretched over the whole control (class
"w-full h-1" + style "height:100%") and blocks clicks to the mode-cycle button;
restrict its hit area to only the color swatch (make it a small, absolutely
positioned element over the swatch rather than full width, or adjust CSS to
remove full-width/100% height) and ensure it uses the existing onColorChange
handler. Also refactor the modeLabel() function to call translateText() for
user-facing strings and add corresponding keys (e.g. "colorMode.custom",
"colorMode.team", "colorMode.random", "colorMode.pickTooltip",
"colorMode.label") to resources/lang/en.json so "Custom", "Team", "Random",
"Pick your territory color", and "Color Mode..." all come from translations.
|
|
||
| @customElement("setting-color") | ||
| export class SettingColor extends LitElement { | ||
| @property() label = "Color"; |
There was a problem hiding this comment.
Default label should be localized.
The default visible label "Color" is hardcoded.
As per coding guidelines: src/client/**/*.{ts,tsx}: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/components/baseComponents/setting/SettingColor.ts` at line 6, The
default visible label on the SettingColor component is hardcoded as "Color";
change the `@property`() label initializer in SettingColor (the label property) to
call translateText(...) instead (e.g., translateText('setting.color')) and add
the matching key/value ("setting.color": "Color") to resources/lang/en.json so
all user-visible text is localized; use the translateText function name and
ensure the key follows existing naming conventions.
| class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate" | ||
| @click=${() => this.setSort("betrayals")} | ||
| > | ||
| Betrayals |
There was a problem hiding this comment.
Hardcoded English text violates localization guidelines.
Replace with translateText("leaderboard.betrayals") and add the corresponding entry to resources/lang/en.json.
As per coding guidelines: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/graphics/layers/Leaderboard.ts` at line 238, The string
"Betrayals" in Leaderboard.ts is hardcoded and must be localized; replace the
literal with translateText("leaderboard.betrayals") where it appears (search for
the "Betrayals" token in the Leaderboard component/render method) and add a
matching "leaderboard.betrayals": "Betrayals" entry to resources/lang/en.json so
the translateText lookup resolves.
| class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap truncate" | ||
| @click=${() => this.setSort("alliances")} | ||
| > | ||
| Alliances |
There was a problem hiding this comment.
Hardcoded English text violates localization guidelines.
Replace with translateText("leaderboard.alliances") and add the corresponding entry to resources/lang/en.json.
As per coding guidelines: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/graphics/layers/Leaderboard.ts` at line 245, The hardcoded
"Alliances" string in Leaderboard.ts must be replaced with
translateText("leaderboard.alliances") and the corresponding key added to
resources/lang/en.json; update any JSX or template code that currently renders
"Alliances" to call translateText and ensure translateText is imported in
Leaderboard.ts (add import if missing), then add "leaderboard.alliances":
"Alliances" to resources/lang/en.json.
| this.updateLeaderboard(); | ||
| }} | ||
| > | ||
| ${this._viewState === "default" ? "Show Alliances" : "Show Stats"} |
There was a problem hiding this comment.
Hardcoded English text violates localization guidelines.
Replace with translateText("leaderboard.showAlliances") and translateText("leaderboard.showStats"), and add the corresponding entries to resources/lang/en.json.
As per coding guidelines: All user-visible text must go through translateText() function with corresponding entries in resources/lang/en.json.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/client/graphics/layers/Leaderboard.ts` at line 329, The hardcoded UI
strings in Leaderboard.ts must be replaced with localized keys: change the
ternary that renders ${this._viewState === "default" ? "Show Alliances" : "Show
Stats"} to use translateText("leaderboard.showAlliances") and
translateText("leaderboard.showStats") respectively (ensure translateText is
imported/available in the Leaderboard class), and add matching entries
"leaderboard.showAlliances" and "leaderboard.showStats" to
resources/lang/en.json so the translations are present.
| const isMyPlayer = this.game.myClientID() === this.data.clientID; | ||
| const colorMode = isMyPlayer ? userSettings.colorMode() : "random"; | ||
|
|
||
| if (colorMode === "custom" && this.team() === null) { | ||
| // Custom color only in solo/FFA — team games always use assigned team color | ||
| this._territoryColor = colord(userSettings.customPrimaryColor()); | ||
| } else if (colorMode === "team" || this.team() !== null) { | ||
| // Force team color (default game behavior) | ||
| this._territoryColor = defaultTerritoryColor; | ||
| } else { | ||
| // "random" — normal game logic | ||
| if (this.team() === null) { | ||
| this._territoryColor = colord( | ||
| this.cosmetics.color?.color ?? | ||
| pattern?.colorPalette?.primaryColor ?? | ||
| defaultTerritoryColor.toHex(), | ||
| ); | ||
| } else { | ||
| this._territoryColor = defaultTerritoryColor; | ||
| } | ||
| } | ||
|
|
||
| this._structureColors = theme.structureColors(this._territoryColor); | ||
|
|
||
| const maybeFocusedBorderColor = | ||
| this.game.myClientID() === this.data.clientID | ||
| const maybeFocusedBorderColor = isMyPlayer | ||
| ? theme.focusedBorderColor() | ||
| : defaultBorderColor; | ||
|
|
||
| this._borderColor = new Colord( | ||
| pattern?.colorPalette?.secondaryColor ?? | ||
| this.cosmetics.color?.color ?? | ||
| maybeFocusedBorderColor.toHex(), | ||
| ); | ||
| if (colorMode === "custom" && this.team() === null) { | ||
| this._borderColor = new Colord(userSettings.customSecondaryColor()); | ||
| } else { | ||
| this._borderColor = new Colord( | ||
| pattern?.colorPalette?.secondaryColor ?? | ||
| this.cosmetics.color?.color ?? | ||
| maybeFocusedBorderColor.toHex(), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Missing tests for color mode rendering logic.
The color selection logic has several branches that need test coverage:
- Custom mode behavior when
team() === nullvsteam() !== null - Team mode forcing default color
- Random mode fallback chain
- Border color selection for each mode
As per coding guidelines: All changes to src/core/ must include tests.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/core/game/GameView.ts` around lines 268 - 304, Add unit tests for
GameView's color selection branches: cover when GameView.game.myClientID() ===
data.clientID vs not to exercise userSettings.colorMode() === "custom" with
team() === null (verify _territoryColor uses userSettings.customPrimaryColor()
and _borderColor uses customSecondaryColor()) and team() !== null (verify team
games use defaultTerritoryColor and border uses maybeFocusedBorderColor),
colorMode === "team" (verify _territoryColor forced to defaultTerritoryColor),
and "random" mode (verify fallback chain: cosmetics.color?.color ->
pattern?.colorPalette?.primaryColor -> defaultTerritoryColor, and secondaryColor
fallback for _borderColor). Instantiate GameView with controlled mocks/stubs for
game, userSettings, theme, pattern, cosmetics and assert _territoryColor,
_structureColors (via theme.structureColors), and _borderColor for each case.
| // Color mode: "random" = game default, "custom" = user picks, "team" = always use team color | ||
| colorMode(): "random" | "custom" | "team" { | ||
| const val = this.getString("settings.colorMode", "random"); | ||
| if (val === "custom" || val === "team") return val; | ||
| return "random"; | ||
| } | ||
|
|
||
| setColorMode(mode: "random" | "custom" | "team"): void { | ||
| this.setString("settings.colorMode", mode); | ||
| } | ||
|
|
||
| // Keep legacy helpers so GameView still compiles | ||
| customColorsEnabled(): boolean { | ||
| return this.colorMode() === "custom"; | ||
| } | ||
|
|
||
| toggleCustomColorsEnabled() { | ||
| this.setColorMode(this.colorMode() === "custom" ? "random" : "custom"); | ||
| } | ||
|
|
||
| customPrimaryColor(): string { | ||
| return this.getString("settings.customPrimaryColor", "#2196f3"); | ||
| } | ||
|
|
||
| setCustomPrimaryColor(color: string): void { | ||
| this.setString("settings.customPrimaryColor", color); | ||
| } | ||
|
|
||
| customSecondaryColor(): string { | ||
| return this.getString("settings.customSecondaryColor", "#1565c0"); | ||
| } | ||
|
|
||
| setCustomSecondaryColor(color: string): void { | ||
| this.setString("settings.customSecondaryColor", color); | ||
| } | ||
|
|
There was a problem hiding this comment.
Missing tests for core game logic changes.
All changes to src/core/ must include tests. These new color mode methods need test coverage to verify:
- Color mode validation logic (line 243-244)
- Default color values (lines 261, 269)
- Toggle behavior (lines 256-258)
- Persistence via localStorage
As per coding guidelines: All changes to src/core/ must include tests.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/core/game/UserSettings.ts` around lines 240 - 275, Add unit tests for the
new color-mode helpers: cover colorMode() validation by asserting unknown stored
values fall back to "random" and setColorMode()/colorMode() round-trip; verify
default customPrimaryColor() and customSecondaryColor() return "`#2196f3`" and
"`#1565c0`" when nothing is stored; test toggleCustomColorsEnabled() and
customColorsEnabled() produce the correct transitions between "custom" and
"random"; and assert persistence by simulating/localStorage (or the storage
layer used by UserSettings) to ensure values written by setColorMode,
setCustomPrimaryColor, and setCustomSecondaryColor are actually persisted and
reloaded. Target tests at the UserSettings class and the methods colorMode,
setColorMode, customColorsEnabled, toggleCustomColorsEnabled,
customPrimaryColor, setCustomPrimaryColor, customSecondaryColor, and
setCustomSecondaryColor.
Description:
Adds two new features to improve the gameplay experience:
1. Custom Territory Colors
2. Alliance Stats on Leaderboard
Please complete the following:
Discord: 100_snankes_93529