Skip to content

Commit e90ad62

Browse files
committed
allow select colors
1 parent 2521466 commit e90ad62

File tree

14 files changed

+220
-336
lines changed

14 files changed

+220
-336
lines changed

resources/lang/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,8 @@
656656
"choose_spawn": "Choose a starting location"
657657
},
658658
"territory_patterns": {
659-
"title": "Select Territory Skin",
659+
"title": "Skins",
660+
"colors": "Colors",
660661
"purchase": "Purchase",
661662
"blocked": {
662663
"login": "You must be logged in to access this pattern.",

src/client/ClientGameRunner.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
GameID,
66
GameRecord,
77
GameStartInfo,
8-
PlayerPattern,
8+
PlayerCosmeticRefs,
99
PlayerRecord,
1010
ServerMessage,
1111
} from "../core/Schemas";
@@ -51,8 +51,7 @@ import SoundManager from "./sound/SoundManager";
5151

5252
export interface LobbyConfig {
5353
serverConfig: ServerConfig;
54-
pattern: PlayerPattern | undefined;
55-
flag: string;
54+
cosmetics: PlayerCosmeticRefs;
5655
playerName: string;
5756
clientID: ClientID;
5857
gameID: GameID;

src/client/Main.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -509,18 +509,24 @@ class Client {
509509
}
510510
const config = await getServerConfigFromClient();
511511

512+
const pattern = this.userSettings.getSelectedPatternName(
513+
await fetchCosmetics(),
514+
);
515+
512516
this.gameStop = joinLobby(
513517
this.eventBus,
514518
{
515519
gameID: lobby.gameID,
516520
serverConfig: config,
517-
pattern:
518-
this.userSettings.getSelectedPatternName(await fetchCosmetics()) ??
519-
undefined,
520-
flag:
521-
this.flagInput === null || this.flagInput.getCurrentFlag() === "xx"
522-
? ""
523-
: this.flagInput.getCurrentFlag(),
521+
cosmetics: {
522+
color: this.userSettings.getSelectedColor() ?? undefined,
523+
patternName: pattern?.name ?? undefined,
524+
patternColorPaletteName: pattern?.colorPalette?.name ?? undefined,
525+
flag:
526+
this.flagInput === null || this.flagInput.getCurrentFlag() === "xx"
527+
? ""
528+
: this.flagInput.getCurrentFlag(),
529+
},
524530
playerName: this.usernameInput?.getCurrentUsername() ?? "",
525531
token: getPlayToken(),
526532
clientID: lobby.clientID,

src/client/SinglePlayerModal.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,8 @@ export class SinglePlayerModal extends LitElement {
449449
? (this.userSettings.getDevOnlyPattern() ?? null)
450450
: null;
451451

452+
const selectedColor = this.userSettings.getSelectedColor();
453+
452454
this.dispatchEvent(
453455
new CustomEvent("join-lobby", {
454456
detail: {
@@ -466,6 +468,7 @@ export class SinglePlayerModal extends LitElement {
466468
? ""
467469
: flagInput.getCurrentFlag(),
468470
pattern: selectedPattern ?? undefined,
471+
color: selectedColor ? { color: selectedColor } : undefined,
469472
},
470473
},
471474
],

src/client/TerritoryPatternsModal.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ export class TerritoryPatternsModal extends LitElement {
2525
public previewButton: HTMLElement | null = null;
2626

2727
@state() private selectedPattern: PlayerPattern | null;
28+
@state() private selectedColor: string | null = null;
29+
30+
@state() private activeTab: "patterns" | "colors" = "patterns";
2831

2932
private cosmetics: Cosmetics | null = null;
3033

@@ -44,20 +47,47 @@ export class TerritoryPatternsModal extends LitElement {
4447
if (userMeResponse === null) {
4548
this.userSettings.setSelectedPatternName(undefined);
4649
this.selectedPattern = null;
50+
this.selectedColor = null;
4751
}
4852
this.userMeResponse = userMeResponse;
4953
this.cosmetics = await fetchCosmetics();
5054
this.selectedPattern =
5155
this.cosmetics !== null
5256
? this.userSettings.getSelectedPatternName(this.cosmetics)
5357
: null;
58+
this.selectedColor = this.userSettings.getSelectedColor() ?? null;
5459
this.refresh();
5560
}
5661

5762
createRenderRoot() {
5863
return this;
5964
}
6065

66+
private renderTabNavigation(): TemplateResult {
67+
return html`
68+
<div class="flex border-b border-gray-600 mb-4 justify-center">
69+
<button
70+
class="px-4 py-2 text-sm font-medium transition-colors duration-200 ${this
71+
.activeTab === "patterns"
72+
? "text-blue-400 border-b-2 border-blue-400 bg-blue-400/10"
73+
: "text-gray-400 hover:text-white"}"
74+
@click=${() => (this.activeTab = "patterns")}
75+
>
76+
${translateText("territory_patterns.title")}
77+
</button>
78+
<button
79+
class="px-4 py-2 text-sm font-medium transition-colors duration-200 ${this
80+
.activeTab === "colors"
81+
? "text-blue-400 border-b-2 border-blue-400 bg-blue-400/10"
82+
: "text-gray-400 hover:text-white"}"
83+
@click=${() => (this.activeTab = "colors")}
84+
>
85+
${translateText("territory_patterns.colors")}
86+
</button>
87+
</div>
88+
`;
89+
}
90+
6191
private renderPatternGrid(): TemplateResult {
6292
const buttons: TemplateResult[] = [];
6393
for (const pattern of Object.values(this.cosmetics?.patterns ?? {})) {
@@ -105,14 +135,39 @@ export class TerritoryPatternsModal extends LitElement {
105135
`;
106136
}
107137

138+
private renderColorSwatchGrid(): TemplateResult {
139+
const hexCodes = (this.userMeResponse?.player.flares ?? [])
140+
.filter((flare) => flare.startsWith("color:"))
141+
.map((flare) => "#" + flare.split(":")[1]);
142+
return html`
143+
<div class="flex flex-wrap gap-3 p-2 justify-center items-center">
144+
${hexCodes.map(
145+
(hexCode) => html`
146+
<div
147+
class="w-12 h-12 rounded-lg border-2 border-white/30 cursor-pointer transition-all duration-200 hover:scale-110 hover:shadow-lg"
148+
style="background-color: ${hexCode};"
149+
title="${hexCode}"
150+
@click=${() => this.selectColor(hexCode)}
151+
></div>
152+
`,
153+
)}
154+
</div>
155+
`;
156+
}
157+
108158
render() {
109159
if (!this.isActive) return html``;
110160
return html`
111161
<o-modal
112162
id="territoryPatternsModal"
113-
title="${translateText("territory_patterns.title")}"
163+
title="${this.activeTab === "patterns"
164+
? translateText("territory_patterns.title")
165+
: translateText("territory_patterns.colors")}"
114166
>
115-
${this.renderPatternGrid()}
167+
${this.renderTabNavigation()}
168+
${this.activeTab === "patterns"
169+
? this.renderPatternGrid()
170+
: this.renderColorSwatchGrid()}
116171
</o-modal>
117172
`;
118173
}
@@ -130,6 +185,8 @@ export class TerritoryPatternsModal extends LitElement {
130185
}
131186

132187
private selectPattern(pattern: PlayerPattern | null) {
188+
this.selectedColor = null;
189+
this.userSettings.setSelectedColor(undefined);
133190
if (pattern === null) {
134191
this.userSettings.setSelectedPatternName(undefined);
135192
} else {
@@ -145,8 +202,32 @@ export class TerritoryPatternsModal extends LitElement {
145202
this.close();
146203
}
147204

205+
private selectColor(hexCode: string) {
206+
this.selectedPattern = null;
207+
this.userSettings.setSelectedPatternName(undefined);
208+
this.selectedColor = hexCode;
209+
this.userSettings.setSelectedColor(hexCode);
210+
this.refresh();
211+
this.close();
212+
}
213+
214+
private renderColorPreview(
215+
hexCode: string,
216+
width: number,
217+
height: number,
218+
): TemplateResult {
219+
return html`
220+
<div
221+
class="rounded"
222+
style="width: ${width}px; height: ${height}px; background-color: ${hexCode};"
223+
></div>
224+
`;
225+
}
226+
148227
public async refresh() {
149-
const preview = renderPatternPreview(this.selectedPattern ?? null, 48, 48);
228+
const preview = this.selectedColor
229+
? this.renderColorPreview(this.selectedColor, 48, 48)
230+
: renderPatternPreview(this.selectedPattern ?? null, 48, 48);
150231
this.requestUpdate();
151232

152233
// Wait for the DOM to be updated and the o-modal element to be available

src/client/Transport.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -377,11 +377,7 @@ export class Transport {
377377
lastTurn: numTurns,
378378
token: this.lobbyConfig.token,
379379
username: this.lobbyConfig.playerName,
380-
cosmetics: {
381-
flag: this.lobbyConfig.flag,
382-
patternName: this.lobbyConfig.pattern?.name,
383-
patternColorPaletteName: this.lobbyConfig.pattern?.colorPalette?.name,
384-
},
380+
cosmetics: this.lobbyConfig.cosmetics,
385381
} satisfies ClientJoinMessage);
386382
}
387383

src/core/Schemas.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export type Player = z.infer<typeof PlayerSchema>;
115115
export type PlayerCosmetics = z.infer<typeof PlayerCosmeticsSchema>;
116116
export type PlayerCosmeticRefs = z.infer<typeof PlayerCosmeticRefsSchema>;
117117
export type PlayerPattern = z.infer<typeof PlayerPatternSchema>;
118+
export type PlayerColor = z.infer<typeof PlayerColorSchema>;
118119
export type Flag = z.infer<typeof FlagSchema>;
119120
export type GameStartInfo = z.infer<typeof GameStartInfoSchema>;
120121

@@ -386,6 +387,7 @@ export const FlagSchema = z
386387

387388
export const PlayerCosmeticRefsSchema = z.object({
388389
flag: FlagSchema.optional(),
390+
color: z.string().optional(),
389391
patternName: PatternNameSchema.optional(),
390392
patternColorPaletteName: z.string().optional(),
391393
});
@@ -395,10 +397,17 @@ export const PlayerPatternSchema = z.object({
395397
patternData: PatternDataSchema,
396398
colorPalette: ColorPaletteSchema.optional(),
397399
});
400+
401+
export const PlayerColorSchema = z.object({
402+
color: z.string(),
403+
});
404+
398405
export const PlayerCosmeticsSchema = z.object({
399406
flag: FlagSchema.optional(),
400407
pattern: PlayerPatternSchema.optional(),
408+
color: PlayerColorSchema.optional(),
401409
});
410+
402411
export const PlayerSchema = z.object({
403412
clientID: ID,
404413
username: UsernameSchema,

src/core/configuration/Config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export interface Theme {
183183
// Don't call directly, use PlayerView
184184
territoryColor(playerInfo: PlayerView): Colord;
185185
// Don't call directly, use PlayerView
186-
borderColor(playerInfo: PlayerView): Colord;
186+
borderColor(territoryColor: Colord): Colord;
187187
// Don't call directly, use PlayerView
188188
defendedBorderColors(territoryColor: Colord): { light: Colord; dark: Colord };
189189
focusedBorderColor(): Colord;

src/core/configuration/PastelTheme.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,8 @@ export class PastelTheme implements Theme {
5555
}
5656

5757
// Don't call directly, use PlayerView
58-
borderColor(player: PlayerView): Colord {
59-
if (this.borderColorCache.has(player.id())) {
60-
return this.borderColorCache.get(player.id())!;
61-
}
62-
const tc = this.territoryColor(player).rgba;
63-
const color = colord({
64-
r: Math.max(tc.r - 40, 0),
65-
g: Math.max(tc.g - 40, 0),
66-
b: Math.max(tc.b - 40, 0),
67-
});
68-
69-
this.borderColorCache.set(player.id(), color);
70-
return color;
58+
borderColor(territoryColor: Colord): Colord {
59+
return territoryColor.darken(0.125);
7160
}
7261

7362
defendedBorderColors(territoryColor: Colord): {

src/core/game/GameView.ts

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -197,36 +197,44 @@ export class PlayerView {
197197
);
198198
}
199199

200+
const defaultTerritoryColor = this.game
201+
.config()
202+
.theme()
203+
.territoryColor(this);
204+
const defaultBorderColor = this.game
205+
.config()
206+
.theme()
207+
.borderColor(defaultTerritoryColor);
208+
200209
const pattern = this.cosmetics.pattern;
201210
if (pattern) {
202-
const territoryColor = this.game.config().theme().territoryColor(this);
203211
pattern.colorPalette ??= {
204212
name: "",
205-
primaryColor: territoryColor.toHex(),
206-
secondaryColor: territoryColor.darken(0.125).toHex(),
213+
primaryColor: defaultTerritoryColor.toHex(),
214+
secondaryColor: defaultBorderColor.toHex(),
207215
} satisfies ColorPalette;
208216
}
209217

210-
if (
211-
this.team() === null &&
212-
this.cosmetics.pattern?.colorPalette?.primaryColor !== undefined
213-
) {
218+
if (this.team() === null) {
214219
this._territoryColor = colord(
215-
this.cosmetics.pattern.colorPalette.primaryColor,
220+
this.cosmetics.color?.color ??
221+
this.cosmetics.pattern?.colorPalette?.primaryColor ??
222+
defaultTerritoryColor.toHex(),
216223
);
217224
} else {
218-
this._territoryColor = this.game.config().theme().territoryColor(this);
225+
this._territoryColor = defaultTerritoryColor;
219226
}
220227

221-
if (this.cosmetics.pattern?.colorPalette?.secondaryColor !== undefined) {
222-
this._borderColor = colord(
223-
this.cosmetics.pattern.colorPalette.secondaryColor,
224-
);
225-
} else if (this.game.myClientID() === this.data.clientID) {
226-
this._borderColor = this.game.config().theme().focusedBorderColor();
227-
} else {
228-
this._borderColor = this.game.config().theme().borderColor(this);
229-
}
228+
const maybeFocusedBorderColor =
229+
this.game.myClientID() === this.data.clientID
230+
? this.game.config().theme().focusedBorderColor()
231+
: defaultBorderColor;
232+
233+
this._borderColor = new Colord(
234+
pattern?.colorPalette?.secondaryColor ??
235+
this.cosmetics.color?.color ??
236+
maybeFocusedBorderColor.toHex(),
237+
);
230238

231239
this._defendedBorderColors = this.game
232240
.config()

0 commit comments

Comments
 (0)