= 10_000_000) {
numStr = (num / 1000000).toFixed(1) + "M";
@@ -51,7 +52,7 @@ export function generateCryptoRandomUUID(): string {
(
c ^
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
- ).toString(16),
+ ).toString(16)
);
}
@@ -63,6 +64,6 @@ export function generateCryptoRandomUUID(): string {
const r: number = (Math.random() * 16) | 0;
const v: number = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
- },
+ }
);
}
diff --git a/src/client/components/FlagInput.ts b/src/client/components/FlagInput.ts
new file mode 100644
index 000000000..e4334d27f
--- /dev/null
+++ b/src/client/components/FlagInput.ts
@@ -0,0 +1,230 @@
+import { LitElement, html, css } from "lit";
+import { customElement, property, state } from "lit/decorators.js";
+import Countries from "../data/countries.json";
+
+const flagKey: string = "flag";
+
+@customElement("flag-input")
+export class FlagInput extends LitElement {
+ @state() private flag: string = "";
+ @state() private search: string = "";
+ @state() private showModal: boolean = false;
+
+ static styles = css`
+ .hidden {
+ display: none;
+ }
+
+ .flag-container {
+ display: flex;
+ }
+
+ .no-selected-flag {
+ position: absolute;
+ left: 8px;
+ top: 8px;
+ height: 50px;
+ border-radius: 0.75rem;
+ border: none;
+ background: none;
+ font-size: 1rem;
+ cursor: pointer;
+ }
+
+ .selected-flag {
+ width: 48px;
+ cursor: pointer;
+ position: absolute;
+ left: 24px;
+ top: 14px;
+ border: 1px solid black;
+ }
+
+ .flag-modal {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ position: absolute;
+ top: 60px;
+ left: 0;
+ width: 560px;
+ height: 500px;
+ background-color: rgb(35 35 35 / 0.8);
+ -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(12px);
+ padding: 10px;
+ border-radius: 8px;
+ }
+
+ .flag-search {
+ height: 2rem;
+ border-radius: 8px;
+ border: none;
+ text-align: center;
+ font-size: 1.3rem;
+ }
+
+ .flag-dropdown {
+ overflow-y: auto;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 1rem;
+ }
+
+ .dropdown-item {
+ opacity: 0.7;
+ width: calc(100% / 4 - 15px);
+ text-align: center;
+ color: white;
+ cursor: pointer;
+ border: none;
+ background: none;
+ }
+
+ .dropdown-item:hover {
+ opacity: 1;
+ }
+
+ .country-flag {
+ width: 100%;
+ height: auto;
+ }
+
+ @media (max-width: 768px) {
+ .flag-modal {
+ left: 0px;
+ width: calc(100% - 16px);
+ height: 50vh;
+ }
+
+ .dropdown-item {
+ width: calc(100% / 3 - 15px);
+ }
+ }
+ `;
+
+ private handleSearch(e: Event) {
+ this.search = String((e.target as HTMLInputElement).value);
+ }
+
+ private setFlag(flag: string) {
+ this.flag = flag;
+ this.showModal = false;
+ this.storeFlag(flag);
+ }
+
+ public getCurrentFlag(): string {
+ return this.flag;
+ }
+
+ private getStoredFlag(): string {
+ const storedFlag = localStorage.getItem(flagKey);
+ if (storedFlag) {
+ return storedFlag;
+ }
+ return "";
+ }
+
+ private storeFlag(flag: string) {
+ if (flag) {
+ localStorage.setItem(flagKey, flag);
+ } else if (flag === "") {
+ localStorage.removeItem(flagKey);
+ }
+ }
+
+ private dispatchFlagEvent() {
+ this.dispatchEvent(
+ new CustomEvent("flag-change", {
+ detail: { flag: this.flag },
+ bubbles: true,
+ composed: true,
+ })
+ );
+ }
+
+ connectedCallback() {
+ super.connectedCallback();
+ this.flag = this.getStoredFlag();
+ this.dispatchFlagEvent();
+ }
+
+ render() {
+ return html`
+
+ ${this.flag === ""
+ ? html`
`
+ : html`

(this.showModal = true)}
+ />`}
+ ${this.showModal
+ ? html`
+
+
+
+
+
+ ${Countries.filter(
+ (country) =>
+ country.name
+ .toLowerCase()
+ .includes(
+ this.search.toLowerCase()
+ ) ||
+ country.code
+ .toLowerCase()
+ .includes(
+ this.search.toLowerCase()
+ )
+ ).map(
+ (country) => html`
+
+ `
+ )}
+
+
+ `
+ : ""}
+
+ `;
+ }
+}
diff --git a/src/client/data/countries.json b/src/client/data/countries.json
new file mode 100644
index 000000000..07bac0fa5
--- /dev/null
+++ b/src/client/data/countries.json
@@ -0,0 +1,974 @@
+[
+ {
+ "name": "Afghanistan",
+ "code": "af"
+ },
+ {
+ "name": "Ã…land Islands",
+ "code": "ax"
+ },
+ {
+ "name": "Albania",
+ "code": "al"
+ },
+ {
+ "name": "Algeria",
+ "code": "dz"
+ },
+ {
+ "name": "American Samoa",
+ "code": "as"
+ },
+ {
+ "name": "AndorrA",
+ "code": "ad"
+ },
+ {
+ "name": "Angola",
+ "code": "ao"
+ },
+ {
+ "name": "Anguilla",
+ "code": "ai"
+ },
+ {
+ "name": "Antarctica",
+ "code": "aq"
+ },
+ {
+ "name": "Antigua and Barbuda",
+ "code": "ag"
+ },
+ {
+ "name": "Argentina",
+ "code": "ar"
+ },
+ {
+ "name": "Armenia",
+ "code": "am"
+ },
+ {
+ "name": "Aruba",
+ "code": "aw"
+ },
+ {
+ "name": "Australia",
+ "code": "au"
+ },
+ {
+ "name": "Austria",
+ "code": "at"
+ },
+ {
+ "name": "Azerbaijan",
+ "code": "az"
+ },
+ {
+ "name": "Bahamas",
+ "code": "bs"
+ },
+ {
+ "name": "Bahrain",
+ "code": "bh"
+ },
+ {
+ "name": "Bangladesh",
+ "code": "bd"
+ },
+ {
+ "name": "Barbados",
+ "code": "bb"
+ },
+ {
+ "name": "Belarus",
+ "code": "by"
+ },
+ {
+ "name": "Belgium",
+ "code": "be"
+ },
+ {
+ "name": "Belize",
+ "code": "bz"
+ },
+ {
+ "name": "Benin",
+ "code": "bj"
+ },
+ {
+ "name": "Bermuda",
+ "code": "bm"
+ },
+ {
+ "name": "Bhutan",
+ "code": "bt"
+ },
+ {
+ "name": "Bolivia",
+ "code": "bo"
+ },
+ {
+ "name": "Bosnia and Herzegovina",
+ "code": "ba"
+ },
+ {
+ "name": "Botswana",
+ "code": "bw"
+ },
+ {
+ "name": "Bouvet Island",
+ "code": "bv"
+ },
+ {
+ "name": "Brazil",
+ "code": "br"
+ },
+ {
+ "name": "British Indian Ocean Territory",
+ "code": "io"
+ },
+ {
+ "name": "Brunei Darussalam",
+ "code": "bn"
+ },
+ {
+ "name": "Bulgaria",
+ "code": "bg"
+ },
+ {
+ "name": "Burkina Faso",
+ "code": "bf"
+ },
+ {
+ "name": "Burundi",
+ "code": "bi"
+ },
+ {
+ "name": "Cambodia",
+ "code": "kh"
+ },
+ {
+ "name": "Cameroon",
+ "code": "cm"
+ },
+ {
+ "name": "Canada",
+ "code": "ca"
+ },
+ {
+ "name": "Cape Verde",
+ "code": "cv"
+ },
+ {
+ "name": "Cayman Islands",
+ "code": "ky"
+ },
+ {
+ "name": "Central African Republic",
+ "code": "cf"
+ },
+ {
+ "name": "Chad",
+ "code": "td"
+ },
+ {
+ "name": "Chile",
+ "code": "cl"
+ },
+ {
+ "name": "China",
+ "code": "cn"
+ },
+ {
+ "name": "Christmas Island",
+ "code": "cx"
+ },
+ {
+ "name": "Cocos (Keeling) Islands",
+ "code": "cc"
+ },
+ {
+ "name": "Colombia",
+ "code": "co"
+ },
+ {
+ "name": "Comoros",
+ "code": "km"
+ },
+ {
+ "name": "Congo",
+ "code": "cg"
+ },
+ {
+ "name": "Congo, The Democratic Republic of the",
+ "code": "cd"
+ },
+ {
+ "name": "Cook Islands",
+ "code": "ck"
+ },
+ {
+ "name": "Costa Rica",
+ "code": "cr"
+ },
+ {
+ "name": "Cote D'Ivoire",
+ "code": "ci"
+ },
+ {
+ "name": "Croatia",
+ "code": "hr"
+ },
+ {
+ "name": "Cuba",
+ "code": "cu"
+ },
+ {
+ "name": "Cyprus",
+ "code": "cy"
+ },
+ {
+ "name": "Czech Republic",
+ "code": "cz"
+ },
+ {
+ "name": "Denmark",
+ "code": "dk"
+ },
+ {
+ "name": "Djibouti",
+ "code": "dj"
+ },
+ {
+ "name": "Dominica",
+ "code": "dm"
+ },
+ {
+ "name": "Dominican Republic",
+ "code": "do"
+ },
+ {
+ "name": "Ecuador",
+ "code": "ec"
+ },
+ {
+ "name": "Egypt",
+ "code": "eg"
+ },
+ {
+ "name": "El Salvador",
+ "code": "sv"
+ },
+ {
+ "name": "Equatorial Guinea",
+ "code": "gq"
+ },
+ {
+ "name": "Eritrea",
+ "code": "er"
+ },
+ {
+ "name": "Estonia",
+ "code": "ee"
+ },
+ {
+ "name": "Ethiopia",
+ "code": "et"
+ },
+ {
+ "name": "Falkland Islands (Malvinas)",
+ "code": "fk"
+ },
+ {
+ "name": "Faroe Islands",
+ "code": "fo"
+ },
+ {
+ "name": "Fiji",
+ "code": "fj"
+ },
+ {
+ "name": "Finland",
+ "code": "fi"
+ },
+ {
+ "name": "France",
+ "code": "fr"
+ },
+ {
+ "name": "French Guiana",
+ "code": "gf"
+ },
+ {
+ "name": "French Polynesia",
+ "code": "pf"
+ },
+ {
+ "name": "French Southern Territories",
+ "code": "tf"
+ },
+ {
+ "name": "Gabon",
+ "code": "ga"
+ },
+ {
+ "name": "Gambia",
+ "code": "gm"
+ },
+ {
+ "name": "Georgia",
+ "code": "ge"
+ },
+ {
+ "name": "Germany",
+ "code": "de"
+ },
+ {
+ "name": "Ghana",
+ "code": "gh"
+ },
+ {
+ "name": "Gibraltar",
+ "code": "gi"
+ },
+ {
+ "name": "Greece",
+ "code": "gr"
+ },
+ {
+ "name": "Greenland",
+ "code": "gl"
+ },
+ {
+ "name": "Grenada",
+ "code": "gd"
+ },
+ {
+ "name": "Guadeloupe",
+ "code": "gp"
+ },
+ {
+ "name": "Guam",
+ "code": "gu"
+ },
+ {
+ "name": "Guatemala",
+ "code": "gt"
+ },
+ {
+ "name": "Guernsey",
+ "code": "gg"
+ },
+ {
+ "name": "Guinea",
+ "code": "gn"
+ },
+ {
+ "name": "Guinea-Bissau",
+ "code": "gw"
+ },
+ {
+ "name": "Guyana",
+ "code": "gy"
+ },
+ {
+ "name": "Haiti",
+ "code": "ht"
+ },
+ {
+ "name": "Heard Island and Mcdonald Islands",
+ "code": "hm"
+ },
+ {
+ "name": "Holy See (Vatican City State)",
+ "code": "va"
+ },
+ {
+ "name": "Honduras",
+ "code": "hn"
+ },
+ {
+ "name": "Hong Kong",
+ "code": "hk"
+ },
+ {
+ "name": "Hungary",
+ "code": "hu"
+ },
+ {
+ "name": "Iceland",
+ "code": "is"
+ },
+ {
+ "name": "India",
+ "code": "in"
+ },
+ {
+ "name": "Indonesia",
+ "code": "id"
+ },
+ {
+ "name": "Iran, Islamic Republic Of",
+ "code": "ir"
+ },
+ {
+ "name": "Iraq",
+ "code": "iq"
+ },
+ {
+ "name": "Ireland",
+ "code": "ie"
+ },
+ {
+ "name": "Isle of Man",
+ "code": "im"
+ },
+ {
+ "name": "Israel",
+ "code": "il"
+ },
+ {
+ "name": "Italy",
+ "code": "it"
+ },
+ {
+ "name": "Jamaica",
+ "code": "jm"
+ },
+ {
+ "name": "Japan",
+ "code": "jp"
+ },
+ {
+ "name": "Jersey",
+ "code": "je"
+ },
+ {
+ "name": "Jordan",
+ "code": "jo"
+ },
+ {
+ "name": "Kazakhstan",
+ "code": "kz"
+ },
+ {
+ "name": "Kenya",
+ "code": "ke"
+ },
+ {
+ "name": "Kiribati",
+ "code": "ki"
+ },
+ {
+ "name": "Korea, Democratic People'S Republic of",
+ "code": "kp"
+ },
+ {
+ "name": "Korea, Republic of",
+ "code": "kr"
+ },
+ {
+ "name": "Kuwait",
+ "code": "kw"
+ },
+ {
+ "name": "Kyrgyzstan",
+ "code": "kg"
+ },
+ {
+ "name": "Lao People'S Democratic Republic",
+ "code": "la"
+ },
+ {
+ "name": "Latvia",
+ "code": "lv"
+ },
+ {
+ "name": "Lebanon",
+ "code": "lb"
+ },
+ {
+ "name": "Lesotho",
+ "code": "ls"
+ },
+ {
+ "name": "Liberia",
+ "code": "lr"
+ },
+ {
+ "name": "Libyan Arab Jamahiriya",
+ "code": "ly"
+ },
+ {
+ "name": "Liechtenstein",
+ "code": "li"
+ },
+ {
+ "name": "Lithuania",
+ "code": "lt"
+ },
+ {
+ "name": "Luxembourg",
+ "code": "lu"
+ },
+ {
+ "name": "Macao",
+ "code": "mo"
+ },
+ {
+ "name": "Macedonia, The Former Yugoslav Republic of",
+ "code": "mk"
+ },
+ {
+ "name": "Madagascar",
+ "code": "mg"
+ },
+ {
+ "name": "Malawi",
+ "code": "mw"
+ },
+ {
+ "name": "Malaysia",
+ "code": "my"
+ },
+ {
+ "name": "Maldives",
+ "code": "mv"
+ },
+ {
+ "name": "Mali",
+ "code": "ml"
+ },
+ {
+ "name": "Malta",
+ "code": "mt"
+ },
+ {
+ "name": "Marshall Islands",
+ "code": "mh"
+ },
+ {
+ "name": "Martinique",
+ "code": "mq"
+ },
+ {
+ "name": "Mauritania",
+ "code": "mr"
+ },
+ {
+ "name": "Mauritius",
+ "code": "mu"
+ },
+ {
+ "name": "Mayotte",
+ "code": "yt"
+ },
+ {
+ "name": "Mexico",
+ "code": "mx"
+ },
+ {
+ "name": "Micronesia, Federated States of",
+ "code": "fm"
+ },
+ {
+ "name": "Moldova, Republic of",
+ "code": "md"
+ },
+ {
+ "name": "Monaco",
+ "code": "mc"
+ },
+ {
+ "name": "Mongolia",
+ "code": "mn"
+ },
+ {
+ "name": "Montserrat",
+ "code": "ms"
+ },
+ {
+ "name": "Morocco",
+ "code": "ma"
+ },
+ {
+ "name": "Mozambique",
+ "code": "mz"
+ },
+ {
+ "name": "Myanmar",
+ "code": "mm"
+ },
+ {
+ "name": "Namibia",
+ "code": "na"
+ },
+ {
+ "name": "Nauru",
+ "code": "nr"
+ },
+ {
+ "name": "Nepal",
+ "code": "np"
+ },
+ {
+ "name": "Netherlands",
+ "code": "nl"
+ },
+ {
+ "name": "Netherlands Antilles",
+ "code": "an"
+ },
+ {
+ "name": "New Caledonia",
+ "code": "nc"
+ },
+ {
+ "name": "New Zealand",
+ "code": "nz"
+ },
+ {
+ "name": "Nicaragua",
+ "code": "ni"
+ },
+ {
+ "name": "Niger",
+ "code": "ne"
+ },
+ {
+ "name": "Nigeria",
+ "code": "ng"
+ },
+ {
+ "name": "Niue",
+ "code": "nu"
+ },
+ {
+ "name": "Norfolk Island",
+ "code": "nf"
+ },
+ {
+ "name": "Northern Mariana Islands",
+ "code": "mp"
+ },
+ {
+ "name": "Norway",
+ "code": "no"
+ },
+ {
+ "name": "Oman",
+ "code": "om"
+ },
+ {
+ "name": "Pakistan",
+ "code": "pk"
+ },
+ {
+ "name": "Palau",
+ "code": "pw"
+ },
+ {
+ "name": "Palestinian Territory, Occupied",
+ "code": "ps"
+ },
+ {
+ "name": "Panama",
+ "code": "pa"
+ },
+ {
+ "name": "Papua New Guinea",
+ "code": "pg"
+ },
+ {
+ "name": "Paraguay",
+ "code": "py"
+ },
+ {
+ "name": "Peru",
+ "code": "pe"
+ },
+ {
+ "name": "Philippines",
+ "code": "ph"
+ },
+ {
+ "name": "Pitcairn",
+ "code": "pn"
+ },
+ {
+ "name": "Poland",
+ "code": "pl"
+ },
+ {
+ "name": "Portugal",
+ "code": "pt"
+ },
+ {
+ "name": "Puerto Rico",
+ "code": "pr"
+ },
+ {
+ "name": "Qatar",
+ "code": "qa"
+ },
+ {
+ "name": "Reunion",
+ "code": "re"
+ },
+ {
+ "name": "Romania",
+ "code": "ro"
+ },
+ {
+ "name": "Russian Federation",
+ "code": "ru"
+ },
+ {
+ "name": "RWANDA",
+ "code": "rw"
+ },
+ {
+ "name": "Saint Helena",
+ "code": "sh"
+ },
+ {
+ "name": "Saint Kitts and Nevis",
+ "code": "kn"
+ },
+ {
+ "name": "Saint Lucia",
+ "code": "lc"
+ },
+ {
+ "name": "Saint Pierre and Miquelon",
+ "code": "pm"
+ },
+ {
+ "name": "Saint Vincent and the Grenadines",
+ "code": "vc"
+ },
+ {
+ "name": "Samoa",
+ "code": "ws"
+ },
+ {
+ "name": "San Marino",
+ "code": "sm"
+ },
+ {
+ "name": "Sao Tome and Principe",
+ "code": "st"
+ },
+ {
+ "name": "Saudi Arabia",
+ "code": "sa"
+ },
+ {
+ "name": "Senegal",
+ "code": "sn"
+ },
+ {
+ "name": "Serbia and Montenegro",
+ "code": "cs"
+ },
+ {
+ "name": "Seychelles",
+ "code": "sc"
+ },
+ {
+ "name": "Sierra Leone",
+ "code": "sl"
+ },
+ {
+ "name": "Singapore",
+ "code": "sg"
+ },
+ {
+ "name": "Slovakia",
+ "code": "sk"
+ },
+ {
+ "name": "Slovenia",
+ "code": "si"
+ },
+ {
+ "name": "Solomon Islands",
+ "code": "sb"
+ },
+ {
+ "name": "Somalia",
+ "code": "so"
+ },
+ {
+ "name": "South Africa",
+ "code": "za"
+ },
+ {
+ "name": "South Georgia and the South Sandwich Islands",
+ "code": "gs"
+ },
+ {
+ "name": "Spain",
+ "code": "es"
+ },
+ {
+ "name": "Sri Lanka",
+ "code": "lk"
+ },
+ {
+ "name": "Sudan",
+ "code": "sd"
+ },
+ {
+ "name": "Suriname",
+ "code": "sr"
+ },
+ {
+ "name": "Svalbard and Jan Mayen",
+ "code": "sj"
+ },
+ {
+ "name": "Swaziland",
+ "code": "sz"
+ },
+ {
+ "name": "Sweden",
+ "code": "se"
+ },
+ {
+ "name": "Switzerland",
+ "code": "ch"
+ },
+ {
+ "name": "Syrian Arab Republic",
+ "code": "sy"
+ },
+ {
+ "name": "Taiwan, Province of China",
+ "code": "tw"
+ },
+ {
+ "name": "Tajikistan",
+ "code": "tj"
+ },
+ {
+ "name": "Tanzania, United Republic of",
+ "code": "tz"
+ },
+ {
+ "name": "Thailand",
+ "code": "th"
+ },
+ {
+ "name": "Timor-Leste",
+ "code": "tl"
+ },
+ {
+ "name": "Togo",
+ "code": "tg"
+ },
+ {
+ "name": "Tokelau",
+ "code": "tk"
+ },
+ {
+ "name": "Tonga",
+ "code": "to"
+ },
+ {
+ "name": "Trinidad and Tobago",
+ "code": "tt"
+ },
+ {
+ "name": "Tunisia",
+ "code": "tn"
+ },
+ {
+ "name": "Turkey",
+ "code": "tr"
+ },
+ {
+ "name": "Turkmenistan",
+ "code": "tm"
+ },
+ {
+ "name": "Turks and Caicos Islands",
+ "code": "tc"
+ },
+ {
+ "name": "Tuvalu",
+ "code": "tv"
+ },
+ {
+ "name": "Uganda",
+ "code": "ug"
+ },
+ {
+ "name": "Ukraine",
+ "code": "ua"
+ },
+ {
+ "name": "United Arab Emirates",
+ "code": "ae"
+ },
+ {
+ "name": "United Kingdom",
+ "code": "gb"
+ },
+ {
+ "name": "United States",
+ "code": "us"
+ },
+ {
+ "name": "United States Minor Outlying Islands",
+ "code": "um"
+ },
+ {
+ "name": "Uruguay",
+ "code": "uy"
+ },
+ {
+ "name": "Uzbekistan",
+ "code": "uz"
+ },
+ {
+ "name": "Vanuatu",
+ "code": "vu"
+ },
+ {
+ "name": "Venezuela",
+ "code": "ve"
+ },
+ {
+ "name": "Viet Nam",
+ "code": "vn"
+ },
+ {
+ "name": "Virgin Islands, British",
+ "code": "vg"
+ },
+ {
+ "name": "Virgin Islands, U.S.",
+ "code": "vi"
+ },
+ {
+ "name": "Wallis and Futuna",
+ "code": "wf"
+ },
+ {
+ "name": "Western Sahara",
+ "code": "eh"
+ },
+ {
+ "name": "Yemen",
+ "code": "ye"
+ },
+ {
+ "name": "Zambia",
+ "code": "zm"
+ },
+ {
+ "name": "Zimbabwe",
+ "code": "zw"
+ }
+]
\ No newline at end of file
diff --git a/src/client/graphics/layers/BuildMenu.ts b/src/client/graphics/layers/BuildMenu.ts
index 4b1f0cf1c..8a6e69c2a 100644
--- a/src/client/graphics/layers/BuildMenu.ts
+++ b/src/client/graphics/layers/BuildMenu.ts
@@ -15,6 +15,7 @@ import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg";
import missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
import portIcon from "../../../../resources/images/PortIcon.svg";
+import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
import { renderNumber } from "../../Utils";
@@ -28,7 +29,8 @@ interface BuildItemDisplay {
const buildTable: BuildItemDisplay[][] = [
[
{ unitType: UnitType.AtomBomb, icon: atomBombIcon },
- { unitType: UnitType.MIRV, icon: hydrogenBombIcon },
+ { unitType: UnitType.MIRV, icon: mirvIcon },
+ { unitType: UnitType.HydrogenBomb, icon: hydrogenBombIcon },
{ unitType: UnitType.Warship, icon: warshipIcon },
{ unitType: UnitType.Port, icon: portIcon },
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
diff --git a/src/client/graphics/layers/ControlPanel.ts b/src/client/graphics/layers/ControlPanel.ts
index aabde5bb6..dd1468265 100644
--- a/src/client/graphics/layers/ControlPanel.ts
+++ b/src/client/graphics/layers/ControlPanel.ts
@@ -195,7 +195,7 @@ export class ControlPanel extends LitElement implements Layer {
type="range"
min="1"
max="100"
- .value=${this.targetTroopRatio * 100}
+ .value=${(this.targetTroopRatio * 100).toString()}
@input=${(e: Event) => {
this.targetTroopRatio =
parseInt((e.target as HTMLInputElement).value) / 100;
@@ -225,7 +225,7 @@ export class ControlPanel extends LitElement implements Layer {
type="range"
min="1"
max="100"
- .value=${this.attackRatio * 100}
+ .value=${(this.attackRatio * 100).toString()}
@input=${(e: Event) => {
this.attackRatio =
parseInt((e.target as HTMLInputElement).value) / 100;
diff --git a/src/client/graphics/layers/EventsDisplay.ts b/src/client/graphics/layers/EventsDisplay.ts
index 1178ddda4..9621185f8 100644
--- a/src/client/graphics/layers/EventsDisplay.ts
+++ b/src/client/graphics/layers/EventsDisplay.ts
@@ -1,8 +1,11 @@
import { LitElement, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
-import { AllPlayers, MessageType } from "../../../core/game/Game";
-import { DisplayMessageUpdate } from "../../../core/game/GameUpdates";
+import { AllPlayers, MessageType, PlayerType } from "../../../core/game/Game";
+import {
+ AttackUpdate,
+ DisplayMessageUpdate,
+} from "../../../core/game/GameUpdates";
import { EmojiUpdate } from "../../../core/game/GameUpdates";
import { TargetPlayerUpdate } from "../../../core/game/GameUpdates";
import { AllianceExpiredUpdate } from "../../../core/game/GameUpdates";
@@ -16,6 +19,7 @@ import { SendAllianceReplyIntentEvent } from "../../Transport";
import { unsafeHTML } from "lit/directives/unsafe-html.js";
import { onlyImages, sanitize } from "../../../core/Util";
import { GameView, PlayerView } from "../../../core/game/GameView";
+import { renderTroops } from "../../Utils";
interface Event {
description: string;
@@ -38,6 +42,8 @@ export class EventsDisplay extends LitElement implements Layer {
public clientID: ClientID;
private events: Event[] = [];
+ @state() private incomingAttacks: AttackUpdate[] = [];
+ @state() private outgoingAttacks: AttackUpdate[] = [];
private updateMap = new Map([
[GameUpdateType.DisplayEvent, (u) => this.onDisplayMessageEvent(u)],
@@ -54,6 +60,8 @@ export class EventsDisplay extends LitElement implements Layer {
constructor() {
super();
this.events = [];
+ this.incomingAttacks = [];
+ this.outgoingAttacks = [];
}
init() {}
@@ -80,6 +88,31 @@ export class EventsDisplay extends LitElement implements Layer {
this.events = remainingEvents;
this.requestUpdate();
}
+
+ const myPlayer = this.game.myPlayer();
+ if (!myPlayer) {
+ return;
+ }
+
+ myPlayer.incomingAttacks().forEach((a) => {
+ console.log(
+ `got type: ${(
+ this.game.playerBySmallID(a.attackerID) as PlayerView
+ ).type()}`
+ );
+ });
+
+ // Update attacks
+ this.incomingAttacks = myPlayer.incomingAttacks().filter((a) => {
+ const t = (this.game.playerBySmallID(a.attackerID) as PlayerView).type();
+ return t != PlayerType.Bot;
+ });
+
+ this.outgoingAttacks = myPlayer
+ .outgoingAttacks()
+ .filter((a) => a.targetID != 0);
+
+ this.requestUpdate();
}
private addEvent(event: Event) {
@@ -125,10 +158,10 @@ export class EventsDisplay extends LitElement implements Layer {
}
const requestor = this.game.playerBySmallID(
- update.requestorID,
+ update.requestorID
) as PlayerView;
const recipient = this.game.playerBySmallID(
- update.recipientID,
+ update.recipientID
) as PlayerView;
this.addEvent({
@@ -139,7 +172,7 @@ export class EventsDisplay extends LitElement implements Layer {
className: "btn",
action: () =>
this.eventBus.emit(
- new SendAllianceReplyIntentEvent(requestor, recipient, true),
+ new SendAllianceReplyIntentEvent(requestor, recipient, true)
),
},
{
@@ -147,7 +180,7 @@ export class EventsDisplay extends LitElement implements Layer {
className: "btn-info",
action: () =>
this.eventBus.emit(
- new SendAllianceReplyIntentEvent(requestor, recipient, false),
+ new SendAllianceReplyIntentEvent(requestor, recipient, false)
),
},
],
@@ -156,7 +189,7 @@ export class EventsDisplay extends LitElement implements Layer {
createdAt: this.game.ticks(),
onDelete: () =>
this.eventBus.emit(
- new SendAllianceReplyIntentEvent(requestor, recipient, false),
+ new SendAllianceReplyIntentEvent(requestor, recipient, false)
),
});
}
@@ -168,7 +201,7 @@ export class EventsDisplay extends LitElement implements Layer {
}
const recipient = this.game.playerBySmallID(
- update.request.recipientID,
+ update.request.recipientID
) as PlayerView;
this.addEvent({
@@ -213,8 +246,8 @@ export class EventsDisplay extends LitElement implements Layer {
update.player1ID === myPlayer.smallID()
? update.player2ID
: update.player2ID === myPlayer.smallID()
- ? update.player1ID
- : null;
+ ? update.player1ID
+ : null;
const other = this.game.playerBySmallID(otherID) as PlayerView;
if (!other || !myPlayer.isAlive() || !other.isAlive()) return;
@@ -250,7 +283,7 @@ export class EventsDisplay extends LitElement implements Layer {
? AllPlayers
: this.game.playerBySmallID(update.emoji.recipientID);
const sender = this.game.playerBySmallID(
- update.emoji.senderID,
+ update.emoji.senderID
) as PlayerView;
if (recipient == myPlayer) {
@@ -289,8 +322,62 @@ export class EventsDisplay extends LitElement implements Layer {
}
}
+ private renderAttacks() {
+ if (
+ this.incomingAttacks.length === 0 &&
+ this.outgoingAttacks.length === 0
+ ) {
+ return html``;
+ }
+
+ return html`
+ ${this.incomingAttacks.length > 0
+ ? html`
+
+ |
+ ${this.incomingAttacks.map(
+ (attack) => html`
+
+ ${renderTroops(attack.troops)}
+ ${(
+ this.game.playerBySmallID(
+ attack.attackerID
+ ) as PlayerView
+ )?.name()}
+
+ `
+ )}
+ |
+
+ `
+ : ""}
+ ${this.outgoingAttacks.length > 0
+ ? html`
+
+ |
+ ${this.outgoingAttacks.map(
+ (attack) => html`
+
+ ${renderTroops(attack.troops)}
+ ${(
+ this.game.playerBySmallID(attack.targetID) as PlayerView
+ )?.name()}
+
+ `
+ )}
+ |
+
+ `
+ : ""}
+ `;
+ }
+
render() {
- if (this.events.length === 0) {
+ if (
+ this.events.length === 0 &&
+ this.incomingAttacks.length === 0 &&
+ this.outgoingAttacks.length === 0
+ ) {
return html``;
}
@@ -306,7 +393,7 @@ export class EventsDisplay extends LitElement implements Layer {
(event, index) => html`
|
@@ -331,15 +418,16 @@ export class EventsDisplay extends LitElement implements Layer {
>
${btn.text}
- `,
+ `
)}
`
: ""}
|
- `,
+ `
)}
+ ${this.renderAttacks()}
@@ -347,6 +435,6 @@ export class EventsDisplay extends LitElement implements Layer {
}
createRenderRoot() {
- return this; // Required for Tailwind classes to work with Lit
+ return this;
}
}
diff --git a/src/client/graphics/layers/NameLayer.ts b/src/client/graphics/layers/NameLayer.ts
index f74e7af70..4d5cfbcf2 100644
--- a/src/client/graphics/layers/NameLayer.ts
+++ b/src/client/graphics/layers/NameLayer.ts
@@ -16,6 +16,7 @@ import targetIcon from "../../../../resources/images/TargetIcon.svg";
import { ClientID } from "../../../core/Schemas";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { createCanvas, renderTroops } from "../../Utils";
+import { sanitize } from "../../../core/Util";
class RenderInfo {
public icons: Map