mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-22 04:43:49 +00:00
Merge branch 'main' into bomb-confirmation
This commit is contained in:
@@ -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
@@ -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 |
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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},
|
||||
|
||||
@@ -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 |
@@ -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",
|
||||
|
||||
@@ -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
@@ -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}
|
||||
|
||||
@@ -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()}`,
|
||||
)}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ const frequency: Partial<Record<GameMapName, number>> = {
|
||||
TwoLakes: 6,
|
||||
StraitOfHormuz: 4,
|
||||
Surrounded: 4,
|
||||
Didier: 2,
|
||||
};
|
||||
|
||||
interface MapWithMode {
|
||||
|
||||
Reference in New Issue
Block a user