This commit is contained in:
Scott Anderson
2025-08-24 15:59:43 -04:00
parent e77e731832
commit b107ff9f24
198 changed files with 1928 additions and 1634 deletions
+30 -23
View File
@@ -1,12 +1,12 @@
import eslintPluginLocal from "./eslint-plugin-local/plugin.js";
import { fileURLToPath } from "node:url";
import globals from "globals";
import { includeIgnoreFile } from "@eslint/compat";
import jest from "eslint-plugin-jest";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { includeIgnoreFile } from "@eslint/compat";
import pluginJs from "@eslint/js";
import stylistic from "@stylistic/eslint-plugin";
import jest from "eslint-plugin-jest";
import globals from "globals";
import tseslint from "typescript-eslint";
import eslintPluginLocal from "./eslint-plugin-local/plugin.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -100,7 +100,10 @@ export default [
"function-call-argument-newline": ["error", "consistent"],
"max-depth": ["error", { max: 5 }],
"max-len": ["error", { code: 120 }],
"max-lines": ["error", { max: 676, skipBlankLines: true, skipComments: true }],
"max-lines": [
"error",
{ max: 676, skipBlankLines: true, skipComments: true },
],
"max-lines-per-function": ["error", { max: 561 }],
"no-loss-of-precision": "error",
"no-multi-spaces": "error",
@@ -108,21 +111,30 @@ export default [
"no-trailing-spaces": "error",
"object-curly-newline": ["error", { multiline: true, consistent: true }],
"object-curly-spacing": ["error", "always"],
"object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
"object-property-newline": [
"error",
{ allowAllPropertiesOnSameLine: true },
],
"object-shorthand": ["error", "always"],
"no-undef": "error",
"no-unused-vars": "off", // @typescript-eslint/no-unused-vars
"prefer-destructuring": ["error", {
array: false,
object: true,
}],
"prefer-destructuring": [
"error",
{
array: false,
object: true,
},
],
"quote-props": ["error", "consistent-as-needed"],
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", {
anonymous: "always",
named: "never",
asyncArrow: "always",
}],
"space-before-function-paren": [
"error",
{
anonymous: "always",
named: "never",
asyncArrow: "always",
},
],
"space-infix-ops": "off",
},
},
@@ -150,17 +162,12 @@ export default [
...globals.jest,
},
},
files: [
"**/*.test.{js,ts,jsx,tsx}",
"tests/**/*.{js,ts,jsx,tsx}",
],
files: ["**/*.test.{js,ts,jsx,tsx}", "tests/**/*.{js,ts,jsx,tsx}"],
plugins: ["jest"],
...jest.configs["flat/style"],
},
{
files: [
"src/client/**/*.{js,ts,jsx,tsx}",
],
files: ["src/client/**/*.{js,ts,jsx,tsx}"],
rules: {
// Disabled rules for frontend
"sort-keys": "off",
+29 -29
View File
@@ -1,11 +1,20 @@
import { translateText } from "../client/Utils";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { EventBus } from "../core/EventBus";
import { PlayerActions, UnitType } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { GameMapLoader } from "../core/game/GameMapLoader";
import {
AutoUpgradeEvent,
DoBoatAttackEvent,
DoGroundAttackEvent,
InputHandler,
MouseMoveEvent,
MouseUpEvent,
} from "./InputHandler";
ErrorUpdate,
GameUpdateType,
GameUpdateViewData,
HashUpdate,
WinUpdate,
} from "../core/game/GameUpdates";
import { GameView, PlayerView } from "../core/game/GameView";
import { loadTerrainMap, TerrainMapData } from "../core/game/TerrainMapLoader";
import { UserSettings } from "../core/game/UserSettings";
import {
ClientID,
GameID,
@@ -14,16 +23,20 @@ import {
PlayerRecord,
ServerMessage,
} from "../core/Schemas";
import { createGameRecord } from "../core/Util";
import { WorkerClient } from "../core/worker/WorkerClient";
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
import {
ErrorUpdate,
GameUpdateType,
GameUpdateViewData,
HashUpdate,
WinUpdate,
} from "../core/game/GameUpdates";
import { GameRenderer, createRenderer } from "./graphics/GameRenderer";
import { GameView, PlayerView } from "../core/game/GameView";
import { PlayerActions, UnitType } from "../core/game/Game";
AutoUpgradeEvent,
DoBoatAttackEvent,
DoGroundAttackEvent,
InputHandler,
MouseMoveEvent,
MouseUpEvent,
} from "./InputHandler";
import { endGame, startGame, startTime } from "./LocalPersistantStats";
import { getPersistentID } from "./Main";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import {
SendAttackIntentEvent,
SendBoatAttackIntentEvent,
@@ -32,20 +45,7 @@ import {
SendUpgradeStructureIntentEvent,
Transport,
} from "./Transport";
import { TerrainMapData, loadTerrainMap } from "../core/game/TerrainMapLoader";
import { endGame, startGame, startTime } from "./LocalPersistantStats";
import { EventBus } from "../core/EventBus";
import { GameMapLoader } from "../core/game/GameMapLoader";
import { ServerConfig } from "../core/configuration/Config";
import { TileRef } from "../core/game/GameMap";
import { UserSettings } from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import { createCanvas } from "./Utils";
import { createGameRecord } from "../core/Util";
import { getConfig } from "../core/configuration/ConfigLoader";
import { getPersistentID } from "./Main";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import { translateText } from "../client/Utils";
export type LobbyConfig = {
serverConfig: ServerConfig;
+3 -3
View File
@@ -1,10 +1,10 @@
import { Cosmetics, CosmeticsSchema, Pattern } from "../core/CosmeticSchemas";
import { z } from "zod";
import {
StripeCreateCheckoutSessionResponseSchema,
UserMeResponse,
} from "../core/ApiSchemas";
import { Cosmetics, CosmeticsSchema, Pattern } from "../core/CosmeticSchemas";
import { getApiBase, getAuthHeader } from "./jwt";
import { z } from "zod";
export async function patterns(
userMe: UserMeResponse | null,
@@ -41,7 +41,7 @@ export async function handlePurchase(priceId: string) {
method: "POST",
headers: {
"Content-Type": "application/json",
"authorization": getAuthHeader(),
authorization: getAuthHeader(),
},
body: JSON.stringify({
priceId,
+1 -1
View File
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { UserSettings } from "../core/game/UserSettings";
+2 -2
View File
@@ -1,7 +1,7 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { FlagSchema } from "../core/Schemas";
import { renderPlayerFlag } from "../core/CustomFlag";
import { FlagSchema } from "../core/Schemas";
const flagKey = "flag";
+11 -6
View File
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import Countries from "./data/countries.json";
@@ -33,10 +33,13 @@ export class FlagInputModal extends LitElement {
<div
class="flex flex-wrap justify-evenly gap-[1rem] overflow-y-auto overflow-x-hidden h-[90%]"
>
${this.isModalOpen ? Countries.filter(
(country) => !country.restricted && this.includedInSearch(country),
).map(
(country) => html`
${
this.isModalOpen
? Countries.filter(
(country) =>
!country.restricted && this.includedInSearch(country),
).map(
(country) => html`
<button
@click=${() => {
this.setFlag(country.code);
@@ -61,7 +64,9 @@ export class FlagInputModal extends LitElement {
<span class="country-name">${country.name}</span>
</button>
`,
) : html``}
)
: html``
}
</div>
</o-modal>
`;
+1 -1
View File
@@ -1,4 +1,4 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "./Utils";
+1 -1
View File
@@ -1,4 +1,4 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
declare global {
+1 -1
View File
@@ -1,6 +1,6 @@
import "./components/Difficulties";
import "./components/Maps";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators.js";
import { getAltKey, getModifierKey, translateText } from "../client/Utils";
+45 -39
View File
@@ -2,6 +2,22 @@
import "./components/Difficulties";
import "./components/Maps";
import "./components/baseComponents/Modal";
import { html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import randomMap from "../../resources/images/RandomMap.webp";
import { translateText } from "../client/Utils";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import {
Difficulty,
Duos,
GameMapType,
GameMode,
mapCategories,
Quads,
Trios,
UnitType,
} from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
import {
ClientInfo,
GameConfig,
@@ -9,26 +25,10 @@ import {
GameInfoSchema,
TeamCountConfig,
} from "../core/Schemas";
import {
Difficulty,
Duos,
GameMapType,
GameMode,
Quads,
Trios,
UnitType,
mapCategories,
} from "../core/game/Game";
import { LitElement, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { generateID } from "../core/Util";
import { DifficultyDescription } from "./components/Difficulties";
import { JoinLobbyEvent } from "./Main";
import { UserSettings } from "../core/game/UserSettings";
import { generateID } from "../core/Util";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import randomMap from "../../resources/images/RandomMap.webp";
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
import { translateText } from "../client/Utils";
@customElement("host-lobby-modal")
export class HostLobbyModal extends LitElement {
@@ -206,8 +206,10 @@ export class HostLobbyModal extends LitElement {
>
<map-display
.mapKey=${mapKey}
.selected=${!this.useRandomMap &&
this.selectedMap === mapValue}
.selected=${
!this.useRandomMap &&
this.selectedMap === mapValue
}
.translation=${translateText(
`map.${mapKey?.toLowerCase()}`,
)}
@@ -248,9 +250,9 @@ export class HostLobbyModal extends LitElement {
.map(
([key, value]) => html`
<div
class="option-card ${this.selectedDifficulty === value
? "selected"
: ""}"
class="option-card ${
this.selectedDifficulty === value ? "selected" : ""
}"
@click=${() => this.handleDifficultySelection(value)}
>
<difficulty-display
@@ -303,17 +305,19 @@ export class HostLobbyModal extends LitElement {
${[2, 3, 4, 5, 6, 7, Quads, Trios, Duos].map(
(o) => html`
<div
class="option-card ${this.teamCount === o
? "selected"
: ""}"
class="option-card ${
this.teamCount === o ? "selected" : ""
}"
@click=${() => this.handleTeamCountSelection(o)}
>
<div class="option-card-title">
${typeof o === "string"
? translateText(`public_lobby.teams_${o}`)
: translateText("public_lobby.teams", {
num: o,
})}
${
typeof o === "string"
? translateText(`public_lobby.teams_${o}`)
: translateText("public_lobby.teams", {
num: o,
})
}
</div>
</div>
`,
@@ -457,9 +461,9 @@ export class HostLobbyModal extends LitElement {
style="display: flex; flex-wrap: wrap; justify-content: center; gap: 12px;"
>
${renderUnitTypeOptions({
disabledUnits: this.disabledUnits,
toggleUnit: this.toggleUnit.bind(this),
})}
disabledUnits: this.disabledUnits,
toggleUnit: this.toggleUnit.bind(this),
})}
</div>
</div>
</div>
@@ -482,11 +486,12 @@ export class HostLobbyModal extends LitElement {
(client) => html`
<span class="player-tag">
${client.username}
${client.clientID === this.lobbyCreatorClientID
? html`<span class="host-badge"
${
client.clientID === this.lobbyCreatorClientID
? html`<span class="host-badge"
>(${translateText("host_modal.host_badge")})</span
>`
: html`
: html`
<button
class="remove-player-btn"
@click=${() => this.kickPlayer(client.clientID)}
@@ -494,7 +499,8 @@ export class HostLobbyModal extends LitElement {
>
×
</button>
`}
`
}
</span>
`,
)}
@@ -695,8 +701,8 @@ export class HostLobbyModal extends LitElement {
await this.putGameConfig();
console.log(
`Starting private game with map: ${
GameMapType[this.selectedMap as keyof typeof GameMapType]} ${
this.useRandomMap ? " (Randomly selected)" : ""}`,
GameMapType[this.selectedMap as keyof typeof GameMapType]
} ${this.useRandomMap ? " (Randomly selected)" : ""}`,
);
this.close();
const config = await getServerConfigFromClient();
+1 -1
View File
@@ -1,8 +1,8 @@
import { EventBus, GameEvent } from "../core/EventBus";
import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
import { UnitType } from "../core/game/Game";
import { UnitView } from "../core/game/GameView";
import { UserSettings } from "../core/game/UserSettings";
import { ReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
export class MouseUpEvent implements GameEvent {
constructor(
+20 -14
View File
@@ -1,16 +1,16 @@
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import { html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { GameInfo, GameInfoSchema } from "../core/Schemas";
import { LitElement, html } from "lit";
import { getClientID } from "../core/Util";
import {
WorkerApiArchivedGameLobbySchema,
WorkerApiGameIdExistsSchema,
} from "../core/WorkerSchemas";
import { customElement, query, state } from "lit/decorators.js";
import { JoinLobbyEvent } from "./Main";
import { getClientID } from "../core/Util";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { translateText } from "../client/Utils";
@customElement("join-private-lobby-modal")
export class JoinPrivateLobbyModal extends LitElement {
@@ -82,13 +82,16 @@ export class JoinPrivateLobbyModal extends LitElement {
${this.message}
</div>
<div class="options-layout">
${this.hasJoined && this.players.length > 0
? html` <div class="options-section">
${
this.hasJoined && this.players.length > 0
? html` <div class="options-section">
<div class="option-title">
${this.players.length}
${this.players.length === 1
? translateText("private_lobby.player")
: translateText("private_lobby.players")}
${
this.players.length === 1
? translateText("private_lobby.player")
: translateText("private_lobby.players")
}
</div>
<div class="players-list">
@@ -97,16 +100,19 @@ export class JoinPrivateLobbyModal extends LitElement {
)}
</div>
</div>`
: ""}
: ""
}
</div>
<div class="flex justify-center">
${!this.hasJoined
? html` <o-button
${
!this.hasJoined
? html` <o-button
title=${translateText("private_lobby.join_lobby")}
block
@click=${this.joinLobby}
></o-button>`
: ""}
: ""
}
</div>
</o-modal>
`;
+12 -12
View File
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import "./LanguageModal";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import ar from "../../resources/lang/ar.json";
@@ -289,16 +289,16 @@ export class LangSelector extends LitElement {
this.languageList.find((l) => l.code === this.currentLang) ??
(this.currentLang === "debug"
? {
code: "debug",
native: "Debug",
en: "Debug",
svg: "xx",
}
code: "debug",
native: "Debug",
en: "Debug",
svg: "xx",
}
: {
native: "English",
en: "English",
svg: "uk_us_flag",
});
native: "English",
en: "English",
svg: "uk_us_flag",
});
return html`
<div class="container__row">
@@ -327,7 +327,7 @@ export class LangSelector extends LitElement {
.languageList=${this.languageList}
.currentLang=${this.currentLang}
@language-selected=${(e: CustomEvent) =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.changeLanguage(e.detail.lang)}
@close-modal=${() => (this.showModal = false)}
></language-modal>
@@ -348,7 +348,7 @@ function flattenTranslations(
if (typeof value === "string") {
result[fullKey] = value;
} else if (value && typeof value === "object" && !Array.isArray(value)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
flattenTranslations(value, fullKey, result);
} else {
console.warn("Unknown type", typeof value, value);
+2 -2
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { translateText } from "../client/Utils";
@@ -111,7 +111,7 @@ export class LanguageModal extends LitElement {
<button
class="${buttonClasses}"
@click=${() =>
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
this.selectLanguage(lang.code)}
>
<img
+2 -2
View File
@@ -1,3 +1,5 @@
import { z } from "zod";
import { ID } from "../core/BaseSchemas";
import {
GameConfig,
GameConfigSchema,
@@ -5,9 +7,7 @@ import {
GameRecord,
GameRecordSchema,
} from "../core/Schemas";
import { ID } from "../core/BaseSchemas";
import { replacer } from "../core/Util";
import { z } from "zod";
const LocalStatsDataSchema = z.record(
ID,
+6 -7
View File
@@ -1,3 +1,5 @@
import { z } from "zod";
import { EventBus } from "../core/EventBus";
import {
AllPlayersStats,
ClientMessage,
@@ -10,12 +12,10 @@ import {
Turn,
} from "../core/Schemas";
import { createGameRecord, decompressGameRecord, replacer } from "../core/Util";
import { EventBus } from "../core/EventBus";
import { LobbyConfig } from "./ClientGameRunner";
import { ReplaySpeedChangeEvent } from "./InputHandler";
import { defaultReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
import { getPersistentID } from "./Main";
import { z } from "zod";
import { defaultReplaySpeedMultiplier } from "./utilities/ReplaySpeedMultiplier";
export class LocalServer {
// All turns from the game record on replay.
@@ -117,10 +117,9 @@ export class LocalServer {
}
if (archivedHash !== clientMsg.hash) {
console.error(
`desync detected on turn ${
clientMsg.turnNumber}, client hash: ${
clientMsg.hash}, server hash: ${
archivedHash}`,
`desync detected on turn ${clientMsg.turnNumber}, client hash: ${
clientMsg.hash
}, server hash: ${archivedHash}`,
);
this.clientMessage({
type: "desync",
+21 -17
View File
@@ -8,37 +8,41 @@ import "./components/NewsButton";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import "./styles.css";
import { GameRecord, GameStartInfo } from "../core/Schemas";
import { discordLogin, getUserMe, isLoggedIn, logOut } from "./jwt";
import { generateCryptoRandomUUID, incrementGamesPlayed, translateText } from "./Utils";
import { DarkModeButton } from "./DarkModeButton";
import version from "../../resources/version.txt";
import { UserMeResponse } from "../core/ApiSchemas";
import { ID } from "../core/BaseSchemas";
import { ServerConfig } from "../core/configuration/Config";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { EventBus } from "../core/EventBus";
import { GameType } from "../core/game/Game";
import { UserSettings } from "../core/game/UserSettings";
import { GameRecord, GameStartInfo } from "../core/Schemas";
import { getClientID } from "../core/Util";
import { joinLobby } from "./ClientGameRunner";
import { OButton } from "./components/baseComponents/Button";
import { NewsButton } from "./components/NewsButton";
import { DarkModeButton } from "./DarkModeButton";
import { FlagInput } from "./FlagInput";
import { FlagInputModal } from "./FlagInputModal";
import { GameStartingModal } from "./GameStartingModal";
import { GameType } from "../core/game/Game";
import { HelpModal } from "./HelpModal";
import { HostLobbyModal } from "./HostLobbyModal";
import { ID } from "../core/BaseSchemas";
import { JoinPrivateLobbyModal } from "./JoinPrivateLobbyModal";
import { discordLogin, getUserMe, isLoggedIn, logOut } from "./jwt";
import { LangSelector } from "./LangSelector";
import { LanguageModal } from "./LanguageModal";
import { NewsButton } from "./components/NewsButton";
import { NewsModal } from "./NewsModal";
import { OButton } from "./components/baseComponents/Button";
import { PublicLobby } from "./PublicLobby";
import { SendKickPlayerIntentEvent } from "./Transport";
import { ServerConfig } from "../core/configuration/Config";
import { SinglePlayerModal } from "./SinglePlayerModal";
import { TerritoryPatternsModal } from "./TerritoryPatternsModal";
import { UserMeResponse } from "../core/ApiSchemas";
import { UserSettingModal } from "./UserSettingModal";
import { UserSettings } from "../core/game/UserSettings";
import { SendKickPlayerIntentEvent } from "./Transport";
import { UsernameInput } from "./UsernameInput";
import { getClientID } from "../core/Util";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { joinLobby } from "./ClientGameRunner";
import version from "../../resources/version.txt";
import { UserSettingModal } from "./UserSettingModal";
import {
generateCryptoRandomUUID,
incrementGamesPlayed,
translateText,
} from "./Utils";
declare global {
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+2 -2
View File
@@ -1,9 +1,9 @@
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, query } from "lit/decorators.js";
import changelog from "../../resources/changelog.md";
import { resolveMarkdown } from "lit-markdown";
import changelog from "../../resources/changelog.md";
import { translateText } from "../client/Utils";
@customElement("news-modal")
+27 -24
View File
@@ -1,12 +1,12 @@
import { GameID, GameInfo } from "../core/Schemas";
import { GameMapType, GameMode } from "../core/game/Game";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { ApiPublicLobbiesResponseSchema } from "../core/ExpressSchemas";
import { JoinLobbyEvent } from "./Main";
import { getClientID } from "../core/Util";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
import { translateText } from "../client/Utils";
import { ApiPublicLobbiesResponseSchema } from "../core/ExpressSchemas";
import { GameMapType, GameMode } from "../core/game/Game";
import { GameID, GameInfo } from "../core/Schemas";
import { getClientID } from "../core/Util";
import { JoinLobbyEvent } from "./Main";
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
@customElement("public-lobby")
export class PublicLobby extends LitElement {
@@ -126,20 +126,21 @@ export class PublicLobby extends LitElement {
? "bg-gradient-to-r from-green-600 to-green-500"
: "bg-gradient-to-r from-blue-600 to-blue-500"
} text-white font-medium rounded-xl transition-opacity duration-200 hover:opacity-90 ${
this.isButtonDebounced
? "opacity-70 cursor-not-allowed"
: ""}"
this.isButtonDebounced ? "opacity-70 cursor-not-allowed" : ""
}"
>
${mapImageSrc
? html`<img
${
mapImageSrc
? html`<img
src="${mapImageSrc}"
alt="${lobby.gameConfig.gameMap}"
class="place-self-start col-span-full row-span-full h-full -z-10"
style="mask-image: linear-gradient(to left, transparent, #fff)"
/>`
: html`<div
: html`<div
class="place-self-start col-span-full row-span-full h-full -z-10 bg-gray-300"
></div>`}
></div>`
}
<div
class="flex flex-col justify-between h-full col-span-full row-span-full p-4 md:p-6 text-right z-0"
>
@@ -149,17 +150,19 @@ export class PublicLobby extends LitElement {
</div>
<div class="text-md font-medium text-blue-100">
<span
class="text-sm ${this.isLobbyHighlighted
? "text-green-600"
: "text-blue-600"} bg-white rounded-sm px-1"
class="text-sm ${
this.isLobbyHighlighted ? "text-green-600" : "text-blue-600"
} bg-white rounded-sm px-1"
>
${lobby.gameConfig.gameMode === GameMode.Team
? typeof teamCount === "string"
? translateText(`public_lobby.teams_${teamCount}`)
: translateText("public_lobby.teams", {
num: teamCount ?? 0,
})
: translateText("game_mode.ffa")}</span
${
lobby.gameConfig.gameMode === GameMode.Team
? typeof teamCount === "string"
? translateText(`public_lobby.teams_${teamCount}`)
: translateText("public_lobby.teams", {
num: teamCount ?? 0,
})
: translateText("game_mode.ffa")
}</span
>
<span
>${translateText(
+46 -36
View File
@@ -2,29 +2,29 @@ import "./components/Difficulties";
import "./components/Maps";
import "./components/baseComponents/Button";
import "./components/baseComponents/Modal";
import { html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import randomMap from "../../resources/images/RandomMap.webp";
import { translateText } from "../client/Utils";
import {
Difficulty,
Duos,
GameMapType,
GameMode,
GameType,
mapCategories,
Quads,
Trios,
UnitType,
mapCategories,
} from "../core/game/Game";
import { LitElement, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { UserSettings } from "../core/game/UserSettings";
import { TeamCountConfig } from "../core/Schemas";
import { generateID, getClientID } from "../core/Util";
import { DifficultyDescription } from "./components/Difficulties";
import { FlagInput } from "./FlagInput";
import { JoinLobbyEvent } from "./Main";
import { TeamCountConfig } from "../core/Schemas";
import { UserSettings } from "../core/game/UserSettings";
import { UsernameInput } from "./UsernameInput";
import randomMap from "../../resources/images/RandomMap.webp";
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
import { translateText } from "../client/Utils";
@customElement("single-player-modal")
export class SinglePlayerModal extends LitElement {
@@ -96,8 +96,10 @@ export class SinglePlayerModal extends LitElement {
>
<map-display
.mapKey=${mapKey}
.selected=${!this.useRandomMap &&
this.selectedMap === mapValue}
.selected=${
!this.useRandomMap &&
this.selectedMap === mapValue
}
.translation=${translateText(
`map.${mapKey?.toLowerCase()}`,
)}
@@ -110,9 +112,9 @@ export class SinglePlayerModal extends LitElement {
`,
)}
<div
class="option-card random-map ${this.useRandomMap
? "selected"
: ""}"
class="option-card random-map ${
this.useRandomMap ? "selected" : ""
}"
@click=${this.handleRandomMapToggle}
>
<div class="option-image">
@@ -140,9 +142,9 @@ export class SinglePlayerModal extends LitElement {
.map(
([key, value]) => html`
<div
class="option-card ${this.selectedDifficulty === value
? "selected"
: ""}"
class="option-card ${
this.selectedDifficulty === value ? "selected" : ""
}"
@click=${() => this.handleDifficultySelection(value)}
>
<difficulty-display
@@ -164,9 +166,9 @@ export class SinglePlayerModal extends LitElement {
<div class="option-title">${translateText("host_modal.mode")}</div>
<div class="option-cards">
<div
class="option-card ${this.gameMode === GameMode.FFA
? "selected"
: ""}"
class="option-card ${
this.gameMode === GameMode.FFA ? "selected" : ""
}"
@click=${() => this.handleGameModeSelection(GameMode.FFA)}
>
<div class="option-card-title">
@@ -174,9 +176,9 @@ export class SinglePlayerModal extends LitElement {
</div>
</div>
<div
class="option-card ${this.gameMode === GameMode.Team
? "selected"
: ""}"
class="option-card ${
this.gameMode === GameMode.Team ? "selected" : ""
}"
@click=${() => this.handleGameModeSelection(GameMode.Team)}
>
<div class="option-card-title">
@@ -186,9 +188,10 @@ export class SinglePlayerModal extends LitElement {
</div>
</div>
${this.gameMode === GameMode.FFA
? ""
: html`
${
this.gameMode === GameMode.FFA
? ""
: html`
<!-- Team Count Selection -->
<div class="options-section">
<div class="option-title">
@@ -198,22 +201,27 @@ export class SinglePlayerModal extends LitElement {
${[2, 3, 4, 5, 6, 7, Quads, Trios, Duos].map(
(o) => html`
<div
class="option-card ${this.teamCount === o
? "selected"
: ""}"
class="option-card ${
this.teamCount === o ? "selected" : ""
}"
@click=${() => this.handleTeamCountSelection(o)}
>
<div class="option-card-title">
${typeof o === "string"
? translateText(`public_lobby.teams_${o}`)
: translateText("public_lobby.teams", { num: o })}
${
typeof o === "string"
? translateText(`public_lobby.teams_${o}`)
: translateText("public_lobby.teams", {
num: o,
})
}
</div>
</div>
`,
)}
</div>
</div>
`}
`
}
<!-- Game Options -->
<div class="options-section">
@@ -233,10 +241,11 @@ export class SinglePlayerModal extends LitElement {
.value="${String(this.bots)}"
/>
<div class="option-card-title">
<span>${translateText("single_modal.bots")}</span>${this
.bots === 0
? translateText("single_modal.bots_disabled")
: this.bots}
<span>${translateText("single_modal.bots")}</span>${
this.bots === 0
? translateText("single_modal.bots_disabled")
: this.bots
}
</div>
</label>
@@ -410,7 +419,8 @@ export class SinglePlayerModal extends LitElement {
}
console.log(
`Starting single player game with map: ${GameMapType[this.selectedMap as keyof typeof GameMapType]
`Starting single player game with map: ${
GameMapType[this.selectedMap as keyof typeof GameMapType]
}${this.useRandomMap ? " (Randomly selected)" : ""}`,
);
const gameID = generateID();
+1 -1
View File
@@ -1,4 +1,4 @@
import { FetchGameMapLoader } from "../core/game/FetchGameMapLoader";
import version from "../../resources/version.txt";
import { FetchGameMapLoader } from "../core/game/FetchGameMapLoader";
export const terrainMapFileLoader = new FetchGameMapLoader("/maps", version);
+41 -29
View File
@@ -1,14 +1,14 @@
import "./components/Difficulties";
import "./components/Maps";
import { LitElement, html, render } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { handlePurchase, patterns } from "./Cosmetics";
import { Pattern } from "../core/CosmeticSchemas";
import { PatternDecoder } from "../core/PatternDecoder";
import type { TemplateResult } from "lit";
import { UserMeResponse } from "../core/ApiSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { base64url } from "jose";
import type { TemplateResult } from "lit";
import { html, LitElement, render } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { UserMeResponse } from "../core/ApiSchemas";
import { Pattern } from "../core/CosmeticSchemas";
import { UserSettings } from "../core/game/UserSettings";
import { PatternDecoder } from "../core/PatternDecoder";
import { handlePurchase, patterns } from "./Cosmetics";
import { translateText } from "./Utils";
@customElement("territory-patterns-modal")
@@ -112,8 +112,9 @@ export class TerritoryPatternsModal extends LitElement {
return html`
<div
class="fixed z-[10000] px-3 py-2 rounded bg-black text-white text-sm pointer-events-none shadow-md"
style="top: ${this.hoverPosition.y + 12}px; left: ${this.hoverPosition
.x + 12}px;"
style="top: ${this.hoverPosition.y + 12}px; left: ${
this.hoverPosition.x + 12
}px;"
>
${translateText("territory_patterns.blocked.purchase")}
</div>
@@ -129,9 +130,11 @@ export class TerritoryPatternsModal extends LitElement {
<div style="flex: 0 1 calc(25% - 1rem); max-width: calc(25% - 1rem);">
<button
class="border p-2 rounded-lg shadow text-black dark:text-white text-left w-full
${isSelected
? "bg-blue-500 text-white"
: "bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"}
${
isSelected
? "bg-blue-500 text-white"
: "bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
}
${pattern.product !== null ? "opacity-50 cursor-not-allowed" : ""}"
@click=${() =>
pattern.product === null && this.selectPattern(pattern.pattern)}
@@ -162,8 +165,9 @@ export class TerritoryPatternsModal extends LitElement {
)}
</div>
</button>
${pattern.product !== null
? html`
${
pattern.product !== null
? html`
<button
class="w-full mt-2 px-3 py-1 bg-green-500 hover:bg-green-600
text-white text-xs font-medium rounded transition-colors"
@@ -177,7 +181,8 @@ export class TerritoryPatternsModal extends LitElement {
(${pattern.product.price})
</button>
`
: null}
: null
}
</div>
`;
}
@@ -198,9 +203,11 @@ export class TerritoryPatternsModal extends LitElement {
>
<button
class="border p-2 rounded-lg shadow text-black dark:text-white text-left
${this.selectedPattern === undefined
? "bg-blue-500 text-white"
: "bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"}"
${
this.selectedPattern === undefined
? "bg-blue-500 text-white"
: "bg-gray-100 hover:bg-gray-200 dark:bg-gray-800 dark:hover:bg-gray-700"
}"
style="flex: 0 1 calc(25% - 1rem); max-width: calc(25% - 1rem);"
@click=${() => this.selectPattern(undefined)}
>
@@ -290,24 +297,29 @@ export class TerritoryPatternsModal extends LitElement {
"
>
<div
style="display: grid; grid-template-columns: repeat(2, ${width /
2}px); grid-template-rows: repeat(2, ${height / 2}px);"
style="display: grid; grid-template-columns: repeat(2, ${
width / 2
}px); grid-template-rows: repeat(2, ${height / 2}px);"
>
<div
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${width /
2}px; height: ${height / 2}px;"
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${
width / 2
}px; height: ${height / 2}px;"
></div>
<div
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${width /
2}px; height: ${height / 2}px;"
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${
width / 2
}px; height: ${height / 2}px;"
></div>
<div
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${width /
2}px; height: ${height / 2}px;"
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${
width / 2
}px; height: ${height / 2}px;"
></div>
<div
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${width /
2}px; height: ${height / 2}px;"
style="background-color: #fff; border: 1px solid rgba(0, 0, 0, 0.1); width: ${
width / 2
}px; height: ${height / 2}px;"
></div>
</div>
</div>
+5 -5
View File
@@ -1,3 +1,5 @@
import { z } from "zod";
import { EventBus, GameEvent } from "../core/EventBus";
import {
AllPlayers,
GameType,
@@ -7,6 +9,8 @@ import {
Tick,
UnitType,
} from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { PlayerView } from "../core/game/GameView";
import {
AllPlayersStats,
ClientHashMessage,
@@ -20,13 +24,9 @@ import {
ServerMessageSchema,
Winner,
} from "../core/Schemas";
import { EventBus, GameEvent } from "../core/EventBus";
import { replacer } from "../core/Util";
import { LobbyConfig } from "./ClientGameRunner";
import { LocalServer } from "./LocalServer";
import { PlayerView } from "../core/game/GameView";
import { TileRef } from "../core/game/GameMap";
import { replacer } from "../core/Util";
import { z } from "zod";
export class PauseGameEvent implements GameEvent {
constructor(public readonly paused: boolean) {}
+27 -18
View File
@@ -2,12 +2,12 @@ import "./components/baseComponents/setting/SettingKeybind";
import "./components/baseComponents/setting/SettingNumber";
import "./components/baseComponents/setting/SettingSlider";
import "./components/baseComponents/setting/SettingToggle";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { SettingKeybind } from "./components/baseComponents/setting/SettingKeybind";
import { UserSettings } from "../core/game/UserSettings";
import { translateText } from "../client/Utils";
import { z } from "zod";
import { translateText } from "../client/Utils";
import { UserSettings } from "../core/game/UserSettings";
import { SettingKeybind } from "./components/baseComponents/setting/SettingKeybind";
const KeybindSchema = z.record(z.string(), z.string());
@@ -233,18 +233,22 @@ export class UserSettingModal extends LitElement {
<div class="flex mb-4 w-full justify-center">
<button
class="w-1/2 text-center px-3 py-1 rounded-l
${this.settingsMode === "basic"
? "bg-white/10 text-white"
: "bg-transparent text-gray-400"}"
${
this.settingsMode === "basic"
? "bg-white/10 text-white"
: "bg-transparent text-gray-400"
}"
@click=${() => (this.settingsMode = "basic")}
>
${translateText("user_setting.tab_basic")}
</button>
<button
class="w-1/2 text-center px-3 py-1 rounded-r
${this.settingsMode === "keybinds"
? "bg-white/10 text-white"
: "bg-transparent text-gray-400"}"
${
this.settingsMode === "keybinds"
? "bg-white/10 text-white"
: "bg-transparent text-gray-400"
}"
@click=${() => (this.settingsMode = "keybinds")}
>
${translateText("user_setting.tab_keybinds")}
@@ -252,9 +256,11 @@ export class UserSettingModal extends LitElement {
</div>
<div class="settings-list">
${this.settingsMode === "basic"
? this.renderBasicSettings()
: this.renderKeybindSettings()}
${
this.settingsMode === "basic"
? this.renderBasicSettings()
: this.renderKeybindSettings()
}
</div>
</div>
</div>
@@ -361,13 +367,15 @@ export class UserSettingModal extends LitElement {
description="${translateText("user_setting.attack_ratio_desc")}"
min="1"
max="100"
.value=${Number(localStorage.getItem("settings.attackRatio") ?? "0.2") *
100}
.value=${
Number(localStorage.getItem("settings.attackRatio") ?? "0.2") * 100
}
@change=${this.sliderAttackRatio}
></setting-slider>
${this.showEasterEggSettings
? html`
${
this.showEasterEggSettings
? html`
<setting-slider
label="${translateText(
"user_setting.easter_writing_speed_label",
@@ -408,7 +416,8 @@ export class UserSettingModal extends LitElement {
}}
></setting-number>
`
: null}
: null
}
`;
}
+10 -8
View File
@@ -1,12 +1,12 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { v4 as uuidv4 } from "uuid";
import { translateText } from "../client/Utils";
import { UserSettings } from "../core/game/UserSettings";
import {
MAX_USERNAME_LENGTH,
validateUsername,
} from "../core/validations/username";
import { customElement, property, state } from "lit/decorators.js";
import { UserSettings } from "../core/game/UserSettings";
import { translateText } from "../client/Utils";
import { v4 as uuidv4 } from "uuid";
const usernameKey = "username";
@@ -48,8 +48,9 @@ export class UsernameInput extends LitElement {
focus:ring-blue-500 focus:border-blue-500 dark:border-gray-300/60
dark:bg-gray-700 dark:text-white"
/>
${this.validationError
? html`<div
${
this.validationError
? html`<div
id="username-validation-error"
class="absolute z-10 w-full mt-2 px-3 py-1 text-lg border rounded
bg-white text-red-600 border-red-600 dark:bg-gray-700
@@ -57,7 +58,8 @@ export class UsernameInput extends LitElement {
>
${this.validationError}
</div>`
: null}
: null
}
`;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import IntlMessageFormat from "intl-messageformat";
import { LangSelector } from "./LangSelector";
import { MessageType } from "../core/game/Game";
import { LangSelector } from "./LangSelector";
export function renderTroops(troops: number): string {
return renderNumber(troops / 10);
+1 -1
View File
@@ -1,4 +1,4 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
export enum DifficultyDescription {
+8 -6
View File
@@ -1,4 +1,4 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { GameMapType } from "../../core/game/Game";
import { terrainMapFileLoader } from "../TerrainMapFileLoader";
@@ -119,17 +119,19 @@ export class MapDisplay extends LitElement {
render() {
return html`
<div class="option-card ${this.selected ? "selected" : ""}">
${this.isLoading
? html`<div class="option-image">
${
this.isLoading
? html`<div class="option-image">
${translateText("map_component.loading")}
</div>`
: this.mapWebpPath
? html`<img
: this.mapWebpPath
? html`<img
src="${this.mapWebpPath}"
alt="${this.mapKey}"
class="option-image"
/>`
: html`<div class="option-image">Error</div>`}
: html`<div class="option-image">Error</div>`
}
<div class="option-card-title">${this.translation || this.mapName}</div>
</div>
`;
+1 -1
View File
@@ -1,4 +1,4 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property } from "lit/decorators";
@customElement("modal-overlay")
+6 -7
View File
@@ -1,9 +1,9 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { NewsModal } from "../NewsModal";
import megaphone from "../../../resources/images/Megaphone.svg";
import { translateText } from "../Utils";
import version from "../../../resources/version.txt";
import { NewsModal } from "../NewsModal";
import { translateText } from "../Utils";
@customElement("news-button")
export class NewsButton extends LitElement {
@@ -38,10 +38,9 @@ export class NewsButton extends LitElement {
render() {
return html`
<div
class="flex relative ${this.hidden ? "parent-hidden" : ""} ${this
.isActive
? "active"
: ""}"
class="flex relative ${this.hidden ? "parent-hidden" : ""} ${
this.isActive ? "active" : ""
}"
>
<button
class="border p-[4px] rounded-lg flex cursor-pointer border-black/30
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { classMap } from "lit/directives/class-map.js";
import { translateText } from "../../Utils";
@@ -28,9 +28,11 @@ export class OButton extends LitElement {
})}
?disabled=${this.disable}
>
${`${this.translationKey}` === ""
? `${this.title}`
: `${translateText(this.translationKey)}`}
${
`${this.translationKey}` === ""
? `${this.title}`
: `${translateText(this.translationKey)}`
}
</button>
`;
}
+14 -10
View File
@@ -1,4 +1,4 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { translateText } from "../../Utils";
@@ -83,18 +83,21 @@ export class OModal extends LitElement {
render() {
return html`
${this.isModalOpen
? html`
${
this.isModalOpen
? html`
<aside class="c-modal">
<div
class="c-modal__wrapper ${this.alwaysMaximized
? "always-maximized"
: ""}"
class="c-modal__wrapper ${
this.alwaysMaximized ? "always-maximized" : ""
}"
>
<header class="c-modal__header">
${`${this.translationKey}` === ""
? `${this.title}`
: `${translateText(this.translationKey)}`}
${
`${this.translationKey}` === ""
? `${this.title}`
: `${translateText(this.translationKey)}`
}
<div class="c-modal__close" @click=${this.close}>✕</div>
</header>
<section class="c-modal__content">
@@ -103,7 +106,8 @@ export class OModal extends LitElement {
</div>
</aside>
`
: html``}
: html``
}
`;
}
}
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { translateText } from "../../../../client/Utils";
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("setting-number")
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("setting-slider")
@@ -1,4 +1,4 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
@customElement("setting-toggle")
+11 -10
View File
@@ -1,19 +1,19 @@
import { AnimatedSprite } from "./AnimatedSprite";
import { FxType } from "./fx/Fx";
import { PlayerView } from "../../core/game/GameView";
import SAMExplosion from "../../../resources/sprites/samExplosion.png";
import { Theme } from "../../core/configuration/Config";
import { colorizeCanvas } from "./SpriteLoader";
import miniBigSmoke from "../../../resources/sprites/bigsmoke.png";
import conquestSword from "../../../resources/sprites/conquestSword.png";
import dust from "../../../resources/sprites/dust.png";
import miniBigSmoke from "../../../resources/sprites/bigsmoke.png";
import miniExplosion from "../../../resources/sprites/miniExplosion.png";
import miniFire from "../../../resources/sprites/minifire.png";
import nuke from "../../../resources/sprites/nukeExplosion.png";
import SAMExplosion from "../../../resources/sprites/samExplosion.png";
import sinkingShip from "../../../resources/sprites/sinkingShip.png";
import miniSmoke from "../../../resources/sprites/smoke.png";
import miniSmokeAndFire from "../../../resources/sprites/smokeAndFire.png";
import nuke from "../../../resources/sprites/nukeExplosion.png";
import sinkingShip from "../../../resources/sprites/sinkingShip.png";
import unitExplosion from "../../../resources/sprites/unitExplosion.png";
import { Theme } from "../../core/configuration/Config";
import { PlayerView } from "../../core/game/GameView";
import { AnimatedSprite } from "./AnimatedSprite";
import { FxType } from "./fx/Fx";
import { colorizeCanvas } from "./SpriteLoader";
type AnimatedSpriteConfig = {
url: string;
@@ -128,7 +128,8 @@ const ANIMATED_SPRITE_CONFIG: Partial<Record<FxType, AnimatedSpriteConfig>> = {
};
export class AnimatedSpriteLoader {
private readonly animatedSpriteImageMap: Map<FxType, HTMLCanvasElement> = new Map();
private readonly animatedSpriteImageMap: Map<FxType, HTMLCanvasElement> =
new Map();
// Do not color the same sprite twice
private readonly coloredAnimatedSpriteCache: Map<string, HTMLCanvasElement> =
new Map();
+7 -7
View File
@@ -1,17 +1,19 @@
import { EventBus } from "../../core/EventBus";
import { GameView } from "../../core/game/GameView";
import { UserSettings } from "../../core/game/UserSettings";
import { GameStartingModal } from "../GameStartingModal";
import { RedrawGraphicsEvent } from "../InputHandler";
import { AlertFrame } from "./layers/AlertFrame";
import { BuildMenu } from "./layers/BuildMenu";
import { ChatDisplay } from "./layers/ChatDisplay";
import { ChatModal } from "./layers/ChatModal";
import { ControlPanel } from "./layers/ControlPanel";
import { EmojiTable } from "./layers/EmojiTable";
import { EventBus } from "../../core/EventBus";
import { EventsDisplay } from "./layers/EventsDisplay";
import { FPSDisplay } from "./layers/FPSDisplay";
import { FxLayer } from "./layers/FxLayer";
import { GameLeftSidebar } from "./layers/GameLeftSidebar";
import { GameRightSidebar } from "./layers/GameRightSidebar";
import { GameStartingModal } from "../GameStartingModal";
import { GameView } from "../../core/game/GameView";
import { GutterAdModal } from "./layers/GutterAdModal";
import { HeadsUpMessage } from "./layers/HeadsUpMessage";
import { Layer } from "./layers/Layer";
@@ -22,7 +24,6 @@ import { NameLayer } from "./layers/NameLayer";
import { PlayerInfoOverlay } from "./layers/PlayerInfoOverlay";
import { PlayerPanel } from "./layers/PlayerPanel";
import { RailroadLayer } from "./layers/RailroadLayer";
import { RedrawGraphicsEvent } from "../InputHandler";
import { ReplayPanel } from "./layers/ReplayPanel";
import { SettingsModal } from "./layers/SettingsModal";
import { SpawnAd } from "./layers/SpawnAd";
@@ -32,13 +33,12 @@ import { StructureLayer } from "./layers/StructureLayer";
import { TeamStats } from "./layers/TeamStats";
import { TerrainLayer } from "./layers/TerrainLayer";
import { TerritoryLayer } from "./layers/TerritoryLayer";
import { TransformHandler } from "./TransformHandler";
import { UILayer } from "./layers/UILayer";
import { UIState } from "./UIState";
import { UnitDisplay } from "./layers/UnitDisplay";
import { UnitLayer } from "./layers/UnitLayer";
import { UserSettings } from "../../core/game/UserSettings";
import { WinModal } from "./layers/WinModal";
import { TransformHandler } from "./TransformHandler";
import { UIState } from "./UIState";
export function createRenderer(
canvas: HTMLCanvasElement,
+4 -4
View File
@@ -1,17 +1,17 @@
import { TrainType, UnitType } from "../../core/game/Game";
import { Colord } from "colord";
import { Theme } from "../../core/configuration/Config";
import { UnitView } from "../../core/game/GameView";
import atomBombSprite from "../../../resources/sprites/atombomb.png";
import hydrogenBombSprite from "../../../resources/sprites/hydrogenbomb.png";
import mirvSprite from "../../../resources/sprites/mirv2.png";
import samMissileSprite from "../../../resources/sprites/samMissile.png";
import tradeShipSprite from "../../../resources/sprites/tradeship.png";
import trainCarriageSprite from "../../../resources/sprites/trainCarriage.png";
import trainEngineSprite from "../../../resources/sprites/trainEngine.png";
import trainLoadedCarriageSprite from "../../../resources/sprites/trainCarriageLoaded.png";
import trainEngineSprite from "../../../resources/sprites/trainEngine.png";
import transportShipSprite from "../../../resources/sprites/transportship.png";
import warshipSprite from "../../../resources/sprites/warship.png";
import { Theme } from "../../core/configuration/Config";
import { TrainType, UnitType } from "../../core/game/Game";
import { UnitView } from "../../core/game/GameView";
// Can't reuse TrainType because "loaded" is not a type, just an attribute
const TrainTypeSprite = {
+3 -3
View File
@@ -1,12 +1,12 @@
import { EventBus } from "../../core/EventBus";
import { Cell } from "../../core/game/Game";
import { GameView } from "../../core/game/GameView";
import { CenterCameraEvent, DragEvent, ZoomEvent } from "../InputHandler";
import {
GoToPlayerEvent,
GoToPositionEvent,
GoToUnitEvent,
} from "./layers/Leaderboard";
import { Cell } from "../../core/game/Game";
import { EventBus } from "../../core/EventBus";
import { GameView } from "../../core/game/GameView";
export const GOTO_INTERVAL_MS = 16;
export const CAMERA_MAX_SPEED = 15;
+4 -4
View File
@@ -1,10 +1,10 @@
import { FadeFx, SpriteFx } from "./SpriteFx";
import { Fx, FxType } from "./Fx";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { ConquestUpdate } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { TextFx } from "./TextFx";
import { renderNumber } from "../../Utils";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Fx, FxType } from "./Fx";
import { FadeFx, SpriteFx } from "./SpriteFx";
import { TextFx } from "./TextFx";
/**
* Conquest FX:
+3 -3
View File
@@ -1,7 +1,7 @@
import { FadeFx, SpriteFx } from "./SpriteFx";
import { Fx, FxType } from "./Fx";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { GameView } from "../../../core/game/GameView";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Fx, FxType } from "./Fx";
import { FadeFx, SpriteFx } from "./SpriteFx";
/**
* Shockwave effect: draw a growing 1px white circle
+3 -3
View File
@@ -1,8 +1,8 @@
import { Fx, FxType } from "./Fx";
import { Theme } from "../../../core/configuration/Config";
import { PlayerView } from "../../../core/game/GameView";
import { AnimatedSprite } from "../AnimatedSprite";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { PlayerView } from "../../../core/game/GameView";
import { Theme } from "../../../core/configuration/Config";
import { Fx, FxType } from "./Fx";
function fadeInOut(t: number, fadeIn = 0.3, fadeOut = 0.7): number {
if (t < fadeIn) {
+2 -2
View File
@@ -1,6 +1,6 @@
import { Fx, FxType } from "./Fx";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { GameView } from "../../../core/game/GameView";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Fx, FxType } from "./Fx";
import { SpriteFx } from "./SpriteFx";
import { Timeline } from "./Timeline";
+3 -3
View File
@@ -1,12 +1,12 @@
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import {
BrokeAllianceUpdate,
GameUpdateType,
} from "../../../core/game/GameUpdates";
import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { UserSettings } from "../../../core/game/UserSettings";
import { Layer } from "./Layer";
// Parameters for the alert animation
const ALERT_SPEED = 1.6;
+40 -32
View File
@@ -1,39 +1,39 @@
import {
BuildUnitIntentEvent,
SendUpgradeStructureIntentEvent,
} from "../../Transport";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg";
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg";
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
import hydrogenBombIcon from "../../../../resources/images/MushroomCloudIconWhite.svg";
import atomBombIcon from "../../../../resources/images/NukeIconWhite.svg";
import portIcon from "../../../../resources/images/PortIcon.svg";
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
import missileSiloIcon from "../../../../resources/non-commercial/svg/MissileSiloIconWhite.svg";
import samlauncherIcon from "../../../../resources/non-commercial/svg/SamLauncherIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import {
BuildableUnit,
Gold,
PlayerActions,
UnitType,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView } from "../../../core/game/GameView";
import {
CloseViewEvent,
MouseDownEvent,
ShowBuildMenuEvent,
ShowEmojiMenuEvent,
} from "../../InputHandler";
import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { TileRef } from "../../../core/game/GameMap";
import { TransformHandler } from "../TransformHandler";
import atomBombIcon from "../../../../resources/images/NukeIconWhite.svg";
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg";
import goldCoinIcon from "../../../../resources/images/GoldCoinIcon.svg";
import hydrogenBombIcon from "../../../../resources/images/MushroomCloudIconWhite.svg";
import mirvIcon from "../../../../resources/images/MIRVIcon.svg";
import missileSiloIcon from "../../../../resources/non-commercial/svg/MissileSiloIconWhite.svg";
import portIcon from "../../../../resources/images/PortIcon.svg";
import {
BuildUnitIntentEvent,
SendUpgradeStructureIntentEvent,
} from "../../Transport";
import { renderNumber } from "../../Utils";
import samlauncherIcon from "../../../../resources/non-commercial/svg/SamLauncherIconWhite.svg";
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
import { translateText } from "../../../client/Utils";
import warshipIcon from "../../../../resources/images/BattleshipIconWhite.svg";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
export type BuildItemDisplay = {
unitType: UnitType;
@@ -389,7 +389,10 @@ export class BuildMenu extends LitElement implements Layer {
return player.totalUnitLevels(item.unitType).toString();
}
public sendBuildOrUpgrade(buildableUnit: BuildableUnit, tile?: TileRef): void {
public sendBuildOrUpgrade(
buildableUnit: BuildableUnit,
tile?: TileRef,
): void {
if (tile === undefined) throw new Error("Missing tile");
if (this.eventBus === undefined) throw new Error("Not initialized");
if (buildableUnit.canUpgrade !== false) {
@@ -430,9 +433,11 @@ export class BuildMenu extends LitElement implements Layer {
@click=${() =>
this.sendBuildOrUpgrade(buildableUnit, this.clickedTile)}
?disabled=${!enabled}
title=${!enabled
? translateText("build_menu.not_enough_money")
: ""}
title=${
!enabled
? translateText("build_menu.not_enough_money")
: ""
}
>
<img
src=${item.icon}
@@ -444,8 +449,9 @@ export class BuildMenu extends LitElement implements Layer {
>${item.key && translateText(item.key)}</span
>
<span class="build-description"
>${item.description &&
translateText(item.description)}</span
>${
item.description && translateText(item.description)
}</span
>
<span class="build-cost" translate="no">
${renderNumber(
@@ -459,11 +465,13 @@ export class BuildMenu extends LitElement implements Layer {
style="vertical-align: middle;"
/>
</span>
${item.countable
? html`<div class="build-count-chip">
${
item.countable
? html`<div class="build-count-chip">
<span class="build-count">${this.count(item)}</span>
</div>`
: ""}
: ""
}
</button>
`;
})}
+19 -21
View File
@@ -1,16 +1,16 @@
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { DirectiveResult } from "lit/directive.js";
import { UnsafeHTMLDirective, unsafeHTML } from "lit/directives/unsafe-html.js";
import { EventBus } from "../../../core/EventBus";
import { MessageType } from "../../../core/game/Game";
import {
DisplayMessageUpdate,
GameUpdateType,
} from "../../../core/game/GameUpdates";
import { LitElement, html } from "lit";
import { UnsafeHTMLDirective, unsafeHTML } from "lit/directives/unsafe-html.js";
import { customElement, state } from "lit/decorators.js";
import { DirectiveResult } from "lit/directive.js";
import { EventBus } from "../../../core/EventBus";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { MessageType } from "../../../core/game/Game";
import { onlyImages } from "../../../core/Util";
import { Layer } from "./Layer";
type ChatEvent = {
description: string;
@@ -136,10 +136,9 @@ export class ChatDisplay extends LitElement implements Layer {
<div>
<div class="w-full bg-black/80 sticky top-0 px-[10px]">
<button
class="text-white cursor-pointer pointer-events-auto ${this
._hidden
? "hidden"
: ""}"
class="text-white cursor-pointer pointer-events-auto ${
this._hidden ? "hidden" : ""
}"
@click=${this.toggleHidden}
>
Hide
@@ -147,25 +146,24 @@ export class ChatDisplay extends LitElement implements Layer {
</div>
<button
class="text-white cursor-pointer pointer-events-auto ${this._hidden
? ""
: "hidden"}"
class="text-white cursor-pointer pointer-events-auto ${
this._hidden ? "" : "hidden"
}"
@click=${this.toggleHidden}
>
Chat
<span
class="${this.newEvents
? ""
: "hidden"} inline-block px-2 bg-red-500 rounded-sm"
class="${
this.newEvents ? "" : "hidden"
} inline-block px-2 bg-red-500 rounded-sm"
>${this.newEvents}</span
>
</button>
<table
class="w-full border-collapse text-white shadow-lg lg:text-xl text-xs ${this
._hidden
? "hidden"
: ""}"
class="w-full border-collapse text-white shadow-lg lg:text-xl text-xs ${
this._hidden ? "hidden" : ""
}"
style="pointer-events: auto;"
>
<tbody>
@@ -1,9 +1,9 @@
import { COLORS, MenuElement, MenuElementParams } from "./RadialMenuElements";
import { ChatModal, QuickChatPhrase, quickChatPhrases } from "./ChatModal";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { EventBus } from "../../../core/EventBus";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { SendQuickChatEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { ChatModal, QuickChatPhrase, quickChatPhrases } from "./ChatModal";
import { COLORS, MenuElement, MenuElementParams } from "./RadialMenuElements";
export class ChatIntegration {
private readonly ctModal: ChatModal;
+37 -30
View File
@@ -1,11 +1,11 @@
import { GameView, PlayerView } from "../../../core/game/GameView";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, query } from "lit/decorators.js";
import { CloseViewEvent } from "../../InputHandler";
import quickChatData from "../../../../resources/QuickChat.json";
import { EventBus } from "../../../core/EventBus";
import { PlayerType } from "../../../core/game/Game";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { CloseViewEvent } from "../../InputHandler";
import { SendQuickChatEvent } from "../../Transport";
import quickChatData from "../../../../resources/QuickChat.json";
import { translateText } from "../../Utils";
export type QuickChatPhrase = {
@@ -78,10 +78,9 @@ export class ChatModal extends LitElement {
${this.categories.map(
(category) => html`
<button
class="chat-option-button ${this.selectedCategory ===
category.id
? "selected"
: ""}"
class="chat-option-button ${
this.selectedCategory === category.id ? "selected" : ""
}"
@click=${() => this.selectCategory(category.id)}
>
${translateText(`chat.cat.${category.id}`)}
@@ -90,8 +89,9 @@ export class ChatModal extends LitElement {
)}
</div>
${this.selectedCategory
? html`
${
this.selectedCategory
? html`
<div class="chat-column">
<div class="column-title">
${translateText("chat.phrase")}
@@ -100,13 +100,14 @@ export class ChatModal extends LitElement {
${this.getPhrasesForCategory(this.selectedCategory).map(
(phrase) => html`
<button
class="chat-option-button ${this
.selectedPhraseText ===
translateText(
`chat.${this.selectedCategory}.${phrase.key}`,
)
? "selected"
: ""}"
class="chat-option-button ${
this.selectedPhraseText ===
translateText(
`chat.${this.selectedCategory}.${phrase.key}`,
)
? "selected"
: ""
}"
@click=${() => this.selectPhrase(phrase)}
>
${this.renderPhrasePreview(phrase)}
@@ -116,9 +117,11 @@ export class ChatModal extends LitElement {
</div>
</div>
`
: null}
${this.requiresPlayerSelection || this.selectedPlayer
? html`
: null
}
${
this.requiresPlayerSelection || this.selectedPlayer
? html`
<div class="chat-column">
<div class="column-title">
${translateText("chat.player")}
@@ -136,10 +139,9 @@ export class ChatModal extends LitElement {
${this.getSortedFilteredPlayers().map(
(player) => html`
<button
class="chat-option-button ${this.selectedPlayer ===
player
? "selected"
: ""}"
class="chat-option-button ${
this.selectedPlayer === player ? "selected" : ""
}"
@click=${() => this.selectPlayer(player)}
>
${player.name()}
@@ -149,20 +151,25 @@ export class ChatModal extends LitElement {
</div>
</div>
`
: null}
: null
}
</div>
<div class="chat-preview">
${this.previewText
? translateText(this.previewText)
: translateText("chat.build")}
${
this.previewText
? translateText(this.previewText)
: translateText("chat.build")
}
</div>
<div class="chat-send">
<button
class="chat-send-button"
@click=${this.sendChatMessage}
?disabled=${!this.previewText ||
(this.requiresPlayerSelection && !this.selectedPlayer)}
?disabled=${
!this.previewText ||
(this.requiresPlayerSelection && !this.selectedPlayer)
}
>
${translateText("chat.send")}
</button>
+20 -16
View File
@@ -1,14 +1,14 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { renderNumber, renderTroops } from "../../Utils";
import { AttackRatioEvent } from "../../InputHandler";
import { ClientID } from "../../../core/Schemas";
import { EventBus } from "../../../core/EventBus";
import { GameView } from "../../../core/game/GameView";
import { Gold } from "../../../core/game/Game";
import { Layer } from "./Layer";
import { UIState } from "../UIState";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import { Gold } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { ClientID } from "../../../core/Schemas";
import { AttackRatioEvent } from "../../InputHandler";
import { renderNumber, renderTroops } from "../../Utils";
import { UIState } from "../UIState";
import { Layer } from "./Layer";
@customElement("control-panel")
export class ControlPanel extends LitElement implements Layer {
@@ -166,10 +166,12 @@ export class ControlPanel extends LitElement implements Layer {
}
</style>
<div
class="${this._isVisible
? "w-full sm:max-w-[320px] text-sm sm:text-base bg-gray-800/70 p-2 " +
"pr-3 sm:p-4 shadow-lg sm:rounded-lg backdrop-blur"
: "hidden"}"
class="${
this._isVisible
? "w-full sm:max-w-[320px] text-sm sm:text-base bg-gray-800/70 p-2 " +
"pr-3 sm:p-4 shadow-lg sm:rounded-lg backdrop-blur"
: "hidden"
}"
@contextmenu=${(e: MouseEvent) => e.preventDefault()}
>
<div class="block bg-black/30 text-white mb-4 p-2 rounded">
@@ -180,9 +182,11 @@ export class ControlPanel extends LitElement implements Layer {
<span translate="no"
>${renderTroops(this._troops)} / ${renderTroops(this._maxTroops)}
<span
class="${this._troopRateIsIncreasing
? "text-green-500"
: "text-yellow-500"}"
class="${
this._troopRateIsIncreasing
? "text-green-500"
: "text-yellow-500"
}"
translate="no"
>(+${renderTroops(this.troopRate)})</span
></span
+7 -11
View File
@@ -1,23 +1,19 @@
import { CloseViewEvent, ShowEmojiMenuEvent } from "../../InputHandler";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { emojiTable, flattenedEmojiTable } from "../../../core/Util";
import { AllPlayers } from "../../../core/game/Game";
import { EventBus } from "../../../core/EventBus";
import { SendEmojiIntentEvent } from "../../Transport";
import { AllPlayers } from "../../../core/game/Game";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { TerraNulliusImpl } from "../../../core/game/TerraNulliusImpl";
import { emojiTable, flattenedEmojiTable } from "../../../core/Util";
import { CloseViewEvent, ShowEmojiMenuEvent } from "../../InputHandler";
import { SendEmojiIntentEvent } from "../../Transport";
import { TransformHandler } from "../TransformHandler";
@customElement("emoji-table")
export class EmojiTable extends LitElement {
@state() public isVisible = false;
init(
transformHandler: TransformHandler,
game: GameView,
eventBus: EventBus,
) {
init(transformHandler: TransformHandler, game: GameView, eventBus: EventBus) {
eventBus.on(ShowEmojiMenuEvent, (e) => {
this.isVisible = true;
const cell = transformHandler.screenToWorldCoordinates(e.x, e.y);
+210 -164
View File
@@ -1,12 +1,22 @@
/* eslint-disable max-lines */
import { html, LitElement, TemplateResult } from "lit";
import { customElement, state } from "lit/decorators.js";
import { DirectiveResult } from "lit/directive.js";
import { UnsafeHTMLDirective, unsafeHTML } from "lit/directives/unsafe-html.js";
import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg";
import chatIcon from "../../../../resources/images/ChatIconWhite.svg";
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
import { EventBus } from "../../../core/EventBus";
import {
AllPlayers,
getMessageCategory,
MessageCategory,
MessageType,
PlayerType,
Tick,
UnitType,
getMessageCategory,
} from "../../../core/game/Game";
import {
AllianceExpiredUpdate,
@@ -21,31 +31,26 @@ import {
TargetPlayerUpdate,
UnitIncomingUpdate,
} from "../../../core/game/GameUpdates";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { onlyImages } from "../../../core/Util";
import {
CancelAttackIntentEvent,
CancelBoatIntentEvent,
SendAllianceExtensionIntentEvent,
SendAllianceReplyIntentEvent,
} from "../../Transport";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import {
getMessageTypeClasses,
renderNumber,
renderTroops,
translateText,
} from "../../Utils";
import { Layer } from "./Layer";
import {
GoToPlayerEvent,
GoToPositionEvent,
GoToUnitEvent,
} from "./Leaderboard";
import { LitElement, TemplateResult, html } from "lit";
import { UnsafeHTMLDirective, unsafeHTML } from "lit/directives/unsafe-html.js";
import { customElement, state } from "lit/decorators.js";
import { getMessageTypeClasses, translateText } from "../../Utils";
import { renderNumber, renderTroops } from "../../Utils";
import { DirectiveResult } from "lit/directive.js";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg";
import chatIcon from "../../../../resources/images/ChatIconWhite.svg";
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
import { onlyImages } from "../../../core/Util";
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
type GameEvent = {
description: string;
@@ -87,15 +92,19 @@ export class EventsDisplay extends LitElement implements Layer {
@state() private latestGoldAmount: bigint | null = null;
@state() private goldAmountAnimating = false;
private goldAmountTimeoutId: ReturnType<typeof setTimeout> | null = null;
@state() private readonly eventsFilters: Map<MessageCategory, boolean> = new Map([
[MessageCategory.ATTACK, false],
[MessageCategory.TRADE, false],
[MessageCategory.ALLIANCE, false],
[MessageCategory.CHAT, false],
]);
@state() private readonly eventsFilters: Map<MessageCategory, boolean> =
new Map([
[MessageCategory.ATTACK, false],
[MessageCategory.TRADE, false],
[MessageCategory.ALLIANCE, false],
[MessageCategory.CHAT, false],
]);
private renderButton(options: {
content: string | TemplateResult | DirectiveResult<typeof UnsafeHTMLDirective>;
content:
| string
| TemplateResult
| DirectiveResult<typeof UnsafeHTMLDirective>;
onClick?: () => void;
className?: string;
disabled?: boolean;
@@ -539,8 +548,8 @@ export class EventsDisplay extends LitElement implements Layer {
traitorDuration === 1
? translateText("events_display.duration_second")
: translateText("events_display.duration_seconds_plural", {
seconds: traitorDuration,
});
seconds: traitorDuration,
});
this.addEvent({
description: translateText("events_display.betrayal_description", {
@@ -738,41 +747,43 @@ export class EventsDisplay extends LitElement implements Layer {
private renderIncomingAttacks() {
return html`
${this.incomingAttacks.length > 0
? html`
${this.incomingAttacks.map(
(attack) => {
const attacker = this.game?.playerBySmallID(attack.attackerID);
return html`
${
this.incomingAttacks.length > 0
? html`
${this.incomingAttacks.map((attack) => {
const attacker = this.game?.playerBySmallID(attack.attackerID);
return html`
${this.renderButton({
content: html`
${renderTroops(attack.troops)}
${attacker?.isPlayer() ? attacker.name() : "unknown"}
${attack.retreating
? `(${translateText("events_display.retreating")}...)`
: ""}
${
attack.retreating
? `(${translateText("events_display.retreating")}...)`
: ""
}
`,
onClick: () => this.attackWarningOnClick(attack),
className: "text-left text-red-400",
translate: false,
})}
`;
},
)}
})}
`
: ""}
: ""
}
`;
}
private renderOutgoingAttacks() {
return html`
${this.outgoingAttacks.length > 0
? html`
${
this.outgoingAttacks.length > 0
? html`
<div class="flex flex-wrap gap-y-1 gap-x-2">
${this.outgoingAttacks.map(
(attack) => {
const target = this.game?.playerBySmallID(attack.targetID);
return html`
${this.outgoingAttacks.map((attack) => {
const target = this.game?.playerBySmallID(attack.targetID);
return html`
<div class="inline-flex items-center gap-1">
${this.renderButton({
content: html`
@@ -783,32 +794,36 @@ export class EventsDisplay extends LitElement implements Layer {
className: "text-left text-blue-400",
translate: false,
})}
${!attack.retreating
? this.renderButton({
content: "❌",
onClick: () => this.emitCancelAttackIntent(attack.id),
className: "text-left flex-shrink-0",
disabled: attack.retreating,
})
: html`<span class="flex-shrink-0 text-blue-400"
${
!attack.retreating
? this.renderButton({
content: "❌",
onClick: () =>
this.emitCancelAttackIntent(attack.id),
className: "text-left flex-shrink-0",
disabled: attack.retreating,
})
: html`<span class="flex-shrink-0 text-blue-400"
>(${translateText(
"events_display.retreating",
)}...)</span
>`}
>`
}
</div>
`;
},
)}
})}
</div>
`
: ""}
: ""
}
`;
}
private renderOutgoingLandAttacks() {
return html`
${this.outgoingLandAttacks.length > 0
? html`
${
this.outgoingLandAttacks.length > 0
? html`
<div class="flex flex-wrap gap-y-1 gap-x-2">
${this.outgoingLandAttacks.map(
(landAttack) => html`
@@ -819,32 +834,36 @@ export class EventsDisplay extends LitElement implements Layer {
className: "text-left text-gray-400",
translate: false,
})}
${!landAttack.retreating
? this.renderButton({
content: "❌",
onClick: () =>
this.emitCancelAttackIntent(landAttack.id),
className: "text-left flex-shrink-0",
disabled: landAttack.retreating,
})
: html`<span class="flex-shrink-0 text-blue-400"
${
!landAttack.retreating
? this.renderButton({
content: "❌",
onClick: () =>
this.emitCancelAttackIntent(landAttack.id),
className: "text-left flex-shrink-0",
disabled: landAttack.retreating,
})
: html`<span class="flex-shrink-0 text-blue-400"
>(${translateText(
"events_display.retreating",
)}...)</span
>`}
>`
}
</div>
`,
)}
</div>
`
: ""}
: ""
}
`;
}
private renderBoats() {
return html`
${this.outgoingBoats.length > 0
? html`
${
this.outgoingBoats.length > 0
? html`
<div class="flex flex-wrap gap-y-1 gap-x-2">
${this.outgoingBoats.map(
(boat) => html`
@@ -856,24 +875,27 @@ export class EventsDisplay extends LitElement implements Layer {
className: "text-left text-blue-400",
translate: false,
})}
${!boat.retreating()
? this.renderButton({
content: "❌",
onClick: () => this.emitBoatCancelIntent(boat.id()),
className: "text-left flex-shrink-0",
disabled: boat.retreating(),
})
: html`<span class="flex-shrink-0 text-blue-400"
${
!boat.retreating()
? this.renderButton({
content: "❌",
onClick: () => this.emitBoatCancelIntent(boat.id()),
className: "text-left flex-shrink-0",
disabled: boat.retreating(),
})
: html`<span class="flex-shrink-0 text-blue-400"
>(${translateText(
"events_display.retreating",
)}...)</span
>`}
>`
}
</div>
`,
)}
</div>
`
: ""}
: ""
}
`;
}
@@ -921,16 +943,17 @@ export class EventsDisplay extends LitElement implements Layer {
return html`
${styles}
<!-- Events Toggle (when hidden) -->
${this._hidden
? html`
${
this._hidden
? html`
<div class="relative w-fit lg:bottom-2.5 lg:right-2.5 z-50">
${this.renderButton({
content: html`
Events
<span
class="${this.newEvents
? ""
: "hidden"} inline-block px-2 bg-red-500 rounded-xl text-sm"
class="${
this.newEvents ? "" : "hidden"
} inline-block px-2 bg-red-500 rounded-xl text-sm"
>${this.newEvents}</span
>
`,
@@ -941,7 +964,7 @@ export class EventsDisplay extends LitElement implements Layer {
})}
</div>
`
: html`
: html`
<!-- Main Events Display -->
<div
class="relative w-full sm:bottom-2.5 sm:right-2.5 z-50 sm:w-96 backdrop-blur"
@@ -956,11 +979,11 @@ export class EventsDisplay extends LitElement implements Layer {
content: html`<img
src="${swordIcon}"
class="w-5 h-5"
style="filter: ${this.eventsFilters.get(
MessageCategory.ATTACK,
)
? "grayscale(1) opacity(0.5)"
: "none"}"
style="filter: ${
this.eventsFilters.get(MessageCategory.ATTACK)
? "grayscale(1) opacity(0.5)"
: "none"
}"
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.ATTACK),
@@ -970,11 +993,11 @@ export class EventsDisplay extends LitElement implements Layer {
content: html`<img
src="${donateGoldIcon}"
class="w-5 h-5"
style="filter: ${this.eventsFilters.get(
MessageCategory.TRADE,
)
? "grayscale(1) opacity(0.5)"
: "none"}"
style="filter: ${
this.eventsFilters.get(MessageCategory.TRADE)
? "grayscale(1) opacity(0.5)"
: "none"
}"
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.TRADE),
@@ -984,11 +1007,11 @@ export class EventsDisplay extends LitElement implements Layer {
content: html`<img
src="${allianceIcon}"
class="w-5 h-5"
style="filter: ${this.eventsFilters.get(
MessageCategory.ALLIANCE,
)
? "grayscale(1) opacity(0.5)"
: "none"}"
style="filter: ${
this.eventsFilters.get(MessageCategory.ALLIANCE)
? "grayscale(1) opacity(0.5)"
: "none"
}"
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.ALLIANCE),
@@ -998,11 +1021,11 @@ export class EventsDisplay extends LitElement implements Layer {
content: html`<img
src="${chatIcon}"
class="w-5 h-5"
style="filter: ${this.eventsFilters.get(
MessageCategory.CHAT,
)
? "grayscale(1) opacity(0.5)"
: "none"}"
style="filter: ${
this.eventsFilters.get(MessageCategory.CHAT)
? "grayscale(1) opacity(0.5)"
: "none"
}"
/>`,
onClick: () =>
this.toggleEventFilter(MessageCategory.CHAT),
@@ -1010,18 +1033,23 @@ export class EventsDisplay extends LitElement implements Layer {
})}
</div>
<div class="flex items-center gap-3">
${this.latestGoldAmount !== null
? html`<span
class="text-green-400 font-semibold transition-all duration-300 ${this
.goldAmountAnimating
? "animate-pulse scale-110"
: "scale-100"}"
style="animation: ${this.goldAmountAnimating
? "goldBounce 0.6s ease-out"
: "none"}"
${
this.latestGoldAmount !== null
? html`<span
class="text-green-400 font-semibold transition-all duration-300 ${
this.goldAmountAnimating
? "animate-pulse scale-110"
: "scale-100"
}"
style="animation: ${
this.goldAmountAnimating
? "goldBounce 0.6s ease-out"
: "none"
}"
>+${renderNumber(this.latestGoldAmount)}</span
>`
: ""}
: ""
}
${this.renderButton({
content: translateText("leaderboard.hide"),
onClick: this.toggleHidden,
@@ -1052,30 +1080,36 @@ export class EventsDisplay extends LitElement implements Layer {
event.type,
)}"
>
${event.focusID
? this.renderButton({
content: this.getEventDescription(event),
onClick: () => {
event.focusID &&
this.emitGoToPlayerEvent(event.focusID);
},
className: "text-left",
})
: event.unitView
${
event.focusID
? this.renderButton({
content: this.getEventDescription(event),
onClick: () => {
event.unitView &&
this.emitGoToUnitEvent(
event.unitView,
content: this.getEventDescription(event),
onClick: () => {
event.focusID &&
this.emitGoToPlayerEvent(
event.focusID,
);
},
className: "text-left",
})
: this.getEventDescription(event)}
},
className: "text-left",
})
: event.unitView
? this.renderButton({
content:
this.getEventDescription(event),
onClick: () => {
event.unitView &&
this.emitGoToUnitEvent(
event.unitView,
);
},
className: "text-left",
})
: this.getEventDescription(event)
}
<!-- Events with buttons (Alliance requests) -->
${event.buttons
? html`
${
event.buttons
? html`
<div class="flex flex-wrap gap-1.5 mt-1">
${event.buttons.map(
(btn) => html`
@@ -1084,13 +1118,13 @@ export class EventsDisplay extends LitElement implements Layer {
text-white rounded text-md
md:text-sm cursor-pointer
transition-colors duration-300
${btn.className.includes("btn-info")
? "bg-blue-500 hover:bg-blue-600"
: btn.className.includes(
"btn-gray",
)
? "bg-gray-500 hover:bg-gray-600"
: "bg-green-600 hover:bg-green-700"}"
${
btn.className.includes("btn-info")
? "bg-blue-500 hover:bg-blue-600"
: btn.className.includes("btn-gray")
? "bg-gray-500 hover:bg-gray-600"
: "bg-green-600 hover:bg-green-700"
}"
@click=${() => {
btn.action();
if (!btn.preventClose) {
@@ -1113,62 +1147,72 @@ export class EventsDisplay extends LitElement implements Layer {
)}
</div>
`
: ""}
: ""
}
</td>
</tr>
`,
)}
<!--- Incoming attacks row -->
${this.incomingAttacks.length > 0
? html`
${
this.incomingAttacks.length > 0
? html`
<tr class="lg:px-2 lg:py-1 p-1">
<td class="lg:px-2 lg:py-1 p-1 text-left">
${this.renderIncomingAttacks()}
</td>
</tr>
`
: ""}
: ""
}
<!--- Outgoing attacks row -->
${this.outgoingAttacks.length > 0
? html`
${
this.outgoingAttacks.length > 0
? html`
<tr class="lg:px-2 lg:py-1 p-1">
<td class="lg:px-2 lg:py-1 p-1 text-left">
${this.renderOutgoingAttacks()}
</td>
</tr>
`
: ""}
: ""
}
<!--- Outgoing land attacks row -->
${this.outgoingLandAttacks.length > 0
? html`
${
this.outgoingLandAttacks.length > 0
? html`
<tr class="lg:px-2 lg:py-1 p-1">
<td class="lg:px-2 lg:py-1 p-1 text-left">
${this.renderOutgoingLandAttacks()}
</td>
</tr>
`
: ""}
: ""
}
<!--- Boats row -->
${this.outgoingBoats.length > 0
? html`
${
this.outgoingBoats.length > 0
? html`
<tr class="lg:px-2 lg:py-1 p-1">
<td class="lg:px-2 lg:py-1 p-1 text-left">
${this.renderBoats()}
</td>
</tr>
`
: ""}
: ""
}
<!--- Empty row when no events or attacks -->
${filteredEvents.length === 0 &&
this.incomingAttacks.length === 0 &&
this.outgoingAttacks.length === 0 &&
this.outgoingLandAttacks.length === 0 &&
this.outgoingBoats.length === 0
? html`
${
filteredEvents.length === 0 &&
this.incomingAttacks.length === 0 &&
this.outgoingAttacks.length === 0 &&
this.outgoingLandAttacks.length === 0 &&
this.outgoingBoats.length === 0
? html`
<tr>
<td
class="lg:px-2 lg:py-1 p-1 min-w-72 text-left"
@@ -1177,13 +1221,15 @@ export class EventsDisplay extends LitElement implements Layer {
</td>
</tr>
`
: ""}
: ""
}
</tbody>
</table>
</div>
</div>
</div>
`}
`
}
`;
}
+3 -3
View File
@@ -1,9 +1,9 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import { TogglePerformanceOverlayEvent } from "../../InputHandler";
import { UserSettings } from "../../../core/game/UserSettings";
import { TogglePerformanceOverlayEvent } from "../../InputHandler";
import { Layer } from "./Layer";
@customElement("fps-display")
export class FPSDisplay extends LitElement implements Layer {
+7 -7
View File
@@ -1,21 +1,21 @@
import { Theme } from "../../../core/configuration/Config";
import { UnitType } from "../../../core/game/Game";
import {
BonusEventUpdate,
ConquestUpdate,
GameUpdateType,
RailroadUpdate,
} from "../../../core/game/GameUpdates";
import { Fx, FxType } from "../fx/Fx";
import { GameView, UnitView } from "../../../core/game/GameView";
import { ShockwaveFx, nukeFxFactory } from "../fx/NukeFx";
import { renderNumber } from "../../Utils";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Layer } from "./Layer";
import { conquestFxFactory } from "../fx/ConquestFx";
import { Fx, FxType } from "../fx/Fx";
import { nukeFxFactory, ShockwaveFx } from "../fx/NukeFx";
import { SpriteFx } from "../fx/SpriteFx";
import { TextFx } from "../fx/TextFx";
import { Theme } from "../../../core/configuration/Config";
import { UnitExplosionFx } from "../fx/UnitExplosionFx";
import { UnitType } from "../../../core/game/Game";
import { conquestFxFactory } from "../fx/ConquestFx";
import { renderNumber } from "../../Utils";
import { Layer } from "./Layer";
export class FxLayer implements Layer {
private canvas: HTMLCanvasElement | undefined;
+25 -17
View File
@@ -1,14 +1,14 @@
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { Colord } from "colord";
import { GameMode } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import leaderboardRegularIcon from "../../../../resources/images/LeaderboardIconRegularWhite.svg";
import leaderboardSolidIcon from "../../../../resources/images/LeaderboardIconSolidWhite.svg";
import teamRegularIcon from "../../../../resources/images/TeamIconRegularWhite.svg";
import teamSolidIcon from "../../../../resources/images/TeamIconSolidWhite.svg";
import { GameMode } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
@customElement("game-left-sidebar")
export class GameLeftSidebar extends LitElement implements Layer {
@@ -97,8 +97,9 @@ export class GameLeftSidebar extends LitElement implements Layer {
transition-transform duration-300 ease-out transform
${this.isVisible ? "translate-x-0" : "-translate-x-full"}`}
>
${this.isPlayerTeamLabelVisible
? html`
${
this.isPlayerTeamLabelVisible
? html`
<div
class="flex items-center w-full h-8 lg:h-10 text-white py-1 lg:p-2"
@contextmenu=${(e: Event) => e.preventDefault()}
@@ -109,7 +110,8 @@ export class GameLeftSidebar extends LitElement implements Layer {
</span>
</div>
`
: null}
: null
}
<div
class=${`flex items-center gap-2 space-x-2 text-white ${
this.isLeaderboardShow || this.isTeamLeaderboardShow ? "mb-2" : ""
@@ -117,31 +119,37 @@ export class GameLeftSidebar extends LitElement implements Layer {
>
<div class="w-6 h-6 cursor-pointer" @click=${this.toggleLeaderboard}>
<img
src=${this.isLeaderboardShow
? leaderboardSolidIcon
: leaderboardRegularIcon}
src=${
this.isLeaderboardShow
? leaderboardSolidIcon
: leaderboardRegularIcon
}
alt="treeIcon"
width="20"
height="20"
/>
</div>
${this.isTeamGame
? html`
${
this.isTeamGame
? html`
<div
class="w-6 h-6 cursor-pointer"
@click=${this.toggleTeamLeaderboard}
>
<img
src=${this.isTeamLeaderboardShow
? teamSolidIcon
: teamRegularIcon}
src=${
this.isTeamLeaderboardShow
? teamSolidIcon
: teamRegularIcon
}
alt="treeIcon"
width="20"
height="20"
/>
</div>
`
: null}
: null
}
</div>
<div class="block lg:flex flex-wrap gap-2">
<leader-board .visible=${this.isLeaderboardShow}></leader-board>
@@ -1,20 +1,20 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { PauseGameEvent } from "../../Transport";
import { ShowReplayPanelEvent } from "./ReplayPanel";
import { ShowSettingsModalEvent } from "./SettingsModal";
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
import pauseIcon from "../../../../resources/images/PauseIconWhite.svg";
import playIcon from "../../../../resources/images/PlayIconWhite.svg";
import replayRegularIcon from "../../../../resources/images/ReplayRegularIconWhite.svg";
import replaySolidIcon from "../../../../resources/images/ReplaySolidIconWhite.svg";
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
import { EventBus } from "../../../core/EventBus";
import { GameType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { PauseGameEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
import { ShowReplayPanelEvent } from "./ReplayPanel";
import { ShowSettingsModalEvent } from "./SettingsModal";
@customElement("game-right-sidebar")
export class GameRightSidebar extends LitElement implements Layer {
+3 -3
View File
@@ -1,8 +1,8 @@
import { EventBus, GameEvent } from "../../../core/EventBus";
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { Layer } from "./Layer";
import { EventBus, GameEvent } from "../../../core/EventBus";
import { getGamesPlayed } from "../../Utils";
import { Layer } from "./Layer";
export class GutterAdModalEvent implements GameEvent {
constructor(public readonly isVisible: boolean) {}
+2 -2
View File
@@ -1,8 +1,8 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
@customElement("heads-up-message")
export class HeadsUpMessage extends LitElement implements Layer {
+32 -27
View File
@@ -1,11 +1,11 @@
import { EventBus, GameEvent } from "../../../core/EventBus";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { Layer } from "./Layer";
import { renderNumber } from "../../Utils";
import { repeat } from "lit/directives/repeat.js";
import { translateText } from "../../../client/Utils";
import { EventBus, GameEvent } from "../../../core/EventBus";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { renderNumber } from "../../Utils";
import { Layer } from "./Layer";
type Entry = {
name: string;
@@ -169,10 +169,9 @@ export class Leaderboard extends LitElement implements Layer {
}
return html`
<div
class="max-h-[35vh] overflow-y-auto text-white text-xs md:text-xs lg:text-sm md:max-h-[50vh] ${this
.visible
? ""
: "hidden"}"
class="max-h-[35vh] overflow-y-auto text-white text-xs md:text-xs lg:text-sm md:max-h-[50vh] ${
this.visible ? "" : "hidden"
}"
@contextmenu=${(e: Event) => e.preventDefault()}
>
<div
@@ -191,33 +190,39 @@ export class Leaderboard extends LitElement implements Layer {
@click=${() => this.setSort("tiles")}
>
${translateText("leaderboard.owned")}
${this._sortKey === "tiles"
? this._sortOrder === "asc"
? "⬆️"
: ""
: ""}
${
this._sortKey === "tiles"
? this._sortOrder === "asc"
? ""
: "⬇️"
: ""
}
</div>
<div
class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap"
@click=${() => this.setSort("gold")}
>
${translateText("leaderboard.gold")}
${this._sortKey === "gold"
? this._sortOrder === "asc"
? "⬆️"
: ""
: ""}
${
this._sortKey === "gold"
? this._sortOrder === "asc"
? ""
: "⬇️"
: ""
}
</div>
<div
class="py-1 md:py-2 text-center border-b border-slate-500 cursor-pointer whitespace-nowrap"
@click=${() => this.setSort("troops")}
>
${translateText("leaderboard.troops")}
${this._sortKey === "troops"
? this._sortOrder === "asc"
? "⬆️"
: ""
: ""}
${
this._sortKey === "troops"
? this._sortOrder === "asc"
? ""
: "⬇️"
: ""
}
</div>
</div>
@@ -226,9 +231,9 @@ export class Leaderboard extends LitElement implements Layer {
(p) => p.player.id(),
(player) => html`
<div
class="contents hover:bg-slate-600/60 ${player.isMyPlayer
? "font-bold"
: ""} cursor-pointer"
class="contents hover:bg-slate-600/60 ${
player.isMyPlayer ? "font-bold" : ""
} cursor-pointer"
@click=${() => this.handleRowClickPlayer(player.player)}
>
<div class="py-1 md:py-2 text-center border-b border-slate-500">
+22 -32
View File
@@ -1,26 +1,26 @@
import {
COLORS,
MenuElementParams,
centerButtonElement,
rootMenuElement,
} from "./RadialMenuElements";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { RadialMenu, RadialMenuConfig } from "./RadialMenu";
import { BuildMenu } from "./BuildMenu";
import { ChatIntegration } from "./ChatIntegration";
import { ContextMenuEvent } from "../../InputHandler";
import { EmojiTable } from "./EmojiTable";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import { LitElement } from "lit";
import { PlayerActionHandler } from "./PlayerActionHandler";
import { PlayerActions } from "../../../core/game/Game";
import { PlayerPanel } from "./PlayerPanel";
import { TileRef } from "../../../core/game/GameMap";
import { TransformHandler } from "../TransformHandler";
import { UIState } from "../UIState";
import { customElement } from "lit/decorators.js";
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
import { EventBus } from "../../../core/EventBus";
import { PlayerActions } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { ContextMenuEvent } from "../../InputHandler";
import { TransformHandler } from "../TransformHandler";
import { UIState } from "../UIState";
import { BuildMenu } from "./BuildMenu";
import { ChatIntegration } from "./ChatIntegration";
import { EmojiTable } from "./EmojiTable";
import { Layer } from "./Layer";
import { PlayerActionHandler } from "./PlayerActionHandler";
import { PlayerPanel } from "./PlayerPanel";
import { RadialMenu, RadialMenuConfig } from "./RadialMenu";
import {
COLORS,
centerButtonElement,
MenuElementParams,
rootMenuElement,
} from "./RadialMenuElements";
@customElement("main-radial-menu")
export class MainRadialMenu extends LitElement implements Layer {
@@ -90,13 +90,7 @@ export class MainRadialMenu extends LitElement implements Layer {
const actions = await myPlayer.actions(tile);
// Stale check: user might have clicked somewhere else already
if (this.clickedTile !== tile) return;
this.updatePlayerActions(
myPlayer,
actions,
tile,
event.x,
event.y,
);
this.updatePlayerActions(myPlayer, actions, tile, event.x, event.y);
} catch (err) {
console.error("Failed to fetch player actions:", err);
}
@@ -152,11 +146,7 @@ export class MainRadialMenu extends LitElement implements Layer {
try {
const actions = await myPlayer.actions(tile);
if (this.clickedTile !== tile) return; // stale
this.updatePlayerActions(
myPlayer,
actions,
tile,
);
this.updatePlayerActions(myPlayer, actions, tile);
} catch (err) {
console.error("Failed to refresh player actions:", err);
}
+2 -2
View File
@@ -1,11 +1,11 @@
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { GameEnv } from "../../../core/configuration/Config";
import { GameType } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { MultiTabDetector } from "../../MultiTabDetector";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
@customElement("multi-tab-modal")
export class MultiTabModal extends LitElement implements Layer {
+13 -13
View File
@@ -1,13 +1,3 @@
import { AllPlayers, Cell, nukeTypes } from "../../../core/game/Game";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { createCanvas, renderNumber, renderTroops } from "../../Utils";
import { AlternateViewEvent } from "../../InputHandler";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import { PseudoRandom } from "../../../core/PseudoRandom";
import { Theme } from "../../../core/configuration/Config";
import { TransformHandler } from "../TransformHandler";
import { UserSettings } from "../../../core/game/UserSettings";
import allianceIcon from "../../../../resources/images/AllianceIcon.svg";
import allianceRequestBlackIcon from "../../../../resources/images/AllianceRequestBlackIcon.svg";
import allianceRequestWhiteIcon from "../../../../resources/images/AllianceRequestWhiteIcon.svg";
@@ -17,10 +7,20 @@ import embargoBlackIcon from "../../../../resources/images/EmbargoBlackIcon.svg"
import embargoWhiteIcon from "../../../../resources/images/EmbargoWhiteIcon.svg";
import nukeRedIcon from "../../../../resources/images/NukeIconRed.svg";
import nukeWhiteIcon from "../../../../resources/images/NukeIconWhite.svg";
import { renderPlayerFlag } from "../../../core/CustomFlag";
import shieldIcon from "../../../../resources/images/ShieldIconBlack.svg";
import targetIcon from "../../../../resources/images/TargetIcon.svg";
import traitorIcon from "../../../../resources/images/TraitorIcon.svg";
import { renderPlayerFlag } from "../../../core/CustomFlag";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { AllPlayers, Cell, nukeTypes } from "../../../core/game/Game";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { PseudoRandom } from "../../../core/PseudoRandom";
import { AlternateViewEvent } from "../../InputHandler";
import { createCanvas, renderNumber, renderTroops } from "../../Utils";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
class RenderInfo {
public icons: Map<string, HTMLImageElement> = new Map(); // Track icon elements
@@ -611,8 +611,8 @@ 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})`;
// eslint-disable-next-line max-len
render.element.style.transform = `translate(${render.location.x}px, ${render.location.y}px) translate(-50%, -50%) scale(${scale})`;
}
}
+5 -8
View File
@@ -1,14 +1,14 @@
import { AlternateViewEvent, RedrawGraphicsEvent } from "../../InputHandler";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { PauseGameEvent } from "../../Transport";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent, RedrawGraphicsEvent } from "../../InputHandler";
import { PauseGameEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
const button = ({
classes = "",
@@ -209,10 +209,7 @@ export class OptionsMenu extends LitElement implements Layer {
<div
class="options-menu flex flex-col justify-around gap-y-3 mt-2
bg-opacity-60 bg-gray-900 p-1 lg:p-2 rounded-lg backdrop-blur-md
${!this
.showSettings
? "hidden"
: ""}"
${!this.showSettings ? "hidden" : ""}"
>
${button({
onClick: this.onTerrainButtonClick,
@@ -1,4 +1,7 @@
import { EventBus } from "../../../core/EventBus";
import { PlayerActions, PlayerID } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { PlayerView } from "../../../core/game/GameView";
import {
SendAllianceRequestIntentEvent,
SendAttackIntentEvent,
@@ -13,9 +16,6 @@ import {
SendSpawnIntentEvent,
SendTargetPlayerIntentEvent,
} from "../../Transport";
import { EventBus } from "../../../core/EventBus";
import { PlayerView } from "../../../core/game/GameView";
import { TileRef } from "../../../core/game/GameMap";
import { UIState } from "../UIState";
export class PlayerActionHandler {
+46 -34
View File
@@ -1,6 +1,9 @@
import { ContextMenuEvent, MouseMoveEvent } from "../../InputHandler";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { LitElement, TemplateResult, html } from "lit";
import { html, LitElement, TemplateResult } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { ref } from "lit-html/directives/ref.js";
import { translateText } from "../../../client/Utils";
import { renderPlayerFlag } from "../../../core/CustomFlag";
import { EventBus } from "../../../core/EventBus";
import {
PlayerProfile,
PlayerType,
@@ -8,16 +11,13 @@ import {
Unit,
UnitType,
} from "../../../core/game/Game";
import { customElement, property, state } from "lit/decorators.js";
import { renderNumber, renderTroops } from "../../Utils";
import { CloseRadialMenuEvent } from "./RadialMenu";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { ContextMenuEvent, MouseMoveEvent } from "../../InputHandler";
import { renderNumber, renderTroops } from "../../Utils";
import { TransformHandler } from "../TransformHandler";
import { ref } from "lit-html/directives/ref.js";
import { renderPlayerFlag } from "../../../core/CustomFlag";
import { translateText } from "../../../client/Utils";
import { Layer } from "./Layer";
import { CloseRadialMenuEvent } from "./RadialMenu";
function euclideanDistWorld(
coord: { x: number; y: number },
@@ -222,17 +222,18 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
return html`
<div class="p-2">
<button
class="text-bold text-sm lg:text-lg font-bold mb-1 inline-flex break-all ${isFriendly
? "text-green-500"
: "text-white"}"
class="text-bold text-sm lg:text-lg font-bold mb-1 inline-flex break-all ${
isFriendly ? "text-green-500" : "text-white"
}"
@click=${() => {
this.showDetails = !this.showDetails;
this.requestUpdate?.();
}}
>
${player.cosmetics.flag
? player.cosmetics.flag.startsWith("!")
? html`<div
${
player.cosmetics.flag
? player.cosmetics.flag.startsWith("!")
? html`<div
class="h-8 mr-1 aspect-[3/4] player-flag"
${ref((el) => {
if (el instanceof HTMLElement) {
@@ -242,38 +243,46 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
}
})}
></div>`
: html`<img
: html`<img
class="h-8 mr-1 aspect-[3/4]"
src=${"/flags/" + player.cosmetics.flag + ".svg"}
/>`
: html``}
: html``
}
${player.name()}
</button>
<!-- Collapsible section -->
${this.showDetails
? html`
${player.team() !== null
? html`<div class="text-sm opacity-80">
${
this.showDetails
? html`
${
player.team() !== null
? html`<div class="text-sm opacity-80">
${translateText("player_info_overlay.team")}:
${player.team()}
</div>`
: ""}
: ""
}
<div class="text-sm opacity-80">
${translateText("player_info_overlay.type")}: ${playerType}
</div>
${player.troops() >= 1
? html`<div class="text-sm opacity-80" translate="no">
${
player.troops() >= 1
? html`<div class="text-sm opacity-80" translate="no">
${translateText("player_info_overlay.d_troops")}:
${renderTroops(player.troops())}
</div>`
: ""}
${attackingTroops >= 1
? html`<div class="text-sm opacity-80" translate="no">
: ""
}
${
attackingTroops >= 1
? html`<div class="text-sm opacity-80" translate="no">
${translateText("player_info_overlay.a_troops")}:
${renderTroops(attackingTroops)}
</div>`
: ""}
: ""
}
<div class="text-sm opacity-80" translate="no">
${translateText("player_info_overlay.gold")}:
${renderNumber(player.gold())}
@@ -310,7 +319,8 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
)}
${relationHtml}
`
: ""}
: ""
}
</div>
`;
}
@@ -328,14 +338,16 @@ export class PlayerInfoOverlay extends LitElement implements Layer {
</div>
<div class="mt-1">
<div class="text-sm opacity-80">${unit.type()}</div>
${unit.hasHealth()
? html`
${
unit.hasHealth()
? html`
<div class="text-sm opacity-80">
${translateText("player_info_overlay.health")}:
${unit.health()}
</div>
`
: ""}
: ""
}
</div>
</div>
`;
+91 -65
View File
@@ -1,7 +1,20 @@
import { html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg";
import chatIcon from "../../../../resources/images/ChatIconWhite.svg";
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg";
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import { AllPlayers, PlayerActions } from "../../../core/game/Game";
import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { LitElement, html } from "lit";
import { flattenedEmojiTable } from "../../../core/Util";
import Countries from "../../data/countries.json";
import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
import {
SendAllianceRequestIntentEvent,
SendBreakAllianceIntentEvent,
@@ -11,24 +24,11 @@ import {
SendEmojiIntentEvent,
SendTargetPlayerIntentEvent,
} from "../../Transport";
import { customElement, state } from "lit/decorators.js";
import { renderNumber, renderTroops } from "../../Utils";
import { ChatModal } from "./ChatModal";
import Countries from "../../data/countries.json";
import { EmojiTable } from "./EmojiTable";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import { TileRef } from "../../../core/game/GameMap";
import { UIState } from "../UIState";
import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg";
import chatIcon from "../../../../resources/images/ChatIconWhite.svg";
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg";
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
import { flattenedEmojiTable } from "../../../core/Util";
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { ChatModal } from "./ChatModal";
import { EmojiTable } from "./EmojiTable";
import { Layer } from "./Layer";
@customElement("player-panel")
export class PlayerPanel extends LitElement implements Layer {
@@ -195,10 +195,7 @@ export class PlayerPanel extends LitElement implements Layer {
const remainingTicks = expiresAt - this.g.ticks();
if (remainingTicks > 0) {
const remainingSeconds = Math.max(
0,
Math.floor(remainingTicks / 10),
); // 10 ticks per second
const remainingSeconds = Math.max(0, Math.floor(remainingTicks / 10)); // 10 ticks per second
this.allianceExpiryText = this.formatDuration(remainingSeconds);
}
} else {
@@ -246,7 +243,10 @@ export class PlayerPanel extends LitElement implements Layer {
//flag icon in the playerPanel
const flagCode = other.cosmetics.flag;
const country = typeof flagCode === "string" ? Countries.find((c) => c.code === flagCode) : undefined;
const country =
typeof flagCode === "string"
? Countries.find((c) => c.code === flagCode)
: undefined;
const flagName = country?.name;
return html`
@@ -283,8 +283,9 @@ export class PlayerPanel extends LitElement implements Layer {
</div>
</div>
<!-- Flag -->
${country
? html`
${
country
? html`
<div>
<div class="text-white text-opacity-80 text-sm px-2">
${translateText("player_panel.flag")}
@@ -298,7 +299,8 @@ export class PlayerPanel extends LitElement implements Layer {
</div>
</div>
`
: ""}
: ""
}
<!-- Resources section -->
<div class="grid grid-cols-2 gap-2">
<div class="flex flex-col gap-1">
@@ -333,9 +335,11 @@ export class PlayerPanel extends LitElement implements Layer {
${translateText("player_panel.traitor")}
</div>
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
${other.isTraitor()
? translateText("player_panel.yes")
: translateText("player_panel.no")}
${
other.isTraitor()
? translateText("player_panel.yes")
: translateText("player_panel.no")
}
</div>
</div>
@@ -355,9 +359,11 @@ export class PlayerPanel extends LitElement implements Layer {
${translateText("player_panel.embargo")}
</div>
<div class="bg-opacity-50 bg-gray-700 rounded p-2 text-white">
${other.hasEmbargoAgainst(myPlayer)
? translateText("player_panel.yes")
: translateText("player_panel.no")}
${
other.hasEmbargoAgainst(myPlayer)
? translateText("player_panel.yes")
: translateText("player_panel.no")
}
</div>
</div>
@@ -371,17 +377,20 @@ export class PlayerPanel extends LitElement implements Layer {
class="bg-opacity-50 bg-gray-700 rounded p-2 text-white max-w-72 max-h-20 overflow-y-auto"
translate="no"
>
${other.allies().length > 0
? other
.allies()
.map((p) => p.name())
.join(", ")
: translateText("player_panel.none")}
${
other.allies().length > 0
? other
.allies()
.map((p) => p.name())
.join(", ")
: translateText("player_panel.none")
}
</div>
</div>
${this.allianceExpiryText !== null
? html`
${
this.allianceExpiryText !== null
? html`
<div class="flex flex-col gap-1">
<div class="text-white text-opacity-80 text-sm px-2">
${translateText("player_panel.alliance_time_remaining")}
@@ -393,7 +402,8 @@ export class PlayerPanel extends LitElement implements Layer {
</div>
</div>
`
: ""}
: ""
}
<!-- Action buttons -->
<div class="flex justify-center gap-2">
@@ -406,8 +416,9 @@ export class PlayerPanel extends LitElement implements Layer {
>
<img src=${chatIcon} alt="Target" class="w-6 h-6" />
</button>
${canTarget
? html`<button
${
canTarget
? html`<button
@click=${(e: MouseEvent) =>
this.handleTargetClick(e, other)}
class="w-10 h-10 flex items-center justify-center
@@ -416,9 +427,11 @@ export class PlayerPanel extends LitElement implements Layer {
>
<img src=${targetIcon} alt="Target" class="w-6 h-6" />
</button>`
: ""}
${canBreakAlliance
? html`<button
: ""
}
${
canBreakAlliance
? html`<button
@click=${(e: MouseEvent) =>
this.handleBreakAllianceClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
@@ -431,9 +444,11 @@ export class PlayerPanel extends LitElement implements Layer {
class="w-6 h-6"
/>
</button>`
: ""}
${canSendAllianceRequest
? html`<button
: ""
}
${
canSendAllianceRequest
? html`<button
@click=${(e: MouseEvent) =>
this.handleAllianceClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
@@ -442,9 +457,11 @@ export class PlayerPanel extends LitElement implements Layer {
>
<img src=${allianceIcon} alt="Alliance" class="w-6 h-6" />
</button>`
: ""}
${canDonateTroops
? html`<button
: ""
}
${
canDonateTroops
? html`<button
@click=${(e: MouseEvent) =>
this.handleDonateTroopClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
@@ -457,9 +474,11 @@ export class PlayerPanel extends LitElement implements Layer {
class="w-6 h-6"
/>
</button>`
: ""}
${canDonateGold
? html`<button
: ""
}
${
canDonateGold
? html`<button
@click=${(e: MouseEvent) =>
this.handleDonateGoldClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
@@ -468,9 +487,11 @@ export class PlayerPanel extends LitElement implements Layer {
>
<img src=${donateGoldIcon} alt="Donate" class="w-6 h-6" />
</button>`
: ""}
${canSendEmoji
? html`<button
: ""
}
${
canSendEmoji
? html`<button
@click=${(e: MouseEvent) =>
this.handleEmojiClick(e, myPlayer, other)}
class="w-10 h-10 flex items-center justify-center
@@ -479,10 +500,12 @@ export class PlayerPanel extends LitElement implements Layer {
>
<img src=${emojiIcon} alt="Emoji" class="w-6 h-6" />
</button>`
: ""}
: ""
}
</div>
${canEmbargo && other !== myPlayer
? html`<button
${
canEmbargo && other !== myPlayer
? html`<button
@click=${(e: MouseEvent) =>
this.handleEmbargoClick(e, myPlayer, other)}
class="w-100 h-10 flex items-center justify-center
@@ -491,9 +514,11 @@ export class PlayerPanel extends LitElement implements Layer {
>
${translateText("player_panel.stop_trade")}
</button>`
: ""}
${!canEmbargo && other !== myPlayer
? html`<button
: ""
}
${
!canEmbargo && other !== myPlayer
? html`<button
@click=${(e: MouseEvent) =>
this.handleStopEmbargoClick(e, myPlayer, other)}
class="w-100 h-10 flex items-center justify-center
@@ -502,7 +527,8 @@ export class PlayerPanel extends LitElement implements Layer {
>
${translateText("player_panel.start_trade")}
</button>`
: ""}
: ""
}
</div>
</div>
</div>
+31 -20
View File
@@ -1,16 +1,16 @@
/* eslint-disable max-lines */
import * as d3 from "d3";
import backIcon from "../../../../resources/images/BackIconWhite.svg";
import { EventBus, GameEvent } from "../../../core/EventBus";
import { CloseViewEvent } from "../../InputHandler";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
import {
CenterButtonElement,
MenuElement,
MenuElementParams,
TooltipKey,
} from "./RadialMenuElements";
import { EventBus, GameEvent } from "../../../core/EventBus";
import { CloseViewEvent } from "../../InputHandler";
import { Layer } from "./Layer";
import backIcon from "../../../../resources/images/BackIconWhite.svg";
import { translateText } from "../../Utils";
export class CloseRadialMenuEvent implements GameEvent {
constructor() {}
@@ -41,7 +41,9 @@ type CenterButtonState = "default" | "back";
type RequiredRadialMenuConfig = Required<RadialMenuConfig>;
export class RadialMenu implements Layer {
private menuElement: d3.Selection<HTMLDivElement, unknown, null, undefined> | undefined;
private menuElement:
| d3.Selection<HTMLDivElement, unknown, null, undefined>
| undefined;
private tooltipElement: HTMLDivElement | null = null;
private isVisible = false;
@@ -150,9 +152,12 @@ export class RadialMenu implements Layer {
.style("position", "absolute")
.style("top", "50%")
.style("left", "50%")
.style("transition", `top ${
this.config.menuTransitionDuration}ms ease, left ${
this.config.menuTransitionDuration}ms ease`)
.style(
"transition",
`top ${this.config.menuTransitionDuration}ms ease, left ${
this.config.menuTransitionDuration
}ms ease`,
)
.style("transform", "translate(-50%, -50%)")
.style("pointer-events", "all")
.on("click", (event) => this.hideRadialMenu());
@@ -392,9 +397,10 @@ export class RadialMenu implements Layer {
>,
level: number,
) {
const onHover = (d: d3.PieArcDatum<MenuElement>, path: d3.Selection<
d3.BaseType, unknown, HTMLElement, unknown
>) => {
const onHover = (
d: d3.PieArcDatum<MenuElement>,
path: d3.Selection<d3.BaseType, unknown, HTMLElement, unknown>,
) => {
const disabled = this.params === null || d.data.disabled(this.params);
if (d.data.tooltipItems && d.data.tooltipItems.length > 0) {
this.showTooltip(d.data.tooltipItems);
@@ -413,9 +419,10 @@ export class RadialMenu implements Layer {
path.attr("stroke-width", "3");
};
const onMouseOut = (d: d3.PieArcDatum<MenuElement>, path: d3.Selection<
d3.BaseType, unknown, HTMLElement, unknown
>) => {
const onMouseOut = (
d: d3.PieArcDatum<MenuElement>,
path: d3.Selection<d3.BaseType, unknown, HTMLElement, unknown>,
) => {
const disabled = this.params === null || d.data.disabled(this.params);
if (this.submenuHoverTimeout !== null) {
window.clearTimeout(this.submenuHoverTimeout);
@@ -1072,14 +1079,18 @@ export class RadialMenu implements Layer {
const vh = window.innerHeight;
// If the menu cannot fully fit on an axis, pin it to the viewport center on that axis.
const clampedX = 2 * margin > vw ? vw / 2 : Math.min(Math.max(this.anchorX, margin), vw - margin);
const clampedY = 2 * margin > vh ? vh / 2 : Math.min(Math.max(this.anchorY, margin), vh - margin);
const clampedX =
2 * margin > vw
? vw / 2
: Math.min(Math.max(this.anchorX, margin), vw - margin);
const clampedY =
2 * margin > vh
? vh / 2
: Math.min(Math.max(this.anchorY, margin), vh - margin);
if (this.menuElement === undefined) throw new Error("Not initialized");
const svgSel = this.menuElement.select("svg");
svgSel
.style("top", `${clampedY}px`)
.style("left", `${clampedX}px`);
svgSel.style("top", `${clampedY}px`).style("left", `${clampedX}px`);
}
private readonly handleResize = () => {
@@ -1,15 +1,3 @@
import { AllPlayers, PlayerActions, UnitType } from "../../../core/game/Game";
import { BuildItemDisplay, BuildMenu, flattenedBuildTable } from "./BuildMenu";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { renderNumber, translateText } from "../../Utils";
import { ChatIntegration } from "./ChatIntegration";
import { Config } from "../../../core/configuration/Config";
import { EmojiTable } from "./EmojiTable";
import { EventBus } from "../../../core/EventBus";
import { PlayerActionHandler } from "./PlayerActionHandler";
import { PlayerPanel } from "./PlayerPanel";
import { TileRef } from "../../../core/game/GameMap";
import { TooltipItem } from "./RadialMenu";
import allianceIcon from "../../../../resources/images/AllianceIconWhite.svg";
import boatIcon from "../../../../resources/images/BoatIconWhite.svg";
import buildIcon from "../../../../resources/images/BuildIconWhite.svg";
@@ -17,12 +5,24 @@ import chatIcon from "../../../../resources/images/ChatIconWhite.svg";
import donateGoldIcon from "../../../../resources/images/DonateGoldIconWhite.svg";
import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg";
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
import { flattenedEmojiTable } from "../../../core/Util";
import infoIcon from "../../../../resources/images/InfoIcon.svg";
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
import traitorIcon from "../../../../resources/images/TraitorIconWhite.svg";
import xIcon from "../../../../resources/images/XIcon.svg";
import { Config } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { AllPlayers, PlayerActions, UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { flattenedEmojiTable } from "../../../core/Util";
import { renderNumber, translateText } from "../../Utils";
import { BuildItemDisplay, BuildMenu, flattenedBuildTable } from "./BuildMenu";
import { ChatIntegration } from "./ChatIntegration";
import { EmojiTable } from "./EmojiTable";
import { PlayerActionHandler } from "./PlayerActionHandler";
import { PlayerPanel } from "./PlayerPanel";
import { TooltipItem } from "./RadialMenu";
export type MenuElementParams = {
myPlayer: PlayerView;
+5 -5
View File
@@ -1,15 +1,15 @@
import { Colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { PlayerID } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import {
GameUpdateType,
RailroadUpdate,
RailTile,
RailType,
RailroadUpdate,
} from "../../../core/game/GameUpdates";
import { Colord } from "colord";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { PlayerID } from "../../../core/game/Game";
import { Theme } from "../../../core/configuration/Config";
import { TileRef } from "../../../core/game/GameMap";
import { getRailroadRects } from "./RailroadSprites";
type RailRef = {
+14 -12
View File
@@ -1,14 +1,14 @@
import { LitElement, html } from "lit";
import {
ReplaySpeedMultiplier,
defaultReplaySpeedMultiplier,
} from "../../utilities/ReplaySpeedMultiplier";
import { html, LitElement } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { ReplaySpeedChangeEvent } from "../../InputHandler";
import { translateText } from "../../Utils";
import {
defaultReplaySpeedMultiplier,
ReplaySpeedMultiplier,
} from "../../utilities/ReplaySpeedMultiplier";
import { Layer } from "./Layer";
export class ShowReplayPanelEvent {
constructor(
@@ -71,9 +71,11 @@ export class ReplayPanel extends LitElement implements Layer {
@contextmenu=${(e: Event) => e.preventDefault()}
>
<label class="block mb-1 text-white" translate="no">
${this.isSingleplayer
? translateText("replay_panel.game_speed")
: translateText("replay_panel.replay_speed")}
${
this.isSingleplayer
? translateText("replay_panel.game_speed")
: translateText("replay_panel.replay_speed")
}
</label>
<div class="grid grid-cols-2 gap-1">
${this.renderSpeedButton(ReplaySpeedMultiplier.slow, "×0.5")}
@@ -92,9 +94,9 @@ export class ReplayPanel extends LitElement implements Layer {
const isActive = this._replaySpeedMultiplier === value;
return html`
<button
class="text-white font-bold py-0 rounded border transition ${isActive
? "bg-blue-500 border-gray-400"
: "border-gray-500"}"
class="text-white font-bold py-0 rounded border transition ${
isActive ? "bg-blue-500 border-gray-400" : "border-gray-500"
}"
@click=${() => this.onReplaySpeedChange(value)}
>
${label}
+92 -58
View File
@@ -1,10 +1,6 @@
import { AlternateViewEvent, RedrawGraphicsEvent } from "../../InputHandler";
import { LitElement, html } from "lit";
import { html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
import { PauseGameEvent } from "../../Transport";
import { UserSettings } from "../../../core/game/UserSettings";
import structureIcon from "../../../../resources/images/CityIconWhite.svg";
import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg";
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
@@ -12,9 +8,13 @@ import explosionIcon from "../../../../resources/images/ExplosionIconWhite.svg";
import mouseIcon from "../../../../resources/images/MouseIconWhite.svg";
import ninjaIcon from "../../../../resources/images/NinjaIconWhite.svg";
import settingsIcon from "../../../../resources/images/SettingIconWhite.svg";
import structureIcon from "../../../../resources/images/CityIconWhite.svg";
import { translateText } from "../../Utils";
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
import { EventBus } from "../../../core/EventBus";
import { UserSettings } from "../../../core/game/UserSettings";
import { AlternateViewEvent, RedrawGraphicsEvent } from "../../InputHandler";
import { PauseGameEvent } from "../../Transport";
import { translateText } from "../../Utils";
import { Layer } from "./Layer";
export class ShowSettingsModalEvent {
constructor(
@@ -199,15 +199,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.toggle_terrain")}
</div>
<div class="text-sm text-slate-400">
${this.alternateView
? translateText("user_setting.terrain_enabled")
: translateText("user_setting.terrain_disabled")}
${
this.alternateView
? translateText("user_setting.terrain_enabled")
: translateText("user_setting.terrain_disabled")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.alternateView
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.alternateView
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -222,15 +226,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.emojis_label")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.emojis()
? translateText("user_setting.emojis_visible")
: translateText("user_setting.emojis_hidden")}
${
this.userSettings?.emojis()
? translateText("user_setting.emojis_visible")
: translateText("user_setting.emojis_hidden")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.emojis()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.emojis()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -250,15 +258,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.dark_mode_label")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.darkMode()
? translateText("user_setting.dark_mode_enabled")
: translateText("user_setting.light_mode_enabled")}
${
this.userSettings?.darkMode()
? translateText("user_setting.dark_mode_enabled")
: translateText("user_setting.light_mode_enabled")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.darkMode()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.darkMode()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -278,15 +290,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.special_effects_label")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.fxLayer()
? translateText("user_setting.special_effects_enabled")
: translateText("user_setting.special_effects_disabled")}
${
this.userSettings?.fxLayer()
? translateText("user_setting.special_effects_enabled")
: translateText("user_setting.special_effects_disabled")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.fxLayer()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.fxLayer()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -306,15 +322,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.structure_sprites_label")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.structureSprites()
? translateText("user_setting.structure_sprites_enabled")
: translateText("user_setting.structure_sprites_disabled")}
${
this.userSettings?.structureSprites()
? translateText("user_setting.structure_sprites_enabled")
: translateText("user_setting.structure_sprites_disabled")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.structureSprites()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.structureSprites()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -329,15 +349,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.anonymous_names_label")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.anonymousNames()
? translateText("user_setting.anonymous_names_enabled")
: translateText("user_setting.real_names_shown")}
${
this.userSettings?.anonymousNames()
? translateText("user_setting.anonymous_names_enabled")
: translateText("user_setting.real_names_shown")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.anonymousNames()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.anonymousNames()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -352,15 +376,19 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.left_click_menu")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.leftClickOpensMenu()
? translateText("user_setting.left_click_opens_menu")
: translateText("user_setting.right_click_opens_menu")}
${
this.userSettings?.leftClickOpensMenu()
? translateText("user_setting.left_click_opens_menu")
: translateText("user_setting.right_click_opens_menu")
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.leftClickOpensMenu()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.leftClickOpensMenu()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
@@ -380,17 +408,23 @@ export class SettingsModal extends LitElement implements Layer {
${translateText("user_setting.performance_overlay_label")}
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.performanceOverlay()
? translateText("user_setting.performance_overlay_enabled")
: translateText(
"user_setting.performance_overlay_disabled",
)}
${
this.userSettings?.performanceOverlay()
? translateText(
"user_setting.performance_overlay_enabled",
)
: translateText(
"user_setting.performance_overlay_disabled",
)
}
</div>
</div>
<div class="text-sm text-slate-400">
${this.userSettings?.performanceOverlay()
? translateText("user_setting.on")
: translateText("user_setting.off")}
${
this.userSettings?.performanceOverlay()
? translateText("user_setting.on")
: translateText("user_setting.off")
}
</div>
</button>
+9 -7
View File
@@ -1,9 +1,9 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { getGamesPlayed } from "../../Utils";
import { translateText } from "../../../client/Utils";
import { GameView } from "../../../core/game/GameView";
import { getGamesPlayed } from "../../Utils";
import { Layer } from "./Layer";
const AD_TYPE = "bottom_rail";
const AD_CONTAINER_ID = "bottom-rail-ad-container";
@@ -125,11 +125,13 @@ export class SpawnAd extends LitElement implements Layer {
id="${AD_CONTAINER_ID}"
class="w-full h-full flex items-center justify-center"
>
${!this.adLoaded
? html`<span class="text-white text-sm"
${
!this.adLoaded
? html`<span class="text-white text-sm"
>${translateText("spawn_ad.loading")}</span
>`
: ""}
: ""
}
</div>
</div>
`;
+1 -1
View File
@@ -1,7 +1,7 @@
import { GameMode, Team } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
export class SpawnTimer implements Layer {
private ratios = [0];
@@ -1,20 +1,20 @@
import * as PIXI from "pixi.js";
import { Cell, PlayerID, UnitType } from "../../../core/game/Game";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { EventBus } from "../../../core/EventBus";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { Layer } from "./Layer";
import { OutlineFilter } from "pixi-filters";
import SAMMissileIcon from "../../../../resources/images/SamLauncherUnit.png";
import { Theme } from "../../../core/configuration/Config";
import { ToggleStructureEvent } from "../../InputHandler";
import { TransformHandler } from "../TransformHandler";
import anchorIcon from "../../../../resources/images/AnchorIcon.png";
import bitmapFont from "../../../../resources/fonts/round_6x6_modified.xml";
import anchorIcon from "../../../../resources/images/AnchorIcon.png";
import cityIcon from "../../../../resources/images/CityIcon.png";
import factoryIcon from "../../../../resources/images/FactoryUnit.png";
import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png";
import SAMMissileIcon from "../../../../resources/images/SamLauncherUnit.png";
import shieldIcon from "../../../../resources/images/ShieldIcon.png";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { Cell, PlayerID, UnitType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { ToggleStructureEvent } from "../../InputHandler";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
type ShapeType = "triangle" | "square" | "pentagon" | "octagon" | "circle";
@@ -357,12 +357,12 @@ export class StructureIconsLayer implements Layer {
const shape = STRUCTURE_SHAPES[structureType];
const texture = shape
? this.createIcon(
unit.owner(),
structureType,
isConstruction,
shape,
renderIcon,
)
unit.owner(),
structureType,
isConstruction,
shape,
renderIcon,
)
: PIXI.Texture.EMPTY;
this.textureCache.set(cacheKey, texture);
+11 -11
View File
@@ -1,18 +1,18 @@
import { Cell, UnitType } from "../../../core/game/Game";
import { Colord, colord } from "colord";
import { GameView, UnitView } from "../../../core/game/GameView";
import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap";
import { EventBus } from "../../../core/EventBus";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { Layer } from "./Layer";
import SAMMissileIcon from "../../../../resources/non-commercial/images/buildings/silo4.png";
import { Theme } from "../../../core/configuration/Config";
import { TransformHandler } from "../TransformHandler";
import anchorIcon from "../../../../resources/non-commercial/images/buildings/port1.png";
import cityIcon from "../../../../resources/non-commercial/images/buildings/cityAlt1.png";
import factoryIcon from "../../../../resources/non-commercial/images/buildings/factoryAlt1.png";
import missileSiloIcon from "../../../../resources/non-commercial/images/buildings/silo1.png";
import shieldIcon from "../../../../resources/non-commercial/images/buildings/fortAlt3.png";
import anchorIcon from "../../../../resources/non-commercial/images/buildings/port1.png";
import missileSiloIcon from "../../../../resources/non-commercial/images/buildings/silo1.png";
import SAMMissileIcon from "../../../../resources/non-commercial/images/buildings/silo4.png";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { Cell, UnitType } from "../../../core/game/Game";
import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
const underConstructionColor = colord({ r: 150, g: 150, b: 150 });
+9 -7
View File
@@ -1,9 +1,9 @@
import { html, LitElement } from "lit";
import { customElement, property } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameMode, Team, UnitType } from "../../../core/game/Game";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { renderNumber, translateText } from "../../Utils";
import { EventBus } from "../../../core/EventBus";
import { Layer } from "./Layer";
type TeamEntry = {
@@ -130,8 +130,9 @@ export class TeamStats extends LitElement implements Layer {
<div class="py-1.5 md:py-2.5 text-center border-b border-slate-500">
${translateText("leaderboard.team")}
</div>
${this.showUnits
? html`
${
this.showUnits
? html`
<div class="py-1.5 text-center border-b border-slate-500">
${translateText("leaderboard.launchers")}
</div>
@@ -145,7 +146,7 @@ export class TeamStats extends LitElement implements Layer {
${translateText("leaderboard.cities")}
</div>
`
: html`
: html`
<div class="py-1.5 text-center border-b border-slate-500">
${translateText("leaderboard.owned")}
</div>
@@ -155,7 +156,8 @@ export class TeamStats extends LitElement implements Layer {
<div class="py-1.5 text-center border-b border-slate-500">
${translateText("leaderboard.troops")}
</div>
`}
`
}
</div>
<!-- Data rows -->
+2 -2
View File
@@ -1,7 +1,7 @@
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { Theme } from "../../../core/configuration/Config";
import { GameView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
export class TerrainLayer implements Layer {
private canvas: HTMLCanvasElement | undefined;
+23 -17
View File
@@ -1,21 +1,21 @@
import { PriorityQueue } from "@datastructures-js/priority-queue";
import { Colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { Cell, PlayerType, UnitType } from "../../../core/game/Game";
import { euclDistFN, TileRef } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { PseudoRandom } from "../../../core/PseudoRandom";
import {
AlternateViewEvent,
DragEvent,
MouseOverEvent,
RedrawGraphicsEvent,
} from "../../InputHandler";
import { Cell, PlayerType, UnitType } from "../../../core/game/Game";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { TileRef, euclDistFN } from "../../../core/game/GameMap";
import { Colord } from "colord";
import { EventBus } from "../../../core/EventBus";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { Layer } from "./Layer";
import { PriorityQueue } from "@datastructures-js/priority-queue";
import { PseudoRandom } from "../../../core/PseudoRandom";
import { Theme } from "../../../core/configuration/Config";
import { TransformHandler } from "../TransformHandler";
import { UserSettings } from "../../../core/game/UserSettings";
import { Layer } from "./Layer";
export class TerritoryLayer implements Layer {
private readonly userSettings: UserSettings;
@@ -323,7 +323,8 @@ export class TerritoryLayer implements Layer {
initImageData() {
this.game.forEachTile((tile) => {
if (this.imageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined)
throw new Error("Not initialized");
const cell = new Cell(this.game.x(tile), this.game.y(tile));
const index = cell.y * this.game.width() + cell.x;
const offset = index * 4;
@@ -337,7 +338,8 @@ export class TerritoryLayer implements Layer {
if (this.highlightCanvas === undefined) throw new Error("Not initialized");
if (this.context === undefined) throw new Error("Not initialized");
if (this.imageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined)
throw new Error("Not initialized");
const now = Date.now();
if (
now > this.lastDragTime + this.nodrawDragDuration &&
@@ -413,7 +415,8 @@ export class TerritoryLayer implements Layer {
return;
}
if (this.imageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined)
throw new Error("Not initialized");
if (!this.game.hasOwner(tile)) {
if (this.game.hasFallout(tile)) {
@@ -509,7 +512,8 @@ export class TerritoryLayer implements Layer {
}
paintAlternateViewTile(tile: TileRef, other: PlayerView) {
if (this.alternativeImageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined)
throw new Error("Not initialized");
const color = this.alternateViewColor(other);
this.paintTile(this.alternativeImageData, tile, color, 255);
}
@@ -525,14 +529,16 @@ export class TerritoryLayer implements Layer {
clearTile(tile: TileRef) {
const offset = tile * 4;
if (this.imageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined)
throw new Error("Not initialized");
this.imageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
}
clearAlternativeTile(tile: TileRef) {
const offset = tile * 4;
if (this.alternativeImageData === undefined) throw new Error("Not initialized");
if (this.alternativeImageData === undefined)
throw new Error("Not initialized");
this.alternativeImageData.data[offset + 3] = 0; // Set alpha to 0 (fully transparent)
}
+8 -9
View File
@@ -1,14 +1,14 @@
import { GameView, UnitView } from "../../../core/game/GameView";
import { Colord } from "colord";
import { EventBus } from "../../../core/EventBus";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { Layer } from "./Layer";
import { ProgressBar } from "../ProgressBar";
import { Theme } from "../../../core/configuration/Config";
import { TransformHandler } from "../TransformHandler";
import { UnitSelectionEvent } from "../../InputHandler";
import { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { UnitSelectionEvent } from "../../InputHandler";
import { ProgressBar } from "../ProgressBar";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
const COLOR_PROGRESSION = [
"rgb(232, 25, 25)",
@@ -69,8 +69,7 @@ export class UILayer implements Layer {
this.game
.updatesSinceLastTick()
?.[GameUpdateType.Unit]
?.map((unit) => this.game.unit(unit.id))
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
?.forEach((unitView) => {
if (unitView === undefined) return;
this.onUnitEvent(unitView);
+13 -13
View File
@@ -1,17 +1,17 @@
import { LitElement, html } from "lit";
import { EventBus } from "../../../core/EventBus";
import { GameView } from "../../../core/game/GameView";
import { Layer } from "./Layer";
import { ToggleStructureEvent } from "../../InputHandler";
import { UnitType } from "../../../core/game/Game";
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
import { html, LitElement } from "lit";
import { customElement } from "lit/decorators.js";
import defensePostIcon from "../../../../resources/images/ShieldIconWhite.svg";
import portIcon from "../../../../resources/images/AnchorIcon.png";
import cityIcon from "../../../../resources/images/CityIconWhite.svg";
import factoryIcon from "../../../../resources/images/FactoryIconWhite.svg";
import missileSiloIcon from "../../../../resources/images/MissileSiloUnit.png";
import portIcon from "../../../../resources/images/AnchorIcon.png";
import { renderNumber } from "../../Utils";
import defensePostIcon from "../../../../resources/images/ShieldIconWhite.svg";
import samLauncherIcon from "../../../../resources/non-commercial/svg/SamLauncherIconWhite.svg";
import { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { ToggleStructureEvent } from "../../InputHandler";
import { renderNumber } from "../../Utils";
import { Layer } from "./Layer";
@customElement("unit-display")
export class UnitDisplay extends LitElement implements Layer {
@@ -69,9 +69,9 @@ export class UnitDisplay extends LitElement implements Layer {
return html`
<div
class="px-2 flex items-center gap-2 cursor-pointer hover:bg-slate-700/50 rounded text-white"
style="background: ${this._selectedStructure === unitType
? "#ffffff2e"
: "none"}"
style="background: ${
this._selectedStructure === unitType ? "#ffffff2e" : "none"
}"
@mouseenter="${() =>
this.eventBus?.emit(new ToggleStructureEvent(unitType))}"
@mouseleave="${() =>
+25 -19
View File
@@ -1,24 +1,24 @@
import { Colord, colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { BezenhamLine } from "../../../core/utilities/Line";
import {
AlternateViewEvent,
MouseUpEvent,
UnitSelectionEvent,
} from "../../InputHandler";
import { Colord, colord } from "colord";
import { GameView, UnitView } from "../../../core/game/GameView";
import { MoveWarshipIntentEvent } from "../../Transport";
import {
getColoredSprite,
isSpriteReady,
loadAllSprites,
} from "../SpriteLoader";
import { BezenhamLine } from "../../../core/utilities/Line";
import { EventBus } from "../../../core/EventBus";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { Layer } from "./Layer";
import { MoveWarshipIntentEvent } from "../../Transport";
import { Theme } from "../../../core/configuration/Config";
import { TileRef } from "../../../core/game/GameMap";
import { TransformHandler } from "../TransformHandler";
import { UnitType } from "../../../core/game/Game";
import { Layer } from "./Layer";
enum Relationship {
Self,
@@ -156,7 +156,8 @@ export class UnitLayer implements Layer {
}
renderLayer(context: CanvasRenderingContext2D) {
if (this.transportShipTrailCanvas === undefined) throw new Error("Not initialized");
if (this.transportShipTrailCanvas === undefined)
throw new Error("Not initialized");
if (this.canvas === undefined) throw new Error("Not initialized");
context.drawImage(
this.transportShipTrailCanvas,
@@ -197,7 +198,8 @@ export class UnitLayer implements Layer {
this.updateUnitsSprites(this.game.units().map((unit) => unit.id()));
this.unitToTrail.forEach((trail, unit) => {
if (this.unitTrailContext === undefined) throw new Error("Not initialized");
if (this.unitTrailContext === undefined)
throw new Error("Not initialized");
for (const t of trail) {
this.paintCell(
this.game.x(t),
@@ -309,7 +311,11 @@ export class UnitLayer implements Layer {
const rel = this.relationship(unit);
// Clear current and previous positions
this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile()), this.context);
this.clearCell(
this.game.x(unit.lastTile()),
this.game.y(unit.lastTile()),
this.context,
);
const oldTile = this.oldShellTile.get(unit);
if (oldTile !== undefined) {
this.clearCell(this.game.x(oldTile), this.game.y(oldTile), this.context);
@@ -432,7 +438,11 @@ export class UnitLayer implements Layer {
const rel = this.relationship(unit);
if (this.context === undefined) throw new Error("Not initialized");
this.clearCell(this.game.x(unit.lastTile()), this.game.y(unit.lastTile()), this.context);
this.clearCell(
this.game.x(unit.lastTile()),
this.game.y(unit.lastTile()),
this.context,
);
if (unit.isActive()) {
// Paint area
@@ -504,11 +514,7 @@ export class UnitLayer implements Layer {
context.fillRect(x, y, 1, 1);
}
clearCell(
x: number,
y: number,
context: CanvasRenderingContext2D,
) {
clearCell(x: number, y: number, context: CanvasRenderingContext2D) {
context.clearRect(x, y, 1, 1);
}
+3 -3
View File
@@ -1,12 +1,12 @@
import { LitElement, css, html } from "lit";
import { css, html, LitElement } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { SendWinnerEvent } from "../../Transport";
import { GutterAdModalEvent } from "./GutterAdModal";
import { Layer } from "./Layer";
import { SendWinnerEvent } from "../../Transport";
import { translateText } from "../../../client/Utils";
@customElement("win-modal")
export class WinModal extends LitElement implements Layer {
+2 -2
View File
@@ -1,3 +1,5 @@
import { decodeJwt } from "jose";
import { z } from "zod";
import {
RefreshResponseSchema,
TokenPayload,
@@ -5,9 +7,7 @@ import {
UserMeResponse,
UserMeResponseSchema,
} from "../core/ApiSchemas";
import { decodeJwt } from "jose";
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
import { z } from "zod";
function getAudience() {
const { hostname } = new URL(window.location.href);
+35 -26
View File
@@ -134,7 +134,7 @@
.option-image {
width: 100%;
aspect-ratio: 4/2;
aspect-ratio: 4 / 2;
color: #aaa;
transition: transform 0.2s ease-in-out;
border-radius: 8px;
@@ -373,17 +373,18 @@ label.option-card:hover {
}
#helpModal .city-icon {
mask: url("../../resources/images/CityIconWhite.svg") no-repeat center / cover;
mask:
url("../../resources/images/CityIconWhite.svg") no-repeat center / cover;
}
#helpModal .factory-icon {
mask: url("../../resources/images/FactoryIconWhite.svg") no-repeat center /
cover;
mask:
url("../../resources/images/FactoryIconWhite.svg") no-repeat center / cover;
}
#helpModal .defense-post-icon {
mask: url("../../resources/images/ShieldIconWhite.svg") no-repeat center /
cover;
mask:
url("../../resources/images/ShieldIconWhite.svg") no-repeat center / cover;
}
#helpModal .port-icon {
@@ -391,27 +392,32 @@ label.option-card:hover {
}
#helpModal .warship-icon {
mask: url("../../resources/images/BattleshipIconWhite.svg") no-repeat center /
mask:
url("../../resources/images/BattleshipIconWhite.svg") no-repeat center /
cover;
}
#helpModal .missile-silo-icon {
mask: url("../../resources/non-commercial/svg/MissileSiloIconWhite.svg")
no-repeat center / cover;
mask:
url("../../resources/non-commercial/svg/MissileSiloIconWhite.svg") no-repeat
center / cover;
}
#helpModal .sam-launcher-icon {
mask: url("../../resources/non-commercial/svg/SamLauncherIconWhite.svg")
no-repeat center / cover;
mask:
url("../../resources/non-commercial/svg/SamLauncherIconWhite.svg") no-repeat
center / cover;
}
#helpModal .atom-bomb-icon {
mask: url("../../resources/images/NukeIconWhite.svg") no-repeat center / cover;
mask:
url("../../resources/images/NukeIconWhite.svg") no-repeat center / cover;
}
#helpModal .hydrogen-bomb-icon {
mask: url("../../resources/images/MushroomCloudIconWhite.svg") no-repeat
center / cover;
mask:
url("../../resources/images/MushroomCloudIconWhite.svg") no-repeat center /
cover;
}
#helpModal .mirv-icon {
@@ -419,7 +425,8 @@ label.option-card:hover {
}
#helpModal .chat-icon {
mask: url("../../resources/images/ChatIconWhite.svg") no-repeat center / cover;
mask:
url("../../resources/images/ChatIconWhite.svg") no-repeat center / cover;
}
#helpModal .target-icon {
@@ -427,33 +434,35 @@ label.option-card:hover {
}
#helpModal .alliance-icon {
mask: url("../../resources/images/AllianceIconWhite.svg") no-repeat center /
cover;
mask:
url("../../resources/images/AllianceIconWhite.svg") no-repeat center / cover;
}
#helpModal .emoji-icon {
mask: url("../../resources/images/EmojiIconWhite.svg") no-repeat center /
cover;
mask:
url("../../resources/images/EmojiIconWhite.svg") no-repeat center / cover;
}
#helpModal .betray-icon {
mask: url("../../resources/images/TraitorIconWhite.svg") no-repeat center /
cover;
mask:
url("../../resources/images/TraitorIconWhite.svg") no-repeat center / cover;
}
#helpModal .donate-icon {
mask: url("../../resources/images/DonateTroopIconWhite.svg") no-repeat
center / cover;
mask:
url("../../resources/images/DonateTroopIconWhite.svg") no-repeat center /
cover;
}
#helpModal .donate-gold-icon {
mask: url("../../resources/images/DonateGoldIconWhite.svg") no-repeat center /
mask:
url("../../resources/images/DonateGoldIconWhite.svg") no-repeat center /
cover;
}
#helpModal .build-icon {
mask: url("../../resources/images/BuildIconWhite.svg") no-repeat center /
cover;
mask:
url("../../resources/images/BuildIconWhite.svg") no-repeat center / cover;
}
#helpModal .info-icon {
@@ -1,5 +1,5 @@
// renderUnitTypeOptions.ts
import { TemplateResult, html } from "lit";
import { html, TemplateResult } from "lit";
import { UnitType } from "../../core/game/Game";
import { translateText } from "../Utils";
@@ -36,7 +36,7 @@ export function renderUnitTypeOptions({
type="checkbox"
.checked=${disabledUnits.includes(type)}
@change=${(e: Event) => {
const { checked } = (e.target as HTMLInputElement);
const { checked } = e.target as HTMLInputElement;
toggleUnit(type, checked);
}}
/>
+2 -1
View File
@@ -1,6 +1,7 @@
// This file contains schemas for api.openfront.io
import { base64urlToUuid } from "./Base64";
import { z } from "zod";
import { base64urlToUuid } from "./Base64";
export const RefreshResponseSchema = z.object({
token: z.string(),
+1 -1
View File
@@ -1,5 +1,5 @@
import { RequiredPatternSchema } from "./Schemas";
import { z } from "zod";
import { RequiredPatternSchema } from "./Schemas";
export const ProductSchema = z.object({
productId: z.string(),
+4 -4
View File
@@ -4,11 +4,11 @@ const ANIMATION_DURATIONS: Record<string, number> = {
"bright-rainbow": 4000,
"copper-glow": 3000,
"gold-glow": 3000,
"lava": 6000,
"neon": 3000,
"rainbow": 4000,
lava: 6000,
neon: 3000,
rainbow: 4000,
"silver-glow": 3000,
"water": 6200,
water: 6200,
};
// TODO: Pass in cosmetics as a parameter when
+4 -2
View File
@@ -6,8 +6,10 @@ export type EventConstructor<T extends GameEvent = GameEvent> = new (
) => T;
export class EventBus {
private readonly listeners: Map<EventConstructor, Array<(event: GameEvent) => void>> =
new Map();
private readonly listeners: Map<
EventConstructor,
Array<(event: GameEvent) => void>
> = new Map();
emit<T extends GameEvent>(event: T): void {
const eventConstructor = event.constructor as EventConstructor<T>;
+2 -1
View File
@@ -1,6 +1,7 @@
// This file contians schemas for the primary openfront express server
import { GameInfoSchema } from "./Schemas";
import { z } from "zod";
import { GameInfoSchema } from "./Schemas";
export const ApiEnvResponseSchema = z.object({
game_env: z.string(),
+18 -18
View File
@@ -1,3 +1,7 @@
import { placeName } from "../client/graphics/NameBoxCalculator";
import { getConfig } from "./configuration/ConfigLoader";
import { Executor } from "./execution/ExecutionManager";
import { WinCheckExecution } from "./execution/WinCheckExecution";
import {
AllPlayers,
Attack,
@@ -14,23 +18,19 @@ import {
PlayerProfile,
PlayerType,
} from "./game/Game";
import { ClientID, GameStartInfo, Turn } from "./Schemas";
import { createGame } from "./game/GameImpl";
import { TileRef } from "./game/GameMap";
import { GameMapLoader } from "./game/GameMapLoader";
import {
ErrorUpdate,
GameUpdateType,
GameUpdateViewData,
} from "./game/GameUpdates";
import { sanitize, simpleHash } from "./Util";
import { Executor } from "./execution/ExecutionManager";
import { GameMapLoader } from "./game/GameMapLoader";
import { PseudoRandom } from "./PseudoRandom";
import { TileRef } from "./game/GameMap";
import { WinCheckExecution } from "./execution/WinCheckExecution";
import { createGame } from "./game/GameImpl";
import { fixProfaneUsername } from "./validations/username";
import { getConfig } from "./configuration/ConfigLoader";
import { loadTerrainMap } from "./game/TerrainMapLoader";
import { placeName } from "../client/graphics/NameBoxCalculator";
import { PseudoRandom } from "./PseudoRandom";
import { ClientID, GameStartInfo, Turn } from "./Schemas";
import { sanitize, simpleHash } from "./Util";
import { fixProfaneUsername } from "./validations/username";
export async function createGameRunner(
gameStart: GameStartInfo,
@@ -57,13 +57,13 @@ export async function createGameRunner(
const nations = gameStart.config.disableNPCs
? []
: gameMap.manifest.nations.map(
(n) =>
new Nation(
new Cell(n.coordinates[0], n.coordinates[1]),
n.strength,
new PlayerInfo(n.name, PlayerType.FakeHuman, null, random.nextID()),
),
);
(n) =>
new Nation(
new Cell(n.coordinates[0], n.coordinates[1]),
n.strength,
new PlayerInfo(n.name, PlayerType.FakeHuman, null, random.nextID()),
),
);
const game: Game = createGame(
humans,
+7 -5
View File
@@ -1,3 +1,10 @@
import { base64url } from "jose";
import { z } from "zod";
import quickChatData from "../../resources/QuickChat.json" with {
type: "json",
};
import countries from "../client/data/countries.json" with { type: "json" };
import { ID } from "./BaseSchemas";
import {
AllPlayers,
Difficulty,
@@ -10,14 +17,9 @@ import {
Trios,
UnitType,
} from "./game/Game";
import { ID } from "./BaseSchemas";
import { PatternDecoder } from "./PatternDecoder";
import { PlayerStatsSchema } from "./StatsSchemas";
import { base64url } from "jose";
import countries from "../client/data/countries.json" with { type: "json" };
import { flattenedEmojiTable } from "./Util";
import quickChatData from "../../resources/QuickChat.json" with { type: "json" };
import { z } from "zod";
export type GameID = string;
export type ClientID = string;
+1 -1
View File
@@ -1,5 +1,5 @@
import { UnitType } from "./game/Game";
import { z } from "zod";
import { UnitType } from "./game/Game";
export const BombUnitSchema = z.union([
z.literal("abomb"),
+11 -6
View File
@@ -1,8 +1,13 @@
import DOMPurify from "dompurify";
import { customAlphabet } from "nanoid";
import { ID } from "./BaseSchemas";
import { ServerConfig } from "./configuration/Config";
import {
BOT_NAME_PREFIXES,
BOT_NAME_SUFFIXES,
} from "./execution/utils/BotNames";
import { Cell, Unit } from "./game/Game";
import { GameMap, TileRef } from "./game/GameMap";
import {
ClientID,
GameConfig,
@@ -12,11 +17,6 @@ import {
Turn,
Winner,
} from "./Schemas";
import { GameMap, TileRef } from "./game/GameMap";
import DOMPurify from "dompurify";
import { ID } from "./BaseSchemas";
import { ServerConfig } from "./configuration/Config";
import { customAlphabet } from "nanoid";
export function manhattanDistWrapped(
c1: Cell,
@@ -233,7 +233,12 @@ export function getClientID(gameID: GameID): ClientID {
const cachedGame = localStorage.getItem("game_id");
const cachedClient = localStorage.getItem("client_id");
if (gameID === cachedGame && cachedClient && ID.safeParse(cachedClient).success) return cachedClient;
if (
gameID === cachedGame &&
cachedClient &&
ID.safeParse(cachedClient).success
)
return cachedClient;
const clientId = generateID();
localStorage.setItem("game_id", gameID);
+2 -1
View File
@@ -1,6 +1,7 @@
// This file contians schemas for the openfront worker express server
import { GameConfigSchema, GameRecordSchema } from "./Schemas";
import { z } from "zod";
import { GameConfigSchema, GameRecordSchema } from "./Schemas";
export const CreateGameInputSchema = GameConfigSchema.or(
z
+6 -5
View File
@@ -1,5 +1,10 @@
import { Colord, extend } from "colord";
import labPlugin from "colord/plugins/lab";
import lchPlugin from "colord/plugins/lch";
import Color from "colorjs.io";
import { ColoredTeams, Team } from "../game/Game";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import {
blueTeamColors,
botTeamColors,
@@ -10,11 +15,7 @@ import {
tealTeamColors,
yellowTeamColors,
} from "./Colors";
import Color from "colorjs.io";
import { PseudoRandom } from "../PseudoRandom";
import labPlugin from "colord/plugins/lab";
import lchPlugin from "colord/plugins/lch";
import { simpleHash } from "../Util";
extend([lchPlugin]);
extend([labPlugin]);
+4 -4
View File
@@ -1,3 +1,5 @@
import { Colord } from "colord";
import { JWK } from "jose";
import {
Difficulty,
Game,
@@ -12,13 +14,11 @@ import {
UnitInfo,
UnitType,
} from "../game/Game";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
import { GameMap, TileRef } from "../game/GameMap";
import { Colord } from "colord";
import { JWK } from "jose";
import { NukeType } from "../StatsSchemas";
import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
import { NukeType } from "../StatsSchemas";
export enum GameEnv {
Dev,
+4 -4
View File
@@ -1,9 +1,9 @@
import { Config, GameEnv, ServerConfig } from "./Config";
import { DevConfig, DevServerConfig } from "./DevConfig";
import { ApiEnvResponseSchema } from "../ExpressSchemas";
import { DefaultConfig } from "./DefaultConfig";
import { GameConfig } from "../Schemas";
import { UserSettings } from "../game/UserSettings";
import { GameConfig } from "../Schemas";
import { Config, GameEnv, ServerConfig } from "./Config";
import { DefaultConfig } from "./DefaultConfig";
import { DevConfig, DevServerConfig } from "./DevConfig";
import { preprodConfig } from "./PreprodConfig";
import { prodConfig } from "./ProdConfig";
+9 -8
View File
@@ -1,5 +1,7 @@
/* eslint-disable max-lines */
import { Config, GameEnv, NukeMagnitude, ServerConfig, Theme } from "./Config";
import { JWK } from "jose";
import { z } from "zod";
import {
Difficulty,
Duos,
@@ -12,23 +14,22 @@ import {
PlayerInfo,
PlayerType,
Quads,
TerraNullius,
TerrainType,
TerraNullius,
Tick,
Trios,
UnitInfo,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
import { assertNever, simpleHash, within } from "../Util";
import { JWK } from "jose";
import { NukeType } from "../StatsSchemas";
import { assertNever, simpleHash, within } from "../Util";
import { Config, GameEnv, NukeMagnitude, ServerConfig, Theme } from "./Config";
import { PastelTheme } from "./PastelTheme";
import { PastelThemeDark } from "./PastelThemeDark";
import { PlayerView } from "../game/GameView";
import { TileRef } from "../game/GameMap";
import { UserSettings } from "../game/UserSettings";
import { z } from "zod";
const JwksSchema = z.object({
keys: z
+3 -3
View File
@@ -1,8 +1,8 @@
import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig";
import { GameEnv, ServerConfig } from "./Config";
import { UnitInfo, UnitType } from "../game/Game";
import { GameConfig } from "../Schemas";
import { UserSettings } from "../game/UserSettings";
import { GameConfig } from "../Schemas";
import { GameEnv, ServerConfig } from "./Config";
import { DefaultConfig, DefaultServerConfig } from "./DefaultConfig";
export class DevServerConfig extends DefaultServerConfig {
adminToken(): string {

Some files were not shown because too many files have changed in this diff Show More