mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-23 00:25:20 +00:00
Merge branch 'openfrontio:main' into main
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
{
|
||||
"lang": {
|
||||
"en": "Spanish",
|
||||
"native": "Español",
|
||||
"svg": "es"
|
||||
},
|
||||
"main": {
|
||||
"join_discord": "¡Únete al Discord!",
|
||||
"create_lobby": "Crear Partida Privada",
|
||||
"join_lobby": "Unirse a una Partida Privada",
|
||||
"single_player": "Un jugador",
|
||||
"instructions": "Instrucciones",
|
||||
"how_to_play": "Cómo jugar",
|
||||
"wiki": "Wiki"
|
||||
},
|
||||
"help_modal": {
|
||||
"hotkeys": "Teclas rápidas",
|
||||
"table_key": "Tecla",
|
||||
"table_action": "Accción",
|
||||
"action_alt_view": "Vista alternativa (terreno/países)",
|
||||
"action_attack_altclick": "Atacar (cuando el clic izquierdo abre el menú)",
|
||||
"action_build": "Abrir el menú de construcción",
|
||||
"action_center": "Centrar la cámara en el jugador",
|
||||
"action_zoom": "Aumentar/Disminuir zoom",
|
||||
"action_move_camera": "Mover la cámara",
|
||||
"action_ratio_change": "Aumentar/Disminuir el ratio de ataque",
|
||||
"action_reset_gfx": "Resetear los gráficos",
|
||||
"ui_section": "Interfaz del Juego",
|
||||
"ui_leaderboard": "Clasificación",
|
||||
"ui_leaderboard_desc": "Muestra los mejores jugadores de la partida junto a sus nombres, % de propiedad y oro.",
|
||||
"ui_control": "Panel de control",
|
||||
"ui_control_desc": "El panel de control contiene los siguientes elementos:",
|
||||
"ui_pop": "Población - La cantidad de unidades que posees, tu población máxima y la velocidad a la que esta aumenta.",
|
||||
"ui_gold": "Oro - La cantidad de oro que tienes y la velocidad a la que lo obtienes.",
|
||||
"ui_troops_workers": "Tropas y Trabajadores - La cantidad de tropas y trabajadores asignados. Las tropas se usan para atacar o defender ataques enemigos. Los trabajadores se usan para generar oro. Puedes ajustar el número de tropas y trabajadores con la barra deslizante.",
|
||||
"ui_attack_ratio": "Ratio de ataque - La cantidad de tropas que se usarán cuando ataques. Puedes ajustar la cantidad usando la barra deslizante.",
|
||||
"ui_options": "Opciones",
|
||||
"ui_options_desc": "Los siguientes elementos se encuentran aquí:",
|
||||
"option_pause": "Pausar/Reanudar la partida - Solo disponible en el modo de un jugador.",
|
||||
"option_timer": "Temporizador - Tiempo transcurrido desde el inicio de la partida.",
|
||||
"option_exit": "Botón de salir.",
|
||||
"option_settings": "Ajustes - Abrir el menú de ajustes. Dentro de él se puede activar la Vista Alternativa, el Modo Oscuro, los Emojis y la Accción del clic izquierdo.",
|
||||
"radial_title": "Menú radial",
|
||||
"radial_desc": "Clic derecho (o tocar en móviles) abre el menú radial. Desde él puedes:",
|
||||
"radial_build": "Abrir el menú de construcción.",
|
||||
"radial_info": "Abrir el menú de información.",
|
||||
"radial_boat": "Mandar un barco a la ubicación seleccionada (sólo está disponible si tienes acceso a agua).",
|
||||
"radial_close": "Cerrar el menú.",
|
||||
"info_title": "Menú de información",
|
||||
"info_enemy_panel": "Panel de información de enemigo",
|
||||
"info_enemy_desc": "Contiene infomación como el nombre del jugador seleccionado, su oro, tropas, así como si éste es un traidor. Un traidor es un jugador que ha roto una alianza con otro jugador y lo ha atacado. Los iconos representan las siguientes interacciones:",
|
||||
"info_target": "Pones una diana en el jugador seleccionado, marcándolo para todos los aliados. Usado para coordinar ataques.",
|
||||
"info_alliance": "Enviar una solicitud de alianza al jugador seleccionado. Los aliados pueden compartir recursos y tropas, pero no se pueden atacar entre ellos.",
|
||||
"info_emoji": "Enviar un emoji al jugador seleccionado.",
|
||||
"info_ally_panel": "Panel de información de aliado",
|
||||
"info_ally_desc": "Cuando tienes una alianza con otro jugador, las siguientes interacciones estarán disponibles:",
|
||||
"ally_betray": "Traicionar a tu aliado, finalizando la alianza. A partir de ese momento tendrás un símbolo de traior junto a tu nombre. Será menos probable que los bots se alíen contigo y el resto de jugadores se lo pensará dos veces antes de hacerlo.",
|
||||
"ally_donate": "Donar parte de tus tropas al jugador aliado. Se puede usar cuando disponen de pocas tropas y están siendo atacados, o cuando necesitan más fuerza para vencer a un enemigo",
|
||||
"build_menu_title": "Menú de construcción",
|
||||
"build_name": "Nombre",
|
||||
"build_icon": "Icono",
|
||||
"build_desc": "Descripción",
|
||||
"build_city": "Ciudad",
|
||||
"build_city_desc": "Aumenta la población máxima. Es útil cuando no puedes expandir tu territorio o estás cerca de alcanzar el límite máximo de población.",
|
||||
"build_defense": "Puesto de defensa",
|
||||
"build_defense_desc": "Aumenta la defensa en las fronteras cercanas. Los ataques enemigos serán más lentos y tendrán más bajas.",
|
||||
"build_port": "Puerto",
|
||||
"build_port_desc": "Envía barcos mercantes automáticamente entre tu país y otros países (a menos que hayas seleccionado \"detener el comercio\" en otro jugador, o éste lo haya seleccionado en ti), proporcionando oro a ambos bandos. Permite construir barcos de guerra. Sólo se puede construir junto a agua.",
|
||||
"build_warship": "Barco de guerra",
|
||||
"build_warship_desc": "Patrulla un area, capturando barcos mercantes y destruyendo Barcos de guerra y Botes enemigos. Aparece desde el puerto más cercano y patrulla el área en el que se clicó al construírlo.",
|
||||
"build_silo": "Silo de misiles",
|
||||
"build_silo_desc": "Permite lanzar misiles.",
|
||||
"build_sam": "Misil tierra-aire",
|
||||
"build_sam_desc": "Tiene un 75% de probabilidad de interceptar misiles enemigos en un radio de 100 píxeles. Tiene un tiempo de recarga de 7.5 segundos y no puede interceptar MIRVs.",
|
||||
"build_atom": "Bomba atómica",
|
||||
"build_atom_desc": "Una bomba pequeña que destruye territorio, construcciones, barcos y botes. Aparece desde el Silo más cercano e impacta en el punto en que se clicó al construirla.",
|
||||
"build_hydrogen": "Bomba de hidrógeno",
|
||||
"build_hydrogen_desc": "Una bomba grande. Aparece desde el Silo más cercano e impacta en el punto en que se clicó al construirla.",
|
||||
"build_mirv": "MIRV",
|
||||
"build_mirv_desc": "La bomba más poderosa del juego. Se divide en bombas menores que cubren una gran cantidad de territorio. Solo daña al jugador sobre el que se clicó al construirla. Aparece desde el Silo más cercano e impacta en el punto en que se clicó al construirla.",
|
||||
"player_icons": "Iconos de jugadores",
|
||||
"icon_desc": "Ejemplos de los iconos que te encontrarás y lo que significan:",
|
||||
"icon_crown": "Corona - Líder. Este jugador es el número 1 de la partida.",
|
||||
"icon_traitor": "Espadas cruzadas - Traidor. Este jugador ha traicionado y atacado a un aliado.",
|
||||
"icon_ally": "Apretón de manos - Aliado. Este jugador es tu aliado."
|
||||
},
|
||||
"single_modal": {
|
||||
"title": "Un jugador",
|
||||
"map": "Mapa",
|
||||
"difficulty": "Dificultad",
|
||||
"allow_alliances": "Permitir alianzas",
|
||||
"options_title": "Opciones",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Deshabilitados",
|
||||
"disable_nations": "Deshabilitar Naciones",
|
||||
"instant_build": "Construcción instantánea",
|
||||
"infinite_gold": "Oro infinito",
|
||||
"infinite_troops": "Tropas infinitas",
|
||||
"disable_nukes": "Deshabilitar bombas",
|
||||
"start": "Iniciar Partida"
|
||||
},
|
||||
"map": {
|
||||
"world": "Mundo",
|
||||
"europe": "Europa",
|
||||
"mena": "Oriente Medio y Norte de África",
|
||||
"northamerica": "Norteamérica",
|
||||
"oceania": "Oceanía",
|
||||
"blacksea": "Mar negro",
|
||||
"africa": "África",
|
||||
"asia": "Asia",
|
||||
"mars": "Marte",
|
||||
"southamerica": "Sudamérica",
|
||||
"britannia": "Britania",
|
||||
"gatewaytotheatlantic": "Salida al Atlántico",
|
||||
"australia": "Australia",
|
||||
"random": "Aleatorio",
|
||||
"iceland": "Islandia",
|
||||
"pangaea": "Pangea"
|
||||
},
|
||||
"private_lobby": {
|
||||
"title": "Unirse a Partida Privada",
|
||||
"enter_id": "Introduce ID de la Partida",
|
||||
"player": "Jugador",
|
||||
"players": "Jugadores",
|
||||
"join_lobby": "Unirse a Partida",
|
||||
"checking": "Comprobando Partida...",
|
||||
"not_found": "No se ha encontrado Partida. Comprueba el ID e inténtalo de nuevo.",
|
||||
"error": "Ha ocurrido un error. Inténtalo de nuevo.",
|
||||
"joined_waiting": "¡Te has unico a la Partida! Esperando a que comience el juego..."
|
||||
},
|
||||
"public_lobby": {
|
||||
"join": "Unirse a la próxima Partida",
|
||||
"waiting": "jugadores en espera"
|
||||
},
|
||||
"username": {
|
||||
"enter_username": "Introduce tu nombre de usuario",
|
||||
"not_string": "El nombre de usuario debe ser una cadena de texto.",
|
||||
"too_short": "El nombre de usuario tiene que tener al menos {min} carácteres.",
|
||||
"too_long": "El nombre de usuario no puede superar los {max} carácteres.",
|
||||
"invalid_chars": "El nombre de usuario sólo puede contener letras, números, espacios, barras bajas y [corchetes]."
|
||||
},
|
||||
"host_modal": {
|
||||
"title": "Partida Privada",
|
||||
"map": "Mapa",
|
||||
"difficulty": "Difficultad",
|
||||
"options_title": "Opciones",
|
||||
"bots": "Bots: ",
|
||||
"bots_disabled": "Deshabilitados",
|
||||
"disable_nations": "Deshabilitar Naciones",
|
||||
"instant_build": "Construcción instantánea",
|
||||
"infinite_gold": "Oro infinito",
|
||||
"infinite_troops": "Tropas infinitas",
|
||||
"disable_nukes": "Deshabilitar bombas",
|
||||
"player": "Jugador",
|
||||
"players": "Jugadores",
|
||||
"waiting": "jugadores en espera...",
|
||||
"start": "Iniciar Partida"
|
||||
},
|
||||
"difficulty": {
|
||||
"Relaxed": "Relajadoa",
|
||||
"Balanced": "Balanceada",
|
||||
"Intense": "Intensa",
|
||||
"Impossible": "Imposible"
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -270,6 +270,7 @@
|
||||
<option value="fr">Français</option>
|
||||
<option value="nl">Nederlands</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="es">Español</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+10
-2
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
];
|
||||
@@ -85,6 +85,10 @@ export interface Config {
|
||||
tilesPerTickUsed: number;
|
||||
};
|
||||
attackAmount(attacker: Player, defender: Player | TerraNullius): number;
|
||||
radiusPortSpawn(): number;
|
||||
// When computing likelihood of trading for any given port, the X closest port
|
||||
// are twice more likely to be selected. X is determined below.
|
||||
proximityBonusPortsNb(totalPorts: number): number;
|
||||
maxPopulation(player: Player | PlayerView): number;
|
||||
cityPopulationIncrease(): number;
|
||||
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number;
|
||||
@@ -109,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;
|
||||
|
||||
@@ -474,6 +474,14 @@ export class DefaultConfig implements Config {
|
||||
return Math.floor(attacker.troops() / 5);
|
||||
}
|
||||
|
||||
radiusPortSpawn() {
|
||||
return 20;
|
||||
}
|
||||
|
||||
proximityBonusPortsNb(totalPorts: number) {
|
||||
return within(totalPorts / 3, 4, totalPorts);
|
||||
}
|
||||
|
||||
attackAmount(attacker: Player, defender: Player | TerraNullius) {
|
||||
if (attacker.type() == PlayerType.Bot) {
|
||||
return attacker.troops() / 20;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -81,6 +81,10 @@ export class AttackExecution implements Execution {
|
||||
? mg.terraNullius()
|
||||
: mg.player(this._targetID);
|
||||
|
||||
if (this.target && this.target.isPlayer()) {
|
||||
(this.target as Player).addEmbargo(this._owner.id());
|
||||
}
|
||||
|
||||
if (this._owner == this.target) {
|
||||
console.error(`Player ${this._owner} cannot attack itself`);
|
||||
this.active = false;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -44,10 +44,10 @@ export class CityExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
if (this.player != this.city.owner()) {
|
||||
this.player = this.city.owner();
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
|
||||
@@ -76,6 +76,10 @@ export class ConstructionExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.player != this.construction.owner()) {
|
||||
this.player = this.construction.owner();
|
||||
}
|
||||
|
||||
if (this.ticksUntilComplete == 0) {
|
||||
this.player = this.construction.owner();
|
||||
this.construction.delete(false);
|
||||
@@ -123,10 +127,6 @@ export class ConstructionExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -45,10 +45,10 @@ export class DefensePostExecution implements Execution {
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
if (this.player != this.post.owner()) {
|
||||
this.player = this.post.owner();
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -42,11 +42,11 @@ export class MissileSiloExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
this.silo = this.player.buildUnit(UnitType.MissileSilo, 0, this.tile);
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
if (this.player != this.silo.owner()) {
|
||||
this.player = this.silo.owner();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
|
||||
@@ -25,11 +25,6 @@ export class MoveWarshipExecution implements Execution {
|
||||
this.active = false;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
const warship = this.mg.units().find((u) => u.id() == this.unitId);
|
||||
return warship ? warship.owner() : null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,27 +38,15 @@ export class PortExecution implements Execution {
|
||||
|
||||
tick(ticks: number): void {
|
||||
if (this.port == null) {
|
||||
// TODO: use canBuild
|
||||
const tile = this.tile;
|
||||
const player = this.mg.player(this._owner);
|
||||
if (!player.canBuild(UnitType.Port, tile)) {
|
||||
const spawn = player.canBuild(UnitType.Port, tile);
|
||||
if (spawn === false) {
|
||||
consolex.warn(`player ${player} cannot build port at ${this.tile}`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||
.filter((t) => this.mg.isOceanShore(t) && this.mg.owner(t) == player)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
this.mg.manhattanDist(a, tile) - this.mg.manhattanDist(b, tile),
|
||||
);
|
||||
|
||||
if (spawns.length == 0) {
|
||||
consolex.warn(`cannot find spawn for port`);
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
this.port = player.buildUnit(UnitType.Port, 0, spawns[0]);
|
||||
this.port = player.buildUnit(UnitType.Port, 0, spawn);
|
||||
}
|
||||
|
||||
if (!this.port.isActive()) {
|
||||
@@ -66,6 +54,10 @@ export class PortExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._owner != this.port.owner().id()) {
|
||||
this._owner = this.port.owner().id();
|
||||
}
|
||||
|
||||
const totalNbOfPorts = this.mg.units(UnitType.Port).length;
|
||||
if (
|
||||
!this.random.chance(this.mg.config().tradeShipSpawnRate(totalNbOfPorts))
|
||||
@@ -73,10 +65,8 @@ export class PortExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
const ports = this.mg
|
||||
.players()
|
||||
.filter((p) => p != this.port.owner() && p.canTrade(this.port.owner()))
|
||||
.flatMap((p) => p.units(UnitType.Port));
|
||||
const ports = this.player().tradingPorts(this.port);
|
||||
|
||||
if (ports.length == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -88,10 +78,6 @@ export class PortExecution implements Execution {
|
||||
);
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -57,6 +57,10 @@ export class SAMLauncherExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.player != this.post.owner()) {
|
||||
this.player = this.post.owner();
|
||||
}
|
||||
|
||||
if (!this.pseudoRandom) {
|
||||
this.pseudoRandom = new PseudoRandom(this.post.id());
|
||||
}
|
||||
@@ -68,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 =
|
||||
@@ -133,10 +136,6 @@ export class SAMLauncherExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -85,9 +85,6 @@ export class SAMMissileExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -63,9 +63,6 @@ export class ShellExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -151,10 +151,6 @@ export class TradeShipExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return null;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import { PathFinder } from "../pathfinding/PathFinding";
|
||||
import { PathFindResultType } from "../pathfinding/AStar";
|
||||
import { PseudoRandom } from "../PseudoRandom";
|
||||
import { distSort, distSortUnit } from "../Util";
|
||||
import { consolex } from "../Consolex";
|
||||
import { TileRef } from "../game/GameMap";
|
||||
import { ShellExecution } from "./ShellExecution";
|
||||
@@ -147,11 +146,11 @@ 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 ||
|
||||
unit.dstPort()?.owner() !== this.owner()),
|
||||
unit.dstPort()?.owner() !== this._owner),
|
||||
);
|
||||
|
||||
this.target =
|
||||
@@ -229,7 +228,7 @@ export class WarshipExecution implements Execution {
|
||||
);
|
||||
switch (result.type) {
|
||||
case PathFindResultType.Completed:
|
||||
this.owner().captureUnit(this.target);
|
||||
this._owner.captureUnit(this.target);
|
||||
this.target = null;
|
||||
return;
|
||||
case PathFindResultType.NextTile:
|
||||
@@ -244,10 +243,6 @@ export class WarshipExecution implements Execution {
|
||||
}
|
||||
}
|
||||
|
||||
owner(): Player {
|
||||
return this._owner;
|
||||
}
|
||||
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+23
-5
@@ -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 {
|
||||
@@ -156,7 +171,6 @@ export interface Execution {
|
||||
activeDuringSpawnPhase(): boolean;
|
||||
init(mg: Game, ticks: number): void;
|
||||
tick(ticks: number): void;
|
||||
owner(): Player;
|
||||
}
|
||||
|
||||
export interface Attack {
|
||||
@@ -328,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[];
|
||||
@@ -375,10 +391,10 @@ export interface Player {
|
||||
executeRetreat(attackID: string): void;
|
||||
|
||||
// Misc
|
||||
executions(): Execution[];
|
||||
toUpdate(): PlayerUpdate;
|
||||
playerProfile(): PlayerProfile;
|
||||
canBoat(tile: TileRef): boolean;
|
||||
tradingPorts(port: Unit): Unit[];
|
||||
}
|
||||
|
||||
export interface Game extends GameMap {
|
||||
@@ -401,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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
+71
-15
@@ -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(),
|
||||
@@ -285,11 +288,6 @@ export class PlayerImpl implements Player {
|
||||
isAlive(): boolean {
|
||||
return this._tiles.size > 0;
|
||||
}
|
||||
executions(): Execution[] {
|
||||
return this.mg
|
||||
.executions()
|
||||
.filter((exec) => exec.owner().id() == this.id());
|
||||
}
|
||||
|
||||
incomingAllianceRequests(): AllianceRequest[] {
|
||||
return this.mg.allianceRequests.filter((ar) => ar.recipient() == this);
|
||||
@@ -329,7 +327,7 @@ export class PlayerImpl implements Player {
|
||||
if (other == this) {
|
||||
return false;
|
||||
}
|
||||
if (this.isAlliedWith(other)) {
|
||||
if (this.isFriendly(other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -431,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_) {
|
||||
@@ -505,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) {
|
||||
@@ -560,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);
|
||||
}
|
||||
@@ -735,7 +751,12 @@ export class PlayerImpl implements Player {
|
||||
}
|
||||
|
||||
portSpawn(tile: TileRef): TileRef | false {
|
||||
const spawns = Array.from(this.mg.bfs(tile, manhattanDistFN(tile, 20)))
|
||||
const spawns = Array.from(
|
||||
this.mg.bfs(
|
||||
tile,
|
||||
manhattanDistFN(tile, this.mg.config().radiusPortSpawn()),
|
||||
),
|
||||
)
|
||||
.filter((t) => this.mg.owner(t) == this && this.mg.isOceanShore(t))
|
||||
.sort(
|
||||
(a, b) =>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -958,4 +980,38 @@ export class PlayerImpl implements Player {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// It's a probability list, so if an element appears twice it's because it's
|
||||
// twice more likely to be picked later.
|
||||
tradingPorts(port: Unit): Unit[] {
|
||||
let ports = this.mg
|
||||
.players()
|
||||
.filter((p) => p != port.owner() && p.canTrade(port.owner()))
|
||||
.flatMap((p) => p.units(UnitType.Port))
|
||||
.sort((p1, p2) => {
|
||||
return (
|
||||
this.mg.manhattanDist(port.tile(), p1.tile()) -
|
||||
this.mg.manhattanDist(port.tile(), p2.tile())
|
||||
);
|
||||
});
|
||||
|
||||
// Make close ports twice more likely by putting them again
|
||||
for (
|
||||
let i = 0;
|
||||
i < this.mg.config().proximityBonusPortsNb(ports.length);
|
||||
i++
|
||||
) {
|
||||
ports.push(ports[i]);
|
||||
}
|
||||
|
||||
// Make ally ports twice more likely by putting them again
|
||||
this.mg
|
||||
.players()
|
||||
.filter((p) => p != port.owner() && p.canTrade(port.owner()))
|
||||
.filter((p) => p.isAlliedWith(port.owner()))
|
||||
.flatMap((p) => p.units(UnitType.Port))
|
||||
.forEach((p) => ports.push(p));
|
||||
|
||||
return ports;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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 });
|
||||
}),
|
||||
|
||||
+29
-11
@@ -9,6 +9,7 @@ import { SpawnExecution } from "../src/core/execution/SpawnExecution";
|
||||
import { setup } from "./util/Setup";
|
||||
import { constructionExecution } from "./util/utils";
|
||||
|
||||
const coastX = 7;
|
||||
let game: Game;
|
||||
let player1: Player;
|
||||
let player2: Player;
|
||||
@@ -36,10 +37,15 @@ describe("Warship", () => {
|
||||
);
|
||||
game.addPlayer(player_2_info, 1000);
|
||||
|
||||
const spawnTile = game.map().ref(0, 0);
|
||||
game.addExecution(
|
||||
new SpawnExecution(game.player(player_1_info.id).info(), spawnTile),
|
||||
new SpawnExecution(game.player(player_2_info.id).info(), spawnTile),
|
||||
new SpawnExecution(
|
||||
game.player(player_1_info.id).info(),
|
||||
game.ref(coastX, 10),
|
||||
),
|
||||
new SpawnExecution(
|
||||
game.player(player_2_info.id).info(),
|
||||
game.ref(coastX, 15),
|
||||
),
|
||||
);
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
@@ -53,8 +59,12 @@ describe("Warship", () => {
|
||||
test("Warship heals only if player has port", async () => {
|
||||
const maxHealth = game.config().unitInfo(UnitType.Warship).maxHealth;
|
||||
|
||||
const port = player1.buildUnit(UnitType.Port, 0, game.ref(0, 0));
|
||||
const warship = player1.buildUnit(UnitType.Warship, 0, game.ref(7, 7));
|
||||
const port = player1.buildUnit(UnitType.Port, 0, game.ref(coastX, 10));
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
0,
|
||||
game.ref(coastX + 1, 10),
|
||||
);
|
||||
|
||||
game.executeNextTick();
|
||||
|
||||
@@ -71,15 +81,19 @@ describe("Warship", () => {
|
||||
});
|
||||
|
||||
test("Warship captures trade if player has port", async () => {
|
||||
constructionExecution(game, player1.id(), 0, 0, UnitType.Port);
|
||||
constructionExecution(game, player1.id(), 7, 7, UnitType.Warship);
|
||||
constructionExecution(game, player1.id(), coastX, 10, UnitType.Port);
|
||||
constructionExecution(game, player1.id(), coastX + 1, 10, UnitType.Warship);
|
||||
// Warship need one more tick (for warship exec to actually build warship)
|
||||
game.executeNextTick();
|
||||
expect(player1.units(UnitType.Warship)).toHaveLength(1);
|
||||
|
||||
// Cannot buildExec with trade ship as it's not buildable (but
|
||||
// we can obviously directly add it to the player)
|
||||
const tradeShip = player2.buildUnit(UnitType.TradeShip, 0, game.ref(6, 6));
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
0,
|
||||
game.ref(coastX + 1, 7),
|
||||
);
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
// Let plenty of time for A* to execute
|
||||
@@ -90,14 +104,18 @@ describe("Warship", () => {
|
||||
});
|
||||
|
||||
test("Warship do not capture trade if player has no port", async () => {
|
||||
constructionExecution(game, player1.id(), 0, 0, UnitType.Port);
|
||||
constructionExecution(game, player1.id(), 7, 7, UnitType.Warship);
|
||||
constructionExecution(game, player1.id(), coastX, 10, UnitType.Port);
|
||||
constructionExecution(game, player1.id(), coastX + 1, 10, UnitType.Warship);
|
||||
expect(player1.units(UnitType.Warship)).toHaveLength(1);
|
||||
|
||||
player1.units(UnitType.Port)[0].delete();
|
||||
// Cannot buildExec with trade ship as it's not buildable (but
|
||||
// we can obviously directly add it to the player)
|
||||
const tradeShip = player2.buildUnit(UnitType.TradeShip, 0, game.ref(6, 6));
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
0,
|
||||
game.ref(coastX + 1, 11),
|
||||
);
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
// Let plenty of time for A* to execute
|
||||
|
||||
BIN
Binary file not shown.
|
Before Width: | Height: | Size: 96 B After Width: | Height: | Size: 108 B |
@@ -1,7 +1,22 @@
|
||||
import { DefaultConfig } from "../../src/core/configuration/DefaultConfig";
|
||||
|
||||
export class TestConfig extends DefaultConfig {
|
||||
_proximityBonusPortsNb: number = 0;
|
||||
|
||||
samHittingChance(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
radiusPortSpawn(): number {
|
||||
return 1;
|
||||
}
|
||||
|
||||
proximityBonusPortsNb(totalPorts: number): number {
|
||||
return this._proximityBonusPortsNb;
|
||||
}
|
||||
|
||||
// Specific to TestConfig
|
||||
setProximityBonusPortsNb(nb: number): void {
|
||||
this._proximityBonusPortsNb = nb;
|
||||
}
|
||||
}
|
||||
|
||||
+10
-77
@@ -1,6 +1,5 @@
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fs from "fs/promises";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import webpack from "webpack";
|
||||
import CopyPlugin from "copy-webpack-plugin";
|
||||
@@ -8,40 +7,6 @@ import CopyPlugin from "copy-webpack-plugin";
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
async function checkResourcesCopied(sourceDir, targetDir) {
|
||||
async function checkDir(source, target) {
|
||||
let items;
|
||||
try {
|
||||
items = await fs.readdir(source, { withFileTypes: true });
|
||||
} catch (error) {
|
||||
console.error(`Error reading directory ${source}:`, error);
|
||||
return false;
|
||||
}
|
||||
for (const item of items) {
|
||||
const sourcePath = path.join(source, item.name);
|
||||
const targetPath = path.join(target, item.name);
|
||||
if (item.isDirectory()) {
|
||||
try {
|
||||
await fs.access(targetPath);
|
||||
} catch (error) {
|
||||
// Target directory does not exist.
|
||||
return false;
|
||||
}
|
||||
const exists = await checkDir(sourcePath, targetPath);
|
||||
if (!exists) return false;
|
||||
} else if (item.isFile()) {
|
||||
try {
|
||||
await fs.access(targetPath);
|
||||
} catch (error) {
|
||||
// Target file does not exist.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return checkDir(sourceDir, targetDir);
|
||||
}
|
||||
export default async (env, argv) => {
|
||||
const isProduction = argv.mode === "production";
|
||||
|
||||
@@ -154,48 +119,16 @@ export default async (env, argv) => {
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
|
||||
}),
|
||||
...(await (async () => {
|
||||
if (isProduction) {
|
||||
return [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: "resources",
|
||||
to: ".",
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
],
|
||||
options: { concurrency: 100 },
|
||||
}),
|
||||
];
|
||||
} else {
|
||||
const resourcesDir = path.resolve(__dirname, "resources");
|
||||
const targetDir = path.resolve(__dirname, "static");
|
||||
const allExist = await checkResourcesCopied(resourcesDir, targetDir);
|
||||
if (allExist) {
|
||||
console.log(
|
||||
"[CopyPlugin] Skipped: All resources already exist in static/.",
|
||||
);
|
||||
return []; // Skip CopyPlugin if all resources are present.
|
||||
} else {
|
||||
console.log(
|
||||
"[CopyPlugin] Copying missing resources to static/ ...",
|
||||
);
|
||||
return [
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: "resources",
|
||||
to: ".",
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
],
|
||||
options: { concurrency: 100 },
|
||||
}),
|
||||
];
|
||||
}
|
||||
}
|
||||
})()),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, "resources"),
|
||||
to: path.resolve(__dirname, "static"),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
],
|
||||
options: { concurrency: 100 },
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
// Add optimization configuration for better caching
|
||||
|
||||
Reference in New Issue
Block a user