Merge branch 'main' into bomb-confirmation

This commit is contained in:
Ryan
2026-01-06 15:03:26 +00:00
committed by GitHub
20 changed files with 2443 additions and 25 deletions
+1
View File
@@ -50,3 +50,4 @@ Copyright © opentopography.org. All Rights Reserved. [Terms of Use](https://ope
Stats icon by [Meko](https://thenounproject.com/mekoda/) https://thenounproject.com/icon/stats-4942475/
Pay Per Click icon by [Fauzan Adiima](https://thenounproject.com/creator/fauzan94/) https://thenounproject.com/icon/pay-per-click-2586454/
Medal icon by [Snow](https://thenounproject.com/snowdoll/) https://thenounproject.com/icon/medal-4567887/
+3 -5
View File
@@ -316,9 +316,6 @@
block
secondary
></o-button>
<div class="container__row">
<lang-selector class="w-full"></lang-selector>
</div>
</div>
</main>
@@ -327,12 +324,12 @@
id="settings-button"
title="Settings"
class="fixed bottom-4 right-4 z-50 rounded-full p-2 shadow-lg transition-colors duration-300 flex items-center justify-center"
style="width: 80px; height: 80px; background-color: #0075ff"
style="width: 60px; height: 60px; background-color: #0075ff"
>
<img
src="/images/SettingIconWhite.svg"
alt="Settings"
style="width: 72px; height: 72px"
style="width: 52px; height: 52px"
/>
</button>
@@ -460,6 +457,7 @@
<help-modal></help-modal>
<game-info-modal></game-info-modal>
<dark-mode-button></dark-mode-button>
<lang-selector></lang-selector>
<stats-button></stats-button>
<alert-frame></alert-frame>
<chat-modal></chat-modal>
Binary file not shown.

After

Width:  |  Height:  |  Size: 725 KiB

+211
View File
@@ -0,0 +1,211 @@
{
"name": "Didier",
"nations": [
{
"coordinates": [120, 680],
"name": "Brest",
"flag": "fr"
},
{
"coordinates": [551, 748],
"name": "Rennes",
"flag": "fr"
},
{
"coordinates": [752, 500],
"name": "Caen",
"flag": "fr"
},
{
"coordinates": [841, 773],
"name": "Le Mans",
"flag": "fr"
},
{
"coordinates": [829, 424],
"name": "Le Havre",
"flag": "fr"
},
{
"coordinates": [980, 437],
"name": "Rouen",
"flag": "fr"
},
{
"coordinates": [1168, 577],
"name": "Paris",
"flag": "fr"
},
{
"coordinates": [1165, 331],
"name": "Amiens",
"flag": "fr"
},
{
"coordinates": [1279, 155],
"name": "Lille",
"flag": "fr"
},
{
"coordinates": [1096, 81],
"name": "Calais",
"flag": "fr"
},
{
"coordinates": [1426, 480],
"name": "Reims",
"flag": "fr"
},
{
"coordinates": [1758, 517],
"name": "Metz",
"flag": "fr"
},
{
"coordinates": [1762, 613],
"name": "Nancy",
"flag": "fr"
},
{
"coordinates": [1999, 642],
"name": "Strasbourg",
"flag": "fr"
},
{
"coordinates": [1939, 831],
"name": "Mulhouse",
"flag": "fr"
},
{
"coordinates": [1735, 948],
"name": "Besançon",
"flag": "fr"
},
{
"coordinates": [1583, 928],
"name": "Dijon",
"flag": "fr"
},
{
"coordinates": [1003, 799],
"name": "Orléans",
"flag": "fr"
},
{
"coordinates": [916, 911],
"name": "Tours",
"flag": "fr"
},
{
"coordinates": [1001, 1259],
"name": "Limoges",
"flag": "fr"
},
{
"coordinates": [861, 1095],
"name": "Poitiers",
"flag": "fr"
},
{
"coordinates": [939, 1186],
"name": "La Rochelle",
"flag": "fr"
},
{
"coordinates": [569, 949],
"name": "Nantes",
"flag": "fr"
},
{
"coordinates": [721, 1479],
"name": "Bordeaux",
"flag": "fr"
},
{
"coordinates": [1033, 1743],
"name": "Toulouse",
"flag": "fr"
},
{
"coordinates": [1258, 1929],
"name": "Perpignan",
"flag": "fr"
},
{
"coordinates": [1407, 1739],
"name": "Montpellier",
"flag": "fr"
},
{
"coordinates": [1480, 1690],
"name": "Nîmes",
"flag": "fr"
},
{
"coordinates": [1555, 1669],
"name": "Avignon",
"flag": "fr"
},
{
"coordinates": [1634, 1808],
"name": "Marseille",
"flag": "fr"
},
{
"coordinates": [1722, 1843],
"name": "Toulon",
"flag": "fr"
},
{
"coordinates": [1829, 1817],
"name": "Saint-Tropez",
"flag": "fr"
},
{
"coordinates": [1887, 1749],
"name": "Cannes",
"flag": "fr"
},
{
"coordinates": [1925, 1718],
"name": "Nice",
"flag": "fr"
},
{
"coordinates": [565, 1784],
"name": "Biarritz",
"flag": "fr"
},
{
"coordinates": [1483, 1349],
"name": "Saint-Étienne",
"flag": "fr"
},
{
"coordinates": [1558, 1278],
"name": "Lyon",
"flag": "fr"
},
{
"coordinates": [1692, 1402],
"name": "Grenoble",
"flag": "fr"
},
{
"coordinates": [1459, 1888],
"name": "Didier's Right Foot"
},
{
"coordinates": [482, 1196],
"name": "Didier's Left Hand"
},
{
"coordinates": [1237, 1147],
"name": "Napolidier"
},
{
"coordinates": [2012, 2198],
"name": "Arryn"
}
]
}
+1
View File
@@ -62,6 +62,7 @@ var maps = []struct {
{Name: "world"},
{Name: "lemnos"},
{Name: "twolakes"},
{Name: "didier"},
{Name: "big_plains", IsTest: true},
{Name: "half_land_half_ocean", IsTest: true},
{Name: "ocean_and_land", IsTest: true},
+87
View File
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 100 100"
fill="none"
x="0px"
y="0px"
version="1.1"
id="svg67"
sodipodi:docname="noun-medal-4567887.svg"
width="100"
height="100"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs71">
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Invert"
id="filter203"
x="0"
y="0"
width="1"
height="1">
<feColorMatrix
type="hueRotate"
values="180"
result="color1"
id="feColorMatrix199" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 -0.21 -0.72 -0.07 2 0 "
result="color2"
id="feColorMatrix201" />
</filter>
<filter
style="color-interpolation-filters:sRGB;"
inkscape:label="Invert"
id="filter209"
x="0"
y="0"
width="1"
height="1">
<feColorMatrix
type="hueRotate"
values="180"
result="color1"
id="feColorMatrix205" />
<feColorMatrix
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 -0.21 -0.72 -0.07 2 0 "
result="color2"
id="feColorMatrix207" />
</filter>
</defs>
<sodipodi:namedview
id="namedview69"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="6.456"
inkscape:cx="49.953532"
inkscape:cy="37.716853"
inkscape:window-width="1920"
inkscape:window-height="1010"
inkscape:window-x="1913"
inkscape:window-y="-6"
inkscape:window-maximized="1"
inkscape:current-layer="svg67" />
<path
d="m 59.903346,13.687732 v 6.602231 h 6.60223 v 19.806691 h -6.60223 v 6.602231 H 40.096654 v -6.602231 h -6.60223 V 20.289963 h 6.60223 v -6.602231 z"
fill="#000000"
id="path59"
style="stroke-width:1.65056;filter:url(#filter209)" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M 33.494424,0.48327138 V 7.0855019 h -6.602231 v 6.6022301 h -6.60223 v 33.011153 h 6.60223 v 6.60223 h 6.602231 v 6.602231 h -6.602231 v 39.613383 h 6.602231 v -6.602231 h 6.60223 v -6.60223 h 4.951673 v -6.602231 h 6.602231 v 6.602231 h 6.60223 v 6.60223 h 6.602231 v 6.602231 h 6.60223 V 59.903346 h -4.951673 v -6.602231 h 6.602231 v -6.60223 h 6.60223 V 13.687732 h -6.60223 V 7.0855019 H 66.505576 V 0.48327138 Z M 58.252788,86.312268 v -6.602231 h -6.60223 v -6.60223 h -6.602231 v 6.60223 h -6.60223 v 6.602231 H 33.494424 V 59.903346 H 64.855019 V 86.312268 Z M 59.903346,7.0855019 H 40.096654 v 6.6022301 h -6.60223 v 6.602231 h -6.602231 v 19.806691 h 6.602231 v 6.602231 h 6.60223 v 6.60223 h 19.806692 v -6.60223 h 6.60223 v -6.602231 h 6.602231 V 20.289963 h -6.602231 v -6.602231 h -6.60223 z"
fill="#000000"
id="path61"
style="stroke-width:1.65056;filter:url(#filter203)" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+3 -1
View File
@@ -139,6 +139,7 @@
"title": "Single Player",
"random_spawn": "Random spawn",
"allow_alliances": "Allow alliances",
"toggle_achievements": "Toggle achievements",
"options_title": "Options",
"bots": "Bots: ",
"bots_disabled": "Disabled",
@@ -252,7 +253,8 @@
"lemnos": "Lemnos",
"twolakes": "Two Lakes",
"straitofhormuz": "Strait of Hormuz",
"surrounded": "Surrounded"
"surrounded": "Surrounded",
"didier": "Didier"
},
"map_categories": {
"continental": "Continental",
+226
View File
@@ -0,0 +1,226 @@
{
"map": {
"height": 2248,
"num_land_tiles": 2303633,
"width": 2100
},
"map16x": {
"height": 562,
"num_land_tiles": 141151,
"width": 525
},
"map4x": {
"height": 1124,
"num_land_tiles": 571943,
"width": 1050
},
"name": "Didier",
"nations": [
{
"coordinates": [120, 680],
"name": "Brest",
"flag": "fr"
},
{
"coordinates": [551, 748],
"name": "Rennes",
"flag": "fr"
},
{
"coordinates": [752, 500],
"name": "Caen",
"flag": "fr"
},
{
"coordinates": [841, 773],
"name": "Le Mans",
"flag": "fr"
},
{
"coordinates": [829, 424],
"name": "Le Havre",
"flag": "fr"
},
{
"coordinates": [980, 437],
"name": "Rouen",
"flag": "fr"
},
{
"coordinates": [1168, 577],
"name": "Paris",
"flag": "fr"
},
{
"coordinates": [1165, 331],
"name": "Amiens",
"flag": "fr"
},
{
"coordinates": [1279, 155],
"name": "Lille",
"flag": "fr"
},
{
"coordinates": [1096, 81],
"name": "Calais",
"flag": "fr"
},
{
"coordinates": [1426, 480],
"name": "Reims",
"flag": "fr"
},
{
"coordinates": [1758, 517],
"name": "Metz",
"flag": "fr"
},
{
"coordinates": [1762, 613],
"name": "Nancy",
"flag": "fr"
},
{
"coordinates": [1999, 642],
"name": "Strasbourg",
"flag": "fr"
},
{
"coordinates": [1939, 831],
"name": "Mulhouse",
"flag": "fr"
},
{
"coordinates": [1735, 948],
"name": "Besançon",
"flag": "fr"
},
{
"coordinates": [1583, 928],
"name": "Dijon",
"flag": "fr"
},
{
"coordinates": [1003, 799],
"name": "Orléans",
"flag": "fr"
},
{
"coordinates": [916, 911],
"name": "Tours",
"flag": "fr"
},
{
"coordinates": [1001, 1259],
"name": "Limoges",
"flag": "fr"
},
{
"coordinates": [861, 1095],
"name": "Poitiers",
"flag": "fr"
},
{
"coordinates": [939, 1186],
"name": "La Rochelle",
"flag": "fr"
},
{
"coordinates": [569, 949],
"name": "Nantes",
"flag": "fr"
},
{
"coordinates": [721, 1479],
"name": "Bordeaux",
"flag": "fr"
},
{
"coordinates": [1033, 1743],
"name": "Toulouse",
"flag": "fr"
},
{
"coordinates": [1258, 1929],
"name": "Perpignan",
"flag": "fr"
},
{
"coordinates": [1407, 1739],
"name": "Montpellier",
"flag": "fr"
},
{
"coordinates": [1480, 1690],
"name": "Nîmes",
"flag": "fr"
},
{
"coordinates": [1555, 1669],
"name": "Avignon",
"flag": "fr"
},
{
"coordinates": [1634, 1808],
"name": "Marseille",
"flag": "fr"
},
{
"coordinates": [1722, 1843],
"name": "Toulon",
"flag": "fr"
},
{
"coordinates": [1829, 1817],
"name": "Saint-Tropez",
"flag": "fr"
},
{
"coordinates": [1887, 1749],
"name": "Cannes",
"flag": "fr"
},
{
"coordinates": [1925, 1718],
"name": "Nice",
"flag": "fr"
},
{
"coordinates": [565, 1784],
"name": "Biarritz",
"flag": "fr"
},
{
"coordinates": [1483, 1349],
"name": "Saint-Étienne",
"flag": "fr"
},
{
"coordinates": [1558, 1278],
"name": "Lyon",
"flag": "fr"
},
{
"coordinates": [1692, 1402],
"name": "Grenoble",
"flag": "fr"
},
{
"coordinates": [1459, 1888],
"name": "Didier's Right Foot"
},
{
"coordinates": [482, 1196],
"name": "Didier's Left Hand"
},
{
"coordinates": [1237, 1147],
"name": "Napolidier"
},
{
"coordinates": [2012, 2198],
"name": "Arryn"
}
]
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

+13 -15
View File
@@ -264,21 +264,19 @@ export class LangSelector extends LitElement {
});
return html`
<div class="container__row">
<button
id="lang-selector"
@click=${this.openModal}
class="text-center appearance-none w-full bg-blue-100 dark:bg-gray-700 hover:bg-blue-200 dark:hover:bg-gray-600 text-blue-900 dark:text-gray-100 p-3 sm:p-4 lg:p-5 font-medium text-sm sm:text-base lg:text-lg rounded-md border-none cursor-pointer transition-colors duration-300 flex items-center gap-2 justify-center"
>
<img
id="lang-flag"
class="w-6 h-4"
src="/flags/${currentLang.svg}.svg"
alt="flag"
/>
<span id="lang-name">${currentLang.native} (${currentLang.en})</span>
</button>
</div>
<button
id="lang-selector"
title="Change Language"
@click=${this.openModal}
class="fixed bottom-4 left-4 z-50 border-none bg-none cursor-pointer"
>
<img
id="lang-flag"
class="w-20 h-14"
src="/flags/${currentLang.svg}.svg"
alt="flag"
/>
</button>
<language-modal
.visible=${this.showModal}
+77 -1
View File
@@ -1,6 +1,7 @@
import { LitElement, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
import { UserMeResponse } from "../core/ApiSchemas";
import {
Difficulty,
Duos,
@@ -49,6 +50,8 @@ export class SinglePlayerModal extends LitElement {
@state() private useRandomMap: boolean = false;
@state() private gameMode: GameMode = GameMode.FFA;
@state() private teamCount: TeamCountConfig = 2;
@state() private showAchievements: boolean = false;
@state() private mapWins: Map<GameMapType, Set<Difficulty>> = new Map();
@state() private disabledUnits: UnitType[] = [];
@@ -57,9 +60,17 @@ export class SinglePlayerModal extends LitElement {
connectedCallback() {
super.connectedCallback();
window.addEventListener("keydown", this.handleKeyDown);
document.addEventListener(
"userMeResponse",
this.handleUserMeResponse as EventListener,
);
}
disconnectedCallback() {
document.removeEventListener(
"userMeResponse",
this.handleUserMeResponse as EventListener,
);
window.removeEventListener("keydown", this.handleKeyDown);
super.disconnectedCallback();
}
@@ -71,13 +82,76 @@ export class SinglePlayerModal extends LitElement {
}
};
private toggleAchievements = () => {
this.showAchievements = !this.showAchievements;
};
private handleUserMeResponse = (
event: CustomEvent<UserMeResponse | false>,
) => {
this.applyAchievements(event.detail);
};
private applyAchievements(userMe: UserMeResponse | false) {
if (!userMe) {
this.mapWins = new Map();
return;
}
const achievements = Array.isArray(userMe.player.achievements)
? userMe.player.achievements
: [];
const completions =
achievements.find(
(achievement) => achievement?.type === "singleplayer-map",
)?.data ?? [];
const winsMap = new Map<GameMapType, Set<Difficulty>>();
for (const entry of completions) {
const { mapName, difficulty } = entry ?? {};
const isValidMap =
typeof mapName === "string" &&
Object.values(GameMapType).includes(mapName as GameMapType);
const isValidDifficulty =
typeof difficulty === "string" &&
Object.values(Difficulty).includes(difficulty as Difficulty);
if (!isValidMap || !isValidDifficulty) continue;
const map = mapName as GameMapType;
const set = winsMap.get(map) ?? new Set<Difficulty>();
set.add(difficulty as Difficulty);
winsMap.set(map, set);
}
this.mapWins = winsMap;
}
render() {
return html`
<o-modal title=${translateText("single_modal.title")}>
<div class="options-layout">
<!-- Map Selection -->
<div class="options-section">
<div class="option-title">${translateText("map.map")}</div>
<div
class="option-title"
style="position:relative; display:flex; align-items:center; justify-content:center; width:100%;"
>
<span style="text-align:center; width:100%;">
${translateText("map.map")}
</span>
<button
@click=${this.toggleAchievements}
title=${translateText("single_modal.toggle_achievements")}
style="display:flex; align-items:center; justify-content:center; width:28px; height:28px; border:1px solid rgba(255,255,255,0.2); border-radius:6px; background:rgba(255,255,255,0.06); cursor:pointer; padding:4px; position:absolute; right:0; top:50%; transform:translateY(-50%);"
>
<img
src="/images/MedalIconWhite.svg"
alt="Toggle achievements"
style=${`width:18px; height:18px; opacity:${this.showAchievements ? "1" : "0.5"};`}
/>
</button>
</div>
<div class="option-cards flex-col">
<!-- Use the imported mapCategories -->
${Object.entries(mapCategories).map(
@@ -103,6 +177,8 @@ export class SinglePlayerModal extends LitElement {
.mapKey=${mapKey}
.selected=${!this.useRandomMap &&
this.selectedMap === mapValue}
.showMedals=${this.showAchievements}
.wins=${this.mapWins.get(mapValue) ?? new Set()}
.translation=${translateText(
`map.${mapKey?.toLowerCase()}`,
)}
+58 -3
View File
@@ -1,6 +1,6 @@
import { LitElement, css, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { GameMapType } from "../../core/game/Game";
import { Difficulty, GameMapType } from "../../core/game/Game";
import { terrainMapFileLoader } from "../TerrainMapFileLoader";
import { translateText } from "../Utils";
@@ -47,6 +47,7 @@ export const MapDescription: Record<keyof typeof GameMapType, string> = {
TwoLakes: "Two Lakes",
StraitOfHormuz: "Strait of Hormuz",
Surrounded: "Surrounded",
Didier: "Didier",
};
@customElement("map-display")
@@ -54,6 +55,8 @@ export class MapDisplay extends LitElement {
@property({ type: String }) mapKey = "";
@property({ type: Boolean }) selected = false;
@property({ type: String }) translation: string = "";
@property({ type: Boolean }) showMedals = false;
@property({ attribute: false }) wins: Set<Difficulty> = new Set();
@state() private mapWebpPath: string | null = null;
@state() private mapName: string | null = null;
@state() private isLoading = true;
@@ -63,7 +66,7 @@ export class MapDisplay extends LitElement {
width: 100%;
min-width: 100px;
max-width: 120px;
padding: 4px 4px 0 4px;
padding: 6px 6px 10px 6px;
display: flex;
flex-direction: column;
align-items: center;
@@ -73,6 +76,7 @@ export class MapDisplay extends LitElement {
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease-in-out;
gap: 6px;
}
.option-card:hover {
@@ -90,7 +94,7 @@ export class MapDisplay extends LitElement {
font-size: 14px;
color: #aaa;
text-align: center;
margin: 0 0 4px 0;
margin: 0;
}
.option-image {
@@ -105,6 +109,26 @@ export class MapDisplay extends LitElement {
align-items: center;
justify-content: center;
}
.medal-row {
display: flex;
gap: 6px;
justify-content: center;
width: 100%;
}
.medal-icon {
width: 20px;
height: 20px;
background: rgba(255, 255, 255, 0.12);
mask: url("/images/MedalIconWhite.svg") no-repeat center / contain;
-webkit-mask: url("/images/MedalIconWhite.svg") no-repeat center / contain;
opacity: 0.25;
}
.medal-icon.earned {
opacity: 1;
}
`;
connectedCallback() {
@@ -142,8 +166,39 @@ export class MapDisplay extends LitElement {
class="option-image"
/>`
: html`<div class="option-image">Error</div>`}
${this.showMedals
? html`<div class="medal-row">${this.renderMedals()}</div>`
: null}
<div class="option-card-title">${this.translation || this.mapName}</div>
</div>
`;
}
private renderMedals() {
const medalOrder: Difficulty[] = [
Difficulty.Easy,
Difficulty.Medium,
Difficulty.Hard,
Difficulty.Impossible,
];
const colors: Record<Difficulty, string> = {
[Difficulty.Easy]: "var(--medal-easy)",
[Difficulty.Medium]: "var(--medal-medium)",
[Difficulty.Hard]: "var(--medal-hard)",
[Difficulty.Impossible]: "var(--medal-impossible)",
};
const wins = this.readWins();
return medalOrder.map((medal) => {
const earned = wins.has(medal);
return html`<div
class="medal-icon ${earned ? "earned" : ""}"
style="background-color:${colors[medal]};"
title=${medal}
></div>`;
});
}
private readWins(): Set<Difficulty> {
return this.wins ?? new Set();
}
}
+7
View File
@@ -23,4 +23,11 @@
--secondaryColorDark: #374151;
--secondaryColorHoverDark: #4b5563;
--fontColorDark: #f3f4f6;
/* Achievements */
--medal-easy: #cd7f32;
--medal-medium: #c0c0c0;
--medal-hard: #ffd700;
--medal-impossible: #d32f2f;
--medal-custom: #2196f3;
}
+13
View File
@@ -42,6 +42,11 @@ export const DiscordUserSchema = z.object({
});
export type DiscordUser = z.infer<typeof DiscordUserSchema>;
const SingleplayerMapAchievementSchema = z.object({
mapName: z.enum(GameMapType),
difficulty: z.enum(Difficulty),
});
export const UserMeResponseSchema = z.object({
user: z.object({
discord: DiscordUserSchema.optional(),
@@ -51,6 +56,14 @@ export const UserMeResponseSchema = z.object({
publicId: z.string(),
roles: z.string().array().optional(),
flares: z.string().array().optional(),
achievements: z
.array(
z.object({
type: z.literal("singleplayer-map"), // TODO: change the shape to be more flexible when we have more achievements
data: z.array(SingleplayerMapAchievementSchema),
}),
)
.optional(),
}),
});
export type UserMeResponse = z.infer<typeof UserMeResponseSchema>;
+1
View File
@@ -88,6 +88,7 @@ const numPlayersConfig = {
[GameMapType.TwoLakes]: [60, 50, 40],
[GameMapType.StraitOfHormuz]: [40, 36, 30],
[GameMapType.Surrounded]: [42, 28, 14], // 3, 2, 1 player(s) per island
[GameMapType.Didier]: [100, 70, 50],
} as const satisfies Record<GameMapType, [number, number, number]>;
export abstract class DefaultServerConfig implements ServerConfig {
+2
View File
@@ -110,6 +110,7 @@ export enum GameMapType {
TwoLakes = "Two Lakes",
StraitOfHormuz = "Strait of Hormuz",
Surrounded = "Surrounded",
Didier = "Didier",
}
export type GameMapName = keyof typeof GameMapType;
@@ -161,6 +162,7 @@ export const mapCategories: Record<string, GameMapType[]> = {
GameMapType.FourIslands,
GameMapType.Svalmel,
GameMapType.Surrounded,
GameMapType.Didier,
],
};
+1
View File
@@ -60,6 +60,7 @@ const frequency: Partial<Record<GameMapName, number>> = {
TwoLakes: 6,
StraitOfHormuz: 4,
Surrounded: 4,
Didier: 2,
};
interface MapWithMode {