This commit is contained in:
evanpelle
2025-03-27 20:43:56 -07:00
committed by GitHub
parent 9ed1fe865c
commit d8fe41de7a
37 changed files with 767 additions and 387 deletions
+5
View File
@@ -138,6 +138,7 @@
"title": "Private Lobby",
"map": "Map",
"difficulty": "Difficulty",
"mode": "Mode",
"options_title": "Options",
"bots": "Bots: ",
"bots_disabled": "Disabled",
@@ -156,5 +157,9 @@
"Balanced": "Balanced",
"Intense": "Intense",
"Impossible": "Impossible"
},
"game_mode": {
"ffa": "Free for All",
"teams": "Teams"
}
}
+18 -2
View File
@@ -1,4 +1,10 @@
import { PlayerID, GameMapType, Difficulty, GameType } from "../core/game/Game";
import {
PlayerID,
GameMapType,
Difficulty,
GameType,
TeamName,
} from "../core/game/Game";
import { EventBus } from "../core/EventBus";
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
import { InputHandler, MouseUpEvent } from "./InputHandler";
@@ -165,6 +171,15 @@ export class ClientGameRunner {
clientID: this.lobby.clientID,
},
];
let winner: ClientID | TeamName | null = null;
if (update.winnerType == "player") {
winner = this.gameView
.playerBySmallID(update.winner as number)
.clientID();
} else {
winner = update.winner as TeamName;
}
const record = createGameRecord(
this.lobby.gameID,
this.lobby.gameConfig,
@@ -173,7 +188,8 @@ export class ClientGameRunner {
[],
LocalPersistantStats.startTime(),
Date.now(),
this.gameView.playerBySmallID(update.winnerID).id(),
winner,
update.winnerType,
update.allPlayersStats,
);
LocalPersistantStats.endGame(record);
+83 -53
View File
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement, query, property, state } from "lit/decorators.js";
import { Difficulty, GameMapType, GameType } from "../core/game/Game";
import { Difficulty, GameMapType, GameMode, GameType } from "../core/game/Game";
import { GameConfig, GameInfo } from "../core/Schemas";
import { consolex } from "../core/Consolex";
import "./components/Difficulties";
@@ -22,6 +22,7 @@ export class HostLobbyModal extends LitElement {
@state() private selectedMap: GameMapType = GameMapType.World;
@state() private selectedDifficulty: Difficulty = Difficulty.Medium;
@state() private disableNPCs = false;
@state() private gameMode: GameMode = GameMode.FFA;
@state() private disableNukes: boolean = false;
@state() private bots: number = 400;
@state() private infiniteGold: boolean = false;
@@ -135,31 +136,54 @@ export class HostLobbyModal extends LitElement {
</div>
</div>
<!-- Game Options -->
<div class="options-section">
<div class="option-title">
${translateText("host_modal.options_title")}
<!-- Game Mode Selection -->
<div class="options-section">
<div class="option-title">${translateText("host_modal.mode")}</div>
<div class="option-cards">
<div
class="option-card ${this.gameMode === GameMode.FFA ? "selected" : ""}"
@click=${() => this.handleGameModeSelection(GameMode.FFA)}
>
<div class="option-card-title">
${translateText("game_mode.ffa")}
</div>
</div>
<div class="option-cards">
<label for="bots-count" class="option-card">
<input
type="range"
id="bots-count"
min="0"
max="400"
step="1"
@input=${this.handleBotsChange}
@change=${this.handleBotsChange}
.value="${String(this.bots)}"
/>
<div class="option-card-title">
<span>${translateText("host_modal.bots")}</span>${
this.bots == 0
? translateText("host_modal.bots_disabled")
: this.bots
}
</div>
</label>
<div
class="option-card ${this.gameMode === GameMode.Team ? "selected" : ""}"
@click=${() => this.handleGameModeSelection(GameMode.Team)}
>
<div class="option-card-title">
${translateText("game_mode.teams")}
</div>
</div>
</div>
</div>
<!-- Game Options -->
<div class="options-section">
<div class="option-title">
${translateText("host_modal.options_title")}
</div>
<div class="option-cards">
<label for="bots-count" class="option-card">
<input
type="range"
id="bots-count"
min="0"
max="400"
step="1"
@input=${this.handleBotsChange}
@change=${this.handleBotsChange}
.value="${String(this.bots)}"
/>
<div class="option-card-title">
<span>${translateText("host_modal.bots")}</span>${
this.bots == 0
? translateText("host_modal.bots_disabled")
: this.bots
}
</div>
</label>
<label
for="host-modal-disable-npcs"
@@ -244,37 +268,37 @@ export class HostLobbyModal extends LitElement {
</div>
</div>
<!-- Lobby Selection -->
<div class="options-section">
<div class="option-title">
${this.players.length}
${
this.players.length === 1
? translateText("host_modal.player")
: translateText("host_modal.players")
}
</div>
<div class="players-list">
${this.players.map(
(player) => html`<span class="player-tag">${player}</span>`,
)}
</div>
</div>
<button
@click=${this.startGame}
?disabled=${this.players.length < 2}
class="start-game-button"
>
<!-- Lobby Selection -->
<div class="options-section">
<div class="option-title">
${this.players.length}
${
this.players.length === 1
? translateText("host_modal.waiting")
: translateText("host_modal.start")
? translateText("host_modal.player")
: translateText("host_modal.players")
}
</button>
</div>
<div class="players-list">
${this.players.map(
(player) => html`<span class="player-tag">${player}</span>`,
)}
</div>
</div>
</o-modal>
<button
@click=${this.startGame}
?disabled=${this.players.length < 2}
class="start-game-button"
>
${
this.players.length === 1
? translateText("host_modal.waiting")
: translateText("host_modal.start")
}
</button>
</div>
</o-modal>
`;
}
@@ -380,6 +404,11 @@ export class HostLobbyModal extends LitElement {
this.putGameConfig();
}
private async handleGameModeSelection(value: GameMode) {
this.gameMode = value;
this.putGameConfig();
}
private async putGameConfig() {
const config = await getServerConfigFromClient();
const response = await fetch(
@@ -398,6 +427,7 @@ export class HostLobbyModal extends LitElement {
infiniteGold: this.infiniteGold,
infiniteTroops: this.infiniteTroops,
instantBuild: this.instantBuild,
gameMode: this.gameMode,
} as GameConfig),
},
);
@@ -456,7 +486,7 @@ export class HostLobbyModal extends LitElement {
})
.then((response) => response.json())
.then((data: GameInfo) => {
console.log(`got response: ${data}`);
console.log(`got game info response: ${JSON.stringify(data)}`);
this.players = data.clients.map((p) => p.username);
});
}
+5 -3
View File
@@ -6,6 +6,7 @@ import {
ClientID,
ClientMessage,
ClientMessageSchema,
ClientSendWinnerMessage,
GameConfig,
GameID,
GameRecordSchema,
@@ -33,7 +34,7 @@ export class LocalServer {
private paused = false;
private winner: ClientID | null = null;
private winner: ClientSendWinnerMessage = null;
private allPlayersStats: AllPlayersStats = {};
constructor(
@@ -124,7 +125,7 @@ export class LocalServer {
}
}
if (clientMsg.type == "winner") {
this.winner = clientMsg.winner;
this.winner = clientMsg;
this.allPlayersStats = clientMsg.allPlayersStats;
}
}
@@ -164,7 +165,8 @@ export class LocalServer {
this.turns,
this.startedAt,
Date.now(),
this.winner,
this.winner?.winner,
this.winner?.winnerType,
this.allPlayersStats,
);
if (!saveFullGame) {
+35 -2
View File
@@ -1,6 +1,6 @@
import { LitElement, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { Difficulty, GameMapType, GameType } from "../core/game/Game";
import { Difficulty, GameMapType, GameMode, GameType } from "../core/game/Game";
import { generateID as generateID } from "../core/Util";
import { consolex } from "../core/Consolex";
import "./components/Difficulties";
@@ -28,6 +28,7 @@ export class SinglePlayerModal extends LitElement {
@state() private infiniteTroops: boolean = false;
@state() private instantBuild: boolean = false;
@state() private useRandomMap: boolean = false;
@state() private gameMode: GameMode = GameMode.FFA;
render() {
return html`
@@ -107,6 +108,33 @@ export class SinglePlayerModal extends LitElement {
</div>
</div>
<!-- Game Mode Selection -->
<div class="options-section">
<div class="option-title">${translateText("host_modal.mode")}</div>
<div class="option-cards">
<div
class="option-card ${this.gameMode === GameMode.FFA
? "selected"
: ""}"
@click=${() => this.handleGameModeSelection(GameMode.FFA)}
>
<div class="option-card-title">
${translateText("game_mode.ffa")}
</div>
</div>
<div
class="option-card ${this.gameMode === GameMode.Team
? "selected"
: ""}"
@click=${() => this.handleGameModeSelection(GameMode.Team)}
>
<div class="option-card-title">
${translateText("game_mode.teams")}
</div>
</div>
</div>
</div>
<!-- Game Options -->
<div class="options-section">
<div class="option-title">
@@ -122,7 +150,7 @@ export class SinglePlayerModal extends LitElement {
step="1"
@input=${this.handleBotsChange}
@change=${this.handleBotsChange}
.value="${this.bots}"
.value="${String(this.bots)}"
/>
<div class="option-card-title">
<span>${translateText("single_modal.bots")}</span>${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,
+4 -1
View File
@@ -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 {
+1 -1
View File
@@ -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;
+3 -3
View File
@@ -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;
@@ -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`
<div class="p-2">
<div
class="text-bold text-sm lg:text-lg font-bold mb-1 inline-flex ${isAlly
class="text-bold text-sm lg:text-lg font-bold mb-1 inline-flex ${isFriendly
? "text-green-500"
: "text-white"}"
>
@@ -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`
+3 -3
View File
@@ -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;
}
+3 -3
View File
@@ -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,
);
}
+1 -1
View File
@@ -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);
+14 -14
View File
@@ -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,
);
}
+31 -11
View File
@@ -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();
});
}
+10 -2
View File
@@ -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(),
+4 -2
View File
@@ -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;
}
+352
View File
@@ -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 }),
];
+5 -5
View File
@@ -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;
+20 -230
View File
@@ -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),
+12 -12
View File
@@ -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),
+2 -2
View File
@@ -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) {
+6 -7
View File
@@ -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;
}
+1 -1
View File
@@ -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);
}
+1 -2
View File
@@ -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 =
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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 ||
+33 -1
View File
@@ -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<Team, number>();
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;
}
@@ -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");
@@ -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
+22 -3
View File
@@ -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
+25 -2
View File
@@ -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,
+5 -1
View File
@@ -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 {
+14
View File
@@ -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);
}
+31 -9
View File
@@ -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;
}
+2 -1
View File
@@ -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,
});
+8 -3
View File
@@ -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,
),
);
+1
View File
@@ -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 });
}),