mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-30 06:19:41 +00:00
Add support for colored patterns (#2062)
## Description: Add support for colored territory patterns/skins * Refactored & updated territory pattern rendering to render colored skins * rename public from pattern to skin (keep pattern name internally, too difficult to rename) * Moved all territory color logic to PlayerView * Updated WinModal to show colored skins * Refactored decode logic into a separate function: decodePatternData * Refactored/updated how cosmetics are sent to server. Players now send a PlayerCosmeticRefsSchema in the ClientJoinMessage. PlayerCosmeticRefsSchema just contains names of the cosmetics, and the server replaces the names/references with actual cosmetic data * Refactored PastelThemeDark: have it extend Pastel theme so duplicate logic can be removed. * ## 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: evan
This commit is contained in:
+69
-13
@@ -1,7 +1,9 @@
|
||||
import { Colord, colord } from "colord";
|
||||
import { base64url } from "jose";
|
||||
import { Config } from "../configuration/Config";
|
||||
import { ColorPalette } from "../CosmeticSchemas";
|
||||
import { PatternDecoder } from "../PatternDecoder";
|
||||
import { ClientID, GameID, Player } from "../Schemas";
|
||||
import { ClientID, GameID, Player, PlayerCosmetics } from "../Schemas";
|
||||
import { createRandomName } from "../Util";
|
||||
import { WorkerClient } from "../worker/WorkerClient";
|
||||
import {
|
||||
@@ -39,11 +41,6 @@ import { UserSettings } from "./UserSettings";
|
||||
|
||||
const userSettings: UserSettings = new UserSettings();
|
||||
|
||||
interface PlayerCosmetics {
|
||||
pattern?: string | undefined;
|
||||
flag?: string | undefined;
|
||||
}
|
||||
|
||||
export class UnitView {
|
||||
public _wasUpdated = true;
|
||||
public lastPos: TileRef[] = [];
|
||||
@@ -181,6 +178,10 @@ export class PlayerView {
|
||||
public anonymousName: string | null = null;
|
||||
private decoder?: PatternDecoder;
|
||||
|
||||
private _territoryColor: Colord;
|
||||
private _borderColor: Colord;
|
||||
private _defendedBorderColors: { light: Colord; dark: Colord };
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
public data: PlayerUpdate,
|
||||
@@ -195,14 +196,72 @@ export class PlayerView {
|
||||
this.data.playerType,
|
||||
);
|
||||
}
|
||||
|
||||
const pattern = this.cosmetics.pattern;
|
||||
if (pattern) {
|
||||
const territoryColor = this.game.config().theme().territoryColor(this);
|
||||
pattern.colorPalette ??= {
|
||||
name: "",
|
||||
primaryColor: territoryColor.toHex(),
|
||||
secondaryColor: territoryColor.darken(0.125).toHex(),
|
||||
} satisfies ColorPalette;
|
||||
}
|
||||
|
||||
if (
|
||||
this.team() === null &&
|
||||
this.cosmetics.pattern?.colorPalette?.primaryColor !== undefined
|
||||
) {
|
||||
this._territoryColor = colord(
|
||||
this.cosmetics.pattern.colorPalette.primaryColor,
|
||||
);
|
||||
} else {
|
||||
this._territoryColor = this.game.config().theme().territoryColor(this);
|
||||
}
|
||||
|
||||
if (this.cosmetics.pattern?.colorPalette?.secondaryColor !== undefined) {
|
||||
this._borderColor = colord(
|
||||
this.cosmetics.pattern.colorPalette.secondaryColor,
|
||||
);
|
||||
} else if (this.game.myClientID() === this.data.clientID) {
|
||||
this._borderColor = this.game.config().theme().focusedBorderColor();
|
||||
} else {
|
||||
this._borderColor = this.game.config().theme().borderColor(this);
|
||||
}
|
||||
|
||||
this._defendedBorderColors = this.game
|
||||
.config()
|
||||
.theme()
|
||||
.defendedBorderColors(this._borderColor);
|
||||
|
||||
this.decoder =
|
||||
this.cosmetics.pattern === undefined
|
||||
? undefined
|
||||
: new PatternDecoder(this.cosmetics.pattern, base64url.decode);
|
||||
}
|
||||
|
||||
patternDecoder(): PatternDecoder | undefined {
|
||||
return this.decoder;
|
||||
territoryColor(tile?: TileRef): Colord {
|
||||
if (tile === undefined || this.decoder === undefined) {
|
||||
return this._territoryColor;
|
||||
}
|
||||
const isPrimary = this.decoder.isPrimary(
|
||||
this.game.x(tile),
|
||||
this.game.y(tile),
|
||||
);
|
||||
return isPrimary ? this._territoryColor : this._borderColor;
|
||||
}
|
||||
|
||||
borderColor(tile?: TileRef, isDefended: boolean = false): Colord {
|
||||
if (tile === undefined || !isDefended) {
|
||||
return this._borderColor;
|
||||
}
|
||||
|
||||
const x = this.game.x(tile);
|
||||
const y = this.game.y(tile);
|
||||
const lightTile =
|
||||
(x % 2 === 0 && y % 2 === 0) || (y % 2 === 1 && x % 2 === 1);
|
||||
return lightTile
|
||||
? this._defendedBorderColors.light
|
||||
: this._defendedBorderColors.dark;
|
||||
}
|
||||
|
||||
async actions(tile: TileRef): Promise<PlayerActions> {
|
||||
@@ -387,16 +446,13 @@ export class GameView implements GameMap {
|
||||
this.lastUpdate = null;
|
||||
this.unitGrid = new UnitGrid(this._map);
|
||||
this._cosmetics = new Map(
|
||||
this.humans.map((h) => [
|
||||
h.clientID,
|
||||
{ flag: h.flag, pattern: h.pattern } satisfies PlayerCosmetics,
|
||||
]),
|
||||
this.humans.map((h) => [h.clientID, h.cosmetics ?? {}]),
|
||||
);
|
||||
for (const nation of this._mapData.manifest.nations) {
|
||||
// Nations don't have client ids, so we use their name as the key instead.
|
||||
this._cosmetics.set(nation.name, {
|
||||
flag: nation.flag,
|
||||
});
|
||||
} satisfies PlayerCosmetics);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user