${this
@@ -277,6 +305,10 @@ export class SinglePlayerModal extends LitElement {
this.disableNukes = Boolean((e.target as HTMLInputElement).checked);
}
+ private handleGameModeSelection(value: GameMode) {
+ this.gameMode = value;
+ }
+
private getRandomMap(): GameMapType {
const maps = Object.values(GameMapType);
const randIdx = Math.floor(Math.random() * maps.length);
@@ -300,6 +332,7 @@ export class SinglePlayerModal extends LitElement {
gameConfig: {
gameMap: this.selectedMap,
gameType: GameType.Singleplayer,
+ gameMode: this.gameMode,
difficulty: this.selectedDifficulty,
disableNPCs: this.disableNPCs,
disableNukes: this.disableNukes,
diff --git a/src/client/Transport.ts b/src/client/Transport.ts
index aeb553593..e66b9d08a 100644
--- a/src/client/Transport.ts
+++ b/src/client/Transport.ts
@@ -9,6 +9,7 @@ import {
Player,
PlayerID,
PlayerType,
+ TeamName,
Tick,
UnitType,
} from "../core/game/Game";
@@ -124,8 +125,9 @@ export class SendSetTargetTroopRatioEvent implements GameEvent {
export class SendWinnerEvent implements GameEvent {
constructor(
- public readonly winner: ClientID,
+ public readonly winner: ClientID | TeamName,
public readonly allPlayersStats: AllPlayersStats,
+ public readonly winnerType: "player" | "team",
) {}
}
export class SendHashEvent implements GameEvent {
@@ -492,6 +494,7 @@ export class Transport {
gameID: this.lobbyConfig.gameID,
winner: event.winner,
allPlayersStats: event.allPlayersStats,
+ winnerType: event.winnerType,
});
this.sendMsg(JSON.stringify(msg));
} else {
diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts
index cc8c305a0..287375da9 100644
--- a/src/client/graphics/layers/EventsDisplay.ts
+++ b/src/client/graphics/layers/EventsDisplay.ts
@@ -304,7 +304,7 @@ export class EventsDisplay extends LitElement implements Layer {
onTargetPlayerEvent(event: TargetPlayerUpdate) {
const other = this.game.playerBySmallID(event.playerID) as PlayerView;
const myPlayer = this.game.playerByClientID(this.clientID) as PlayerView;
- if (!myPlayer || !myPlayer.isAlliedWith(other)) return;
+ if (!myPlayer || !myPlayer.isFriendly(other)) return;
const target = this.game.playerBySmallID(event.targetID) as PlayerView;
diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts
index 2b4f56fc3..fc675671a 100644
--- a/src/client/graphics/layers/NameLayer.ts
+++ b/src/client/graphics/layers/NameLayer.ts
@@ -195,7 +195,7 @@ export class NameLayer implements Layer {
nameDiv.appendChild(flagImg);
}
nameDiv.classList.add("player-name");
- nameDiv.style.color = this.theme.textColor(player.info());
+ nameDiv.style.color = this.theme.textColor(player);
nameDiv.style.fontFamily = this.theme.font();
nameDiv.style.whiteSpace = "nowrap";
nameDiv.style.textOverflow = "ellipsis";
@@ -213,7 +213,7 @@ export class NameLayer implements Layer {
troopsDiv.classList.add("player-troops");
troopsDiv.setAttribute("translate", "no");
troopsDiv.textContent = renderTroops(player.troops());
- troopsDiv.style.color = this.theme.textColor(player.info());
+ troopsDiv.style.color = this.theme.textColor(player);
troopsDiv.style.fontFamily = this.theme.font();
troopsDiv.style.zIndex = "3";
troopsDiv.style.marginTop = "-5%";
@@ -242,7 +242,7 @@ export class NameLayer implements Layer {
// Calculate base size and scale
const baseSize = Math.max(1, Math.floor(render.player.nameLocation().size));
render.fontSize = Math.max(4, Math.floor(baseSize * 0.4));
- render.fontColor = this.theme.textColor(render.player.info());
+ render.fontColor = this.theme.textColor(render.player);
// Screen space calculations
const size = this.transformHandler.scale * baseSize;
diff --git a/src/client/graphics/layers/PlayerInfoOverlay.ts b/src/client/graphics/layers/PlayerInfoOverlay.ts
index e495d3d26..3b8678017 100644
--- a/src/client/graphics/layers/PlayerInfoOverlay.ts
+++ b/src/client/graphics/layers/PlayerInfoOverlay.ts
@@ -163,7 +163,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
private renderPlayerInfo(player: PlayerView) {
const myPlayer = this.myPlayer();
- const isAlly = myPlayer?.isAlliedWith(player);
+ const isFriendly = myPlayer?.isFriendly(player);
let relationHtml = null;
const attackingTroops = player
.outgoingAttacks()
@@ -198,7 +198,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
return html`
@@ -244,7 +244,7 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
private renderUnitInfo(unit: UnitView) {
const isAlly =
(unit.owner() == this.myPlayer() ||
- this.myPlayer()?.isAlliedWith(unit.owner())) ??
+ this.myPlayer()?.isFriendly(unit.owner())) ??
false;
return html`
diff --git a/src/client/graphics/layers/StructureLayer.ts b/src/client/graphics/layers/StructureLayer.ts
index 3bb61bba6..87190a303 100644
--- a/src/client/graphics/layers/StructureLayer.ts
+++ b/src/client/graphics/layers/StructureLayer.ts
@@ -188,7 +188,7 @@ export class StructureLayer implements Layer {
new Cell(this.game.x(tile), this.game.y(tile)),
unit.type() == UnitType.Construction
? underConstructionColor
- : this.theme.territoryColor(unit.owner().info()),
+ : this.theme.territoryColor(unit.owner()),
130,
);
}
@@ -234,7 +234,7 @@ export class StructureLayer implements Layer {
if (!unit.isActive()) return;
- let borderColor = this.theme.borderColor(unit.owner().info());
+ let borderColor = this.theme.borderColor(unit.owner());
if (unitType == UnitType.SAMLauncher && unit.isSamCooldown()) {
borderColor = reloadingColor;
} else if (unit.type() == UnitType.Construction) {
@@ -257,7 +257,7 @@ export class StructureLayer implements Layer {
height: number,
unit: UnitView,
) {
- let color = this.theme.borderColor(unit.owner().info());
+ let color = this.theme.borderColor(unit.owner());
if (unit.type() == UnitType.Construction) {
color = underConstructionColor;
}
diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts
index bd9be5383..b77693e51 100644
--- a/src/client/graphics/layers/TerritoryLayer.ts
+++ b/src/client/graphics/layers/TerritoryLayer.ts
@@ -251,14 +251,14 @@ export class TerritoryLayer implements Layer {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
- this.theme.defendedBorderColor(owner.info()),
+ this.theme.defendedBorderColor(owner),
255,
);
} else {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
- this.theme.borderColor(owner.info()),
+ this.theme.borderColor(owner),
255,
);
}
@@ -266,7 +266,7 @@ export class TerritoryLayer implements Layer {
this.paintCell(
this.game.x(tile),
this.game.y(tile),
- this.theme.territoryColor(owner.info()),
+ this.theme.territoryColor(owner),
150,
);
}
diff --git a/src/client/graphics/layers/UILayer.ts b/src/client/graphics/layers/UILayer.ts
index b743f7687..15fdec017 100644
--- a/src/client/graphics/layers/UILayer.ts
+++ b/src/client/graphics/layers/UILayer.ts
@@ -136,7 +136,7 @@ export class UILayer implements Layer {
baseOpacity + Math.sin(this.selectionAnimTime * 0.1) * pulseAmount;
// Get the unit's owner color for the box
- const ownerColor = this.theme.territoryColor(unit.owner().info());
+ const ownerColor = this.theme.territoryColor(unit.owner());
// Create a brighter version of the owner color for the selection
const selectionColor = ownerColor.lighten(0.2);
diff --git a/src/client/graphics/layers/UnitLayer.ts b/src/client/graphics/layers/UnitLayer.ts
index 0e06dba0a..e648947cf 100644
--- a/src/client/graphics/layers/UnitLayer.ts
+++ b/src/client/graphics/layers/UnitLayer.ts
@@ -194,7 +194,7 @@ export class UnitLayer implements Layer {
if (this.myPlayer == unit.owner()) {
return Relationship.Self;
}
- if (this.myPlayer.isAlliedWith(unit.owner())) {
+ if (this.myPlayer.isFriendly(unit.owner())) {
return Relationship.Ally;
}
return Relationship.Enemy;
@@ -248,7 +248,7 @@ export class UnitLayer implements Layer {
return;
}
- let outerColor = this.theme.territoryColor(unit.owner().info());
+ let outerColor = this.theme.territoryColor(unit.owner());
if (unit.warshipTargetId()) {
const targetOwner = this.game
.units()
@@ -276,7 +276,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -290,7 +290,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.territoryColor(unit.owner().info()),
+ this.theme.territoryColor(unit.owner()),
255,
);
}
@@ -316,14 +316,14 @@ export class UnitLayer implements Layer {
this.game.x(unit.tile()),
this.game.y(unit.tile()),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
this.paintCell(
this.game.x(unit.lastTile()),
this.game.y(unit.lastTile()),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -358,7 +358,7 @@ export class UnitLayer implements Layer {
this.game.x(unit.tile()),
this.game.y(unit.tile()),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -408,7 +408,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -426,7 +426,7 @@ export class UnitLayer implements Layer {
this.game.x(unit.tile()),
this.game.y(unit.tile()),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -453,7 +453,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.territoryColor(unit.owner().info()),
+ this.theme.territoryColor(unit.owner()),
255,
);
}
@@ -467,7 +467,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -498,7 +498,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.territoryColor(unit.owner().info()),
+ this.theme.territoryColor(unit.owner()),
150,
);
}
@@ -512,7 +512,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.borderColor(unit.owner().info()),
+ this.theme.borderColor(unit.owner()),
255,
);
}
@@ -526,7 +526,7 @@ export class UnitLayer implements Layer {
this.game.x(t),
this.game.y(t),
rel,
- this.theme.territoryColor(unit.owner().info()),
+ this.theme.territoryColor(unit.owner()),
255,
);
}
diff --git a/src/client/graphics/layers/WinModal.ts b/src/client/graphics/layers/WinModal.ts
index 40e0cc7d9..067b18983 100644
--- a/src/client/graphics/layers/WinModal.ts
+++ b/src/client/graphics/layers/WinModal.ts
@@ -1,6 +1,6 @@
import { LitElement, html, css } from "lit";
import { customElement, property, state } from "lit/decorators.js";
-import { Player } from "../../../core/game/Game";
+import { Player, TeamName } from "../../../core/game/Game";
import { ClientID } from "../../../core/Schemas";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
@@ -218,18 +218,38 @@ export class WinModal extends LitElement implements Layer {
this.show();
}
this.game.updatesSinceLastTick()[GameUpdateType.Win].forEach((wu) => {
- const winner = this.game.playerBySmallID(wu.winnerID) as PlayerView;
- this.eventBus.emit(
- new SendWinnerEvent(winner.clientID(), wu.allPlayersStats),
- );
- if (winner == this.game.myPlayer()) {
- this._title = "You Won!";
- this.won = true;
+ if (wu.winnerType === "team") {
+ this.eventBus.emit(
+ new SendWinnerEvent(
+ wu.winner as TeamName,
+ wu.allPlayersStats,
+ "team",
+ ),
+ );
+ if (wu.winner == this.game.myPlayer()?.teamName()) {
+ this._title = "Your team won!";
+ this.won = true;
+ } else {
+ this._title = `${wu.winner} team has won!`;
+ this.won = false;
+ }
+ this.show();
} else {
- this._title = `${winner.name()} has won!`;
- this.won = false;
+ const winner = this.game.playerBySmallID(
+ wu.winner as number,
+ ) as PlayerView;
+ this.eventBus.emit(
+ new SendWinnerEvent(winner.clientID(), wu.allPlayersStats, "player"),
+ );
+ if (winner == this.game.myPlayer()) {
+ this._title = "You Won!";
+ this.won = true;
+ } else {
+ this._title = `${winner.name()} has won!`;
+ this.won = false;
+ }
+ this.show();
}
- this.show();
});
}
diff --git a/src/core/Schemas.ts b/src/core/Schemas.ts
index e430d63b2..fc5421d61 100644
--- a/src/core/Schemas.ts
+++ b/src/core/Schemas.ts
@@ -3,8 +3,10 @@ import {
AllPlayers,
Difficulty,
GameMapType,
+ GameMode,
GameType,
PlayerType,
+ TeamName,
UnitType,
} from "./game/Game";
@@ -107,6 +109,7 @@ const GameConfigSchema = z.object({
gameMap: z.nativeEnum(GameMapType),
difficulty: z.nativeEnum(Difficulty),
gameType: z.nativeEnum(GameType),
+ gameMode: z.nativeEnum(GameMode),
disableNPCs: z.boolean(),
disableNukes: z.boolean(),
bots: z.number().int().min(0).max(400),
@@ -345,8 +348,9 @@ const ClientBaseMessageSchema = z.object({
export const ClientSendWinnerSchema = ClientBaseMessageSchema.extend({
type: z.literal("winner"),
- winner: ID.nullable(),
+ winner: ID.or(z.nativeEnum(TeamName)).nullable(),
allPlayersStats: AllPlayersStatsSchema,
+ winnerType: z.enum(["player", "team"]),
});
export const ClientHashSchema = ClientBaseMessageSchema.extend({
@@ -404,7 +408,11 @@ export const GameRecordSchema = z.object({
date: SafeString,
num_turns: z.number(),
turns: z.array(TurnSchema),
- winner: ID.nullable(),
+ winner: z
+ .union([ID, z.nativeEnum(TeamName)])
+ .nullable()
+ .optional(),
+ winnerType: z.enum(["player", "team"]).nullable().optional(),
allPlayersStats: z.record(ID, PlayerStatsSchema),
version: z.enum(["v0.0.1"]),
gitCommit: z.string().nullable().optional(),
diff --git a/src/core/Util.ts b/src/core/Util.ts
index 438da4988..c86c52439 100644
--- a/src/core/Util.ts
+++ b/src/core/Util.ts
@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from "uuid";
import twemoji from "twemoji";
import DOMPurify from "dompurify";
-import { Cell, Game, Player, Unit } from "./game/Game";
+import { Cell, Game, Player, TeamName, Unit } from "./game/Game";
import {
AllPlayersStats,
ClientID,
@@ -255,7 +255,8 @@ export function createGameRecord(
turns: Turn[],
start: number,
end: number,
- winner: ClientID | null,
+ winner: ClientID | TeamName | null,
+ winnerType: "player" | "team" | null,
allPlayersStats: AllPlayersStats,
): GameRecord {
const record: GameRecord = {
@@ -289,6 +290,7 @@ export function createGameRecord(
);
record.num_turns = turns.length;
record.winner = winner;
+ record.winnerType = winnerType;
return record;
}
diff --git a/src/core/configuration/Colors.ts b/src/core/configuration/Colors.ts
new file mode 100644
index 000000000..6354359f0
--- /dev/null
+++ b/src/core/configuration/Colors.ts
@@ -0,0 +1,352 @@
+import { colord, Colord } from "colord";
+
+export const territoryColors: Colord[] = [
+ colord({ r: 230, g: 100, b: 100 }), // Bright Red
+ colord({ r: 100, g: 180, b: 230 }), // Sky Blue
+ colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
+ colord({ r: 180, g: 100, b: 230 }), // Purple
+ colord({ r: 80, g: 200, b: 120 }), // Emerald Green
+ colord({ r: 230, g: 130, b: 180 }), // Pink
+ colord({ r: 100, g: 160, b: 80 }), // Olive Green
+ colord({ r: 230, g: 150, b: 100 }), // Peach
+ colord({ r: 80, g: 130, b: 190 }), // Navy Blue
+ colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
+ colord({ r: 190, g: 100, b: 130 }), // Maroon
+ colord({ r: 100, g: 210, b: 210 }), // Turquoise
+ colord({ r: 210, g: 140, b: 80 }), // Light Orange
+ colord({ r: 150, g: 110, b: 190 }), // Lavender
+ colord({ r: 180, g: 210, b: 120 }), // Light Green
+ colord({ r: 210, g: 100, b: 160 }), // Hot Pink
+ colord({ r: 100, g: 140, b: 110 }), // Sea Green
+ colord({ r: 230, g: 180, b: 180 }), // Light Pink
+ colord({ r: 120, g: 120, b: 190 }), // Periwinkle
+ colord({ r: 190, g: 170, b: 100 }), // Sand
+ colord({ r: 100, g: 180, b: 160 }), // Aquamarine
+ colord({ r: 210, g: 160, b: 200 }), // Orchid
+ colord({ r: 170, g: 190, b: 100 }), // Yellow Green
+ colord({ r: 100, g: 130, b: 150 }), // Steel Blue
+ colord({ r: 230, g: 140, b: 140 }), // Salmon
+ colord({ r: 140, g: 180, b: 220 }), // Light Blue
+ colord({ r: 200, g: 160, b: 110 }), // Tan
+ colord({ r: 180, g: 130, b: 180 }), // Plum
+ colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
+ colord({ r: 220, g: 120, b: 120 }), // Coral
+ colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
+ colord({ r: 200, g: 200, b: 140 }), // Khaki
+ colord({ r: 160, g: 120, b: 160 }), // Purple Gray
+ colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
+ colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
+ colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
+ colord({ r: 190, g: 180, b: 160 }), // Tan Gray
+ colord({ r: 170, g: 140, b: 190 }), // Medium Purple
+ colord({ r: 160, g: 190, b: 160 }), // Pale Green
+ colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
+ colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
+ colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
+ colord({ r: 150, g: 130, b: 150 }), // Thistle
+ colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
+ colord({ r: 190, g: 140, b: 150 }), // Puce
+ colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
+ colord({ r: 180, g: 160, b: 180 }), // Mauve
+ colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
+ colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
+ colord({ r: 100, g: 180, b: 230 }), // Sky Blue
+ colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
+ colord({ r: 180, g: 100, b: 230 }), // Purple
+ colord({ r: 80, g: 200, b: 120 }), // Emerald Green
+ colord({ r: 230, g: 130, b: 180 }), // Pink
+ colord({ r: 100, g: 160, b: 80 }), // Olive Green
+ colord({ r: 230, g: 150, b: 100 }), // Peach
+ colord({ r: 80, g: 130, b: 190 }), // Navy Blue
+ colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
+ colord({ r: 190, g: 100, b: 130 }), // Maroon
+ colord({ r: 100, g: 210, b: 210 }), // Turquoise
+ colord({ r: 210, g: 140, b: 80 }), // Light Orange
+ colord({ r: 150, g: 110, b: 190 }), // Lavender
+ colord({ r: 180, g: 210, b: 120 }), // Light Green
+ colord({ r: 210, g: 100, b: 160 }), // Hot Pink
+ colord({ r: 100, g: 140, b: 110 }), // Sea Green
+ colord({ r: 230, g: 180, b: 180 }), // Light Pink
+ colord({ r: 120, g: 120, b: 190 }), // Periwinkle
+ colord({ r: 190, g: 170, b: 100 }), // Sand
+ colord({ r: 100, g: 180, b: 160 }), // Aquamarine
+ colord({ r: 210, g: 160, b: 200 }), // Orchid
+ colord({ r: 170, g: 190, b: 100 }), // Yellow Green
+ colord({ r: 100, g: 130, b: 150 }), // Steel Blue
+ colord({ r: 230, g: 140, b: 140 }), // Salmon
+ colord({ r: 140, g: 180, b: 220 }), // Light Blue
+ colord({ r: 200, g: 160, b: 110 }), // Tan
+ colord({ r: 180, g: 130, b: 180 }), // Plum
+ colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
+ colord({ r: 220, g: 120, b: 120 }), // Coral
+ colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
+ colord({ r: 200, g: 200, b: 140 }), // Khaki
+ colord({ r: 160, g: 120, b: 160 }), // Purple Gray
+ colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
+ colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
+ colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
+ colord({ r: 190, g: 180, b: 160 }), // Tan Gray
+ colord({ r: 170, g: 140, b: 190 }), // Medium Purple
+ colord({ r: 160, g: 190, b: 160 }), // Pale Green
+ colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
+ colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
+ colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
+ colord({ r: 150, g: 130, b: 150 }), // Thistle
+ colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
+ colord({ r: 190, g: 140, b: 150 }), // Puce
+ colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
+ colord({ r: 180, g: 160, b: 180 }), // Mauve
+ colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
+ colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
+];
+
+export const humanColors: Colord[] = [
+ // Original set
+ colord({ r: 235, g: 75, b: 75 }), // Bright Red
+ colord({ r: 67, g: 190, b: 84 }), // Fresh Green
+ colord({ r: 59, g: 130, b: 246 }), // Royal Blue
+ colord({ r: 245, g: 158, b: 11 }), // Amber
+ colord({ r: 236, g: 72, b: 153 }), // Deep Pink
+ colord({ r: 48, g: 178, b: 180 }), // Teal
+ colord({ r: 168, g: 85, b: 247 }), // Vibrant Purple
+ colord({ r: 251, g: 191, b: 36 }), // Marigold
+ colord({ r: 74, g: 222, b: 128 }), // Mint
+ colord({ r: 239, g: 68, b: 68 }), // Crimson
+ colord({ r: 34, g: 197, b: 94 }), // Emerald
+ colord({ r: 96, g: 165, b: 250 }), // Sky Blue
+ colord({ r: 249, g: 115, b: 22 }), // Tangerine
+ colord({ r: 192, g: 132, b: 252 }), // Lavender
+ colord({ r: 45, g: 212, b: 191 }), // Turquoise
+ colord({ r: 244, g: 114, b: 182 }), // Rose
+ colord({ r: 132, g: 204, b: 22 }), // Lime
+ colord({ r: 56, g: 189, b: 248 }), // Light Blue
+ colord({ r: 234, g: 179, b: 8 }), // Sunflower
+ colord({ r: 217, g: 70, b: 239 }), // Fuchsia
+ colord({ r: 16, g: 185, b: 129 }), // Sea Green
+ colord({ r: 251, g: 146, b: 60 }), // Light Orange
+ colord({ r: 147, g: 51, b: 234 }), // Bright Purple
+ colord({ r: 79, g: 70, b: 229 }), // Indigo
+ colord({ r: 245, g: 101, b: 101 }), // Coral
+ colord({ r: 134, g: 239, b: 172 }), // Light Green
+ colord({ r: 59, g: 130, b: 246 }), // Cerulean
+ colord({ r: 253, g: 164, b: 175 }), // Salmon Pink
+ colord({ r: 147, g: 197, b: 253 }), // Powder Blue
+ colord({ r: 252, g: 211, b: 77 }), // Golden
+ colord({ r: 190, g: 92, b: 251 }), // Amethyst
+ colord({ r: 82, g: 183, b: 136 }), // Jade
+ colord({ r: 248, g: 113, b: 113 }), // Warm Red
+ colord({ r: 99, g: 202, b: 253 }), // Azure
+ colord({ r: 240, g: 171, b: 252 }), // Orchid
+ colord({ r: 163, g: 230, b: 53 }), // Yellow Green
+ colord({ r: 234, g: 88, b: 12 }), // Burnt Orange
+ colord({ r: 125, g: 211, b: 252 }), // Crystal Blue
+ colord({ r: 251, g: 113, b: 133 }), // Watermelon
+ colord({ r: 52, g: 211, b: 153 }), // Spearmint
+ colord({ r: 167, g: 139, b: 250 }), // Periwinkle
+ colord({ r: 245, g: 158, b: 11 }), // Honey
+ colord({ r: 110, g: 231, b: 183 }), // Seafoam
+ colord({ r: 233, g: 213, b: 255 }), // Light Lilac
+ colord({ r: 202, g: 138, b: 4 }), // Rich Gold
+ colord({ r: 151, g: 255, b: 187 }), // Fresh Mint
+ colord({ r: 220, g: 38, b: 38 }), // Ruby
+ colord({ r: 124, g: 58, b: 237 }), // Royal Purple
+ colord({ r: 45, g: 212, b: 191 }), // Ocean
+ colord({ r: 252, g: 165, b: 165 }), // Peach
+
+ // Additional 50 colors
+ colord({ r: 179, g: 136, b: 255 }), // Light Purple
+ colord({ r: 133, g: 77, b: 14 }), // Chocolate
+ colord({ r: 52, g: 211, b: 153 }), // Aquamarine
+ colord({ r: 234, g: 179, b: 8 }), // Mustard
+ colord({ r: 236, g: 72, b: 153 }), // Hot Pink
+ colord({ r: 147, g: 197, b: 253 }), // Sky
+ colord({ r: 249, g: 115, b: 22 }), // Pumpkin
+ colord({ r: 167, g: 139, b: 250 }), // Iris
+ colord({ r: 16, g: 185, b: 129 }), // Pine
+ colord({ r: 251, g: 146, b: 60 }), // Mango
+ colord({ r: 192, g: 132, b: 252 }), // Wisteria
+ colord({ r: 79, g: 70, b: 229 }), // Sapphire
+ colord({ r: 245, g: 101, b: 101 }), // Salmon
+ colord({ r: 134, g: 239, b: 172 }), // Spring Green
+ colord({ r: 59, g: 130, b: 246 }), // Ocean Blue
+ colord({ r: 253, g: 164, b: 175 }), // Rose Gold
+ colord({ r: 16, g: 185, b: 129 }), // Forest
+ colord({ r: 252, g: 211, b: 77 }), // Sunshine
+ colord({ r: 190, g: 92, b: 251 }), // Grape
+ colord({ r: 82, g: 183, b: 136 }), // Eucalyptus
+ colord({ r: 248, g: 113, b: 113 }), // Cherry
+ colord({ r: 99, g: 202, b: 253 }), // Arctic
+ colord({ r: 240, g: 171, b: 252 }), // Lilac
+ colord({ r: 163, g: 230, b: 53 }), // Chartreuse
+ colord({ r: 234, g: 88, b: 12 }), // Rust
+ colord({ r: 125, g: 211, b: 252 }), // Ice Blue
+ colord({ r: 251, g: 113, b: 133 }), // Strawberry
+ colord({ r: 52, g: 211, b: 153 }), // Sage
+ colord({ r: 167, g: 139, b: 250 }), // Violet
+ colord({ r: 245, g: 158, b: 11 }), // Apricot
+ colord({ r: 110, g: 231, b: 183 }), // Mint Green
+ colord({ r: 233, g: 213, b: 255 }), // Thistle
+ colord({ r: 202, g: 138, b: 4 }), // Bronze
+ colord({ r: 151, g: 255, b: 187 }), // Pistachio
+ colord({ r: 220, g: 38, b: 38 }), // Fire Engine
+ colord({ r: 124, g: 58, b: 237 }), // Electric Purple
+ colord({ r: 45, g: 212, b: 191 }), // Caribbean
+ colord({ r: 252, g: 165, b: 165 }), // Melon
+ colord({ r: 168, g: 85, b: 247 }), // Byzantium
+ colord({ r: 74, g: 222, b: 128 }), // Kelly Green
+ colord({ r: 239, g: 68, b: 68 }), // Cardinal
+ colord({ r: 34, g: 197, b: 94 }), // Shamrock
+ colord({ r: 96, g: 165, b: 250 }), // Marina
+ colord({ r: 249, g: 115, b: 22 }), // Carrot
+ colord({ r: 192, g: 132, b: 252 }), // Heliotrope
+ colord({ r: 45, g: 212, b: 191 }), // Lagoon
+ colord({ r: 244, g: 114, b: 182 }), // Bubble Gum
+ colord({ r: 132, g: 204, b: 22 }), // Apple
+ colord({ r: 56, g: 189, b: 248 }), // Electric Blue
+ colord({ r: 234, g: 179, b: 8 }), // Daffodil
+];
+
+export const redShades = [
+ colord({ r: 210, g: 0, b: 0 }), // Most distinct colors sorted by brightness
+ colord({ r: 225, g: 0, b: 0 }),
+ colord({ r: 240, g: 0, b: 0 }),
+ colord({ r: 255, g: 0, b: 0 }),
+ colord({ r: 210, g: 21, b: 21 }),
+ colord({ r: 225, g: 23, b: 23 }),
+ colord({ r: 240, g: 25, b: 25 }),
+ colord({ r: 255, g: 26, b: 26 }),
+ colord({ r: 210, g: 42, b: 42 }),
+ colord({ r: 225, g: 45, b: 45 }),
+ colord({ r: 240, g: 48, b: 48 }),
+ colord({ r: 255, g: 51, b: 51 }),
+ colord({ r: 195, g: 59, b: 59 }),
+ colord({ r: 210, g: 63, b: 63 }),
+ colord({ r: 225, g: 68, b: 68 }),
+ colord({ r: 240, g: 72, b: 72 }),
+ colord({ r: 255, g: 77, b: 77 }),
+ colord({ r: 180, g: 72, b: 72 }),
+ colord({ r: 195, g: 78, b: 78 }),
+ colord({ r: 210, g: 84, b: 84 }),
+ colord({ r: 225, g: 90, b: 90 }),
+ colord({ r: 240, g: 96, b: 96 }),
+ colord({ r: 255, g: 102, b: 102 }),
+ colord({ r: 165, g: 83, b: 83 }),
+ colord({ r: 180, g: 90, b: 90 }),
+ colord({ r: 195, g: 98, b: 98 }),
+ colord({ r: 210, g: 105, b: 105 }),
+ colord({ r: 225, g: 113, b: 113 }),
+ colord({ r: 240, g: 120, b: 120 }),
+ colord({ r: 255, g: 128, b: 128 }),
+ colord({ r: 150, g: 90, b: 90 }),
+ colord({ r: 165, g: 99, b: 99 }),
+ colord({ r: 180, g: 108, b: 108 }),
+ colord({ r: 195, g: 117, b: 117 }),
+ colord({ r: 210, g: 126, b: 126 }),
+ colord({ r: 225, g: 135, b: 135 }),
+ colord({ r: 240, g: 144, b: 144 }),
+ colord({ r: 255, g: 153, b: 153 }),
+ colord({ r: 135, g: 95, b: 95 }),
+ colord({ r: 150, g: 105, b: 105 }),
+ colord({ r: 165, g: 116, b: 116 }),
+ colord({ r: 180, g: 126, b: 126 }),
+ colord({ r: 195, g: 137, b: 137 }),
+ colord({ r: 210, g: 147, b: 147 }),
+ colord({ r: 225, g: 158, b: 158 }),
+ colord({ r: 240, g: 168, b: 168 }),
+ colord({ r: 255, g: 179, b: 179 }),
+ colord({ r: 120, g: 108, b: 108 }),
+ colord({ r: 135, g: 122, b: 122 }),
+ colord({ r: 150, g: 135, b: 135 }),
+ colord({ r: 165, g: 149, b: 149 }),
+ colord({ r: 180, g: 162, b: 162 }),
+ colord({ r: 195, g: 176, b: 176 }),
+ colord({ r: 210, g: 189, b: 189 }),
+ colord({ r: 225, g: 203, b: 203 }),
+ colord({ r: 240, g: 216, b: 216 }),
+ colord({ r: 255, g: 230, b: 230 }),
+ colord({ r: 195, g: 0, b: 0 }),
+ colord({ r: 195, g: 20, b: 20 }),
+ colord({ r: 195, g: 39, b: 39 }),
+ colord({ r: 180, g: 54, b: 54 }),
+ colord({ r: 165, g: 66, b: 66 }),
+ colord({ r: 150, g: 75, b: 75 }),
+ colord({ r: 135, g: 81, b: 81 }),
+ colord({ r: 120, g: 84, b: 84 }),
+ colord({ r: 135, g: 108, b: 108 }),
+ colord({ r: 150, g: 120, b: 120 }),
+ colord({ r: 165, g: 132, b: 132 }),
+ colord({ r: 180, g: 144, b: 144 }),
+];
+
+export const blueShades = [
+ colord({ r: 0, g: 0, b: 120 }), // Most distinct colors sorted by brightness
+ colord({ r: 0, g: 0, b: 135 }),
+ colord({ r: 0, g: 0, b: 150 }),
+ colord({ r: 0, g: 0, b: 165 }),
+ colord({ r: 0, g: 0, b: 180 }),
+ colord({ r: 0, g: 0, b: 195 }),
+ colord({ r: 0, g: 0, b: 210 }),
+ colord({ r: 0, g: 0, b: 225 }),
+ colord({ r: 0, g: 0, b: 240 }),
+ colord({ r: 0, g: 0, b: 255 }),
+ colord({ r: 12, g: 12, b: 120 }),
+ colord({ r: 14, g: 14, b: 135 }),
+ colord({ r: 15, g: 15, b: 150 }),
+ colord({ r: 17, g: 17, b: 165 }),
+ colord({ r: 18, g: 18, b: 180 }),
+ colord({ r: 20, g: 20, b: 195 }),
+ colord({ r: 21, g: 21, b: 210 }),
+ colord({ r: 23, g: 23, b: 225 }),
+ colord({ r: 25, g: 25, b: 240 }),
+ colord({ r: 26, g: 26, b: 255 }),
+ colord({ r: 24, g: 24, b: 120 }),
+ colord({ r: 27, g: 27, b: 135 }),
+ colord({ r: 30, g: 30, b: 150 }),
+ colord({ r: 33, g: 33, b: 165 }),
+ colord({ r: 36, g: 36, b: 180 }),
+ colord({ r: 39, g: 39, b: 195 }),
+ colord({ r: 42, g: 42, b: 210 }),
+ colord({ r: 45, g: 45, b: 225 }),
+ colord({ r: 48, g: 48, b: 240 }),
+ colord({ r: 51, g: 51, b: 255 }),
+ colord({ r: 36, g: 36, b: 120 }),
+ colord({ r: 41, g: 41, b: 135 }),
+ colord({ r: 45, g: 45, b: 150 }),
+ colord({ r: 50, g: 50, b: 165 }),
+ colord({ r: 54, g: 54, b: 180 }),
+ colord({ r: 59, g: 59, b: 195 }),
+ colord({ r: 63, g: 63, b: 210 }),
+ colord({ r: 68, g: 68, b: 225 }),
+ colord({ r: 72, g: 72, b: 240 }),
+ colord({ r: 77, g: 77, b: 255 }),
+ colord({ r: 48, g: 48, b: 120 }),
+ colord({ r: 54, g: 54, b: 135 }),
+ colord({ r: 60, g: 60, b: 150 }),
+ colord({ r: 66, g: 66, b: 165 }),
+ colord({ r: 72, g: 72, b: 180 }),
+ colord({ r: 78, g: 78, b: 195 }),
+ colord({ r: 84, g: 84, b: 210 }),
+ colord({ r: 90, g: 90, b: 225 }),
+ colord({ r: 96, g: 96, b: 240 }),
+ colord({ r: 102, g: 102, b: 255 }),
+ colord({ r: 60, g: 60, b: 120 }),
+ colord({ r: 68, g: 68, b: 135 }),
+ colord({ r: 75, g: 75, b: 150 }),
+ colord({ r: 83, g: 83, b: 165 }),
+ colord({ r: 90, g: 90, b: 180 }),
+ colord({ r: 98, g: 98, b: 195 }),
+ colord({ r: 105, g: 105, b: 210 }),
+ colord({ r: 113, g: 113, b: 225 }),
+ colord({ r: 120, g: 120, b: 240 }),
+ colord({ r: 128, g: 128, b: 255 }),
+ colord({ r: 72, g: 72, b: 120 }),
+ colord({ r: 81, g: 81, b: 135 }),
+ colord({ r: 90, g: 90, b: 150 }),
+ colord({ r: 99, g: 99, b: 165 }),
+ colord({ r: 108, g: 108, b: 180 }),
+ colord({ r: 117, g: 117, b: 195 }),
+ colord({ r: 126, g: 126, b: 210 }),
+ colord({ r: 135, g: 135, b: 225 }),
+ colord({ r: 144, g: 144, b: 240 }),
+ colord({ r: 153, g: 153, b: 255 }),
+];
diff --git a/src/core/configuration/Config.ts b/src/core/configuration/Config.ts
index 1f12f454b..33f3dd3a3 100644
--- a/src/core/configuration/Config.ts
+++ b/src/core/configuration/Config.ts
@@ -113,15 +113,15 @@ export interface Config {
}
export interface Theme {
- territoryColor(playerInfo: PlayerInfo): Colord;
- specialBuildingColor(playerInfo: PlayerInfo): Colord;
- borderColor(playerInfo: PlayerInfo): Colord;
- defendedBorderColor(playerInfo: PlayerInfo): Colord;
+ territoryColor(playerInfo: PlayerView): Colord;
+ specialBuildingColor(playerInfo: PlayerView): Colord;
+ borderColor(playerInfo: PlayerView): Colord;
+ defendedBorderColor(playerInfo: PlayerView): Colord;
terrainColor(gm: GameMap, tile: TileRef): Colord;
backgroundColor(): Colord;
falloutColor(): Colord;
font(): string;
- textColor(playerInfo: PlayerInfo): string;
+ textColor(playerInfo: PlayerView): string;
// unit color for alternate view
selfColor(): Colord;
allyColor(): Colord;
diff --git a/src/core/configuration/PastelTheme.ts b/src/core/configuration/PastelTheme.ts
index a1c3a1ecc..062a6ff4c 100644
--- a/src/core/configuration/PastelTheme.ts
+++ b/src/core/configuration/PastelTheme.ts
@@ -1,17 +1,11 @@
import { Colord, colord, random } from "colord";
-import {
- Game,
- PlayerID,
- PlayerInfo,
- PlayerType,
- TerrainType,
-} from "../game/Game";
+import { PlayerType, TeamName, TerrainType } from "../game/Game";
import { Theme } from "./Config";
-import { time } from "console";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
+import { blueShades, humanColors, redShades, territoryColors } from "./Colors";
export const pastelTheme = new (class implements Theme {
private rand = new PseudoRandom(123);
@@ -29,235 +23,31 @@ export const pastelTheme = new (class implements Theme {
private water = colord({ r: 70, g: 132, b: 180 });
private shorelineWater = colord({ r: 100, g: 143, b: 255 });
- private territoryColors: Colord[] = [
- colord({ r: 230, g: 100, b: 100 }), // Bright Red
- colord({ r: 100, g: 180, b: 230 }), // Sky Blue
- colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
- colord({ r: 180, g: 100, b: 230 }), // Purple
- colord({ r: 80, g: 200, b: 120 }), // Emerald Green
- colord({ r: 230, g: 130, b: 180 }), // Pink
- colord({ r: 100, g: 160, b: 80 }), // Olive Green
- colord({ r: 230, g: 150, b: 100 }), // Peach
- colord({ r: 80, g: 130, b: 190 }), // Navy Blue
- colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
- colord({ r: 190, g: 100, b: 130 }), // Maroon
- colord({ r: 100, g: 210, b: 210 }), // Turquoise
- colord({ r: 210, g: 140, b: 80 }), // Light Orange
- colord({ r: 150, g: 110, b: 190 }), // Lavender
- colord({ r: 180, g: 210, b: 120 }), // Light Green
- colord({ r: 210, g: 100, b: 160 }), // Hot Pink
- colord({ r: 100, g: 140, b: 110 }), // Sea Green
- colord({ r: 230, g: 180, b: 180 }), // Light Pink
- colord({ r: 120, g: 120, b: 190 }), // Periwinkle
- colord({ r: 190, g: 170, b: 100 }), // Sand
- colord({ r: 100, g: 180, b: 160 }), // Aquamarine
- colord({ r: 210, g: 160, b: 200 }), // Orchid
- colord({ r: 170, g: 190, b: 100 }), // Yellow Green
- colord({ r: 100, g: 130, b: 150 }), // Steel Blue
- colord({ r: 230, g: 140, b: 140 }), // Salmon
- colord({ r: 140, g: 180, b: 220 }), // Light Blue
- colord({ r: 200, g: 160, b: 110 }), // Tan
- colord({ r: 180, g: 130, b: 180 }), // Plum
- colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
- colord({ r: 220, g: 120, b: 120 }), // Coral
- colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
- colord({ r: 200, g: 200, b: 140 }), // Khaki
- colord({ r: 160, g: 120, b: 160 }), // Purple Gray
- colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
- colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
- colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
- colord({ r: 190, g: 180, b: 160 }), // Tan Gray
- colord({ r: 170, g: 140, b: 190 }), // Medium Purple
- colord({ r: 160, g: 190, b: 160 }), // Pale Green
- colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
- colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
- colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
- colord({ r: 150, g: 130, b: 150 }), // Thistle
- colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
- colord({ r: 190, g: 140, b: 150 }), // Puce
- colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
- colord({ r: 180, g: 160, b: 180 }), // Mauve
- colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
- colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
- colord({ r: 100, g: 180, b: 230 }), // Sky Blue
- colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
- colord({ r: 180, g: 100, b: 230 }), // Purple
- colord({ r: 80, g: 200, b: 120 }), // Emerald Green
- colord({ r: 230, g: 130, b: 180 }), // Pink
- colord({ r: 100, g: 160, b: 80 }), // Olive Green
- colord({ r: 230, g: 150, b: 100 }), // Peach
- colord({ r: 80, g: 130, b: 190 }), // Navy Blue
- colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
- colord({ r: 190, g: 100, b: 130 }), // Maroon
- colord({ r: 100, g: 210, b: 210 }), // Turquoise
- colord({ r: 210, g: 140, b: 80 }), // Light Orange
- colord({ r: 150, g: 110, b: 190 }), // Lavender
- colord({ r: 180, g: 210, b: 120 }), // Light Green
- colord({ r: 210, g: 100, b: 160 }), // Hot Pink
- colord({ r: 100, g: 140, b: 110 }), // Sea Green
- colord({ r: 230, g: 180, b: 180 }), // Light Pink
- colord({ r: 120, g: 120, b: 190 }), // Periwinkle
- colord({ r: 190, g: 170, b: 100 }), // Sand
- colord({ r: 100, g: 180, b: 160 }), // Aquamarine
- colord({ r: 210, g: 160, b: 200 }), // Orchid
- colord({ r: 170, g: 190, b: 100 }), // Yellow Green
- colord({ r: 100, g: 130, b: 150 }), // Steel Blue
- colord({ r: 230, g: 140, b: 140 }), // Salmon
- colord({ r: 140, g: 180, b: 220 }), // Light Blue
- colord({ r: 200, g: 160, b: 110 }), // Tan
- colord({ r: 180, g: 130, b: 180 }), // Plum
- colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
- colord({ r: 220, g: 120, b: 120 }), // Coral
- colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
- colord({ r: 200, g: 200, b: 140 }), // Khaki
- colord({ r: 160, g: 120, b: 160 }), // Purple Gray
- colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
- colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
- colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
- colord({ r: 190, g: 180, b: 160 }), // Tan Gray
- colord({ r: 170, g: 140, b: 190 }), // Medium Purple
- colord({ r: 160, g: 190, b: 160 }), // Pale Green
- colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
- colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
- colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
- colord({ r: 150, g: 130, b: 150 }), // Thistle
- colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
- colord({ r: 190, g: 140, b: 150 }), // Puce
- colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
- colord({ r: 180, g: 160, b: 180 }), // Mauve
- colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
- colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
- ];
-
- private humanColors: Colord[] = [
- // Original set
- colord({ r: 235, g: 75, b: 75 }), // Bright Red
- colord({ r: 67, g: 190, b: 84 }), // Fresh Green
- colord({ r: 59, g: 130, b: 246 }), // Royal Blue
- colord({ r: 245, g: 158, b: 11 }), // Amber
- colord({ r: 236, g: 72, b: 153 }), // Deep Pink
- colord({ r: 48, g: 178, b: 180 }), // Teal
- colord({ r: 168, g: 85, b: 247 }), // Vibrant Purple
- colord({ r: 251, g: 191, b: 36 }), // Marigold
- colord({ r: 74, g: 222, b: 128 }), // Mint
- colord({ r: 239, g: 68, b: 68 }), // Crimson
- colord({ r: 34, g: 197, b: 94 }), // Emerald
- colord({ r: 96, g: 165, b: 250 }), // Sky Blue
- colord({ r: 249, g: 115, b: 22 }), // Tangerine
- colord({ r: 192, g: 132, b: 252 }), // Lavender
- colord({ r: 45, g: 212, b: 191 }), // Turquoise
- colord({ r: 244, g: 114, b: 182 }), // Rose
- colord({ r: 132, g: 204, b: 22 }), // Lime
- colord({ r: 56, g: 189, b: 248 }), // Light Blue
- colord({ r: 234, g: 179, b: 8 }), // Sunflower
- colord({ r: 217, g: 70, b: 239 }), // Fuchsia
- colord({ r: 16, g: 185, b: 129 }), // Sea Green
- colord({ r: 251, g: 146, b: 60 }), // Light Orange
- colord({ r: 147, g: 51, b: 234 }), // Bright Purple
- colord({ r: 79, g: 70, b: 229 }), // Indigo
- colord({ r: 245, g: 101, b: 101 }), // Coral
- colord({ r: 134, g: 239, b: 172 }), // Light Green
- colord({ r: 59, g: 130, b: 246 }), // Cerulean
- colord({ r: 253, g: 164, b: 175 }), // Salmon Pink
- colord({ r: 147, g: 197, b: 253 }), // Powder Blue
- colord({ r: 252, g: 211, b: 77 }), // Golden
- colord({ r: 190, g: 92, b: 251 }), // Amethyst
- colord({ r: 82, g: 183, b: 136 }), // Jade
- colord({ r: 248, g: 113, b: 113 }), // Warm Red
- colord({ r: 99, g: 202, b: 253 }), // Azure
- colord({ r: 240, g: 171, b: 252 }), // Orchid
- colord({ r: 163, g: 230, b: 53 }), // Yellow Green
- colord({ r: 234, g: 88, b: 12 }), // Burnt Orange
- colord({ r: 125, g: 211, b: 252 }), // Crystal Blue
- colord({ r: 251, g: 113, b: 133 }), // Watermelon
- colord({ r: 52, g: 211, b: 153 }), // Spearmint
- colord({ r: 167, g: 139, b: 250 }), // Periwinkle
- colord({ r: 245, g: 158, b: 11 }), // Honey
- colord({ r: 110, g: 231, b: 183 }), // Seafoam
- colord({ r: 233, g: 213, b: 255 }), // Light Lilac
- colord({ r: 202, g: 138, b: 4 }), // Rich Gold
- colord({ r: 151, g: 255, b: 187 }), // Fresh Mint
- colord({ r: 220, g: 38, b: 38 }), // Ruby
- colord({ r: 124, g: 58, b: 237 }), // Royal Purple
- colord({ r: 45, g: 212, b: 191 }), // Ocean
- colord({ r: 252, g: 165, b: 165 }), // Peach
-
- // Additional 50 colors
- colord({ r: 179, g: 136, b: 255 }), // Light Purple
- colord({ r: 133, g: 77, b: 14 }), // Chocolate
- colord({ r: 52, g: 211, b: 153 }), // Aquamarine
- colord({ r: 234, g: 179, b: 8 }), // Mustard
- colord({ r: 236, g: 72, b: 153 }), // Hot Pink
- colord({ r: 147, g: 197, b: 253 }), // Sky
- colord({ r: 249, g: 115, b: 22 }), // Pumpkin
- colord({ r: 167, g: 139, b: 250 }), // Iris
- colord({ r: 16, g: 185, b: 129 }), // Pine
- colord({ r: 251, g: 146, b: 60 }), // Mango
- colord({ r: 192, g: 132, b: 252 }), // Wisteria
- colord({ r: 79, g: 70, b: 229 }), // Sapphire
- colord({ r: 245, g: 101, b: 101 }), // Salmon
- colord({ r: 134, g: 239, b: 172 }), // Spring Green
- colord({ r: 59, g: 130, b: 246 }), // Ocean Blue
- colord({ r: 253, g: 164, b: 175 }), // Rose Gold
- colord({ r: 16, g: 185, b: 129 }), // Forest
- colord({ r: 252, g: 211, b: 77 }), // Sunshine
- colord({ r: 190, g: 92, b: 251 }), // Grape
- colord({ r: 82, g: 183, b: 136 }), // Eucalyptus
- colord({ r: 248, g: 113, b: 113 }), // Cherry
- colord({ r: 99, g: 202, b: 253 }), // Arctic
- colord({ r: 240, g: 171, b: 252 }), // Lilac
- colord({ r: 163, g: 230, b: 53 }), // Chartreuse
- colord({ r: 234, g: 88, b: 12 }), // Rust
- colord({ r: 125, g: 211, b: 252 }), // Ice Blue
- colord({ r: 251, g: 113, b: 133 }), // Strawberry
- colord({ r: 52, g: 211, b: 153 }), // Sage
- colord({ r: 167, g: 139, b: 250 }), // Violet
- colord({ r: 245, g: 158, b: 11 }), // Apricot
- colord({ r: 110, g: 231, b: 183 }), // Mint Green
- colord({ r: 233, g: 213, b: 255 }), // Thistle
- colord({ r: 202, g: 138, b: 4 }), // Bronze
- colord({ r: 151, g: 255, b: 187 }), // Pistachio
- colord({ r: 220, g: 38, b: 38 }), // Fire Engine
- colord({ r: 124, g: 58, b: 237 }), // Electric Purple
- colord({ r: 45, g: 212, b: 191 }), // Caribbean
- colord({ r: 252, g: 165, b: 165 }), // Melon
- colord({ r: 168, g: 85, b: 247 }), // Byzantium
- colord({ r: 74, g: 222, b: 128 }), // Kelly Green
- colord({ r: 239, g: 68, b: 68 }), // Cardinal
- colord({ r: 34, g: 197, b: 94 }), // Shamrock
- colord({ r: 96, g: 165, b: 250 }), // Marina
- colord({ r: 249, g: 115, b: 22 }), // Carrot
- colord({ r: 192, g: 132, b: 252 }), // Heliotrope
- colord({ r: 45, g: 212, b: 191 }), // Lagoon
- colord({ r: 244, g: 114, b: 182 }), // Bubble Gum
- colord({ r: 132, g: 204, b: 22 }), // Apple
- colord({ r: 56, g: 189, b: 248 }), // Electric Blue
- colord({ r: 234, g: 179, b: 8 }), // Daffodil
- ];
-
private _selfColor = colord({ r: 0, g: 255, b: 0 });
private _allyColor = colord({ r: 255, g: 255, b: 0 });
private _enemyColor = colord({ r: 255, g: 0, b: 0 });
private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 });
- territoryColor(playerInfo: PlayerInfo): Colord {
- if (playerInfo.playerType == PlayerType.Human) {
- return this.humanColors[
- simpleHash(playerInfo.name) % this.humanColors.length
- ];
+ territoryColor(player: PlayerView): Colord {
+ if (player.teamName() == TeamName.Red) {
+ return redShades[simpleHash(player.id()) % redShades.length];
}
- return this.territoryColors[
- simpleHash(playerInfo.name) % this.territoryColors.length
- ];
+ if (player.teamName() == TeamName.Blue) {
+ return blueShades[simpleHash(player.id()) % blueShades.length];
+ }
+ if (player.info().playerType == PlayerType.Human) {
+ return humanColors[simpleHash(player.id()) % humanColors.length];
+ }
+ return territoryColors[simpleHash(player.id()) % territoryColors.length];
}
- textColor(playerInfo: PlayerInfo): string {
- return playerInfo.playerType == PlayerType.Human ? "#000000" : "#4D4D4D";
+ textColor(player: PlayerView): string {
+ return player.info().playerType == PlayerType.Human ? "#000000" : "#4D4D4D";
}
- specialBuildingColor(playerInfo: PlayerInfo): Colord {
- const tc = this.territoryColor(playerInfo).rgba;
+ specialBuildingColor(player: PlayerView): Colord {
+ const tc = this.territoryColor(player).rgba;
return colord({
r: Math.max(tc.r - 50, 0),
g: Math.max(tc.g - 50, 0),
@@ -265,16 +55,16 @@ export const pastelTheme = new (class implements Theme {
});
}
- borderColor(playerInfo: PlayerInfo): Colord {
- const tc = this.territoryColor(playerInfo).rgba;
+ borderColor(player: PlayerView): Colord {
+ const tc = this.territoryColor(player).rgba;
return colord({
r: Math.max(tc.r - 40, 0),
g: Math.max(tc.g - 40, 0),
b: Math.max(tc.b - 40, 0),
});
}
- defendedBorderColor(playerInfo: PlayerInfo): Colord {
- const bc = this.borderColor(playerInfo).rgba;
+ defendedBorderColor(player: PlayerView): Colord {
+ const bc = this.borderColor(player).rgba;
return colord({
r: Math.max(bc.r - 40, 0),
g: Math.max(bc.g - 40, 0),
diff --git a/src/core/configuration/PastelThemeDark.ts b/src/core/configuration/PastelThemeDark.ts
index a7b030889..725e6b283 100644
--- a/src/core/configuration/PastelThemeDark.ts
+++ b/src/core/configuration/PastelThemeDark.ts
@@ -241,23 +241,23 @@ export const pastelThemeDark = new (class implements Theme {
private _spawnHighlightColor = colord({ r: 255, g: 213, b: 79 });
- territoryColor(playerInfo: PlayerInfo): Colord {
- if (playerInfo.playerType == PlayerType.Human) {
+ territoryColor(player: PlayerView): Colord {
+ if (player.info().playerType == PlayerType.Human) {
return this.humanColors[
- simpleHash(playerInfo.name) % this.humanColors.length
+ simpleHash(player.info().name) % this.humanColors.length
];
}
return this.territoryColors[
- simpleHash(playerInfo.name) % this.territoryColors.length
+ simpleHash(player.info().name) % this.territoryColors.length
];
}
- textColor(playerInfo: PlayerInfo): string {
- return playerInfo.playerType == PlayerType.Human ? "#ffffff" : "#e6e6e6";
+ textColor(player: PlayerView): string {
+ return player.info().playerType == PlayerType.Human ? "#ffffff" : "#e6e6e6";
}
- specialBuildingColor(playerInfo: PlayerInfo): Colord {
- const tc = this.territoryColor(playerInfo).rgba;
+ specialBuildingColor(player: PlayerView): Colord {
+ const tc = this.territoryColor(player).rgba;
return colord({
r: Math.max(tc.r - 50, 0),
g: Math.max(tc.g - 50, 0),
@@ -265,16 +265,16 @@ export const pastelThemeDark = new (class implements Theme {
});
}
- borderColor(playerInfo: PlayerInfo): Colord {
- const tc = this.territoryColor(playerInfo).rgba;
+ borderColor(player: PlayerView): Colord {
+ const tc = this.territoryColor(player).rgba;
return colord({
r: Math.max(tc.r - 40, 0),
g: Math.max(tc.g - 40, 0),
b: Math.max(tc.b - 40, 0),
});
}
- defendedBorderColor(playerInfo: PlayerInfo): Colord {
- const bc = this.borderColor(playerInfo).rgba;
+ defendedBorderColor(player: PlayerView): Colord {
+ const bc = this.borderColor(player).rgba;
return colord({
r: Math.max(bc.r - 40, 0),
g: Math.max(bc.g - 40, 0),
diff --git a/src/core/execution/BotExecution.ts b/src/core/execution/BotExecution.ts
index 956fb90a8..cda58140b 100644
--- a/src/core/execution/BotExecution.ts
+++ b/src/core/execution/BotExecution.ts
@@ -54,7 +54,7 @@ export class BotExecution implements Execution {
.filter((n) => n.isPlayer() && n.isTraitor()) as Player[];
if (traitors.length > 0) {
const toAttack = this.random.randElement(traitors);
- const odds = this.bot.isAlliedWith(toAttack) ? 6 : 3;
+ const odds = this.bot.isFriendly(toAttack) ? 6 : 3;
if (this.random.chance(odds)) {
this.sendAttack(toAttack);
return;
@@ -85,7 +85,7 @@ export class BotExecution implements Execution {
const owner = this.mg.owner(toAttack);
if (owner.isPlayer()) {
- if (this.bot.isAlliedWith(owner)) {
+ if (this.bot.isFriendly(owner)) {
return;
}
if (owner.type() == PlayerType.FakeHuman) {
diff --git a/src/core/execution/FakeHumanExecution.ts b/src/core/execution/FakeHumanExecution.ts
index 87a5eea29..8b90528da 100644
--- a/src/core/execution/FakeHumanExecution.ts
+++ b/src/core/execution/FakeHumanExecution.ts
@@ -192,7 +192,7 @@ export class FakeHumanExecution implements Execution {
}
private shouldAttack(other: Player): boolean {
- if (this.player.isAlliedWith(other)) {
+ if (this.player.isFriendly(other)) {
if (this.shouldDiscourageAttack(other)) {
return this.random.chance(200);
}
@@ -271,12 +271,11 @@ export class FakeHumanExecution implements Execution {
}
}
- if (this.player.isAlliedWith(this.enemy)) {
- this.enemy = null;
- return;
- }
-
if (this.enemy) {
+ if (this.player.isFriendly(this.enemy)) {
+ this.enemy = null;
+ return;
+ }
this.maybeSendNuke(this.enemy);
if (this.player.sharesBorderWith(this.enemy)) {
this.sendAttack(this.enemy);
@@ -537,7 +536,7 @@ export class FakeHumanExecution implements Execution {
}
if (
this.mg.owner(dst).isPlayer() &&
- this.player.isAlliedWith(this.mg.owner(dst) as Player)
+ this.player.isFriendly(this.mg.owner(dst) as Player)
) {
continue;
}
diff --git a/src/core/execution/PlayerExecution.ts b/src/core/execution/PlayerExecution.ts
index 4b0a5a64b..0a5d55864 100644
--- a/src/core/execution/PlayerExecution.ts
+++ b/src/core/execution/PlayerExecution.ts
@@ -116,7 +116,7 @@ export class PlayerExecution implements Execution {
const main = clusters.shift();
this.player.largestClusterBoundingBox = calculateBoundingBox(this.mg, main);
const surroundedBy = this.surroundedBySamePlayer(main);
- if (surroundedBy && !this.player.isAlliedWith(surroundedBy)) {
+ if (surroundedBy && !this.player.isFriendly(surroundedBy)) {
this.removeCluster(main);
}
diff --git a/src/core/execution/SAMLauncherExecution.ts b/src/core/execution/SAMLauncherExecution.ts
index 50d8e6a90..16b241bbd 100644
--- a/src/core/execution/SAMLauncherExecution.ts
+++ b/src/core/execution/SAMLauncherExecution.ts
@@ -72,8 +72,7 @@ export class SAMLauncherExecution implements Execution {
])
.filter(
({ unit }) =>
- unit.owner() !== this.player &&
- !unit.owner().isAlliedWith(this.player),
+ unit.owner() !== this.player && !this.player.isFriendly(unit.owner()),
);
this.target =
diff --git a/src/core/execution/TransportShipExecution.ts b/src/core/execution/TransportShipExecution.ts
index 14eaa6419..1ba358e19 100644
--- a/src/core/execution/TransportShipExecution.ts
+++ b/src/core/execution/TransportShipExecution.ts
@@ -152,7 +152,7 @@ export class TransportShipExecution implements Execution {
this.active = false;
return;
}
- if (this.target.isPlayer() && this.attacker.isAlliedWith(this.target)) {
+ if (this.target.isPlayer() && this.attacker.isFriendly(this.target)) {
this.target.addTroops(this.troops);
} else {
this.attacker.conquer(this.dst);
diff --git a/src/core/execution/WarshipExecution.ts b/src/core/execution/WarshipExecution.ts
index ddaf65c02..c958b4d20 100644
--- a/src/core/execution/WarshipExecution.ts
+++ b/src/core/execution/WarshipExecution.ts
@@ -146,7 +146,7 @@ export class WarshipExecution implements Execution {
({ unit }) =>
unit.owner() !== this.warship.owner() &&
unit !== this.warship &&
- !unit.owner().isAlliedWith(this.warship.owner()) &&
+ !unit.owner().isFriendly(this.warship.owner()) &&
!this.alreadySentShell.has(unit) &&
(unit.type() !== UnitType.TradeShip || hasPort) &&
(unit.type() !== UnitType.TradeShip ||
diff --git a/src/core/execution/WinCheckExecution.ts b/src/core/execution/WinCheckExecution.ts
index 1b2645c02..edf5a1e5d 100644
--- a/src/core/execution/WinCheckExecution.ts
+++ b/src/core/execution/WinCheckExecution.ts
@@ -1,5 +1,5 @@
import { EventBus, GameEvent } from "../EventBus";
-import { Execution, Game, Player, PlayerID } from "../game/Game";
+import { Execution, Game, GameMode, Player, Team } from "../game/Game";
export class WinEvent implements GameEvent {
constructor(public readonly winner: Player) {}
@@ -20,6 +20,14 @@ export class WinCheckExecution implements Execution {
if (ticks % 10 != 0) {
return;
}
+ if (this.mg.config().gameConfig().gameMode == GameMode.FFA) {
+ this.checkWinnerFFA();
+ } else {
+ this.checkWinnerTeam();
+ }
+ }
+
+ checkWinnerFFA(): void {
const sorted = this.mg
.players()
.sort((a, b) => b.numTilesOwned() - a.numTilesOwned());
@@ -39,6 +47,30 @@ export class WinCheckExecution implements Execution {
}
}
+ checkWinnerTeam(): void {
+ const teamToTiles = new Map();
+ for (const player of this.mg.players()) {
+ teamToTiles.set(
+ player.team(),
+ (teamToTiles.get(player.team()) ?? 0) + player.numTilesOwned(),
+ );
+ }
+ const sorted = Array.from(teamToTiles.entries()).sort(
+ (a, b) => b[1] - a[1],
+ );
+ if (sorted.length == 0) {
+ return;
+ }
+ const max = sorted[0];
+ const numTilesWithoutFallout =
+ this.mg.numLandTiles() - this.mg.numTilesWithFallout();
+ const percentage = (max[1] / numTilesWithoutFallout) * 100;
+ if (percentage > this.mg.config().percentageTilesOwnedToWin()) {
+ this.mg.setWinner(max[0].name, this.mg.stats().stats());
+ console.log(`${max[0].name} has won the game`);
+ this.active = false;
+ }
+ }
owner(): Player {
return null;
}
diff --git a/src/core/execution/alliance/AllianceRequestExecution.ts b/src/core/execution/alliance/AllianceRequestExecution.ts
index 21128d840..3703c96ba 100644
--- a/src/core/execution/alliance/AllianceRequestExecution.ts
+++ b/src/core/execution/alliance/AllianceRequestExecution.ts
@@ -40,7 +40,7 @@ export class AllianceRequestExecution implements Execution {
}
tick(ticks: number): void {
- if (this.requestor.isAlliedWith(this.recipient)) {
+ if (this.requestor.isFriendly(this.recipient)) {
consolex.warn("already allied");
} else if (!this.requestor.canSendAllianceRequest(this.recipient)) {
consolex.warn("recent or pending alliance request");
diff --git a/src/core/execution/alliance/AllianceRequestReplyExecution.ts b/src/core/execution/alliance/AllianceRequestReplyExecution.ts
index d0c0d4688..04a79b316 100644
--- a/src/core/execution/alliance/AllianceRequestReplyExecution.ts
+++ b/src/core/execution/alliance/AllianceRequestReplyExecution.ts
@@ -40,7 +40,7 @@ export class AllianceRequestReplyExecution implements Execution {
}
tick(ticks: number): void {
- if (this.requestor.isAlliedWith(this.recipient)) {
+ if (this.requestor.isFriendly(this.recipient)) {
consolex.warn("already allied");
} else {
const request = this.requestor
diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts
index 201ede85a..4a920164d 100644
--- a/src/core/game/Game.ts
+++ b/src/core/game/Game.ts
@@ -38,6 +38,11 @@ export enum Difficulty {
Impossible = "Impossible",
}
+export enum TeamName {
+ Red = "Red",
+ Blue = "Blue",
+}
+
export enum GameMapType {
World = "World",
Europe = "Europe",
@@ -62,6 +67,15 @@ export enum GameType {
Private = "Private",
}
+export enum GameMode {
+ FFA = "Free For All",
+ Team = "Team",
+}
+
+export interface Team {
+ name: TeamName;
+}
+
export interface UnitInfo {
cost: (player: Player | PlayerView) => Gold;
// Determines if its owner changes when its tile is conquered.
@@ -95,6 +109,7 @@ export const nukeTypes = [
UnitType.MIRVWarhead,
UnitType.MIRV,
] as UnitType[];
+
export type NukeType = (typeof nukeTypes)[number];
export enum Relation {
@@ -327,8 +342,10 @@ export interface Player {
allRelationsSorted(): { player: Player; relation: Relation }[];
updateRelation(other: Player, delta: number): void;
decayRelations(): void;
-
- // Alliances
+ isOnSameTeam(other: Player): boolean;
+ // Either allied or on same team.
+ isFriendly(other: Player): boolean;
+ team(): Team | null;
incomingAllianceRequests(): AllianceRequest[];
outgoingAllianceRequests(): AllianceRequest[];
alliances(): MutableAlliance[];
@@ -400,11 +417,13 @@ export interface Game extends GameMap {
terraNullius(): TerraNullius;
owner(ref: TileRef): Player | TerraNullius;
+ teams(): Team[];
+
// Game State
ticks(): Tick;
inSpawnPhase(): boolean;
executeNextTick(): GameUpdates;
- setWinner(winner: Player, allPlayersStats: AllPlayersStats): void;
+ setWinner(winner: Player | TeamName, allPlayersStats: AllPlayersStats): void;
config(): Config;
// Units
diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts
index e80d0bc72..6725bcd2e 100644
--- a/src/core/game/GameImpl.ts
+++ b/src/core/game/GameImpl.ts
@@ -16,6 +16,9 @@ import {
GameUpdates,
TerrainType,
EmojiMessage,
+ Team,
+ GameMode,
+ TeamName,
} from "./Game";
import { GameUpdate } from "./GameUpdates";
import { GameUpdateType } from "./GameUpdates";
@@ -32,6 +35,7 @@ import { GameMap, GameMapImpl, TileRef, TileUpdate } from "./GameMap";
import { UnitGrid } from "./UnitGrid";
import { StatsImpl } from "./StatsImpl";
import { Stats } from "./Stats";
+import { simpleHash } from "../Util";
export function createGame(
gameMap: GameMap,
@@ -70,6 +74,8 @@ export class GameImpl implements Game {
private _stats: StatsImpl = new StatsImpl();
+ private teams_: Team[] = [];
+
constructor(
private _map: GameMap,
private miniGameMap: GameMap,
@@ -89,6 +95,9 @@ export class GameImpl implements Game {
),
);
this.unitGrid = new UnitGrid(this._map);
+ if (this._config.gameConfig().gameMode == GameMode.Team) {
+ this.teams_ = [{ name: TeamName.Red }, { name: TeamName.Blue }];
+ }
}
isOnEdgeOfMap(ref: TileRef): boolean {
return this._map.isOnEdgeOfMap(ref);
@@ -322,6 +331,7 @@ export class GameImpl implements Game {
this.nextPlayerID,
playerInfo,
manpower,
+ this.maybeAssignTeam(playerInfo),
);
this._playersBySmallID.push(player);
this.nextPlayerID++;
@@ -329,6 +339,14 @@ export class GameImpl implements Game {
return player;
}
+ private maybeAssignTeam(player: PlayerInfo): Team | null {
+ if (this._config.gameConfig().gameMode != GameMode.Team) {
+ return null;
+ }
+ const rand = simpleHash(player.id);
+ return this.teams_[rand % this.teams_.length];
+ }
+
player(id: PlayerID | null): Player {
if (!this._players.has(id)) {
throw new Error(`Player with id ${id} not found`);
@@ -513,14 +531,19 @@ export class GameImpl implements Game {
});
}
- setWinner(winner: Player, allPlayersStats: AllPlayersStats): void {
+ setWinner(winner: Player | TeamName, allPlayersStats: AllPlayersStats): void {
this.addUpdate({
type: GameUpdateType.Win,
- winnerID: winner.smallID(),
+ winner: typeof winner === "string" ? winner : winner.smallID(),
+ winnerType: typeof winner === "string" ? "team" : "player",
allPlayersStats,
});
}
+ teams(): Team[] {
+ return this.teams_;
+ }
+
displayMessage(
message: string,
type: MessageType,
diff --git a/src/core/game/GameUpdates.ts b/src/core/game/GameUpdates.ts
index 9461a05b4..cea31f9f9 100644
--- a/src/core/game/GameUpdates.ts
+++ b/src/core/game/GameUpdates.ts
@@ -8,6 +8,7 @@ import {
NameViewData,
PlayerID,
PlayerType,
+ TeamName,
Tick,
UnitType,
} from "./Game";
@@ -93,6 +94,7 @@ export interface PlayerUpdate {
name: string;
displayName: string;
id: PlayerID;
+ teamName?: TeamName;
smallID: number;
playerType: PlayerType;
isAlive: boolean;
@@ -159,7 +161,9 @@ export interface DisplayMessageUpdate {
export interface WinUpdate {
type: GameUpdateType.Win;
allPlayersStats: AllPlayersStats;
- winnerID: number;
+ // Player id or team name.
+ winner: number | TeamName;
+ winnerType: "player" | "team";
}
export interface HashUpdate {
diff --git a/src/core/game/GameView.ts b/src/core/game/GameView.ts
index 3a4a75936..f598d816a 100644
--- a/src/core/game/GameView.ts
+++ b/src/core/game/GameView.ts
@@ -6,6 +6,7 @@ import {
Player,
PlayerActions,
PlayerProfile,
+ TeamName,
} from "./Game";
import { AttackUpdate, PlayerUpdate } from "./GameUpdates";
import { UnitUpdate } from "./GameUpdates";
@@ -168,6 +169,9 @@ export class PlayerView {
id(): PlayerID {
return this.data.id;
}
+ teamName(): TeamName {
+ return this.data.teamName;
+ }
type(): PlayerType {
return this.data.playerType;
}
@@ -210,6 +214,16 @@ export class PlayerView {
return this.data.allies.some((n) => other.smallID() == n);
}
+ isOnSameTeam(other: PlayerView): boolean {
+ return (
+ this.data.teamName != null && this.data.teamName == other.data.teamName
+ );
+ }
+
+ isFriendly(other: PlayerView): boolean {
+ return this.isAlliedWith(other) || this.isOnSameTeam(other);
+ }
+
isRequestingAllianceWith(other: PlayerView) {
return this.data.outgoingAllianceRequests.some((id) => other.id() == id);
}
diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts
index 56cd55fce..6bcb93076 100644
--- a/src/core/game/PlayerImpl.ts
+++ b/src/core/game/PlayerImpl.ts
@@ -19,6 +19,7 @@ import {
PlayerProfile,
Attack,
UnitSpecificInfos,
+ Team,
} from "./Game";
import { AttackUpdate, PlayerUpdate } from "./GameUpdates";
import { GameUpdateType } from "./GameUpdates";
@@ -100,6 +101,7 @@ export class PlayerImpl implements Player {
private _smallID: number,
private readonly playerInfo: PlayerInfo,
startTroops: number,
+ private _team: Team | null,
) {
this._flag = playerInfo.flag;
this._name = sanitizeUsername(playerInfo.name);
@@ -125,6 +127,7 @@ export class PlayerImpl implements Player {
name: this.name(),
displayName: this.displayName(),
id: this.id(),
+ teamName: this.team()?.name,
smallID: this.smallID(),
playerType: this.type(),
isAlive: this.isAlive(),
@@ -324,7 +327,7 @@ export class PlayerImpl implements Player {
if (other == this) {
return false;
}
- if (this.isAlliedWith(other)) {
+ if (this.isFriendly(other)) {
return false;
}
@@ -426,7 +429,7 @@ export class PlayerImpl implements Player {
if (this == other) {
return false;
}
- if (this.isAlliedWith(other)) {
+ if (this.isFriendly(other)) {
return false;
}
for (const t of this.targets_) {
@@ -500,7 +503,7 @@ export class PlayerImpl implements Player {
}
canDonate(recipient: Player): boolean {
- if (!this.isAlliedWith(recipient)) {
+ if (!this.isFriendly(recipient)) {
return false;
}
for (const donation of this.sentDonations) {
@@ -555,6 +558,24 @@ export class PlayerImpl implements Player {
.filter((other) => other != this && this.canTrade(other));
}
+ team(): Team | null {
+ return this._team;
+ }
+
+ isOnSameTeam(other: Player): boolean {
+ if (other == this) {
+ return false;
+ }
+ if (this.team() == null || other.team() == null) {
+ return false;
+ }
+ return this._team == other.team();
+ }
+
+ isFriendly(other: Player): boolean {
+ return this.isOnSameTeam(other) || this.isAlliedWith(other);
+ }
+
gold(): Gold {
return Number(this._gold);
}
@@ -837,7 +858,7 @@ export class PlayerImpl implements Player {
if (other == this) {
return false;
}
- if (other.isPlayer() && this.allianceWith(other)) {
+ if (other.isPlayer() && this.isFriendly(other)) {
return false;
}
@@ -930,12 +951,13 @@ export class PlayerImpl implements Player {
if (this.mg.owner(tile) == this) {
return false;
}
- if (
- this.mg.hasOwner(tile) &&
- this.isAlliedWith(this.mg.owner(tile) as Player)
- ) {
- return false;
+ if (this.mg.hasOwner(tile)) {
+ const other = this.mg.owner(tile) as Player;
+ if (this.isFriendly(other)) {
+ return false;
+ }
}
+
if (!this.mg.isLand(tile)) {
return false;
}
diff --git a/src/server/GameManager.ts b/src/server/GameManager.ts
index 08049c22a..ec88ec343 100644
--- a/src/server/GameManager.ts
+++ b/src/server/GameManager.ts
@@ -2,7 +2,7 @@ import { ServerConfig } from "../core/configuration/Config";
import { GameConfig, GameID } from "../core/Schemas";
import { Client } from "./Client";
import { GamePhase, GameServer } from "./GameServer";
-import { Difficulty, GameMapType, GameType } from "../core/game/Game";
+import { Difficulty, GameMapType, GameMode, GameType } from "../core/game/Game";
import { Logger } from "winston";
export class GameManager {
@@ -38,6 +38,7 @@ export class GameManager {
infiniteGold: false,
infiniteTroops: false,
instantBuild: false,
+ gameMode: GameMode.FFA,
bots: 400,
...gameConfig,
});
diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts
index 2663186c7..b133191e1 100644
--- a/src/server/GameServer.ts
+++ b/src/server/GameServer.ts
@@ -5,6 +5,7 @@ import {
ClientID,
ClientMessage,
ClientMessageSchema,
+ ClientSendWinnerMessage,
GameConfig,
GameInfo,
Intent,
@@ -45,7 +46,7 @@ export class GameServer {
private lastPingUpdate = 0;
- private winner: ClientID | null = null;
+ private winner: ClientSendWinnerMessage = null;
// This field is currently only filled at victory
private allPlayersStats: AllPlayersStats = {};
@@ -86,6 +87,9 @@ export class GameServer {
if (gameConfig.instantBuild != null) {
this.gameConfig.instantBuild = gameConfig.instantBuild;
}
+ if (gameConfig.gameMode != null) {
+ this.gameConfig.gameMode = gameConfig.gameMode;
+ }
}
public addClient(client: Client, lastTurn: number) {
@@ -171,7 +175,7 @@ export class GameServer {
client.hashes.set(clientMsg.turnNumber, clientMsg.hash);
}
if (clientMsg.type == "winner") {
- this.winner = clientMsg.winner;
+ this.winner = clientMsg;
this.allPlayersStats = clientMsg.allPlayersStats;
}
} catch (error) {
@@ -318,7 +322,8 @@ export class GameServer {
this.turns,
this._startTime,
Date.now(),
- this.winner,
+ this.winner.winner,
+ this.winner.winnerType,
this.allPlayersStats,
),
);
diff --git a/src/server/Worker.ts b/src/server/Worker.ts
index d4def7141..04180e072 100644
--- a/src/server/Worker.ts
+++ b/src/server/Worker.ts
@@ -165,6 +165,7 @@ export function startWorker() {
bots: req.body.bots,
disableNPCs: req.body.disableNPCs,
disableNukes: req.body.disableNukes,
+ gameMode: req.body.gameMode,
});
res.status(200).json({ success: true });
}),