mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 23:01:54 +00:00
Merge branch 'main' into player-text-opacity
This commit is contained in:
Generated
+36
-29
@@ -85,8 +85,8 @@
|
||||
"lit": "^3.3.1",
|
||||
"lit-markdown": "^1.3.2",
|
||||
"mrmime": "^2.0.0",
|
||||
"pixi-filters": "^6.1.4",
|
||||
"pixi.js": "^8.11.0",
|
||||
"pixi-filters": "^6.1.5",
|
||||
"pixi.js": "^8.17.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-sh": "^0.17.4",
|
||||
@@ -4198,13 +4198,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/css-font-loading-module": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz",
|
||||
"integrity": "sha512-x2tZZYkSxXqWvTDgveSynfjq/T2HyiZHXb00j/+gy19yp70PHCizM48XFdjBCWH7eHBD0R5i/pw9yMBP/BH5uA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/d3": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
|
||||
@@ -5219,16 +5212,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.61",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.61.tgz",
|
||||
"integrity": "sha512-w2HbBvH+qO19SB5pJOJFKs533CdZqxl3fcGonqL321VHkW7W/iBo6H8bjDy6pr/+pbMwIu5dnuaAxH7NxBqUrQ==",
|
||||
"version": "0.1.69",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz",
|
||||
"integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.10",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||
"integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
|
||||
"version": "0.8.11",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
|
||||
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -7108,9 +7101,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/earcut": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.1.tgz",
|
||||
"integrity": "sha512-0l1/0gOjESMeQyYaK5IDiPNvFeu93Z/cO0TjZh9eZ1vyCtZnA7KMZ8rQggpsJHIbGSdrqYq9OhuveadOVHCshw==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/earcut/-/earcut-3.0.2.tgz",
|
||||
"integrity": "sha512-X7hshQbLyMJ/3RPhyObLARM2sNxxmRALLKx1+NVFFnQ9gKzmCrxm9+uLIAdBcvc8FNLpctqlQ2V6AE92Ol9UDQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -10277,9 +10270,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pixi-filters": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.1.4.tgz",
|
||||
"integrity": "sha512-6QdkhR8hZ/jXyV7GZG8R0UKkRy9jPeZsOnHaQiKSFEe4tGJ4PfUG90vaC9eyi7g+YKxhKLpNOXu6tmO1+R2tpQ==",
|
||||
"version": "6.1.5",
|
||||
"resolved": "https://registry.npmjs.org/pixi-filters/-/pixi-filters-6.1.5.tgz",
|
||||
"integrity": "sha512-Ewb/J+kxAbaNN+0/ATJbglAJG+skGJfh7BIDP3ILIDdD6wWk1p0pGa25pVf1T8hGBOQSUNVAmwwJBwkj+cyLLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -10290,22 +10283,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/pixi.js": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.11.0.tgz",
|
||||
"integrity": "sha512-dyuThzncsgEgJZnvd/A/5x6IkUERbK+phXqUQrI+0C6WE+8xqGH5VChRTLecemhgZF0kQ+gZOM3tJTX9937xpg==",
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/pixi.js/-/pixi.js-8.17.1.tgz",
|
||||
"integrity": "sha512-OB4TpZHrP5RYy+7FqmFrAc0IHRhfOoNIfF4sVeinvK3aG1r2pYrSMneJAKi9+WvGKC70Dj7GEpZ2OZGB6o/xdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"examples",
|
||||
"playground"
|
||||
],
|
||||
"dependencies": {
|
||||
"@pixi/colord": "^2.9.6",
|
||||
"@types/css-font-loading-module": "^0.0.12",
|
||||
"@types/earcut": "^3.0.0",
|
||||
"@webgpu/types": "^0.1.40",
|
||||
"@xmldom/xmldom": "^0.8.10",
|
||||
"earcut": "^3.0.1",
|
||||
"@webgpu/types": "^0.1.69",
|
||||
"@xmldom/xmldom": "^0.8.11",
|
||||
"earcut": "^3.0.2",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"gifuct-js": "^2.1.2",
|
||||
"ismobilejs": "^1.1.1",
|
||||
"parse-svg-path": "^0.1.2"
|
||||
"parse-svg-path": "^0.1.2",
|
||||
"tiny-lru": "^11.4.7"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -11629,6 +11626,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-lru": {
|
||||
"version": "11.4.7",
|
||||
"resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.4.7.tgz",
|
||||
"integrity": "sha512-w/Te7uMUVeH0CR8vZIjr+XiN41V+30lkDdK+NRIDCUYKKuL9VcmaUEmaPISuwGhLlrTGh5yu18lENtR9axSxYw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
|
||||
+2
-2
@@ -69,8 +69,8 @@
|
||||
"lit": "^3.3.1",
|
||||
"lit-markdown": "^1.3.2",
|
||||
"mrmime": "^2.0.0",
|
||||
"pixi-filters": "^6.1.4",
|
||||
"pixi.js": "^8.11.0",
|
||||
"pixi-filters": "^6.1.5",
|
||||
"pixi.js": "^8.17.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-sh": "^0.17.4",
|
||||
|
||||
@@ -121,12 +121,13 @@
|
||||
"ui_options": "Options",
|
||||
"ui_options_desc": "The following elements can be found inside:",
|
||||
"ui_playeroverlay": "Player info overlay",
|
||||
"ui_playeroverlay_desc": "When you hover over a country, the Player info overlay is displayed under Options. It shows the type of player: Human, Nation, or Tribe. A Nation's attitude towards you, ranging from Hostile to Friendly. And defending troops, gold, plus the number of Warships and various buildings the player has.",
|
||||
"ui_playeroverlay_desc": "When you hover over a country, the Player info overlay appears. It shows the player type (Human, Nation, or Tribe), a Nation's attitude toward you (Hostile to Friendly), defending troops, gold, and the number of Warships and buildings they have.",
|
||||
"ui_wilderness": "Wilderness",
|
||||
"option_pause": "Pause/Unpause the game - Only available in single player mode.",
|
||||
"option_pause": "Pause/Unpause the game - Unavailable in public games.",
|
||||
"option_speed": "Speed - Adjust the game speed. Unavailable in public games.",
|
||||
"option_timer": "Timer - Time passed since the start of the game.",
|
||||
"option_exit": "Exit button.",
|
||||
"option_settings": "Settings - Open the settings menu. Inside you can toggle the Alternate view, Emojis, Dark Mode, Ninja (anonymous/random names mode), and action on left click.",
|
||||
"option_settings": "Settings - Open the settings menu. Inside you can toggle things like Alternate view, Emojis, Dark Mode, Hidden names, action on left click and more.",
|
||||
"radial_title": "Radial menu",
|
||||
"radial_desc": "Right clicking (or touch on mobile) opens the Radial menu. Right click outside it to close it. From the menu you can:",
|
||||
"radial_build": "Open the Build menu.",
|
||||
@@ -367,7 +368,10 @@
|
||||
"error": "An error occurred. Please try again or contact support.",
|
||||
"joined_waiting": "Lobby joined! Waiting for host to start...",
|
||||
"version_mismatch": "This game was created with a different version. Cannot join.",
|
||||
"disabled_units": "Disabled Units"
|
||||
"disabled_units": "Disabled Units",
|
||||
"game_length": "Game length",
|
||||
"pvp_immunity": "PVP immunity duration",
|
||||
"starting_gold": "Starting Gold"
|
||||
},
|
||||
"public_lobby": {
|
||||
"title": "Waiting for Game Start...",
|
||||
@@ -405,7 +409,6 @@
|
||||
"title": "Create Private Lobby",
|
||||
"mode": "Mode",
|
||||
"team_count": "Number of Teams",
|
||||
"team_type": "Team Type",
|
||||
"options_title": "Options",
|
||||
"bots": "Tribes: ",
|
||||
"bots_disabled": "Disabled",
|
||||
|
||||
+9
-12
@@ -624,10 +624,11 @@ export class HelpModal extends BaseModal {
|
||||
${translateText("help_modal.ui_options_desc")}
|
||||
</p>
|
||||
<ul class="space-y-2 list-disc pl-4 text-white/60">
|
||||
<li>${translateText("help_modal.option_pause")}</li>
|
||||
<li>${translateText("help_modal.option_timer")}</li>
|
||||
<li>${translateText("help_modal.option_exit")}</li>
|
||||
<li>${translateText("help_modal.option_speed")}</li>
|
||||
<li>${translateText("help_modal.option_pause")}</li>
|
||||
<li>${translateText("help_modal.option_settings")}</li>
|
||||
<li>${translateText("help_modal.option_exit")}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -718,8 +719,7 @@ export class HelpModal extends BaseModal {
|
||||
<li class="flex items-center gap-3">
|
||||
<img
|
||||
src="/images/InfoIcon.svg"
|
||||
class="w-5 h-5 opacity-80"
|
||||
loading="lazy"
|
||||
class="w-8 h-8 scale-75 origin-left"
|
||||
/>
|
||||
<span>${translateText("help_modal.radial_info")}</span>
|
||||
</li>
|
||||
@@ -848,14 +848,11 @@ export class HelpModal extends BaseModal {
|
||||
<span>${translateText("help_modal.info_emoji")}</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex items-center justify-center w-8 h-8 opacity-80"
|
||||
>
|
||||
<img
|
||||
src="/images/helpModal/stopTrading.webp"
|
||||
class="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
src="/images/StopIconWhite.png"
|
||||
class="w-8 h-8 scale-75 origin-left"
|
||||
loading="lazy"
|
||||
/>
|
||||
<span>${translateText("help_modal.info_trade")}</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
+199
-41
@@ -2,13 +2,10 @@ import { html, TemplateResult } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import {
|
||||
calculateServerTimeOffset,
|
||||
getActiveModifiers,
|
||||
getGameModeLabel,
|
||||
getMapName,
|
||||
getSecondsUntilServerTimestamp,
|
||||
getServerNow,
|
||||
renderDuration,
|
||||
renderNumber,
|
||||
translateText,
|
||||
} from "../client/Utils";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
@@ -22,11 +19,18 @@ import {
|
||||
PublicGameInfo,
|
||||
} from "../core/Schemas";
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
import { GameMode, GameType, HumansVsNations } from "../core/game/Game";
|
||||
import {
|
||||
Difficulty,
|
||||
GameMapSize,
|
||||
GameMode,
|
||||
GameType,
|
||||
HumansVsNations,
|
||||
} from "../core/game/Game";
|
||||
import { getApiBase } from "./Api";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
import { normaliseMapKey } from "./Utils";
|
||||
import { BaseModal } from "./components/BaseModal";
|
||||
import "./components/CopyButton";
|
||||
import "./components/LobbyConfigItem";
|
||||
@@ -426,45 +430,197 @@ export class JoinLobbyModal extends BaseModal {
|
||||
|
||||
const c = this.gameConfig;
|
||||
const mapName = getMapName(c.gameMap);
|
||||
const modeName = getGameModeLabel(c);
|
||||
const modifiers = getActiveModifiers(c.publicGameModifiers);
|
||||
const normalizedMap = normaliseMapKey(c.gameMap);
|
||||
const thumbnailUrl = `/maps/${encodeURIComponent(normalizedMap)}/thumbnail.webp`;
|
||||
const isTeam = c.gameMode === GameMode.Team;
|
||||
|
||||
let modeSubtitle: string;
|
||||
if (!isTeam) {
|
||||
modeSubtitle = translateText("game_mode.ffa");
|
||||
} else if (c.playerTeams === HumansVsNations) {
|
||||
modeSubtitle = translateText("host_modal.teams_Humans Vs Nations");
|
||||
} else if (typeof c.playerTeams === "string") {
|
||||
modeSubtitle = translateText("host_modal.teams_" + c.playerTeams);
|
||||
} else if (typeof c.playerTeams === "number") {
|
||||
modeSubtitle = translateText("public_lobby.teams", {
|
||||
num: c.playerTeams,
|
||||
});
|
||||
} else {
|
||||
modeSubtitle = translateText("game_mode.ffa");
|
||||
}
|
||||
|
||||
const pm = c.publicGameModifiers;
|
||||
const cards: TemplateResult[] = [];
|
||||
if (pm?.isCrowded)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.crowded")}
|
||||
.value=${translateText("common.enabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (
|
||||
pm?.isHardNations ||
|
||||
(c.gameType === GameType.Private && c.difficulty !== Difficulty.Easy)
|
||||
)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("difficulty.difficulty")}
|
||||
.value=${translateText(`difficulty.${c.difficulty.toLowerCase()}`)}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.infiniteTroops)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.infinite_troops")}
|
||||
.value=${translateText("common.enabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.infiniteGold)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.infinite_gold")}
|
||||
.value=${translateText("common.enabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.instantBuild)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.instant_build")}
|
||||
.value=${translateText("common.enabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.randomSpawn)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.random_spawn")}
|
||||
.value=${translateText("common.enabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.maxTimerValue)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("private_lobby.game_length")}
|
||||
.value=${`${c.maxTimerValue} min`}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (
|
||||
c.spawnImmunityDuration &&
|
||||
Math.round(c.spawnImmunityDuration / 10) !== 5
|
||||
) {
|
||||
const totalSeconds = Math.round(c.spawnImmunityDuration / 10);
|
||||
const immunityValue =
|
||||
totalSeconds < 60
|
||||
? `${totalSeconds}s`
|
||||
: totalSeconds % 60 > 0
|
||||
? `${Math.floor(totalSeconds / 60)}m ${totalSeconds % 60}s`
|
||||
: `${Math.floor(totalSeconds / 60)} min`;
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("private_lobby.pvp_immunity")}
|
||||
.value=${immunityValue}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
}
|
||||
if (c.startingGold)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("private_lobby.starting_gold")}
|
||||
.value=${`${parseFloat((c.startingGold / 1_000_000).toPrecision(12))}M`}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.goldMultiplier)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.gold_multiplier")}
|
||||
.value=${`x${c.goldMultiplier}`}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if (c.disableAlliances)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText(
|
||||
"public_game_modifier.disable_alliances_label",
|
||||
)}
|
||||
.value=${translateText("common.disabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if ((isTeam && !c.donateGold) || (!isTeam && c.donateGold))
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.donate_gold")}
|
||||
.value=${translateText(
|
||||
c.donateGold ? "common.enabled" : "common.disabled",
|
||||
)}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
if ((isTeam && !c.donateTroops) || (!isTeam && c.donateTroops))
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.donate_troops")}
|
||||
.value=${translateText(
|
||||
c.donateTroops ? "common.enabled" : "common.disabled",
|
||||
)}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
const isCompact =
|
||||
c.gameMapSize === GameMapSize.Compact || c.publicGameModifiers?.isCompact;
|
||||
if (isCompact)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.compact_map")}
|
||||
.value=${translateText("common.enabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
{
|
||||
const defaultBots = isCompact ? 100 : 400;
|
||||
if (c.bots !== defaultBots)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.bots")}
|
||||
.value=${String(c.bots)}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
}
|
||||
{
|
||||
const defaultNations = isCompact
|
||||
? Math.max(0, Math.floor(this.nationCount * 0.25))
|
||||
: this.nationCount;
|
||||
if (typeof c.nations === "number" && c.nations !== defaultNations)
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.nations")}
|
||||
.value=${String(c.nations)}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
}
|
||||
if (c.nations === "disabled" && !(c.gameType === GameType.Public && isTeam))
|
||||
cards.push(
|
||||
html`<lobby-config-item
|
||||
.label=${translateText("host_modal.nations")}
|
||||
.value=${translateText("common.disabled")}
|
||||
></lobby-config-item>`,
|
||||
);
|
||||
|
||||
return html`
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<lobby-config-item
|
||||
.label=${translateText("map.map")}
|
||||
.value=${mapName}
|
||||
></lobby-config-item>
|
||||
<lobby-config-item
|
||||
.label=${translateText("host_modal.mode")}
|
||||
.value=${modeName}
|
||||
></lobby-config-item>
|
||||
${modifiers.map(
|
||||
(m) => html`
|
||||
<lobby-config-item
|
||||
.label=${translateText(m.labelKey)}
|
||||
.value=${m.formattedValue ??
|
||||
(m.value !== undefined
|
||||
? renderNumber(m.value)
|
||||
: translateText("common.enabled"))}
|
||||
></lobby-config-item>
|
||||
`,
|
||||
)}
|
||||
${c.gameMode !== GameMode.FFA &&
|
||||
c.playerTeams &&
|
||||
c.playerTeams !== HumansVsNations
|
||||
? html`
|
||||
<lobby-config-item
|
||||
.label=${typeof c.playerTeams === "string"
|
||||
? translateText("host_modal.team_type")
|
||||
: translateText("host_modal.team_count")}
|
||||
.value=${typeof c.playerTeams === "string"
|
||||
? translateText("host_modal.teams_" + c.playerTeams)
|
||||
: c.playerTeams.toString()}
|
||||
></lobby-config-item>
|
||||
`
|
||||
: html``}
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<img
|
||||
src=${thumbnailUrl}
|
||||
alt=${mapName ?? c.gameMap}
|
||||
class="w-20 h-20 rounded-lg object-cover border border-white/10 shrink-0"
|
||||
@error=${(e: Event) => {
|
||||
(e.target as HTMLImageElement).style.display = "none";
|
||||
}}
|
||||
/>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-lg font-bold text-white">${mapName}</span>
|
||||
<span class="text-sm text-white/60">${modeSubtitle}</span>
|
||||
</div>
|
||||
</div>
|
||||
${cards.length > 0
|
||||
? html`<div class="grid grid-cols-2 sm:grid-cols-3 gap-2 mb-6">
|
||||
${cards}
|
||||
</div>`
|
||||
: html``}
|
||||
${this.renderDisabledUnits()}
|
||||
`;
|
||||
}
|
||||
@@ -495,7 +651,9 @@ export class JoinLobbyModal extends BaseModal {
|
||||
};
|
||||
|
||||
return html`
|
||||
<div class="mt-4 p-3 bg-red-500/10 border border-red-500/20 rounded-lg">
|
||||
<div
|
||||
class="mt-4 mb-6 p-3 bg-red-500/10 border border-red-500/20 rounded-lg"
|
||||
>
|
||||
<div
|
||||
class="text-xs font-bold text-red-400 uppercase tracking-widest mb-2"
|
||||
>
|
||||
|
||||
@@ -13,6 +13,12 @@ import {
|
||||
} from "../core/validations/username";
|
||||
import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
|
||||
interface LangSelectorLike {
|
||||
currentLang?: string;
|
||||
translations?: Record<string, string>;
|
||||
defaultTranslations?: Record<string, string>;
|
||||
}
|
||||
|
||||
const usernameKey: string = "username";
|
||||
const clanTagKey: string = "clanTag";
|
||||
|
||||
@@ -23,6 +29,7 @@ export class UsernameInput extends LitElement {
|
||||
|
||||
@property({ type: String }) validationError: string = "";
|
||||
private _isValid: boolean = true;
|
||||
private _lastValidatedLang: string | null = null;
|
||||
|
||||
// Remove static styles since we're using Tailwind
|
||||
|
||||
@@ -60,6 +67,23 @@ export class UsernameInput extends LitElement {
|
||||
});
|
||||
}
|
||||
|
||||
protected updated(): void {
|
||||
// Re-validate when translations become available or language changes,
|
||||
// since initial validation may run before translations are loaded.
|
||||
if (this.validationError) {
|
||||
const langSelector = document.querySelector<LangSelectorLike & Element>(
|
||||
"lang-selector",
|
||||
);
|
||||
const lang = langSelector?.currentLang;
|
||||
const hasTranslations =
|
||||
langSelector?.translations ?? langSelector?.defaultTranslations;
|
||||
if (hasTranslations && lang && lang !== this._lastValidatedLang) {
|
||||
this._lastValidatedLang = lang;
|
||||
this.validateAndStore();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private loadStoredUsername() {
|
||||
const storedUsername = localStorage.getItem(usernameKey);
|
||||
if (storedUsername) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Cell } from "../../../core/game/Game";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import { AlternateViewEvent } from "../../InputHandler";
|
||||
import { createCanvas, renderNumber, renderTroops } from "../../Utils";
|
||||
import { renderTroops } from "../../Utils";
|
||||
import {
|
||||
computeAllianceClipPath,
|
||||
createAllianceProgressIcon,
|
||||
@@ -16,11 +16,22 @@ import {
|
||||
} from "../PlayerIcons";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
import shieldIcon from "/images/ShieldIconBlack.svg?url";
|
||||
|
||||
const PLAYER_NAME = "player-name";
|
||||
const PLAYER_NAME_SPAN = "player-name-span";
|
||||
const PLAYER_TROOPS = "player-troops";
|
||||
const PLAYER_ICONS = "player-icons";
|
||||
const PLAYER_FLAG = "player-flag";
|
||||
|
||||
class RenderInfo {
|
||||
public icons: Map<PlayerIconId, HTMLElement> = new Map(); // Track icon elements
|
||||
|
||||
public nameDiv: HTMLDivElement;
|
||||
public nameSpan: HTMLSpanElement | null;
|
||||
public troopsDiv: HTMLDivElement;
|
||||
public flagDiv: HTMLDivElement | null;
|
||||
public iconsDiv: HTMLDivElement;
|
||||
|
||||
constructor(
|
||||
public player: PlayerView,
|
||||
public lastRenderCalc: number,
|
||||
@@ -28,39 +39,41 @@ class RenderInfo {
|
||||
public fontSize: number,
|
||||
public fontColor: string,
|
||||
public element: HTMLElement,
|
||||
) {}
|
||||
) {
|
||||
// Traverse the DOM once, upon creation
|
||||
this.nameDiv = element.querySelector(`.${PLAYER_NAME}`) as HTMLDivElement;
|
||||
this.nameSpan = element.querySelector(
|
||||
`.${PLAYER_NAME_SPAN}`,
|
||||
) as HTMLSpanElement | null;
|
||||
this.troopsDiv = element.querySelector(
|
||||
`.${PLAYER_TROOPS}`,
|
||||
) as HTMLDivElement;
|
||||
this.flagDiv = element.querySelector(
|
||||
`.${PLAYER_FLAG}`,
|
||||
) as HTMLDivElement | null;
|
||||
this.iconsDiv = element.querySelector(`.${PLAYER_ICONS}`) as HTMLDivElement;
|
||||
}
|
||||
}
|
||||
|
||||
export class NameLayer implements Layer {
|
||||
private canvas: HTMLCanvasElement;
|
||||
private lastChecked = 0;
|
||||
private renderCheckRate = 100;
|
||||
private renderRefreshRate = 500;
|
||||
private rand = new PseudoRandom(10);
|
||||
private renders: RenderInfo[] = [];
|
||||
private seenPlayers: Set<PlayerView> = new Set();
|
||||
private shieldIconImage: HTMLImageElement;
|
||||
private container: HTMLDivElement;
|
||||
private theme: Theme = this.game.config().theme();
|
||||
private userSettings: UserSettings = new UserSettings();
|
||||
private isVisible: boolean = true;
|
||||
private firstPlace: PlayerView | null = null;
|
||||
private lastContainerTransform: string = "";
|
||||
|
||||
constructor(
|
||||
private game: GameView,
|
||||
private transformHandler: TransformHandler,
|
||||
private eventBus: EventBus,
|
||||
) {
|
||||
this.shieldIconImage = new Image();
|
||||
this.shieldIconImage.src = shieldIcon;
|
||||
this.shieldIconImage = new Image();
|
||||
this.shieldIconImage.src = shieldIcon;
|
||||
}
|
||||
|
||||
resizeCanvas() {
|
||||
this.canvas.width = window.innerWidth;
|
||||
this.canvas.height = window.innerHeight;
|
||||
}
|
||||
) {}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
return false;
|
||||
@@ -71,10 +84,6 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
|
||||
public init() {
|
||||
this.canvas = createCanvas();
|
||||
window.addEventListener("resize", () => this.resizeCanvas());
|
||||
this.resizeCanvas();
|
||||
|
||||
this.container = document.createElement("div");
|
||||
this.container.style.position = "fixed";
|
||||
this.container.style.left = "50%";
|
||||
@@ -109,12 +118,13 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
private updateElementVisibility(render: RenderInfo) {
|
||||
private updateElementVisibility(render: RenderInfo, baseSize?: number) {
|
||||
if (!render.player.nameLocation() || !render.player.isAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseSize = Math.max(1, Math.floor(render.player.nameLocation().size));
|
||||
baseSize =
|
||||
baseSize ?? Math.max(1, Math.floor(render.player.nameLocation().size));
|
||||
const size = this.transformHandler.scale * baseSize;
|
||||
const isOnScreen = render.location
|
||||
? this.transformHandler.isOnScreen(render.location)
|
||||
@@ -160,7 +170,7 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
}
|
||||
|
||||
public renderLayer(mainContex: CanvasRenderingContext2D) {
|
||||
public renderLayer() {
|
||||
const screenPosOld = this.transformHandler.worldToScreenCoordinates(
|
||||
new Cell(0, 0),
|
||||
);
|
||||
@@ -168,7 +178,11 @@ export class NameLayer implements Layer {
|
||||
screenPosOld.x - window.innerWidth / 2,
|
||||
screenPosOld.y - window.innerHeight / 2,
|
||||
);
|
||||
this.container.style.transform = `translate(${screenPos.x}px, ${screenPos.y}px) scale(${this.transformHandler.scale})`;
|
||||
const newTransform = `translate(${screenPos.x}px, ${screenPos.y}px) scale(${this.transformHandler.scale})`;
|
||||
if (this.lastContainerTransform !== newTransform) {
|
||||
this.container.style.transform = newTransform;
|
||||
this.lastContainerTransform = newTransform;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
if (now > this.lastChecked + this.renderCheckRate) {
|
||||
@@ -177,14 +191,6 @@ export class NameLayer implements Layer {
|
||||
this.renderPlayerInfo(render);
|
||||
}
|
||||
}
|
||||
|
||||
mainContex.drawImage(
|
||||
this.canvas,
|
||||
0,
|
||||
0,
|
||||
mainContex.canvas.width,
|
||||
mainContex.canvas.height,
|
||||
);
|
||||
}
|
||||
|
||||
private createPlayerElement(player: PlayerView): HTMLDivElement {
|
||||
@@ -196,7 +202,7 @@ export class NameLayer implements Layer {
|
||||
element.style.gap = "0px";
|
||||
|
||||
const iconsDiv = document.createElement("div");
|
||||
iconsDiv.classList.add("player-icons");
|
||||
iconsDiv.classList.add(PLAYER_ICONS);
|
||||
iconsDiv.style.display = "flex";
|
||||
iconsDiv.style.gap = "4px";
|
||||
iconsDiv.style.justifyContent = "center";
|
||||
@@ -207,7 +213,7 @@ export class NameLayer implements Layer {
|
||||
|
||||
const nameDiv = document.createElement("div");
|
||||
const applyFlagStyles = (element: HTMLElement): void => {
|
||||
element.classList.add("player-flag");
|
||||
element.classList.add(PLAYER_FLAG);
|
||||
element.style.opacity = "0.8";
|
||||
element.style.zIndex = "1";
|
||||
element.style.aspectRatio = "3/4";
|
||||
@@ -227,7 +233,7 @@ export class NameLayer implements Layer {
|
||||
nameDiv.appendChild(flagImg);
|
||||
}
|
||||
}
|
||||
nameDiv.classList.add("player-name");
|
||||
nameDiv.classList.add(PLAYER_NAME);
|
||||
nameDiv.style.color = this.theme.textColor(player);
|
||||
nameDiv.style.fontFamily = this.theme.font();
|
||||
nameDiv.style.whiteSpace = "nowrap";
|
||||
@@ -238,13 +244,13 @@ export class NameLayer implements Layer {
|
||||
nameDiv.style.alignItems = "center";
|
||||
|
||||
const nameSpan = document.createElement("span");
|
||||
nameSpan.className = "player-name-span";
|
||||
nameSpan.className = PLAYER_NAME_SPAN;
|
||||
nameSpan.textContent = player.displayName();
|
||||
nameDiv.appendChild(nameSpan);
|
||||
element.appendChild(nameDiv);
|
||||
|
||||
const troopsDiv = document.createElement("div");
|
||||
troopsDiv.classList.add("player-troops");
|
||||
troopsDiv.classList.add(PLAYER_TROOPS);
|
||||
troopsDiv.setAttribute("translate", "no");
|
||||
troopsDiv.textContent = renderTroops(player.troops());
|
||||
troopsDiv.style.color = this.theme.textColor(player);
|
||||
@@ -253,33 +259,6 @@ export class NameLayer implements Layer {
|
||||
troopsDiv.style.marginTop = "-5%";
|
||||
element.appendChild(troopsDiv);
|
||||
|
||||
// TODO: Remove the shield icon.
|
||||
/* eslint-disable no-constant-condition */
|
||||
if (false) {
|
||||
const shieldDiv = document.createElement("div");
|
||||
shieldDiv.classList.add("player-shield");
|
||||
shieldDiv.style.zIndex = "3";
|
||||
shieldDiv.style.marginTop = "-5%";
|
||||
shieldDiv.style.display = "flex";
|
||||
shieldDiv.style.alignItems = "center";
|
||||
shieldDiv.style.gap = "0px";
|
||||
const shieldImg = document.createElement("img");
|
||||
shieldImg.src = this.shieldIconImage.src;
|
||||
shieldImg.style.width = "16px";
|
||||
shieldImg.style.height = "16px";
|
||||
|
||||
const shieldSpan = document.createElement("span");
|
||||
shieldSpan.textContent = "0";
|
||||
shieldSpan.style.color = "black";
|
||||
shieldSpan.style.fontSize = "10px";
|
||||
shieldSpan.style.marginTop = "-2px";
|
||||
|
||||
shieldDiv.appendChild(shieldImg);
|
||||
shieldDiv.appendChild(shieldSpan);
|
||||
element.appendChild(shieldDiv);
|
||||
}
|
||||
/* eslint-enable no-constant-condition */
|
||||
|
||||
// Start off invisible so it doesn't flash at 0,0
|
||||
element.style.display = "none";
|
||||
|
||||
@@ -297,85 +276,57 @@ export class NameLayer implements Layer {
|
||||
return;
|
||||
}
|
||||
|
||||
const oldLocation = render.location;
|
||||
render.location = new Cell(
|
||||
render.player.nameLocation().x,
|
||||
render.player.nameLocation().y,
|
||||
);
|
||||
// Update location and size, show or hide dependent on those
|
||||
const nameLocation = render.player.nameLocation();
|
||||
const newX = nameLocation.x;
|
||||
const newY = nameLocation.y;
|
||||
|
||||
// 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);
|
||||
if (
|
||||
!render.location ||
|
||||
render.location.x !== newX ||
|
||||
render.location.y !== newY
|
||||
) {
|
||||
render.location = new Cell(newX, newY);
|
||||
}
|
||||
|
||||
// Update element visibility (handles Ctrl key, size, and screen position)
|
||||
this.updateElementVisibility(render);
|
||||
const baseSize = Math.max(1, Math.floor(nameLocation.size));
|
||||
this.updateElementVisibility(render, baseSize);
|
||||
|
||||
// If element is hidden, don't continue with rendering
|
||||
if (render.element.style.display === "none") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Throttle updates
|
||||
// Throttle further updates
|
||||
const now = Date.now();
|
||||
if (now - render.lastRenderCalc <= this.renderRefreshRate) {
|
||||
return;
|
||||
}
|
||||
render.lastRenderCalc = now + this.rand.nextInt(0, 100);
|
||||
|
||||
// Update text sizes
|
||||
const nameDiv = render.element.querySelector(
|
||||
".player-name",
|
||||
) as HTMLDivElement;
|
||||
const flagDiv = render.element.querySelector(
|
||||
".player-flag",
|
||||
) as HTMLDivElement;
|
||||
const troopsDiv = render.element.querySelector(
|
||||
".player-troops",
|
||||
) as HTMLDivElement;
|
||||
// Update text sizes and opacity
|
||||
render.fontSize = Math.max(4, Math.floor(baseSize * 0.4));
|
||||
render.fontColor = this.theme.textColor(render.player);
|
||||
const nameOpacityPercent = this.userSettings.playerNameOpacity();
|
||||
const nameOpacity = nameOpacityPercent / 100;
|
||||
const hideFlag = nameOpacityPercent === 0;
|
||||
nameDiv.style.fontSize = `${render.fontSize}px`;
|
||||
nameDiv.style.lineHeight = `${render.fontSize}px`;
|
||||
nameDiv.style.color = render.fontColor;
|
||||
const span = nameDiv.querySelector(
|
||||
".player-name-span",
|
||||
) as HTMLSpanElement | null;
|
||||
if (span) {
|
||||
span.textContent = render.player.displayName();
|
||||
span.style.opacity = `${nameOpacity}`;
|
||||
}
|
||||
if (flagDiv) {
|
||||
flagDiv.style.height = `${render.fontSize}px`;
|
||||
flagDiv.style.display = hideFlag ? "none" : "";
|
||||
}
|
||||
troopsDiv.style.fontSize = `${render.fontSize}px`;
|
||||
troopsDiv.style.opacity = `${nameOpacity}`;
|
||||
troopsDiv.style.color = render.fontColor;
|
||||
troopsDiv.textContent = renderTroops(render.player.troops());
|
||||
|
||||
const density = renderNumber(
|
||||
render.player.troops() / render.player.numTilesOwned(),
|
||||
);
|
||||
const shieldDiv: HTMLDivElement | null =
|
||||
render.element.querySelector(".player-shield");
|
||||
const shieldImg = shieldDiv?.querySelector("img");
|
||||
const shieldNumber = shieldDiv?.querySelector("span");
|
||||
if (shieldImg) {
|
||||
shieldImg.style.width = `${render.fontSize * 0.8}px`;
|
||||
shieldImg.style.height = `${render.fontSize * 0.8}px`;
|
||||
render.nameDiv.style.fontSize = `${render.fontSize}px`;
|
||||
render.nameDiv.style.lineHeight = `${render.fontSize}px`;
|
||||
render.nameDiv.style.color = render.fontColor;
|
||||
if (render.nameSpan) {
|
||||
render.nameSpan.textContent = render.player.displayName();
|
||||
render.nameSpan.style.opacity = `${nameOpacity}`;
|
||||
}
|
||||
if (shieldNumber) {
|
||||
shieldNumber.style.fontSize = `${render.fontSize * 0.6}px`;
|
||||
shieldNumber.style.marginTop = `${-render.fontSize * 0.1}px`;
|
||||
shieldNumber.textContent = density;
|
||||
if (render.flagDiv) {
|
||||
render.flagDiv.style.height = `${render.fontSize}px`;
|
||||
render.flagDiv.style.display = hideFlag ? "none" : "";
|
||||
}
|
||||
render.troopsDiv.style.fontSize = `${render.fontSize}px`;
|
||||
render.troopsDiv.style.opacity = `${nameOpacity}`;
|
||||
render.troopsDiv.style.color = render.fontColor;
|
||||
render.troopsDiv.textContent = renderTroops(render.player.troops());
|
||||
|
||||
// Handle icons
|
||||
const iconsDiv = render.element.querySelector(
|
||||
".player-icons",
|
||||
) as HTMLDivElement;
|
||||
const iconSize = Math.min(render.fontSize * 1.5, 48);
|
||||
|
||||
// Compute which icons should be shown for this player using shared logic
|
||||
@@ -407,7 +358,7 @@ export class NameLayer implements Layer {
|
||||
emojiDiv.style.position = "absolute";
|
||||
emojiDiv.style.top = "50%";
|
||||
emojiDiv.style.transform = "translateY(-50%)";
|
||||
iconsDiv.appendChild(emojiDiv);
|
||||
render.iconsDiv.appendChild(emojiDiv);
|
||||
render.icons.set(icon.id, emojiDiv);
|
||||
}
|
||||
|
||||
@@ -444,7 +395,7 @@ export class NameLayer implements Layer {
|
||||
hasExtensionRequest,
|
||||
this.userSettings.darkMode(),
|
||||
);
|
||||
iconsDiv.appendChild(allianceWrapper);
|
||||
render.iconsDiv.appendChild(allianceWrapper);
|
||||
render.icons.set(icon.id, allianceWrapper);
|
||||
} else {
|
||||
// Update existing alliance icon
|
||||
@@ -484,7 +435,7 @@ export class NameLayer implements Layer {
|
||||
|
||||
if (!imgElement) {
|
||||
imgElement = this.createIconElement(icon.src, iconSize, icon.center);
|
||||
iconsDiv.appendChild(imgElement);
|
||||
render.iconsDiv.appendChild(imgElement);
|
||||
render.icons.set(icon.id, imgElement);
|
||||
}
|
||||
|
||||
@@ -527,10 +478,11 @@ export class NameLayer implements Layer {
|
||||
}
|
||||
|
||||
// Position element with scale
|
||||
if (render.location && render.location !== oldLocation) {
|
||||
const scale = Math.min(baseSize * 0.25, 3);
|
||||
render.element.style.transform = `translate(${render.location.x}px, ${render.location.y}px) translate(-50%, -50%) scale(${scale})`;
|
||||
}
|
||||
// Even when positionChanged is false: Scale update otherwise sometimes only happens after seconds which looks buggy.
|
||||
// Because of sometimes overlapping delays of 20 ticks for nameLocation() (largestClusterBoundingBox in PlayerExecution)
|
||||
// and the 500ms renderRefreshRate in NameLayer.
|
||||
const scale = Math.min(baseSize * 0.25, 3);
|
||||
render.element.style.transform = `translate(${newX}px, ${newY}px) translate(-50%, -50%) scale(${scale})`;
|
||||
}
|
||||
|
||||
private createIconElement(
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Game,
|
||||
MessageType,
|
||||
Player,
|
||||
PlayerType,
|
||||
TerraNullius,
|
||||
Unit,
|
||||
UnitType,
|
||||
@@ -76,6 +77,16 @@ export class TransportShipExecution implements Execution {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.target.isPlayer()) {
|
||||
const targetPlayer = this.target as Player;
|
||||
if (
|
||||
targetPlayer.type() !== PlayerType.Bot &&
|
||||
this.attacker.type() !== PlayerType.Bot
|
||||
) {
|
||||
this.rejectIncomingAllianceRequests(targetPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.target.isPlayer() && !this.attacker.canAttackPlayer(this.target)) {
|
||||
this.active = false;
|
||||
return;
|
||||
@@ -290,4 +301,13 @@ export class TransportShipExecution implements Execution {
|
||||
isActive(): boolean {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
private rejectIncomingAllianceRequests(target: Player) {
|
||||
const request = this.attacker
|
||||
.incomingAllianceRequests()
|
||||
.find((ar) => ar.requestor() === target);
|
||||
if (request !== undefined) {
|
||||
request.reject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export class GameManager {
|
||||
gameMap: GameMapType.World,
|
||||
gameType: GameType.Private,
|
||||
gameMapSize: GameMapSize.Normal,
|
||||
difficulty: Difficulty.Medium,
|
||||
difficulty: Difficulty.Easy,
|
||||
nations: "default",
|
||||
infiniteGold: false,
|
||||
infiniteTroops: false,
|
||||
|
||||
@@ -333,6 +333,54 @@ describe("Attack race condition with alliance requests", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Transport ship alliance rejection", () => {
|
||||
beforeEach(async () => {
|
||||
game = await setup("ocean_and_land", {
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
infiniteTroops: true,
|
||||
});
|
||||
|
||||
const playerAInfo = new PlayerInfo(
|
||||
"playerA",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"playerA_id",
|
||||
);
|
||||
// close to the water to send boats
|
||||
playerA = addPlayerToGame(playerAInfo, game, game.ref(7, 0));
|
||||
|
||||
const playerBInfo = new PlayerInfo(
|
||||
"playerB",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"playerB_id",
|
||||
);
|
||||
playerB = addPlayerToGame(playerBInfo, game, game.ref(7, 15));
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
});
|
||||
|
||||
test("Should cancel alliance requests if the recipient sends a transport ship", async () => {
|
||||
// Player A sends alliance request to Player B
|
||||
const allianceRequest = playerA.createAllianceRequest(playerB);
|
||||
expect(allianceRequest).not.toBeNull();
|
||||
expect(playerB.incomingAllianceRequests()).toHaveLength(1);
|
||||
|
||||
// Player B sends a transport ship toward Player A's territory
|
||||
game.addExecution(new TransportShipExecution(playerB, game.ref(7, 0), 0));
|
||||
|
||||
// Execute a tick to process the transport ship launch
|
||||
game.executeNextTick();
|
||||
|
||||
// Alliance request should be rejected since player B sent a naval invasion
|
||||
expect(playerA.outgoingAllianceRequests()).toHaveLength(0);
|
||||
expect(playerB.incomingAllianceRequests()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Attack immunity", () => {
|
||||
beforeEach(async () => {
|
||||
game = await setup("ocean_and_land", {
|
||||
|
||||
Reference in New Issue
Block a user