Files
OpenFrontIO/src/core/configuration/Config.ts
T
Mike Harris 2b44b68362 Feature - Improve Structure Color Contrast (#2454)
If this PR fixes an issue, link it below. If not, delete these two
lines.
Resolves #2447

## Description:

This PR updates the logic used to generate structure fill and border
colors. Currently, (v0.26.16 and earlier), some light territory colors
have structures that are difficult to see and identify. This PR ensures
that all territory colors have structures that are easily visible.

Instead of using `Colord.lighten()` and `Colord.darken()` to generate
structure colors, the logic now:
- queries the territory color and border color of the structure owner
- Converts these colors to the [LAB color
space](https://en.wikipedia.org/wiki/CIELAB_color_space) (which is a
human-perception-uniform color space).
- Darkens the border color (by decreasing LAB luminance) and sometimes
lightens the territory color (by increasing LAB luminance) until a
specific `Color Delta` is achieved (currently `delta > 0.5`)
- This ensures contrast between the structure and the territory
background.

Additionally, this PR re-organizes colors in the `Colors.ts` file for
better visibility and removes redundant colors from the `nationColors`
list.

This PR is an implementation of the proposed mock-up posted on imgur in
issue #2447. Screenshots of the original, final, and side-by-side
comparison of structure colors (for all available player colors) are in
the [imgur
album](https://imgur.com/a/openfront-color-playground-4cxSbbj).

I'd recommend inclusion as a feature/fix for v27.

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

GlacialDrift
2025-11-16 20:58:34 -08:00

214 lines
6.2 KiB
TypeScript

import { Colord } from "colord";
import { JWK } from "jose";
import {
Difficulty,
Game,
GameMapType,
GameMode,
Gold,
Player,
PlayerInfo,
Team,
TerraNullius,
Tick,
UnitInfo,
UnitType,
} from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
import { NukeType } from "../StatsSchemas";
export enum GameEnv {
Dev,
Preprod,
Prod,
}
export interface ServerConfig {
turnIntervalMs(): number;
gameCreationRate(): number;
lobbyMaxPlayers(
map: GameMapType,
mode: GameMode,
numPlayerTeams: TeamCountConfig | undefined,
): number;
numWorkers(): number;
workerIndex(gameID: GameID): number;
workerPath(gameID: GameID): string;
workerPort(gameID: GameID): number;
workerPortByIndex(workerID: number): number;
env(): GameEnv;
adminToken(): string;
adminHeader(): string;
// Only available on the server
gitCommit(): string;
r2Bucket(): string;
r2Endpoint(): string;
r2AccessKey(): string;
r2SecretKey(): string;
apiKey(): string;
otelEndpoint(): string;
otelAuthHeader(): string;
otelEnabled(): boolean;
jwtAudience(): string;
jwtIssuer(): string;
jwkPublicKey(): Promise<JWK>;
domain(): string;
subdomain(): string;
cloudflareAccountId(): string;
cloudflareApiToken(): string;
cloudflareConfigPath(): string;
cloudflareCredsPath(): string;
stripePublishableKey(): string;
allowedFlares(): string[] | undefined;
enableMatchmaking(): boolean;
}
export interface NukeMagnitude {
inner: number;
outer: number;
}
export interface Config {
samHittingChance(): number;
samWarheadHittingChance(): number;
spawnImmunityDuration(): Tick;
serverConfig(): ServerConfig;
gameConfig(): GameConfig;
theme(): Theme;
percentageTilesOwnedToWin(): number;
numBots(): number;
spawnNPCs(): boolean;
isUnitDisabled(unitType: UnitType): boolean;
bots(): number;
infiniteGold(): boolean;
donateGold(): boolean;
infiniteTroops(): boolean;
donateTroops(): boolean;
instantBuild(): boolean;
isRandomSpawn(): boolean;
numSpawnPhaseTurns(): number;
userSettings(): UserSettings;
playerTeams(): TeamCountConfig;
startManpower(playerInfo: PlayerInfo): number;
troopIncreaseRate(player: Player | PlayerView): number;
goldAdditionRate(player: Player | PlayerView): Gold;
attackTilesPerTick(
attckTroops: number,
attacker: Player,
defender: Player | TerraNullius,
numAdjacentTilesWithEnemy: number,
): number;
attackLogic(
gm: Game,
attackTroops: number,
attacker: Player,
defender: Player | TerraNullius,
tileToConquer: TileRef,
): {
attackerTroopLoss: number;
defenderTroopLoss: number;
tilesPerTickUsed: number;
};
attackAmount(attacker: Player, defender: Player | TerraNullius): number;
radiusPortSpawn(): number;
// When computing likelihood of trading for any given port, the X closest port
// are twice more likely to be selected. X is determined below.
proximityBonusPortsNb(totalPorts: number): number;
maxTroops(player: Player | PlayerView): number;
cityTroopIncrease(): number;
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number;
shellLifetime(): number;
boatMaxNumber(): number;
allianceDuration(): Tick;
allianceRequestDuration(): Tick;
allianceRequestCooldown(): Tick;
temporaryEmbargoDuration(): Tick;
targetDuration(): Tick;
targetCooldown(): Tick;
emojiMessageCooldown(): Tick;
emojiMessageDuration(): Tick;
donateCooldown(): Tick;
embargoAllCooldown(): Tick;
deletionMarkDuration(): Tick;
deleteUnitCooldown(): Tick;
defaultDonationAmount(sender: Player): number;
unitInfo(type: UnitType): UnitInfo;
tradeShipShortRangeDebuff(): number;
tradeShipGold(dist: number, numPorts: number): Gold;
tradeShipSpawnRate(
numTradeShips: number,
numPlayerPorts: number,
numPlayerTradeShips: number,
): number;
trainGold(rel: "self" | "team" | "ally" | "other"): Gold;
trainSpawnRate(numPlayerFactories: number): number;
trainStationMinRange(): number;
trainStationMaxRange(): number;
railroadMaxSize(): number;
safeFromPiratesCooldownMax(): number;
defensePostRange(): number;
SAMCooldown(): number;
SiloCooldown(): number;
defensePostDefenseBonus(): number;
defensePostSpeedBonus(): number;
falloutDefenseModifier(percentOfFallout: number): number;
difficultyModifier(difficulty: Difficulty): number;
warshipPatrolRange(): number;
warshipShellAttackRate(): number;
warshipTargettingRange(): number;
defensePostShellAttackRate(): number;
defensePostTargettingRange(): number;
// 0-1
traitorDefenseDebuff(): number;
traitorDuration(): number;
nukeMagnitudes(unitType: UnitType): NukeMagnitude;
// Number of tiles destroyed to break an alliance
nukeAllianceBreakThreshold(): number;
defaultNukeSpeed(): number;
defaultNukeTargetableRange(): number;
defaultSamMissileSpeed(): number;
defaultSamRange(): number;
samRange(level: number): number;
maxSamRange(): number;
nukeDeathFactor(
nukeType: NukeType,
humans: number,
tilesOwned: number,
maxTroops: number,
): number;
structureMinDist(): number;
isReplay(): boolean;
allianceExtensionPromptOffset(): number;
}
export interface Theme {
teamColor(team: Team): Colord;
// Don't call directly, use PlayerView
territoryColor(playerInfo: PlayerView): Colord;
// Don't call directly, use PlayerView
structureColors(territoryColor: Colord): { light: Colord; dark: Colord };
// Don't call directly, use PlayerView
borderColor(territoryColor: Colord): Colord;
// Don't call directly, use PlayerView
defendedBorderColors(territoryColor: Colord): { light: Colord; dark: Colord };
focusedBorderColor(): Colord;
terrainColor(gm: GameMap, tile: TileRef): Colord;
backgroundColor(): Colord;
falloutColor(): Colord;
font(): string;
textColor(playerInfo: PlayerView): string;
// unit color for alternate view
selfColor(): Colord;
allyColor(): Colord;
neutralColor(): Colord;
enemyColor(): Colord;
spawnHighlightColor(): Colord;
spawnHighlightSelfColor(): Colord;
spawnHighlightTeamColor(): Colord;
spawnHighlightEnemyColor(): Colord;
}