mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 06:20:44 +00:00
build: migrate build system to Vite and test runner to Vitest & Remove depracated husky usage (#2703)
- Replace Webpack with Vite for faster client bundling and HMR. - Migrate tests from Jest to Vitest and update configuration. - Update Web Worker instantiation to standard ESM syntax. - Implement Env utility in `src/core` for safe, hybrid environment variable access (Vite vs Node). - Refactor configuration loaders to remove direct `process.env` dependencies in shared code. - Update TypeScript environment definitions and project scripts for the new toolchain. - Remove the [depracated usage of the husky](https://github.com/typicode/husky/releases/tag/v9.0.1). ## Description: migrate build system to Vite and test runner to Vitest & Remove depracated husky usage ## Please complete the following: - [X] I have added screenshots for all UI updates - [X] I process any text displayed to the user through translateText() and I've added it to the en.json file - [ ] I have added relevant tests to the test directory - [X] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: wraith4081 --------- Co-authored-by: evanpelle <evanpelle@gmail.com>
This commit is contained in:
@@ -11,3 +11,5 @@ resources/.DS_Store
|
||||
.clinic/
|
||||
CLAUDE.md
|
||||
.idea/
|
||||
# this is autogenerated by script
|
||||
src/assets/
|
||||
|
||||
Executable → Regular
+2
-1
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
# Deprecated with husky v9
|
||||
#. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# Add PATH setup to ensure npx is found
|
||||
export PATH="/usr/local/bin:$HOME/.npm-global/bin:$HOME/.nvm/versions/node/$(node -v)/bin:$PATH"
|
||||
|
||||
+1
-2
@@ -12,8 +12,7 @@ RUN --mount=type=cache,target=/root/.npm \
|
||||
|
||||
# Copy only what's needed for build
|
||||
COPY tsconfig.json ./
|
||||
COPY tsconfig.jest.json ./
|
||||
COPY webpack.config.js ./
|
||||
COPY vite.config.ts ./
|
||||
COPY tailwind.config.js ./
|
||||
COPY postcss.config.js ./
|
||||
COPY eslint.config.js ./
|
||||
|
||||
+1
-2
@@ -26,10 +26,9 @@ export default [
|
||||
allowDefaultProject: [
|
||||
"__mocks__/fileMock.js",
|
||||
"eslint.config.js",
|
||||
"jest.config.ts",
|
||||
"postcss.config.js",
|
||||
"tailwind.config.js",
|
||||
"webpack.config.js",
|
||||
"scripts/sync-assets.mjs",
|
||||
],
|
||||
},
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
|
||||
@@ -7,12 +7,8 @@
|
||||
content="width=device-width, initial-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<title>OpenFront (ALPHA)</title>
|
||||
<link rel="manifest" href="../../resources/manifest.json" />
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/x-icon"
|
||||
href="../../resources/images/Favicon.svg"
|
||||
/>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="icon" type="image/x-icon" href="/images/Favicon.svg" />
|
||||
|
||||
<!-- SEO -->
|
||||
<link rel="canonical" href="https://openfront.io/" />
|
||||
@@ -119,6 +115,7 @@
|
||||
from {
|
||||
transform: translateY(-100%) rotate(0deg); /* Start off-screen */
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(105vh) rotate(360deg); /* Fall completely out of view */
|
||||
}
|
||||
@@ -338,7 +335,7 @@
|
||||
style="width: 80px; height: 80px; background-color: var(--primaryColor)"
|
||||
>
|
||||
<img
|
||||
src="../../resources/images/SettingIconWhite.svg"
|
||||
src="/images/SettingIconWhite.svg"
|
||||
alt="Settings"
|
||||
style="width: 72px; height: 72px"
|
||||
/>
|
||||
@@ -416,7 +413,7 @@
|
||||
>
|
||||
© OpenFront™ and Contributors
|
||||
<img
|
||||
src="../../resources/icons/github-mark-white.svg"
|
||||
src="/icons/github-mark-white.svg"
|
||||
alt="GitHub"
|
||||
width="20"
|
||||
height="20"
|
||||
@@ -511,5 +508,6 @@
|
||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||
data-cf-beacon='{"token": "03d93e6fefb349c28ee69b408fa25a13"}'
|
||||
></script>
|
||||
<script type="module" src="/src/client/Main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
export default {
|
||||
testEnvironment: "node",
|
||||
testRegex: "/tests/.*\\.(test|spec)?\\.(ts|tsx)$",
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
moduleNameMapper: {
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$":
|
||||
"<rootDir>/__mocks__/fileMock.js",
|
||||
"\\.(css|less)$": "<rootDir>/__mocks__/fileMock.js",
|
||||
},
|
||||
transform: {
|
||||
"^.+\\.tsx?$": ["@swc/jest"],
|
||||
"^.+\\.mjs$": ["@swc/jest"],
|
||||
"^.+\\.js$": ["@swc/jest"],
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
"node_modules/(?!(nanoid|@jsep|fastpriorityqueue|@datastructures-js|jose|lit.*|@lit)/)",
|
||||
],
|
||||
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
statements: 21,
|
||||
branches: 16,
|
||||
lines: 21.0,
|
||||
functions: 20.5,
|
||||
},
|
||||
},
|
||||
coverageReporters: ["text", "lcov", "html"],
|
||||
};
|
||||
Generated
+1896
-9526
File diff suppressed because it is too large
Load Diff
+23
-32
@@ -1,26 +1,33 @@
|
||||
{
|
||||
"name": "openfront-client",
|
||||
"scripts": {
|
||||
"build-dev": "webpack --config webpack.config.js --mode development",
|
||||
"build-prod": "webpack --config webpack.config.js --mode production",
|
||||
"start:client": "webpack serve --open --node-env development",
|
||||
"start:server": "node --loader ts-node/esm --experimental-specifier-resolution=node src/server/Server.ts",
|
||||
"start:server-dev": "cross-env GAME_ENV=dev node --loader ts-node/esm --experimental-specifier-resolution=node src/server/Server.ts",
|
||||
"build-dev": "concurrently \"tsc --noEmit\" \"vite build --mode development\"",
|
||||
"build-prod": "concurrently --kill-others-on-fail \"tsc --noEmit\" \"vite build\"",
|
||||
"start:client": "vite",
|
||||
"start:server": "tsx src/server/Server.ts",
|
||||
"start:server-dev": "cross-env GAME_ENV=dev tsx src/server/Server.ts",
|
||||
"dev": "cross-env GAME_ENV=dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
|
||||
"dev:staging": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.dev concurrently \"npm run start:client\" \"npm run start:server-dev\"",
|
||||
"dev:prod": "cross-env GAME_ENV=dev API_DOMAIN=api.openfront.io concurrently \"npm run start:client\" \"npm run start:server-dev\"",
|
||||
"docs:map-generator": "cd map-generator && go doc -cmd -u -all",
|
||||
"tunnel": "npm run build-prod && npm run start:server",
|
||||
"test": "jest",
|
||||
"test": "vitest run",
|
||||
"perf": "npx tsx tests/perf/*.ts",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:coverage": "vitest run --coverage",
|
||||
"format": "prettier --ignore-unknown --write .",
|
||||
"format:map-generator": "cd map-generator && go fmt .",
|
||||
"lint": "eslint",
|
||||
"lint:fix": "eslint --fix",
|
||||
"prepare": "husky",
|
||||
"gen-maps": "cd map-generator && go run . && npm run format",
|
||||
"inst": "npm ci --ignore-scripts"
|
||||
"inst": "npm ci --ignore-scripts",
|
||||
"sync-assets": "node scripts/sync-assets.mjs",
|
||||
"predev": "npm run sync-assets",
|
||||
"prebuild-dev": "npm run sync-assets",
|
||||
"prebuild-prod": "npm run sync-assets",
|
||||
"prestart:client": "npm run sync-assets",
|
||||
"pretest": "npm run sync-assets",
|
||||
"pretest:coverage": "npm run sync-assets"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*": [
|
||||
@@ -29,13 +36,9 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/preset-env": "^7.25.3",
|
||||
"@babel/preset-typescript": "^7.24.7",
|
||||
"@datastructures-js/priority-queue": "^6.3.3",
|
||||
"@eslint/compat": "^1.2.7",
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@swc/jest": "^0.2.39",
|
||||
"@types/benchmark": "^2.1.5",
|
||||
"@types/chai": "^4.3.17",
|
||||
"@types/d3": "^7.4.3",
|
||||
@@ -43,7 +46,6 @@
|
||||
"@types/google-protobuf": "^3.15.12",
|
||||
"@types/hammerjs": "^2.0.46",
|
||||
"@types/howler": "^2.2.12",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/jquery": "^3.5.31",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/msgpack5": "^3.4.6",
|
||||
@@ -53,29 +55,21 @@
|
||||
"@types/sinon": "^17.0.3",
|
||||
"@types/systeminformation": "^3.23.1",
|
||||
"@types/ws": "^8.5.11",
|
||||
"@vitest/coverage-v8": "^4.0.16",
|
||||
"@vitest/ui": "^4.0.16",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"benchmark": "^2.1.4",
|
||||
"binary-base64-loader": "^1.0.0",
|
||||
"binary-loader": "^0.0.1",
|
||||
"canvas": "^3.1.0",
|
||||
"chai": "^5.1.1",
|
||||
"concurrently": "^8.2.2",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^7.1.2",
|
||||
"d3": "^7.9.0",
|
||||
"eslint": "^9.21.0",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-formatter-gha": "^1.5.2",
|
||||
"eslint-webpack-plugin": "^5.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"globals": "^16.0.0",
|
||||
"html-inline-script-webpack-plugin": "^3.2.1",
|
||||
"html-loader": "^5.1.0",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^30.0.0",
|
||||
"jest-environment-jsdom": "^30.0.0",
|
||||
"jsdom": "^27.4.0",
|
||||
"lint-staged": "^16.1.2",
|
||||
"lit": "^3.3.1",
|
||||
"lit-markdown": "^1.3.2",
|
||||
@@ -83,25 +77,22 @@
|
||||
"pixi-filters": "^6.1.4",
|
||||
"pixi.js": "^8.11.0",
|
||||
"postcss": "^8.5.1",
|
||||
"postcss-loader": "^8.1.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-sh": "^0.17.4",
|
||||
"protobufjs": "^7.5.3",
|
||||
"raw-loader": "^4.0.2",
|
||||
"sinon": "^21.0.0",
|
||||
"sinon-chai": "^4.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"ts-loader": "^9.5.2",
|
||||
"tsconfig-paths": "^4.2.0",
|
||||
"tsx": "^4.17.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript-eslint": "^8.26.0",
|
||||
"webpack": "^5.100.2",
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.2",
|
||||
"worker-loader": "^3.0.8"
|
||||
"vite": "^7.3.0",
|
||||
"vite-plugin-html": "^3.2.2",
|
||||
"vite-plugin-static-copy": "^3.1.4",
|
||||
"vite-tsconfig-paths": "^6.0.3",
|
||||
"vitest": "^4.0.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.758.0",
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const root = path.resolve(__dirname, "..");
|
||||
|
||||
const resourcesDir = path.join(root, "resources");
|
||||
const assetsDir = path.join(root, "src", "assets");
|
||||
const dataDir = path.join(assetsDir, "data");
|
||||
const langDir = path.join(assetsDir, "lang");
|
||||
|
||||
const dataFiles = ["version.txt", "countries.json", "QuickChat.json"];
|
||||
|
||||
async function ensureDir(dir) {
|
||||
await fs.mkdir(dir, { recursive: true });
|
||||
}
|
||||
|
||||
async function copyFile(src, dest) {
|
||||
await ensureDir(path.dirname(dest));
|
||||
await fs.copyFile(src, dest);
|
||||
}
|
||||
|
||||
async function copyDataFiles() {
|
||||
await Promise.all(
|
||||
dataFiles.map((name) =>
|
||||
copyFile(path.join(resourcesDir, name), path.join(dataDir, name)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function copyLangFiles() {
|
||||
const sourceDir = path.join(resourcesDir, "lang");
|
||||
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
|
||||
await Promise.all(
|
||||
entries
|
||||
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
||||
.map((entry) =>
|
||||
copyFile(
|
||||
path.join(sourceDir, entry.name),
|
||||
path.join(langDir, entry.name),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await ensureDir(dataDir);
|
||||
await ensureDir(langDir);
|
||||
await copyDataFiles();
|
||||
await copyLangFiles();
|
||||
console.log("Synced resources to src/assets.");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("sync-assets failed:", error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import Countries from "./data/countries.json";
|
||||
import Countries from "../assets/data/countries.json" with { type: "json" };
|
||||
import { translateText } from "./Utils";
|
||||
|
||||
@customElement("flag-input-modal")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { LitElement, html } 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 {
|
||||
@@ -32,6 +31,7 @@ import { crazyGamesSDK } from "./CrazyGamesSDK";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { terrainMapFileLoader } from "./TerrainMapFileLoader";
|
||||
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
|
||||
import randomMap from "/images/RandomMap.webp?url";
|
||||
@customElement("host-lobby-modal")
|
||||
export class HostLobbyModal extends LitElement {
|
||||
@query("o-modal") private modalEl!: HTMLElement & {
|
||||
|
||||
+34
-34
@@ -2,40 +2,40 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import "./LanguageModal";
|
||||
|
||||
import ar from "../../resources/lang/ar.json";
|
||||
import bg from "../../resources/lang/bg.json";
|
||||
import bn from "../../resources/lang/bn.json";
|
||||
import cs from "../../resources/lang/cs.json";
|
||||
import da from "../../resources/lang/da.json";
|
||||
import de from "../../resources/lang/de.json";
|
||||
import el from "../../resources/lang/el.json";
|
||||
import en from "../../resources/lang/en.json";
|
||||
import eo from "../../resources/lang/eo.json";
|
||||
import es from "../../resources/lang/es.json";
|
||||
import fa from "../../resources/lang/fa.json";
|
||||
import fi from "../../resources/lang/fi.json";
|
||||
import fr from "../../resources/lang/fr.json";
|
||||
import gl from "../../resources/lang/gl.json";
|
||||
import he from "../../resources/lang/he.json";
|
||||
import hi from "../../resources/lang/hi.json";
|
||||
import hu from "../../resources/lang/hu.json";
|
||||
import it from "../../resources/lang/it.json";
|
||||
import ja from "../../resources/lang/ja.json";
|
||||
import ko from "../../resources/lang/ko.json";
|
||||
import mk from "../../resources/lang/mk.json";
|
||||
import nl from "../../resources/lang/nl.json";
|
||||
import pl from "../../resources/lang/pl.json";
|
||||
import pt_BR from "../../resources/lang/pt-BR.json";
|
||||
import pt_PT from "../../resources/lang/pt-PT.json";
|
||||
import ru from "../../resources/lang/ru.json";
|
||||
import sh from "../../resources/lang/sh.json";
|
||||
import sk from "../../resources/lang/sk.json";
|
||||
import sl from "../../resources/lang/sl.json";
|
||||
import sv_SE from "../../resources/lang/sv-SE.json";
|
||||
import tp from "../../resources/lang/tp.json";
|
||||
import tr from "../../resources/lang/tr.json";
|
||||
import uk from "../../resources/lang/uk.json";
|
||||
import zh_CN from "../../resources/lang/zh-CN.json";
|
||||
import ar from "../assets/lang/ar.json";
|
||||
import bg from "../assets/lang/bg.json";
|
||||
import bn from "../assets/lang/bn.json";
|
||||
import cs from "../assets/lang/cs.json";
|
||||
import da from "../assets/lang/da.json";
|
||||
import de from "../assets/lang/de.json";
|
||||
import el from "../assets/lang/el.json";
|
||||
import en from "../assets/lang/en.json";
|
||||
import eo from "../assets/lang/eo.json";
|
||||
import es from "../assets/lang/es.json";
|
||||
import fa from "../assets/lang/fa.json";
|
||||
import fi from "../assets/lang/fi.json";
|
||||
import fr from "../assets/lang/fr.json";
|
||||
import gl from "../assets/lang/gl.json";
|
||||
import he from "../assets/lang/he.json";
|
||||
import hi from "../assets/lang/hi.json";
|
||||
import hu from "../assets/lang/hu.json";
|
||||
import it from "../assets/lang/it.json";
|
||||
import ja from "../assets/lang/ja.json";
|
||||
import ko from "../assets/lang/ko.json";
|
||||
import mk from "../assets/lang/mk.json";
|
||||
import nl from "../assets/lang/nl.json";
|
||||
import pl from "../assets/lang/pl.json";
|
||||
import pt_BR from "../assets/lang/pt-BR.json";
|
||||
import pt_PT from "../assets/lang/pt-PT.json";
|
||||
import ru from "../assets/lang/ru.json";
|
||||
import sh from "../assets/lang/sh.json";
|
||||
import sk from "../assets/lang/sk.json";
|
||||
import sl from "../assets/lang/sl.json";
|
||||
import sv_SE from "../assets/lang/sv-SE.json";
|
||||
import tp from "../assets/lang/tp.json";
|
||||
import tr from "../assets/lang/tr.json";
|
||||
import uk from "../assets/lang/uk.json";
|
||||
import zh_CN from "../assets/lang/zh-CN.json";
|
||||
|
||||
@customElement("lang-selector")
|
||||
export class LangSelector extends LitElement {
|
||||
|
||||
+12
-3
@@ -1,5 +1,4 @@
|
||||
import Snowflake3Png from "../../resources/images/Snowflake.webp";
|
||||
import version from "../../resources/version.txt";
|
||||
import version from "../assets/data/version.txt?raw";
|
||||
import { UserMeResponse } from "../core/ApiSchemas";
|
||||
import { EventBus } from "../core/EventBus";
|
||||
import { GameRecord, GameStartInfo, ID } from "../core/Schemas";
|
||||
@@ -46,7 +45,17 @@ import "./components/baseComponents/Button";
|
||||
import "./components/baseComponents/Modal";
|
||||
import "./snow.css";
|
||||
import "./styles.css";
|
||||
|
||||
import "./styles/components/button.css";
|
||||
import "./styles/components/controls.css";
|
||||
import "./styles/components/modal.css";
|
||||
import "./styles/components/setting.css";
|
||||
import "./styles/core/flag-animation.css";
|
||||
import "./styles/core/typography.css";
|
||||
import "./styles/core/variables.css";
|
||||
import "./styles/layout/container.css";
|
||||
import "./styles/layout/header.css";
|
||||
import "./styles/modal/chat.css";
|
||||
import Snowflake3Png from "/images/Snowflake.webp?url";
|
||||
declare global {
|
||||
interface Window {
|
||||
turnstile: any;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { resolveMarkdown } from "lit-markdown";
|
||||
import { customElement, property, query } from "lit/decorators.js";
|
||||
import changelog from "../../resources/changelog.md";
|
||||
import megaphone from "../../resources/images/Megaphone.svg";
|
||||
import santaHatIcon from "../../resources/images/SantaHat.webp";
|
||||
import version from "../../resources/version.txt";
|
||||
import version from "../assets/data/version.txt?raw";
|
||||
import { translateText } from "../client/Utils";
|
||||
import "./components/baseComponents/Button";
|
||||
import "./components/baseComponents/Modal";
|
||||
import changelog from "/changelog.md?url";
|
||||
import megaphone from "/images/Megaphone.svg?url";
|
||||
import santaHatIcon from "/images/SantaHat.webp?url";
|
||||
|
||||
@customElement("news-modal")
|
||||
export class NewsModal extends LitElement {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import randomMap from "../../resources/images/RandomMap.webp";
|
||||
import { translateText } from "../client/Utils";
|
||||
import {
|
||||
Difficulty,
|
||||
@@ -28,6 +27,7 @@ import { FlagInput } from "./FlagInput";
|
||||
import { JoinLobbyEvent } from "./Main";
|
||||
import { UsernameInput } from "./UsernameInput";
|
||||
import { renderUnitTypeOptions } from "./utilities/RenderUnitTypeOptions";
|
||||
import randomMap from "/images/RandomMap.webp?url";
|
||||
|
||||
@customElement("single-player-modal")
|
||||
export class SinglePlayerModal extends LitElement {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import version from "../../resources/version.txt";
|
||||
import version from "../assets/data/version.txt?raw";
|
||||
import { FetchGameMapLoader } from "../core/game/FetchGameMapLoader";
|
||||
|
||||
export const terrainMapFileLoader = new FetchGameMapLoader(`/maps`, version);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import miniBigSmoke from "../../../resources/sprites/bigsmoke.png";
|
||||
import buildingExplosion from "../../../resources/sprites/buildingExplosion.png";
|
||||
import happyElf from "../../../resources/sprites/christmas/happy_elf.png";
|
||||
import sadElf from "../../../resources/sprites/christmas/sad_elf.png";
|
||||
import santa from "../../../resources/sprites/christmas/santa.png";
|
||||
import snowman from "../../../resources/sprites/christmas/snowman.png";
|
||||
import sparks from "../../../resources/sprites/christmas/sparks.png";
|
||||
import conquestSword from "../../../resources/sprites/conquestSword.png";
|
||||
import dust from "../../../resources/sprites/dust.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 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";
|
||||
import miniBigSmoke from "/sprites/bigsmoke.png?url";
|
||||
import buildingExplosion from "/sprites/buildingExplosion.png?url";
|
||||
import happyElf from "/sprites/christmas/happy_elf.png?url";
|
||||
import sadElf from "/sprites/christmas/sad_elf.png?url";
|
||||
import santa from "/sprites/christmas/santa.png?url";
|
||||
import snowman from "/sprites/christmas/snowman.png?url";
|
||||
import sparks from "/sprites/christmas/sparks.png?url";
|
||||
import conquestSword from "/sprites/conquestSword.png?url";
|
||||
import dust from "/sprites/dust.png?url";
|
||||
import miniExplosion from "/sprites/miniExplosion.png?url";
|
||||
import miniFire from "/sprites/minifire.png?url";
|
||||
import nuke from "/sprites/nukeExplosion.png?url";
|
||||
import SAMExplosion from "/sprites/samExplosion.png?url";
|
||||
import sinkingShip from "/sprites/sinkingShip.png?url";
|
||||
import miniSmoke from "/sprites/smoke.png?url";
|
||||
import miniSmokeAndFire from "/sprites/smokeAndFire.png?url";
|
||||
import unitExplosion from "/sprites/unitExplosion.png?url";
|
||||
|
||||
type AnimatedSpriteConfig = {
|
||||
url: string;
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import allianceIcon from "../../../resources/images/AllianceIcon.svg";
|
||||
import allianceIconFaded from "../../../resources/images/AllianceIconFaded.svg";
|
||||
import allianceRequestBlackIcon from "../../../resources/images/AllianceRequestBlackIcon.svg";
|
||||
import allianceRequestWhiteIcon from "../../../resources/images/AllianceRequestWhiteIcon.svg";
|
||||
import crownIcon from "../../../resources/images/CrownIcon.svg";
|
||||
import disconnectedIcon from "../../../resources/images/DisconnectedIcon.svg";
|
||||
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 questionMarkIcon from "../../../resources/images/QuestionMarkIcon.svg";
|
||||
import targetIcon from "../../../resources/images/TargetIcon.svg";
|
||||
import traitorIcon from "../../../resources/images/TraitorIcon.svg";
|
||||
import { AllPlayers, nukeTypes } from "../../core/game/Game";
|
||||
import { GameView, PlayerView } from "../../core/game/GameView";
|
||||
import allianceIcon from "/images/AllianceIcon.svg?url";
|
||||
import allianceIconFaded from "/images/AllianceIconFaded.svg?url";
|
||||
import allianceRequestBlackIcon from "/images/AllianceRequestBlackIcon.svg?url";
|
||||
import allianceRequestWhiteIcon from "/images/AllianceRequestWhiteIcon.svg?url";
|
||||
import crownIcon from "/images/CrownIcon.svg?url";
|
||||
import disconnectedIcon from "/images/DisconnectedIcon.svg?url";
|
||||
import embargoBlackIcon from "/images/EmbargoBlackIcon.svg?url";
|
||||
import embargoWhiteIcon from "/images/EmbargoWhiteIcon.svg?url";
|
||||
import nukeRedIcon from "/images/NukeIconRed.svg?url";
|
||||
import nukeWhiteIcon from "/images/NukeIconWhite.svg?url";
|
||||
import questionMarkIcon from "/images/QuestionMarkIcon.svg?url";
|
||||
import targetIcon from "/images/TargetIcon.svg?url";
|
||||
import traitorIcon from "/images/TraitorIcon.svg?url";
|
||||
|
||||
export type PlayerIconId =
|
||||
| "crown"
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { Colord } from "colord";
|
||||
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 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";
|
||||
import atomBombSprite from "/sprites/atombomb.png?url";
|
||||
import hydrogenBombSprite from "/sprites/hydrogenbomb.png?url";
|
||||
import mirvSprite from "/sprites/mirv2.png?url";
|
||||
import samMissileSprite from "/sprites/samMissile.png?url";
|
||||
import tradeShipSprite from "/sprites/tradeship.png?url";
|
||||
import trainCarriageSprite from "/sprites/trainCarriage.png?url";
|
||||
import trainLoadedCarriageSprite from "/sprites/trainCarriageLoaded.png?url";
|
||||
import trainEngineSprite from "/sprites/trainEngine.png?url";
|
||||
import transportShipSprite from "/sprites/transportship.png?url";
|
||||
import warshipSprite from "/sprites/warship.png?url";
|
||||
|
||||
// Can't reuse TrainType because "loaded" is not a type, just an attribute
|
||||
const TrainTypeSprite = {
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
import { LitElement, css, html } 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 missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
|
||||
import hydrogenBombIcon from "../../../../resources/images/MushroomCloudIconWhite.svg";
|
||||
import atomBombIcon from "../../../../resources/images/NukeIconWhite.svg";
|
||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||
import samlauncherIcon from "../../../../resources/images/SamLauncherIconWhite.svg";
|
||||
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
||||
import { translateText } from "../../../client/Utils";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import {
|
||||
@@ -34,6 +23,17 @@ import {
|
||||
import { renderNumber } from "../../Utils";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import mirvIcon from "/images/MIRVIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import hydrogenBombIcon from "/images/MushroomCloudIconWhite.svg?url";
|
||||
import atomBombIcon from "/images/NukeIconWhite.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samlauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
import shieldIcon from "/images/ShieldIconWhite.svg?url";
|
||||
|
||||
export interface BuildItemDisplay {
|
||||
unitType: UnitType;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { customElement, query } from "lit/decorators.js";
|
||||
import { PlayerType } from "../../../core/game/Game";
|
||||
import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
|
||||
import quickChatData from "../../../../resources/QuickChat.json";
|
||||
import quickChatData from "../../../assets/data/QuickChat.json" with { type: "json" };
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { CloseViewEvent } from "../../InputHandler";
|
||||
import { SendQuickChatEvent } from "../../Transport";
|
||||
|
||||
@@ -2,11 +2,6 @@ import { html, LitElement } from "lit";
|
||||
import { customElement, query, state } from "lit/decorators.js";
|
||||
import { DirectiveResult } from "lit/directive.js";
|
||||
import { unsafeHTML, UnsafeHTMLDirective } 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 nukeIcon from "../../../../resources/images/NukeIconWhite.svg";
|
||||
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import {
|
||||
AllPlayers,
|
||||
@@ -50,6 +45,11 @@ import {
|
||||
|
||||
import { getMessageTypeClasses, translateText } from "../../Utils";
|
||||
import { UIState } from "../UIState";
|
||||
import allianceIcon from "/images/AllianceIconWhite.svg?url";
|
||||
import chatIcon from "/images/ChatIconWhite.svg?url";
|
||||
import donateGoldIcon from "/images/DonateGoldIconWhite.svg?url";
|
||||
import nukeIcon from "/images/NukeIconWhite.svg?url";
|
||||
import swordIcon from "/images/SwordIconWhite.svg?url";
|
||||
|
||||
interface GameEvent {
|
||||
description: string;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Colord } from "colord";
|
||||
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";
|
||||
import leaderboardRegularIcon from "/images/LeaderboardIconRegularWhite.svg?url";
|
||||
import leaderboardSolidIcon from "/images/LeaderboardIconSolidWhite.svg?url";
|
||||
import teamRegularIcon from "/images/TeamIconRegularWhite.svg?url";
|
||||
import teamSolidIcon from "/images/TeamIconSolidWhite.svg?url";
|
||||
|
||||
@customElement("game-left-sidebar")
|
||||
export class GameLeftSidebar extends LitElement implements Layer {
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
|
||||
import FastForwardIconSolid from "../../../../resources/images/FastForwardIconSolidWhite.svg";
|
||||
import pauseIcon from "../../../../resources/images/PauseIconWhite.svg";
|
||||
import playIcon from "../../../../resources/images/PlayIconWhite.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";
|
||||
@@ -15,6 +10,11 @@ import { translateText } from "../../Utils";
|
||||
import { Layer } from "./Layer";
|
||||
import { ShowReplayPanelEvent } from "./ReplayPanel";
|
||||
import { ShowSettingsModalEvent } from "./SettingsModal";
|
||||
import exitIcon from "/images/ExitIconWhite.svg?url";
|
||||
import FastForwardIconSolid from "/images/FastForwardIconSolidWhite.svg?url";
|
||||
import pauseIcon from "/images/PauseIconWhite.svg?url";
|
||||
import playIcon from "/images/PlayIconWhite.svg?url";
|
||||
import settingsIcon from "/images/SettingIconWhite.svg?url";
|
||||
|
||||
@customElement("game-right-sidebar")
|
||||
export class GameRightSidebar extends LitElement implements Layer {
|
||||
|
||||
@@ -19,9 +19,9 @@ import {
|
||||
MenuElementParams,
|
||||
rootMenuElement,
|
||||
} from "./RadialMenuElements";
|
||||
import donateTroopIcon from "/images/DonateTroopIconWhite.svg?url";
|
||||
import swordIcon from "/images/SwordIconWhite.svg?url";
|
||||
|
||||
import donateTroopIcon from "../../../../resources/images/DonateTroopIconWhite.svg";
|
||||
import swordIcon from "../../../../resources/images/SwordIconWhite.svg";
|
||||
import { ContextMenuEvent } from "../../InputHandler";
|
||||
|
||||
@customElement("main-radial-menu")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import shieldIcon from "../../../../resources/images/ShieldIconBlack.svg";
|
||||
import { renderPlayerFlag } from "../../../core/CustomFlag";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { PseudoRandom } from "../../../core/PseudoRandom";
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
} from "../PlayerIcons";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
import shieldIcon from "/images/ShieldIconBlack.svg?url";
|
||||
|
||||
class RenderInfo {
|
||||
public icons: Map<PlayerIconId, HTMLElement> = new Map(); // Track icon elements
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
import { LitElement, TemplateResult, html } from "lit";
|
||||
import { ref } from "lit-html/directives/ref.js";
|
||||
import { customElement, property, state } from "lit/decorators.js";
|
||||
import allianceIcon from "../../../../resources/images/AllianceIcon.svg";
|
||||
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 missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
|
||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||
import samLauncherIcon from "../../../../resources/images/SamLauncherIconWhite.svg";
|
||||
import { renderPlayerFlag } from "../../../core/CustomFlag";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import {
|
||||
@@ -32,6 +24,14 @@ import { getFirstPlacePlayer, getPlayerIcons } from "../PlayerIcons";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
import { CloseRadialMenuEvent } from "./RadialMenu";
|
||||
import allianceIcon from "/images/AllianceIcon.svg?url";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import goldCoinIcon from "/images/GoldCoinIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
|
||||
function euclideanDistWorld(
|
||||
coord: { x: number; y: number },
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
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 stopTradingIcon from "../../../../resources/images/StopIconWhite.png";
|
||||
import targetIcon from "../../../../resources/images/TargetIconWhite.svg";
|
||||
import startTradingIcon from "../../../../resources/images/TradingIconWhite.png";
|
||||
import traitorIcon from "../../../../resources/images/TraitorIconLightRed.svg";
|
||||
import breakAllianceIcon from "../../../../resources/images/TraitorIconWhite.svg";
|
||||
import Countries from "../../../assets/data/countries.json" with { type: "json" };
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import {
|
||||
AllPlayers,
|
||||
@@ -23,7 +14,6 @@ import { GameView, PlayerView } from "../../../core/game/GameView";
|
||||
import { Emoji, flattenedEmojiTable } from "../../../core/Util";
|
||||
import { actionButton } from "../../components/ui/ActionButton";
|
||||
import "../../components/ui/Divider";
|
||||
import Countries from "../../data/countries.json";
|
||||
import { CloseViewEvent, MouseUpEvent } from "../../InputHandler";
|
||||
import {
|
||||
SendAllianceRequestIntentEvent,
|
||||
@@ -44,6 +34,16 @@ import { ChatModal } from "./ChatModal";
|
||||
import { EmojiTable } from "./EmojiTable";
|
||||
import { Layer } from "./Layer";
|
||||
import "./SendResourceModal";
|
||||
import allianceIcon from "/images/AllianceIconWhite.svg?url";
|
||||
import chatIcon from "/images/ChatIconWhite.svg?url";
|
||||
import donateGoldIcon from "/images/DonateGoldIconWhite.svg?url";
|
||||
import donateTroopIcon from "/images/DonateTroopIconWhite.svg?url";
|
||||
import emojiIcon from "/images/EmojiIconWhite.svg?url";
|
||||
import stopTradingIcon from "/images/StopIconWhite.png?url";
|
||||
import targetIcon from "/images/TargetIconWhite.svg?url";
|
||||
import startTradingIcon from "/images/TradingIconWhite.png?url";
|
||||
import traitorIcon from "/images/TraitorIconLightRed.svg?url";
|
||||
import breakAllianceIcon from "/images/TraitorIconWhite.svg?url";
|
||||
|
||||
@customElement("player-panel")
|
||||
export class PlayerPanel extends LitElement implements Layer {
|
||||
@@ -445,7 +445,7 @@ export class PlayerPanel extends LitElement implements Layer {
|
||||
${country && typeof flagCode === "string"
|
||||
? html`<img
|
||||
src="/flags/${encodeURIComponent(flagCode)}.svg"
|
||||
alt=${country?.name || "Flag"}
|
||||
alt=${country?.name ?? "Flag"}
|
||||
class="h-10 w-10 rounded-full object-cover"
|
||||
@error=${(e: Event) => {
|
||||
(e.target as HTMLImageElement).style.display = "none";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as d3 from "d3";
|
||||
import backIcon from "../../../../resources/images/BackIconWhite.svg";
|
||||
import { EventBus, GameEvent } from "../../../core/EventBus";
|
||||
import { CloseViewEvent } from "../../InputHandler";
|
||||
import { getSvgAspectRatio, translateText } from "../../Utils";
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
MenuElementParams,
|
||||
TooltipKey,
|
||||
} from "./RadialMenuElements";
|
||||
import backIcon from "/images/BackIconWhite.svg?url";
|
||||
|
||||
export class CloseRadialMenuEvent implements GameEvent {
|
||||
constructor() {}
|
||||
|
||||
@@ -12,19 +12,19 @@ import { PlayerActionHandler } from "./PlayerActionHandler";
|
||||
import { PlayerPanel } from "./PlayerPanel";
|
||||
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";
|
||||
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 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 { EventBus } from "../../../core/EventBus";
|
||||
import allianceIcon from "/images/AllianceIconWhite.svg?url";
|
||||
import boatIcon from "/images/BoatIconWhite.svg?url";
|
||||
import buildIcon from "/images/BuildIconWhite.svg?url";
|
||||
import chatIcon from "/images/ChatIconWhite.svg?url";
|
||||
import donateGoldIcon from "/images/DonateGoldIconWhite.svg?url";
|
||||
import donateTroopIcon from "/images/DonateTroopIconWhite.svg?url";
|
||||
import emojiIcon from "/images/EmojiIconWhite.svg?url";
|
||||
import infoIcon from "/images/InfoIcon.svg?url";
|
||||
import swordIcon from "/images/SwordIconWhite.svg?url";
|
||||
import targetIcon from "/images/TargetIconWhite.svg?url";
|
||||
import traitorIcon from "/images/TraitorIconWhite.svg?url";
|
||||
import xIcon from "/images/XIcon.svg?url";
|
||||
|
||||
export interface MenuElementParams {
|
||||
myPlayer: PlayerView;
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators.js";
|
||||
import structureIcon from "../../../../resources/images/CityIconWhite.svg";
|
||||
import cursorPriceIcon from "../../../../resources/images/CursorPriceIconWhite.svg";
|
||||
import darkModeIcon from "../../../../resources/images/DarkModeIconWhite.svg";
|
||||
import emojiIcon from "../../../../resources/images/EmojiIconWhite.svg";
|
||||
import exitIcon from "../../../../resources/images/ExitIconWhite.svg";
|
||||
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 sirenIcon from "../../../../resources/images/SirenIconWhite.svg";
|
||||
import treeIcon from "../../../../resources/images/TreeIconWhite.svg";
|
||||
import musicIcon from "../../../../resources/images/music.svg";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { UserSettings } from "../../../core/game/UserSettings";
|
||||
import { AlternateViewEvent, RefreshGraphicsEvent } from "../../InputHandler";
|
||||
@@ -19,6 +7,18 @@ import { PauseGameIntentEvent } from "../../Transport";
|
||||
import { translateText } from "../../Utils";
|
||||
import SoundManager from "../../sound/SoundManager";
|
||||
import { Layer } from "./Layer";
|
||||
import structureIcon from "/images/CityIconWhite.svg?url";
|
||||
import cursorPriceIcon from "/images/CursorPriceIconWhite.svg?url";
|
||||
import darkModeIcon from "/images/DarkModeIconWhite.svg?url";
|
||||
import emojiIcon from "/images/EmojiIconWhite.svg?url";
|
||||
import exitIcon from "/images/ExitIconWhite.svg?url";
|
||||
import explosionIcon from "/images/ExplosionIconWhite.svg?url";
|
||||
import mouseIcon from "/images/MouseIconWhite.svg?url";
|
||||
import ninjaIcon from "/images/NinjaIconWhite.svg?url";
|
||||
import settingsIcon from "/images/SettingIconWhite.svg?url";
|
||||
import sirenIcon from "/images/SirenIconWhite.svg?url";
|
||||
import treeIcon from "/images/TreeIconWhite.svg?url";
|
||||
import musicIcon from "/images/music.svg?url";
|
||||
|
||||
export class ShowSettingsModalEvent {
|
||||
constructor(
|
||||
|
||||
@@ -3,13 +3,12 @@ import { Theme } from "../../../core/configuration/Config";
|
||||
import { Cell, UnitType } from "../../../core/game/Game";
|
||||
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
|
||||
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 anchorIcon from "/images/AnchorIcon.png?url";
|
||||
import cityIcon from "/images/CityIcon.png?url";
|
||||
import factoryIcon from "/images/FactoryUnit.png?url";
|
||||
import missileSiloIcon from "/images/MissileSiloUnit.png?url";
|
||||
import SAMMissileIcon from "/images/SamLauncherUnit.png?url";
|
||||
import shieldIcon from "/images/ShieldIcon.png?url";
|
||||
|
||||
export const STRUCTURE_SHAPES: Partial<Record<UnitType, ShapeType>> = {
|
||||
[UnitType.City]: "circle",
|
||||
|
||||
@@ -2,7 +2,6 @@ import { extend } from "colord";
|
||||
import a11yPlugin from "colord/plugins/a11y";
|
||||
import { OutlineFilter } from "pixi-filters";
|
||||
import * as PIXI from "pixi.js";
|
||||
import bitmapFont from "../../../../resources/fonts/round_6x6_modified.xml";
|
||||
import { Theme } from "../../../core/configuration/Config";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import {
|
||||
@@ -40,6 +39,7 @@ import {
|
||||
STRUCTURE_SHAPES,
|
||||
ZOOM_THRESHOLD,
|
||||
} from "./StructureDrawingUtils";
|
||||
import bitmapFont from "/fonts/round_6x6_modified.xml?url";
|
||||
|
||||
extend([a11yPlugin]);
|
||||
|
||||
@@ -75,7 +75,8 @@ export class StructureIconsLayer implements Layer {
|
||||
public playerActions: PlayerActions | null = null;
|
||||
private dotsStage: PIXI.Container;
|
||||
private readonly theme: Theme;
|
||||
private renderer: PIXI.Renderer;
|
||||
private renderer: PIXI.Renderer | null = null;
|
||||
private rendererInitialized: boolean = false;
|
||||
private renders: StructureRenderInfo[] = [];
|
||||
private readonly seenUnits: Set<UnitView> = new Set();
|
||||
private readonly mousePos = { x: 0, y: 0 };
|
||||
@@ -113,7 +114,7 @@ export class StructureIconsLayer implements Layer {
|
||||
} catch (error) {
|
||||
console.error("Failed to load bitmap font:", error);
|
||||
}
|
||||
this.renderer = new PIXI.WebGLRenderer();
|
||||
const renderer = new PIXI.WebGLRenderer();
|
||||
this.pixicanvas = document.createElement("canvas");
|
||||
this.pixicanvas.width = window.innerWidth;
|
||||
this.pixicanvas.height = window.innerHeight;
|
||||
@@ -143,7 +144,7 @@ export class StructureIconsLayer implements Layer {
|
||||
this.rootStage.position.set(0, 0);
|
||||
this.rootStage.setSize(this.pixicanvas.width, this.pixicanvas.height);
|
||||
|
||||
await this.renderer.init({
|
||||
await renderer.init({
|
||||
canvas: this.pixicanvas,
|
||||
resolution: 1,
|
||||
width: this.pixicanvas.width,
|
||||
@@ -153,6 +154,9 @@ export class StructureIconsLayer implements Layer {
|
||||
backgroundAlpha: 0,
|
||||
backgroundColor: 0x00000000,
|
||||
});
|
||||
|
||||
this.renderer = renderer;
|
||||
this.rendererInitialized = true;
|
||||
}
|
||||
|
||||
shouldTransform(): boolean {
|
||||
@@ -202,7 +206,7 @@ export class StructureIconsLayer implements Layer {
|
||||
}
|
||||
|
||||
renderLayer(mainContext: CanvasRenderingContext2D) {
|
||||
if (!this.renderer) {
|
||||
if (!this.renderer || !this.rendererInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,16 @@ import { EventBus } from "../../../core/EventBus";
|
||||
import { TransformHandler } from "../TransformHandler";
|
||||
import { Layer } from "./Layer";
|
||||
|
||||
import cityIcon from "../../../../resources/images/buildings/cityAlt1.png";
|
||||
import factoryIcon from "../../../../resources/images/buildings/factoryAlt1.png";
|
||||
import shieldIcon from "../../../../resources/images/buildings/fortAlt3.png";
|
||||
import anchorIcon from "../../../../resources/images/buildings/port1.png";
|
||||
import missileSiloIcon from "../../../../resources/images/buildings/silo1.png";
|
||||
import SAMMissileIcon from "../../../../resources/images/buildings/silo4.png";
|
||||
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 cityIcon from "/images/buildings/cityAlt1.png?url";
|
||||
import factoryIcon from "/images/buildings/factoryAlt1.png?url";
|
||||
import shieldIcon from "/images/buildings/fortAlt3.png?url";
|
||||
import anchorIcon from "/images/buildings/port1.png?url";
|
||||
import missileSiloIcon from "/images/buildings/silo1.png?url";
|
||||
import SAMMissileIcon from "/images/buildings/silo4.png?url";
|
||||
|
||||
const underConstructionColor = colord("rgb(150,150,150)");
|
||||
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { customElement } 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 mirvIcon from "../../../../resources/images/MIRVIcon.svg";
|
||||
import missileSiloIcon from "../../../../resources/images/MissileSiloIconWhite.svg";
|
||||
import hydrogenBombIcon from "../../../../resources/images/MushroomCloudIconWhite.svg";
|
||||
import atomBombIcon from "../../../../resources/images/NukeIconWhite.svg";
|
||||
import portIcon from "../../../../resources/images/PortIcon.svg";
|
||||
import samLauncherIcon from "../../../../resources/images/SamLauncherIconWhite.svg";
|
||||
import defensePostIcon from "../../../../resources/images/ShieldIconWhite.svg";
|
||||
import { EventBus } from "../../../core/EventBus";
|
||||
import { Gold, PlayerActions, UnitType } from "../../../core/game/Game";
|
||||
import { GameView } from "../../../core/game/GameView";
|
||||
@@ -20,6 +10,16 @@ import {
|
||||
import { renderNumber, translateText } from "../../Utils";
|
||||
import { UIState } from "../UIState";
|
||||
import { Layer } from "./Layer";
|
||||
import warshipIcon from "/images/BattleshipIconWhite.svg?url";
|
||||
import cityIcon from "/images/CityIconWhite.svg?url";
|
||||
import factoryIcon from "/images/FactoryIconWhite.svg?url";
|
||||
import mirvIcon from "/images/MIRVIcon.svg?url";
|
||||
import missileSiloIcon from "/images/MissileSiloIconWhite.svg?url";
|
||||
import hydrogenBombIcon from "/images/MushroomCloudIconWhite.svg?url";
|
||||
import atomBombIcon from "/images/NukeIconWhite.svg?url";
|
||||
import portIcon from "/images/PortIcon.svg?url";
|
||||
import samLauncherIcon from "/images/SamLauncherIconWhite.svg?url";
|
||||
import defensePostIcon from "/images/ShieldIconWhite.svg?url";
|
||||
|
||||
@customElement("unit-display")
|
||||
export class UnitDisplay extends LitElement implements Layer {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Howl } from "howler";
|
||||
import of4 from "../../../proprietary/sounds/music/of4.mp3";
|
||||
import openfront from "../../../proprietary/sounds/music/openfront.mp3";
|
||||
import war from "../../../proprietary/sounds/music/war.mp3";
|
||||
import kaChingSound from "../../../resources/sounds/effects/ka-ching.mp3";
|
||||
import kaChingSound from "/sounds/effects/ka-ching.mp3?url";
|
||||
|
||||
export enum SoundEffect {
|
||||
KaChing = "ka-ching",
|
||||
|
||||
+1
-10
@@ -1,16 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import url("./styles/core/flag-animation.css");
|
||||
@import url("./styles/core/variables.css");
|
||||
@import url("./styles/core/typography.css");
|
||||
@import url("./styles/layout/header.css");
|
||||
@import url("./styles/layout/container.css");
|
||||
@import url("./styles/components/button.css");
|
||||
@import url("./styles/components/modal.css");
|
||||
@import url("./styles/modal/chat.css");
|
||||
@import url("./styles/components/setting.css");
|
||||
@import url("./styles/components/controls.css");
|
||||
|
||||
* {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module "*.bin" {
|
||||
const binContent: string;
|
||||
export default binContent;
|
||||
}
|
||||
|
||||
declare module "*.md" {
|
||||
const mdContent: string;
|
||||
export default mdContent;
|
||||
}
|
||||
|
||||
declare module "*.html" {
|
||||
const htmlContent: string;
|
||||
export default htmlContent;
|
||||
}
|
||||
|
||||
declare module "*.xml" {
|
||||
const xmlContent: string;
|
||||
export default xmlContent;
|
||||
}
|
||||
|
||||
declare module "*.webp" {
|
||||
const webpContent: string;
|
||||
export default webpContent;
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import quickChatData from "../../resources/QuickChat.json" with { type: "json" };
|
||||
import countries from "../client/data/countries.json" with { type: "json" };
|
||||
import countries from "../assets/data/countries.json" with { type: "json" };
|
||||
import quickChatData from "../assets/data/QuickChat.json" with { type: "json" };
|
||||
import {
|
||||
ColorPaletteSchema,
|
||||
PatternDataSchema,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { GameConfig } from "../Schemas";
|
||||
import { Config, GameEnv, ServerConfig } from "./Config";
|
||||
import { DefaultConfig } from "./DefaultConfig";
|
||||
import { DevConfig, DevServerConfig } from "./DevConfig";
|
||||
import { Env } from "./Env";
|
||||
import { preprodConfig } from "./PreprodConfig";
|
||||
import { prodConfig } from "./ProdConfig";
|
||||
|
||||
@@ -22,7 +23,7 @@ export async function getConfig(
|
||||
console.log("using prod config");
|
||||
return new DefaultConfig(sc, gameConfig, userSettings, isReplay);
|
||||
default:
|
||||
throw Error(`unsupported server configuration: ${process.env.GAME_ENV}`);
|
||||
throw Error(`unsupported server configuration: ${Env.GAME_ENV}`);
|
||||
}
|
||||
}
|
||||
export async function getServerConfigFromClient(): Promise<ServerConfig> {
|
||||
@@ -44,7 +45,7 @@ export async function getServerConfigFromClient(): Promise<ServerConfig> {
|
||||
return cachedSC;
|
||||
}
|
||||
export function getServerConfigFromServer(): ServerConfig {
|
||||
const gameEnv = process.env.GAME_ENV ?? "dev";
|
||||
const gameEnv = Env.GAME_ENV;
|
||||
return getServerConfig(gameEnv);
|
||||
}
|
||||
export function getServerConfig(gameEnv: string) {
|
||||
|
||||
@@ -27,6 +27,7 @@ import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
|
||||
import { NukeType } from "../StatsSchemas";
|
||||
import { assertNever, sigmoid, simpleHash, within } from "../Util";
|
||||
import { Config, GameEnv, NukeMagnitude, ServerConfig, Theme } from "./Config";
|
||||
import { Env } from "./Env";
|
||||
import { PastelTheme } from "./PastelTheme";
|
||||
import { PastelThemeDark } from "./PastelThemeDark";
|
||||
|
||||
@@ -88,20 +89,20 @@ const numPlayersConfig = {
|
||||
|
||||
export abstract class DefaultServerConfig implements ServerConfig {
|
||||
turnstileSecretKey(): string {
|
||||
return process.env.TURNSTILE_SECRET_KEY ?? "";
|
||||
return Env.TURNSTILE_SECRET_KEY ?? "";
|
||||
}
|
||||
abstract turnstileSiteKey(): string;
|
||||
allowedFlares(): string[] | undefined {
|
||||
return;
|
||||
}
|
||||
stripePublishableKey(): string {
|
||||
return process.env.STRIPE_PUBLISHABLE_KEY ?? "";
|
||||
return Env.STRIPE_PUBLISHABLE_KEY ?? "";
|
||||
}
|
||||
domain(): string {
|
||||
return process.env.DOMAIN ?? "";
|
||||
return Env.DOMAIN ?? "";
|
||||
}
|
||||
subdomain(): string {
|
||||
return process.env.SUBDOMAIN ?? "";
|
||||
return Env.SUBDOMAIN ?? "";
|
||||
}
|
||||
|
||||
private publicKey: JWK;
|
||||
@@ -134,24 +135,24 @@ export abstract class DefaultServerConfig implements ServerConfig {
|
||||
);
|
||||
}
|
||||
otelEndpoint(): string {
|
||||
return process.env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "";
|
||||
return Env.OTEL_EXPORTER_OTLP_ENDPOINT ?? "";
|
||||
}
|
||||
otelAuthHeader(): string {
|
||||
return process.env.OTEL_AUTH_HEADER ?? "";
|
||||
return Env.OTEL_AUTH_HEADER ?? "";
|
||||
}
|
||||
gitCommit(): string {
|
||||
return process.env.GIT_COMMIT ?? "";
|
||||
return Env.GIT_COMMIT ?? "";
|
||||
}
|
||||
|
||||
apiKey(): string {
|
||||
return process.env.API_KEY ?? "";
|
||||
return Env.API_KEY ?? "";
|
||||
}
|
||||
|
||||
adminHeader(): string {
|
||||
return "x-admin-key";
|
||||
}
|
||||
adminToken(): string {
|
||||
const token = process.env.ADMIN_TOKEN;
|
||||
const token = Env.ADMIN_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error("ADMIN_TOKEN not set");
|
||||
}
|
||||
@@ -225,7 +226,7 @@ export class DefaultConfig implements Config {
|
||||
) {}
|
||||
|
||||
stripePublishableKey(): string {
|
||||
return process.env.STRIPE_PUBLISHABLE_KEY ?? "";
|
||||
return Env.STRIPE_PUBLISHABLE_KEY ?? "";
|
||||
}
|
||||
|
||||
isReplay(): boolean {
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Safely access environment variables in both Node.js and Vite environments.
|
||||
* - In Vite (Browser), it uses `import.meta.env`.
|
||||
* - In Node.js (Server), it uses `process.env`.
|
||||
*/
|
||||
|
||||
declare global {
|
||||
interface ImportMetaEnv {
|
||||
[key: string]: string | boolean | undefined;
|
||||
}
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
}
|
||||
|
||||
function getEnv(key: string, viteKey?: string): string | undefined {
|
||||
const vKey = viteKey ?? key;
|
||||
|
||||
// Try import.meta.env (Vite/Browser)
|
||||
// We use a try-catch block or check existence to avoid ReferenceErrors
|
||||
try {
|
||||
if (typeof import.meta !== "undefined" && import.meta.env) {
|
||||
const val = import.meta.env[vKey] ?? import.meta.env[key];
|
||||
if (val !== undefined) {
|
||||
return String(val);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors accessing import.meta
|
||||
}
|
||||
|
||||
// Try process.env (Node.js)
|
||||
try {
|
||||
if (typeof process !== "undefined" && process.env) {
|
||||
const val = process.env[key];
|
||||
if (val !== undefined) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors accessing process
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export const Env = {
|
||||
get GAME_ENV(): string {
|
||||
// Check MODE for Vite, GAME_ENV for Node
|
||||
try {
|
||||
if (
|
||||
typeof import.meta !== "undefined" &&
|
||||
import.meta.env &&
|
||||
import.meta.env.MODE
|
||||
) {
|
||||
return import.meta.env.MODE;
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors accessing import.meta
|
||||
}
|
||||
|
||||
return getEnv("GAME_ENV") ?? "dev";
|
||||
},
|
||||
|
||||
get TURNSTILE_SECRET_KEY() {
|
||||
return getEnv("TURNSTILE_SECRET_KEY");
|
||||
},
|
||||
get STRIPE_PUBLISHABLE_KEY() {
|
||||
return getEnv("STRIPE_PUBLISHABLE_KEY");
|
||||
},
|
||||
get DOMAIN() {
|
||||
return getEnv("DOMAIN");
|
||||
},
|
||||
get SUBDOMAIN() {
|
||||
return getEnv("SUBDOMAIN");
|
||||
},
|
||||
get OTEL_EXPORTER_OTLP_ENDPOINT() {
|
||||
return getEnv("OTEL_EXPORTER_OTLP_ENDPOINT");
|
||||
},
|
||||
get OTEL_AUTH_HEADER() {
|
||||
return getEnv("OTEL_AUTH_HEADER");
|
||||
},
|
||||
get GIT_COMMIT() {
|
||||
return getEnv("GIT_COMMIT");
|
||||
},
|
||||
get API_KEY() {
|
||||
return getEnv("API_KEY");
|
||||
},
|
||||
get ADMIN_TOKEN() {
|
||||
return getEnv("ADMIN_TOKEN");
|
||||
},
|
||||
};
|
||||
@@ -2,14 +2,6 @@ import { GameMapType } from "./Game";
|
||||
import { GameMapLoader, MapData } from "./GameMapLoader";
|
||||
import { MapManifest } from "./TerrainMapLoader";
|
||||
|
||||
export interface BinModule {
|
||||
default: string;
|
||||
}
|
||||
|
||||
interface NationMapModule {
|
||||
default: MapManifest;
|
||||
}
|
||||
|
||||
export class BinaryLoaderGameMapLoader implements GameMapLoader {
|
||||
private maps: Map<GameMapType, MapData>;
|
||||
|
||||
@@ -36,59 +28,38 @@ export class BinaryLoaderGameMapLoader implements GameMapLoader {
|
||||
);
|
||||
const fileName = key?.toLowerCase();
|
||||
|
||||
const loadBinary = (url: string) =>
|
||||
fetch(url)
|
||||
.then((res) => {
|
||||
if (!res.ok) throw new Error(`Failed to load ${url}`);
|
||||
return res.arrayBuffer();
|
||||
})
|
||||
.then((buf) => new Uint8Array(buf));
|
||||
|
||||
const mapBasePath = `/maps/${fileName}`;
|
||||
|
||||
const mapData = {
|
||||
mapBin: this.createLazyLoader(() =>
|
||||
(
|
||||
import(
|
||||
`!!binary-loader!../../../resources/maps/${fileName}/map.bin`
|
||||
) as Promise<BinModule>
|
||||
).then((m) => this.toUInt8Array(m.default)),
|
||||
),
|
||||
mapBin: this.createLazyLoader(() => loadBinary(`${mapBasePath}/map.bin`)),
|
||||
map4xBin: this.createLazyLoader(() =>
|
||||
(
|
||||
import(
|
||||
`!!binary-loader!../../../resources/maps/${fileName}/map4x.bin`
|
||||
) as Promise<BinModule>
|
||||
).then((m) => this.toUInt8Array(m.default)),
|
||||
loadBinary(`${mapBasePath}/map4x.bin`),
|
||||
),
|
||||
map16xBin: this.createLazyLoader(() =>
|
||||
(
|
||||
import(
|
||||
`!!binary-loader!../../../resources/maps/${fileName}/map16x.bin`
|
||||
) as Promise<BinModule>
|
||||
).then((m) => this.toUInt8Array(m.default)),
|
||||
loadBinary(`${mapBasePath}/map16x.bin`),
|
||||
),
|
||||
manifest: this.createLazyLoader(() =>
|
||||
(
|
||||
import(
|
||||
`../../../resources/maps/${fileName}/manifest.json`
|
||||
) as Promise<NationMapModule>
|
||||
).then((m) => m.default),
|
||||
fetch(`${mapBasePath}/manifest.json`).then((res) => {
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to load ${mapBasePath}/manifest.json`);
|
||||
}
|
||||
return res.json() as Promise<MapManifest>;
|
||||
}),
|
||||
),
|
||||
webpPath: this.createLazyLoader(() =>
|
||||
(
|
||||
import(
|
||||
`../../../resources/maps/${fileName}/thumbnail.webp`
|
||||
) as Promise<{ default: string }>
|
||||
).then((m) => m.default),
|
||||
Promise.resolve(`${mapBasePath}/thumbnail.webp`),
|
||||
),
|
||||
} satisfies MapData;
|
||||
|
||||
this.maps.set(map, mapData);
|
||||
return mapData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given string into a UInt8Array where each character in the string
|
||||
* is represented as an 8-bit unsigned integer.
|
||||
*/
|
||||
private toUInt8Array(data: string) {
|
||||
const rawData = new Uint8Array(data.length);
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
rawData[i] = data.charCodeAt(i);
|
||||
}
|
||||
|
||||
return rawData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,9 @@ export class FetchGameMapLoader implements GameMapLoader {
|
||||
let url = `${this.prefix}/${map}/${path}`;
|
||||
|
||||
if (this.cacheBuster) {
|
||||
url += `${url.includes("?") ? "&" : "?"}v=${this.cacheBuster}`;
|
||||
url += `${url.includes("?") ? "&" : "?"}v=${encodeURIComponent(
|
||||
this.cacheBuster.trim(),
|
||||
)}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import version from "../../../resources/version.txt";
|
||||
import version from "../../assets/data/version.txt?raw";
|
||||
import { createGameRunner, GameRunner } from "../GameRunner";
|
||||
import { FetchGameMapLoader } from "../game/FetchGameMapLoader";
|
||||
import { ErrorUpdate, GameUpdateViewData } from "../game/GameUpdates";
|
||||
|
||||
@@ -23,7 +23,9 @@ export class WorkerClient {
|
||||
private gameStartInfo: GameStartInfo,
|
||||
private clientID: ClientID,
|
||||
) {
|
||||
this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url));
|
||||
this.worker = new Worker(new URL("./Worker.worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
this.messageHandlers = new Map();
|
||||
|
||||
// Set up global message handler
|
||||
|
||||
Vendored
-47
@@ -1,47 +0,0 @@
|
||||
declare module "*.png" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
declare module "*.jpg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.webp" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.jpeg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.svg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.bin" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.md" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.txt" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.html" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
declare module "*.xml" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.mp3" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
Regular → Executable
+1
-1
@@ -1,7 +1,7 @@
|
||||
// tailwind.config.js
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./src/**/*.{html,ts,js}"],
|
||||
content: ["./index.html", "./src/**/*.{html,ts,js}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
|
||||
@@ -35,9 +35,9 @@ describe("AllianceExtensionExecution", () => {
|
||||
});
|
||||
|
||||
test("Successfully extends existing alliance between Humans", () => {
|
||||
jest.spyOn(player1, "canSendAllianceRequest").mockReturnValue(true);
|
||||
jest.spyOn(player2, "isAlive").mockReturnValue(true);
|
||||
jest.spyOn(player1, "isAlive").mockReturnValue(true);
|
||||
vi.spyOn(player1, "canSendAllianceRequest").mockReturnValue(true);
|
||||
vi.spyOn(player2, "isAlive").mockReturnValue(true);
|
||||
vi.spyOn(player1, "isAlive").mockReturnValue(true);
|
||||
|
||||
game.addExecution(new AllianceRequestExecution(player1, player2.id()));
|
||||
game.executeNextTick();
|
||||
@@ -53,7 +53,7 @@ describe("AllianceExtensionExecution", () => {
|
||||
expect(player2.allianceWith(player1)).toBeTruthy();
|
||||
|
||||
const allianceBefore = player1.allianceWith(player2)!;
|
||||
const allianceSpy = jest.spyOn(allianceBefore, "extend");
|
||||
const allianceSpy = vi.spyOn(allianceBefore, "extend");
|
||||
|
||||
const expirationBefore = allianceBefore.expiresAt();
|
||||
|
||||
@@ -82,9 +82,9 @@ describe("AllianceExtensionExecution", () => {
|
||||
});
|
||||
|
||||
test("Successfully extends existing alliance between Human and non-Human", () => {
|
||||
jest.spyOn(player1, "canSendAllianceRequest").mockReturnValue(true);
|
||||
jest.spyOn(player3, "isAlive").mockReturnValue(true);
|
||||
jest.spyOn(player1, "isAlive").mockReturnValue(true);
|
||||
vi.spyOn(player1, "canSendAllianceRequest").mockReturnValue(true);
|
||||
vi.spyOn(player3, "isAlive").mockReturnValue(true);
|
||||
vi.spyOn(player1, "isAlive").mockReturnValue(true);
|
||||
|
||||
game.addExecution(new AllianceRequestExecution(player1, player3.id()));
|
||||
game.executeNextTick();
|
||||
@@ -100,7 +100,7 @@ describe("AllianceExtensionExecution", () => {
|
||||
expect(player3.allianceWith(player1)).toBeTruthy();
|
||||
|
||||
const allianceBefore = player1.allianceWith(player3)!;
|
||||
const allianceSpy = jest.spyOn(allianceBefore, "extend");
|
||||
const allianceSpy = vi.spyOn(allianceBefore, "extend");
|
||||
const expirationBefore = allianceBefore.expiresAt();
|
||||
|
||||
game.addExecution(new AllianceExtensionExecution(player1, player3.id()));
|
||||
@@ -120,9 +120,9 @@ describe("AllianceExtensionExecution", () => {
|
||||
});
|
||||
|
||||
test("Sends message to other player when one player requests renewal", () => {
|
||||
jest.spyOn(player1, "canSendAllianceRequest").mockReturnValue(true);
|
||||
jest.spyOn(player2, "isAlive").mockReturnValue(true);
|
||||
jest.spyOn(player1, "isAlive").mockReturnValue(true);
|
||||
vi.spyOn(player1, "canSendAllianceRequest").mockReturnValue(true);
|
||||
vi.spyOn(player2, "isAlive").mockReturnValue(true);
|
||||
vi.spyOn(player1, "isAlive").mockReturnValue(true);
|
||||
|
||||
// Create alliance between player1 and player2
|
||||
game.addExecution(new AllianceRequestExecution(player1, player2.id()));
|
||||
@@ -139,7 +139,7 @@ describe("AllianceExtensionExecution", () => {
|
||||
expect(player2.allianceWith(player1)).toBeTruthy();
|
||||
|
||||
// Spy on displayMessage to verify it's called
|
||||
const displayMessageSpy = jest.spyOn(game, "displayMessage");
|
||||
const displayMessageSpy = vi.spyOn(game, "displayMessage");
|
||||
|
||||
// Player1 requests renewal
|
||||
game.addExecution(new AllianceExtensionExecution(player1, player2.id()));
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { AutoUpgradeEvent } from "../src/client/InputHandler";
|
||||
import { EventBus } from "../src/core/EventBus";
|
||||
|
||||
@@ -19,7 +16,7 @@ describe("AutoUpgrade Feature", () => {
|
||||
});
|
||||
|
||||
test("should emit AutoUpgradeEvent when created", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const event = new AutoUpgradeEvent(150, 250);
|
||||
eventBus.emit(event);
|
||||
@@ -36,7 +33,7 @@ describe("AutoUpgrade Feature", () => {
|
||||
|
||||
describe("AutoUpgradeEvent Integration", () => {
|
||||
test("should handle multiple AutoUpgradeEvents", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const event1 = new AutoUpgradeEvent(100, 200);
|
||||
const event2 = new AutoUpgradeEvent(300, 400);
|
||||
@@ -70,7 +67,7 @@ describe("AutoUpgrade Feature", () => {
|
||||
|
||||
describe("AutoUpgradeEvent Event Bus Integration", () => {
|
||||
test("should allow event listeners to subscribe to AutoUpgradeEvent", () => {
|
||||
const mockListener = jest.fn();
|
||||
const mockListener = vi.fn();
|
||||
const event = new AutoUpgradeEvent(100, 200);
|
||||
|
||||
eventBus.on(AutoUpgradeEvent, mockListener);
|
||||
@@ -80,8 +77,8 @@ describe("AutoUpgrade Feature", () => {
|
||||
});
|
||||
|
||||
test("should allow multiple listeners for AutoUpgradeEvent", () => {
|
||||
const mockListener1 = jest.fn();
|
||||
const mockListener2 = jest.fn();
|
||||
const mockListener1 = vi.fn();
|
||||
const mockListener2 = vi.fn();
|
||||
const event = new AutoUpgradeEvent(100, 200);
|
||||
|
||||
eventBus.on(AutoUpgradeEvent, mockListener1);
|
||||
@@ -93,7 +90,7 @@ describe("AutoUpgrade Feature", () => {
|
||||
});
|
||||
|
||||
test("should not call unsubscribed listeners", () => {
|
||||
const mockListener = jest.fn();
|
||||
const mockListener = vi.fn();
|
||||
const event = new AutoUpgradeEvent(100, 200);
|
||||
|
||||
eventBus.on(AutoUpgradeEvent, mockListener);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Mocking the obscenity library to control its behavior in tests.
|
||||
jest.mock("obscenity", () => {
|
||||
vi.mock("obscenity", () => {
|
||||
return {
|
||||
RegExpMatcher: class {
|
||||
private dummy: string[] = ["foo", "bar", "leet", "code"];
|
||||
@@ -26,7 +26,7 @@ jest.mock("obscenity", () => {
|
||||
});
|
||||
|
||||
// Mocks the output of translation functions to return predictable values.
|
||||
jest.mock("../src/client/Utils", () => ({
|
||||
vi.mock("../src/client/Utils", () => ({
|
||||
translateText: (key: string, vars?: any) =>
|
||||
vars ? `${key}:${JSON.stringify(vars)}` : key,
|
||||
}));
|
||||
|
||||
@@ -112,7 +112,7 @@ describe("DeleteUnitExecution Security Tests", () => {
|
||||
});
|
||||
|
||||
it("should prevent deleting units during spawn phase", () => {
|
||||
jest.spyOn(game, "inSpawnPhase").mockReturnValue(true);
|
||||
vi.spyOn(game, "inSpawnPhase").mockReturnValue(true);
|
||||
|
||||
const execution = new DeleteUnitExecution(player, unit.id());
|
||||
execution.init(game, 0);
|
||||
@@ -122,7 +122,7 @@ describe("DeleteUnitExecution Security Tests", () => {
|
||||
});
|
||||
|
||||
it("should allow deleting units when all conditions are met", () => {
|
||||
jest.spyOn(game, "inSpawnPhase").mockReturnValue(false);
|
||||
vi.spyOn(game, "inSpawnPhase").mockReturnValue(false);
|
||||
|
||||
const execution = new DeleteUnitExecution(player, unit.id());
|
||||
execution.init(game, 0);
|
||||
@@ -131,7 +131,7 @@ describe("DeleteUnitExecution Security Tests", () => {
|
||||
});
|
||||
|
||||
it("should delete after deletion delay", () => {
|
||||
jest.spyOn(game, "inSpawnPhase").mockReturnValue(false);
|
||||
vi.spyOn(game, "inSpawnPhase").mockReturnValue(false);
|
||||
|
||||
const execution = new DeleteUnitExecution(player, unit.id());
|
||||
game.addExecution(execution);
|
||||
@@ -144,7 +144,7 @@ describe("DeleteUnitExecution Security Tests", () => {
|
||||
});
|
||||
|
||||
it("should reset deletion if captured", () => {
|
||||
jest.spyOn(game, "inSpawnPhase").mockReturnValue(false);
|
||||
vi.spyOn(game, "inSpawnPhase").mockReturnValue(false);
|
||||
|
||||
const execution = new DeleteUnitExecution(player, unit.id());
|
||||
game.addExecution(execution);
|
||||
|
||||
+19
-22
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { AutoUpgradeEvent, InputHandler } from "../src/client/InputHandler";
|
||||
import { EventBus } from "../src/core/EventBus";
|
||||
|
||||
@@ -18,7 +15,7 @@ class MockPointerEvent {
|
||||
this.clientX = init.clientX;
|
||||
this.clientY = init.clientY;
|
||||
this.pointerId = init.pointerId;
|
||||
this.preventDefault = jest.fn();
|
||||
this.preventDefault = vi.fn();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +42,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
|
||||
describe("Middle Mouse Button Handling", () => {
|
||||
test("should emit AutoUpgradeEvent on middle mouse button press", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -65,7 +62,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should emit MouseDownEvent on left mouse button press instead of AutoUpgradeEvent", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 0,
|
||||
@@ -89,7 +86,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should not emit AutoUpgradeEvent on right mouse button press", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 2,
|
||||
@@ -109,7 +106,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle multiple middle mouse button presses", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent1 = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -145,7 +142,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle middle mouse button press with zero coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -165,7 +162,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle middle mouse button press with negative coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -185,7 +182,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle middle mouse button press with decimal coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -207,7 +204,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
|
||||
describe("Pointer Event Handling", () => {
|
||||
test("should handle pointer events with different pointer IDs", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent1 = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -229,7 +226,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle pointer events with same pointer ID", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent1 = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -253,7 +250,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
test("should handle very large coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -273,7 +270,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle very small coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -293,7 +290,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle NaN coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -313,7 +310,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should handle Infinity coordinates", () => {
|
||||
const mockEmit = jest.spyOn(eventBus, "emit");
|
||||
const mockEmit = vi.spyOn(eventBus, "emit");
|
||||
|
||||
const pointerEvent = new PointerEvent("pointerdown", {
|
||||
button: 1,
|
||||
@@ -335,7 +332,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
|
||||
describe("Integration with Event Bus", () => {
|
||||
test("should allow event listeners to receive AutoUpgradeEvents", () => {
|
||||
const mockListener = jest.fn();
|
||||
const mockListener = vi.fn();
|
||||
|
||||
eventBus.on(AutoUpgradeEvent, mockListener);
|
||||
|
||||
@@ -356,8 +353,8 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should allow multiple listeners for AutoUpgradeEvent", () => {
|
||||
const mockListener1 = jest.fn();
|
||||
const mockListener2 = jest.fn();
|
||||
const mockListener1 = vi.fn();
|
||||
const mockListener2 = vi.fn();
|
||||
|
||||
eventBus.on(AutoUpgradeEvent, mockListener1);
|
||||
eventBus.on(AutoUpgradeEvent, mockListener2);
|
||||
@@ -385,7 +382,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("should not call unsubscribed listeners", () => {
|
||||
const mockListener = jest.fn();
|
||||
const mockListener = vi.fn();
|
||||
|
||||
eventBus.on(AutoUpgradeEvent, mockListener);
|
||||
eventBus.off(AutoUpgradeEvent, mockListener);
|
||||
@@ -444,7 +441,7 @@ describe("InputHandler AutoUpgrade", () => {
|
||||
});
|
||||
|
||||
test("handles invalid JSON gracefully and warns", () => {
|
||||
const spy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const spy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
localStorage.setItem("settings.keybinds", "not a json");
|
||||
|
||||
inputHandler.initialize();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { vi, type MockInstance } from "vitest";
|
||||
import { getMessageTypeClasses, severityColors } from "../src/client/Utils";
|
||||
import { MessageType } from "../src/core/game/Game";
|
||||
|
||||
describe("getMessageTypeClasses", () => {
|
||||
// Spy on console.warn to track when the default case is hit
|
||||
let consoleSpy: jest.SpyInstance;
|
||||
let consoleSpy: MockInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
consoleSpy = jest.spyOn(console, "warn").mockImplementation(() => {});
|
||||
consoleSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -77,19 +77,17 @@ describe("AllianceBehavior.handleAllianceRequests", () => {
|
||||
}
|
||||
});
|
||||
|
||||
jest.spyOn(player, "alliances").mockReturnValue(new Array(alliancesCount));
|
||||
vi.spyOn(player, "alliances").mockReturnValue(new Array(alliancesCount));
|
||||
|
||||
const mockRequest = {
|
||||
requestor: () => requestor,
|
||||
recipient: () => player,
|
||||
createdAt: () => 0 as unknown as Tick,
|
||||
accept: jest.fn(),
|
||||
reject: jest.fn(),
|
||||
accept: vi.fn(),
|
||||
reject: vi.fn(),
|
||||
} as unknown as AllianceRequest;
|
||||
|
||||
jest
|
||||
.spyOn(player, "incomingAllianceRequests")
|
||||
.mockReturnValue([mockRequest]);
|
||||
vi.spyOn(player, "incomingAllianceRequests").mockReturnValue([mockRequest]);
|
||||
|
||||
return mockRequest;
|
||||
}
|
||||
@@ -151,19 +149,19 @@ describe("AllianceBehavior.handleAllianceExtensionRequests", () => {
|
||||
let allianceBehavior: NationAllianceBehavior;
|
||||
|
||||
beforeEach(() => {
|
||||
mockGame = { addExecution: jest.fn() };
|
||||
mockHuman = { id: jest.fn(() => "human_id") };
|
||||
mockGame = { addExecution: vi.fn() };
|
||||
mockHuman = { id: vi.fn(() => "human_id") };
|
||||
mockAlliance = {
|
||||
onlyOneAgreedToExtend: jest.fn(() => true),
|
||||
other: jest.fn(() => mockHuman),
|
||||
onlyOneAgreedToExtend: vi.fn(() => true),
|
||||
other: vi.fn(() => mockHuman),
|
||||
};
|
||||
mockRandom = { chance: jest.fn() };
|
||||
mockRandom = { chance: vi.fn() };
|
||||
|
||||
mockPlayer = {
|
||||
alliances: jest.fn(() => [mockAlliance]),
|
||||
relation: jest.fn(),
|
||||
id: jest.fn(() => "bot_id"),
|
||||
type: jest.fn(() => PlayerType.Nation),
|
||||
alliances: vi.fn(() => [mockAlliance]),
|
||||
relation: vi.fn(),
|
||||
id: vi.fn(() => "bot_id"),
|
||||
type: vi.fn(() => PlayerType.Nation),
|
||||
};
|
||||
|
||||
allianceBehavior = new NationAllianceBehavior(
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { FluentSlider } from "../../../src/client/components/FluentSlider";
|
||||
|
||||
// Mock the translateText function
|
||||
jest.mock("../../../src/client/Utils", () => ({
|
||||
translateText: jest.fn((key: string) => key),
|
||||
vi.mock("../../../src/client/Utils", () => ({
|
||||
translateText: vi.fn((key: string) => key),
|
||||
}));
|
||||
|
||||
describe("FluentSlider", () => {
|
||||
@@ -84,7 +81,7 @@ describe("FluentSlider", () => {
|
||||
|
||||
describe("Value-Changed Event - CRITICAL FOR BUG FIX", () => {
|
||||
it("should dispatch CustomEvent with detail.value (not event.target.value)", async () => {
|
||||
const eventSpy = jest.fn();
|
||||
const eventSpy = vi.fn();
|
||||
slider.addEventListener("value-changed", eventSpy);
|
||||
|
||||
const rangeInput = slider.shadowRoot?.querySelector(
|
||||
@@ -107,7 +104,7 @@ describe("FluentSlider", () => {
|
||||
});
|
||||
|
||||
it("should not dispatch event on input, only on change", async () => {
|
||||
const eventSpy = jest.fn();
|
||||
const eventSpy = vi.fn();
|
||||
slider.addEventListener("value-changed", eventSpy);
|
||||
|
||||
const rangeInput = slider.shadowRoot?.querySelector(
|
||||
@@ -128,7 +125,7 @@ describe("FluentSlider", () => {
|
||||
|
||||
it("should work with the handler pattern used in HostLobbyModal", async () => {
|
||||
// This simulates the actual handler code in HostLobbyModal.ts:656-660
|
||||
const mockHandler = jest.fn((e: Event) => {
|
||||
const mockHandler = vi.fn((e: Event) => {
|
||||
const customEvent = e as CustomEvent<{ value: number }>;
|
||||
const value = customEvent.detail.value;
|
||||
if (isNaN(value) || value < 0 || value > 400) {
|
||||
@@ -154,7 +151,7 @@ describe("FluentSlider", () => {
|
||||
|
||||
it("should work with the handler pattern used in SinglePlayerModal", async () => {
|
||||
// This simulates the actual handler code in SinglePlayerModal.ts:444-451
|
||||
const mockHandler = jest.fn((e: Event) => {
|
||||
const mockHandler = vi.fn((e: Event) => {
|
||||
const customEvent = e as CustomEvent<{ value: number }>;
|
||||
const value = customEvent.detail.value;
|
||||
if (isNaN(value) || value < 0 || value > 400) {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { ProgressBar } from "../../../src/client/graphics/ProgressBar";
|
||||
|
||||
describe("ProgressBar", () => {
|
||||
@@ -15,9 +12,9 @@ describe("ProgressBar", () => {
|
||||
});
|
||||
|
||||
it("should initialize and draw the background", () => {
|
||||
const spyClearRect = jest.spyOn(ctx, "clearRect");
|
||||
const spyFillRect = jest.spyOn(ctx, "fillRect");
|
||||
const spyFillStyle = jest.spyOn(ctx, "fillStyle", "set");
|
||||
const spyClearRect = vi.spyOn(ctx, "clearRect");
|
||||
const spyFillRect = vi.spyOn(ctx, "fillRect");
|
||||
const spyFillStyle = vi.spyOn(ctx, "fillStyle", "set");
|
||||
const bar = new ProgressBar(["#ff0000", "#00ff00"], ctx, 2, 2, 80, 10, 0.5);
|
||||
expect(spyClearRect).toHaveBeenCalledWith(0, 0, 82, 12);
|
||||
expect(spyFillRect).toHaveBeenCalledWith(1, 1, 80, 10);
|
||||
@@ -28,7 +25,7 @@ describe("ProgressBar", () => {
|
||||
|
||||
it("should set progress and draw the progress bar", () => {
|
||||
const bar = new ProgressBar(["#ff0000", "#00ff00"], ctx, 2, 2, 80, 10);
|
||||
const spyFillRect = jest.spyOn(ctx, "fillRect");
|
||||
const spyFillRect = vi.spyOn(ctx, "fillRect");
|
||||
bar.setProgress(0.5);
|
||||
expect(bar.getProgress()).toBe(0.5);
|
||||
expect(spyFillRect).toHaveBeenCalledWith(
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { vi, type Mock } from "vitest";
|
||||
import {
|
||||
attackMenuElement,
|
||||
buildMenuElement,
|
||||
@@ -13,13 +11,15 @@ import { UnitType } from "../../../src/core/game/Game";
|
||||
import { TileRef } from "../../../src/core/game/GameMap";
|
||||
import { GameView, PlayerView } from "../../../src/core/game/GameView";
|
||||
|
||||
jest.mock("../../../src/client/Utils", () => ({
|
||||
translateText: jest.fn((key: string) => key),
|
||||
renderNumber: jest.fn((num: number) => num.toString()),
|
||||
vi.mock("../../../src/client/Utils", () => ({
|
||||
translateText: vi.fn((key: string) => key),
|
||||
renderNumber: vi.fn((num: number) => num.toString()),
|
||||
}));
|
||||
|
||||
jest.mock("../../../src/client/graphics/layers/BuildMenu", () => {
|
||||
const { UnitType } = jest.requireActual("../../../src/core/game/Game");
|
||||
vi.mock("../../../src/client/graphics/layers/BuildMenu", async () => {
|
||||
const { UnitType } = await vi.importActual<
|
||||
typeof import("../../../src/core/game/Game")
|
||||
>("../../../src/core/game/Game");
|
||||
return {
|
||||
flattenedBuildTable: [
|
||||
{
|
||||
@@ -68,14 +68,14 @@ jest.mock("../../../src/client/graphics/layers/BuildMenu", () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock("nanoid", () => ({
|
||||
customAlphabet: jest.fn(() => jest.fn(() => "mock-id")),
|
||||
vi.mock("nanoid", () => ({
|
||||
customAlphabet: vi.fn(() => vi.fn(() => "mock-id")),
|
||||
}));
|
||||
|
||||
jest.mock("dompurify", () => ({
|
||||
vi.mock("dompurify", () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
sanitize: jest.fn((str: string) => str),
|
||||
sanitize: vi.fn((str: string) => str),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -90,29 +90,29 @@ describe("RadialMenuElements", () => {
|
||||
beforeEach(() => {
|
||||
mockPlayer = {
|
||||
id: () => 1,
|
||||
isAlliedWith: jest.fn(() => false),
|
||||
isPlayer: jest.fn(() => true),
|
||||
isAlliedWith: vi.fn(() => false),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
|
||||
mockGame = {
|
||||
inSpawnPhase: jest.fn(() => false),
|
||||
owner: jest.fn(() => mockPlayer),
|
||||
isLand: jest.fn(() => true),
|
||||
config: jest.fn(() => ({
|
||||
inSpawnPhase: vi.fn(() => false),
|
||||
owner: vi.fn(() => mockPlayer),
|
||||
isLand: vi.fn(() => true),
|
||||
config: vi.fn(() => ({
|
||||
theme: () => ({
|
||||
territoryColor: () => ({
|
||||
lighten: () => ({ alpha: () => ({ toRgbString: () => "#fff" }) }),
|
||||
}),
|
||||
}),
|
||||
isUnitDisabled: jest.fn(() => false),
|
||||
isUnitDisabled: vi.fn(() => false),
|
||||
})),
|
||||
} as unknown as GameView;
|
||||
|
||||
mockBuildMenu = {
|
||||
canBuildOrUpgrade: jest.fn(() => true),
|
||||
cost: jest.fn(() => 100),
|
||||
count: jest.fn(() => 5),
|
||||
sendBuildOrUpgrade: jest.fn(),
|
||||
canBuildOrUpgrade: vi.fn(() => true),
|
||||
cost: vi.fn(() => 100),
|
||||
count: vi.fn(() => 5),
|
||||
sendBuildOrUpgrade: vi.fn(),
|
||||
};
|
||||
|
||||
mockPlayerActions = {
|
||||
@@ -148,7 +148,7 @@ describe("RadialMenuElements", () => {
|
||||
playerPanel: {} as any,
|
||||
chatIntegration: {} as any,
|
||||
eventBus: {} as any,
|
||||
closeMenu: jest.fn(),
|
||||
closeMenu: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -161,19 +161,19 @@ describe("RadialMenuElements", () => {
|
||||
});
|
||||
|
||||
it("should be disabled during spawn phase", () => {
|
||||
mockGame.inSpawnPhase = jest.fn(() => true);
|
||||
mockGame.inSpawnPhase = vi.fn(() => true);
|
||||
expect(attackMenuElement.disabled(mockParams)).toBe(true);
|
||||
});
|
||||
|
||||
it("should be enabled when not in spawn phase", () => {
|
||||
mockGame.inSpawnPhase = jest.fn(() => false);
|
||||
mockGame.inSpawnPhase = vi.fn(() => false);
|
||||
expect(attackMenuElement.disabled(mockParams)).toBe(false);
|
||||
});
|
||||
|
||||
it("should return attack submenu with attack units only", () => {
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = enemyPlayer;
|
||||
|
||||
@@ -203,7 +203,7 @@ describe("RadialMenuElements", () => {
|
||||
it("should not include construction units in attack menu", () => {
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = enemyPlayer;
|
||||
|
||||
@@ -237,12 +237,12 @@ describe("RadialMenuElements", () => {
|
||||
});
|
||||
|
||||
it("should be disabled during spawn phase", () => {
|
||||
mockGame.inSpawnPhase = jest.fn(() => true);
|
||||
mockGame.inSpawnPhase = vi.fn(() => true);
|
||||
expect(buildMenuElement.disabled(mockParams)).toBe(true);
|
||||
});
|
||||
|
||||
it("should be enabled when not in spawn phase", () => {
|
||||
mockGame.inSpawnPhase = jest.fn(() => false);
|
||||
mockGame.inSpawnPhase = vi.fn(() => false);
|
||||
expect(buildMenuElement.disabled(mockParams)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -313,9 +313,9 @@ describe("RadialMenuElements", () => {
|
||||
it("should show attack and boat menu on enemy territory", () => {
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockGame.owner = jest.fn(() => enemyPlayer);
|
||||
mockGame.owner = vi.fn(() => enemyPlayer);
|
||||
|
||||
const subMenu = rootMenuElement.subMenu!(mockParams);
|
||||
const buildMenu = subMenu.find((item) => item.id === Slot.Build);
|
||||
@@ -337,8 +337,8 @@ describe("RadialMenuElements", () => {
|
||||
it("should handle ally menu correctly", () => {
|
||||
const allyPlayer = {
|
||||
id: () => 2,
|
||||
isAlliedWith: jest.fn(() => true),
|
||||
isPlayer: jest.fn(() => true),
|
||||
isAlliedWith: vi.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = allyPlayer;
|
||||
|
||||
@@ -367,7 +367,7 @@ describe("RadialMenuElements", () => {
|
||||
it("should execute attack action correctly", () => {
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = enemyPlayer;
|
||||
|
||||
@@ -389,7 +389,7 @@ describe("RadialMenuElements", () => {
|
||||
|
||||
it("should not execute action when buildable unit is not found", () => {
|
||||
mockPlayerActions.buildableUnits = [];
|
||||
mockBuildMenu.canBuildOrUpgrade = jest.fn(() => false);
|
||||
mockBuildMenu.canBuildOrUpgrade = vi.fn(() => false);
|
||||
|
||||
const subMenu = buildMenuElement.subMenu!(mockParams);
|
||||
const cityElement = subMenu.find((item) => item.id === "build_City");
|
||||
@@ -420,7 +420,7 @@ describe("RadialMenuElements", () => {
|
||||
it("should generate correct tooltip items for attack elements", () => {
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = enemyPlayer;
|
||||
|
||||
@@ -452,7 +452,7 @@ describe("RadialMenuElements", () => {
|
||||
it("should use correct colors for attack elements", () => {
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = enemyPlayer;
|
||||
|
||||
@@ -465,7 +465,7 @@ describe("RadialMenuElements", () => {
|
||||
});
|
||||
|
||||
it("should not set color when element is disabled", () => {
|
||||
mockBuildMenu.canBuildOrUpgrade = jest.fn(() => false);
|
||||
mockBuildMenu.canBuildOrUpgrade = vi.fn(() => false);
|
||||
|
||||
const subMenu = buildMenuElement.subMenu!(mockParams);
|
||||
const cityElement = subMenu.find((item) => item.id === "build_City");
|
||||
@@ -475,10 +475,12 @@ describe("RadialMenuElements", () => {
|
||||
});
|
||||
|
||||
describe("Translation integration", () => {
|
||||
it("should use translateText for tooltip items in build menu", () => {
|
||||
const { translateText } = jest.requireMock("../../../src/client/Utils");
|
||||
it("should use translateText for tooltip items in build menu", async () => {
|
||||
const { translateText } = await vi.importMock<
|
||||
typeof import("../../../src/client/Utils")
|
||||
>("../../../src/client/Utils");
|
||||
|
||||
(translateText as jest.Mock).mockClear();
|
||||
(translateText as Mock).mockClear();
|
||||
|
||||
buildMenuElement.subMenu!(mockParams);
|
||||
|
||||
@@ -488,14 +490,16 @@ describe("RadialMenuElements", () => {
|
||||
expect(translateText).toHaveBeenCalledWith("unit_type.factory_desc");
|
||||
});
|
||||
|
||||
it("should use translateText for tooltip items in attack menu", () => {
|
||||
const { translateText } = jest.requireMock("../../../src/client/Utils");
|
||||
it("should use translateText for tooltip items in attack menu", async () => {
|
||||
const { translateText } = await vi.importMock<
|
||||
typeof import("../../../src/client/Utils")
|
||||
>("../../../src/client/Utils");
|
||||
|
||||
(translateText as jest.Mock).mockClear();
|
||||
(translateText as Mock).mockClear();
|
||||
|
||||
const enemyPlayer = {
|
||||
id: () => 2,
|
||||
isPlayer: jest.fn(() => true),
|
||||
isPlayer: vi.fn(() => true),
|
||||
} as unknown as PlayerView;
|
||||
mockParams.selected = enemyPlayer;
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
import { UILayer } from "../../../src/client/graphics/layers/UILayer";
|
||||
import { UnitSelectionEvent } from "../../../src/client/InputHandler";
|
||||
import { UnitView } from "../../../src/core/game/GameView";
|
||||
@@ -28,7 +25,7 @@ describe("UILayer", () => {
|
||||
ticks: () => 1,
|
||||
updatesSinceLastTick: () => undefined,
|
||||
};
|
||||
eventBus = { on: jest.fn() };
|
||||
eventBus = { on: vi.fn() };
|
||||
transformHandler = {};
|
||||
});
|
||||
|
||||
@@ -50,7 +47,7 @@ describe("UILayer", () => {
|
||||
owner: () => ({}),
|
||||
};
|
||||
const event = { isSelected: true, unit };
|
||||
ui.drawSelectionBox = jest.fn();
|
||||
ui.drawSelectionBox = vi.fn();
|
||||
ui["onUnitSelection"](event as UnitSelectionEvent);
|
||||
expect(ui.drawSelectionBox).toHaveBeenCalledWith(unit);
|
||||
});
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
jest.mock("lit", () => ({
|
||||
vi.mock("lit", () => ({
|
||||
html: () => {},
|
||||
LitElement: class {},
|
||||
}));
|
||||
|
||||
jest.mock("lit/decorators.js", () => ({
|
||||
vi.mock("lit/decorators.js", () => ({
|
||||
customElement: () => (clazz: any) => clazz,
|
||||
query: () => () => {},
|
||||
state: () => () => {},
|
||||
property: () => () => {},
|
||||
}));
|
||||
|
||||
jest.mock("lit/directive.js", () => ({
|
||||
vi.mock("lit/directive.js", () => ({
|
||||
DirectiveResult: class {},
|
||||
}));
|
||||
|
||||
jest.mock("lit/directives/unsafe-html.js", () => ({
|
||||
vi.mock("lit/directives/unsafe-html.js", () => ({
|
||||
unsafeHTML: () => {},
|
||||
UnsafeHTMLDirective: class {},
|
||||
}));
|
||||
|
||||
@@ -28,11 +28,11 @@ describe("NukeExecution", () => {
|
||||
],
|
||||
);
|
||||
|
||||
(game.config() as TestConfig).nukeMagnitudes = jest.fn(() => ({
|
||||
(game.config() as TestConfig).nukeMagnitudes = vi.fn(() => ({
|
||||
inner: 10,
|
||||
outer: 10,
|
||||
}));
|
||||
(game.config() as TestConfig).nukeAllianceBreakThreshold = jest.fn(() => 5);
|
||||
(game.config() as TestConfig).nukeAllianceBreakThreshold = vi.fn(() => 5);
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
@@ -51,14 +51,14 @@ describe("NukeExecution", () => {
|
||||
player.buildUnit(UnitType.MissileSilo, game.ref(1, 10), {});
|
||||
// Build a SAM out of range
|
||||
const sam = player.buildUnit(UnitType.SAMLauncher, game.ref(1, 11), {});
|
||||
sam.touch = jest.fn();
|
||||
sam.touch = vi.fn();
|
||||
// Build a Defense post out of range AND out of redraw range
|
||||
const defensePost = player.buildUnit(
|
||||
UnitType.DefensePost,
|
||||
game.ref(1, 27),
|
||||
{},
|
||||
);
|
||||
defensePost.touch = jest.fn();
|
||||
defensePost.touch = vi.fn();
|
||||
// Add a nuke execution targeting the city
|
||||
const nukeExec = new NukeExecution(
|
||||
UnitType.AtomBomb,
|
||||
|
||||
@@ -20,70 +20,70 @@ describe("TradeShipExecution", () => {
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
});
|
||||
game.displayMessage = jest.fn();
|
||||
game.displayMessage = vi.fn();
|
||||
origOwner = {
|
||||
canBuild: jest.fn(() => true),
|
||||
buildUnit: jest.fn((type, spawn, opts) => tradeShip),
|
||||
displayName: jest.fn(() => "Origin"),
|
||||
addGold: jest.fn(),
|
||||
units: jest.fn(() => [dstPort]),
|
||||
unitCount: jest.fn(() => 1),
|
||||
id: jest.fn(() => 1),
|
||||
clientID: jest.fn(() => 1),
|
||||
canTrade: jest.fn(() => true),
|
||||
canBuild: vi.fn(() => true),
|
||||
buildUnit: vi.fn((type, spawn, opts) => tradeShip),
|
||||
displayName: vi.fn(() => "Origin"),
|
||||
addGold: vi.fn(),
|
||||
units: vi.fn(() => [dstPort]),
|
||||
unitCount: vi.fn(() => 1),
|
||||
id: vi.fn(() => 1),
|
||||
clientID: vi.fn(() => 1),
|
||||
canTrade: vi.fn(() => true),
|
||||
} as any;
|
||||
|
||||
dstOwner = {
|
||||
id: jest.fn(() => 2),
|
||||
addGold: jest.fn(),
|
||||
displayName: jest.fn(() => "Destination"),
|
||||
units: jest.fn(() => [dstPort]),
|
||||
unitCount: jest.fn(() => 1),
|
||||
clientID: jest.fn(() => 2),
|
||||
canTrade: jest.fn(() => true),
|
||||
id: vi.fn(() => 2),
|
||||
addGold: vi.fn(),
|
||||
displayName: vi.fn(() => "Destination"),
|
||||
units: vi.fn(() => [dstPort]),
|
||||
unitCount: vi.fn(() => 1),
|
||||
clientID: vi.fn(() => 2),
|
||||
canTrade: vi.fn(() => true),
|
||||
} as any;
|
||||
|
||||
pirate = {
|
||||
id: jest.fn(() => 3),
|
||||
addGold: jest.fn(),
|
||||
displayName: jest.fn(() => "Destination"),
|
||||
units: jest.fn(() => [piratePort]),
|
||||
unitCount: jest.fn(() => 1),
|
||||
canTrade: jest.fn(() => true),
|
||||
id: vi.fn(() => 3),
|
||||
addGold: vi.fn(),
|
||||
displayName: vi.fn(() => "Destination"),
|
||||
units: vi.fn(() => [piratePort]),
|
||||
unitCount: vi.fn(() => 1),
|
||||
canTrade: vi.fn(() => true),
|
||||
} as any;
|
||||
|
||||
piratePort = {
|
||||
tile: jest.fn(() => 40011),
|
||||
owner: jest.fn(() => pirate),
|
||||
isActive: jest.fn(() => true),
|
||||
tile: vi.fn(() => 40011),
|
||||
owner: vi.fn(() => pirate),
|
||||
isActive: vi.fn(() => true),
|
||||
} as any;
|
||||
|
||||
srcPort = {
|
||||
tile: jest.fn(() => 20011),
|
||||
owner: jest.fn(() => origOwner),
|
||||
isActive: jest.fn(() => true),
|
||||
tile: vi.fn(() => 20011),
|
||||
owner: vi.fn(() => origOwner),
|
||||
isActive: vi.fn(() => true),
|
||||
} as any;
|
||||
|
||||
dstPort = {
|
||||
tile: jest.fn(() => 30015), // 15x15
|
||||
owner: jest.fn(() => dstOwner),
|
||||
isActive: jest.fn(() => true),
|
||||
tile: vi.fn(() => 30015), // 15x15
|
||||
owner: vi.fn(() => dstOwner),
|
||||
isActive: vi.fn(() => true),
|
||||
} as any;
|
||||
|
||||
tradeShip = {
|
||||
isActive: jest.fn(() => true),
|
||||
owner: jest.fn(() => origOwner),
|
||||
move: jest.fn(),
|
||||
setTargetUnit: jest.fn(),
|
||||
setSafeFromPirates: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
tile: jest.fn(() => 2001),
|
||||
isActive: vi.fn(() => true),
|
||||
owner: vi.fn(() => origOwner),
|
||||
move: vi.fn(),
|
||||
setTargetUnit: vi.fn(),
|
||||
setSafeFromPirates: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
tile: vi.fn(() => 2001),
|
||||
} as any;
|
||||
|
||||
tradeShipExecution = new TradeShipExecution(origOwner, srcPort, dstPort);
|
||||
tradeShipExecution.init(game, 0);
|
||||
tradeShipExecution["pathFinder"] = {
|
||||
nextTile: jest.fn(() => ({ type: 0, node: 2001 })),
|
||||
nextTile: vi.fn(() => ({ type: 0, node: 2001 })),
|
||||
} as any;
|
||||
tradeShipExecution["tradeShip"] = tradeShip;
|
||||
});
|
||||
@@ -94,27 +94,27 @@ describe("TradeShipExecution", () => {
|
||||
});
|
||||
|
||||
it("should deactivate if tradeShip is not active", () => {
|
||||
tradeShip.isActive = jest.fn(() => false);
|
||||
tradeShip.isActive = vi.fn(() => false);
|
||||
tradeShipExecution.tick(1);
|
||||
expect(tradeShipExecution.isActive()).toBe(false);
|
||||
});
|
||||
|
||||
it("should delete ship if port owner changes to current owner", () => {
|
||||
dstPort.owner = jest.fn(() => origOwner);
|
||||
dstPort.owner = vi.fn(() => origOwner);
|
||||
tradeShipExecution.tick(1);
|
||||
expect(tradeShip.delete).toHaveBeenCalledWith(false);
|
||||
expect(tradeShipExecution.isActive()).toBe(false);
|
||||
});
|
||||
|
||||
it("should pick another port if ship is captured", () => {
|
||||
tradeShip.owner = jest.fn(() => pirate);
|
||||
tradeShip.owner = vi.fn(() => pirate);
|
||||
tradeShipExecution.tick(1);
|
||||
expect(tradeShip.setTargetUnit).toHaveBeenCalledWith(piratePort);
|
||||
});
|
||||
|
||||
it("should complete trade and award gold", () => {
|
||||
tradeShipExecution["pathFinder"] = {
|
||||
nextTile: jest.fn(() => ({ type: 2, node: 2001 })),
|
||||
nextTile: vi.fn(() => ({ type: 2, node: 2001 })),
|
||||
} as any;
|
||||
tradeShipExecution.tick(1);
|
||||
expect(tradeShip.delete).toHaveBeenCalledWith(false);
|
||||
|
||||
@@ -13,52 +13,52 @@ describe("WinCheckExecution", () => {
|
||||
maxTimerValue: 5,
|
||||
instantBuild: true,
|
||||
});
|
||||
mg.setWinner = jest.fn();
|
||||
mg.setWinner = vi.fn();
|
||||
winCheck = new WinCheckExecution();
|
||||
winCheck.init(mg, 0);
|
||||
});
|
||||
|
||||
it("should call checkWinnerFFA in FFA mode", () => {
|
||||
const spy = jest.spyOn(winCheck as any, "checkWinnerFFA");
|
||||
const spy = vi.spyOn(winCheck as any, "checkWinnerFFA");
|
||||
winCheck.tick(10);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should call checkWinnerTeam in non-FFA mode", () => {
|
||||
mg.config = jest.fn(() => ({
|
||||
gameConfig: jest.fn(() => ({
|
||||
mg.config = vi.fn(() => ({
|
||||
gameConfig: vi.fn(() => ({
|
||||
maxTimerValue: 5,
|
||||
gameMode: GameMode.Team,
|
||||
})),
|
||||
percentageTilesOwnedToWin: jest.fn(() => 50),
|
||||
percentageTilesOwnedToWin: vi.fn(() => 50),
|
||||
}));
|
||||
winCheck.init(mg, 0);
|
||||
const spy = jest.spyOn(winCheck as any, "checkWinnerTeam");
|
||||
const spy = vi.spyOn(winCheck as any, "checkWinnerTeam");
|
||||
winCheck.tick(10);
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should set winner in FFA if percentage is reached", () => {
|
||||
const player = {
|
||||
numTilesOwned: jest.fn(() => 81),
|
||||
name: jest.fn(() => "P1"),
|
||||
numTilesOwned: vi.fn(() => 81),
|
||||
name: vi.fn(() => "P1"),
|
||||
};
|
||||
mg.players = jest.fn(() => [player]);
|
||||
mg.numLandTiles = jest.fn(() => 100);
|
||||
mg.numTilesWithFallout = jest.fn(() => 0);
|
||||
mg.players = vi.fn(() => [player]);
|
||||
mg.numLandTiles = vi.fn(() => 100);
|
||||
mg.numTilesWithFallout = vi.fn(() => 0);
|
||||
winCheck.checkWinnerFFA();
|
||||
expect(mg.setWinner).toHaveBeenCalledWith(player, expect.anything());
|
||||
});
|
||||
|
||||
it("should set winner in FFA if timer is 0", () => {
|
||||
const player = {
|
||||
numTilesOwned: jest.fn(() => 10),
|
||||
name: jest.fn(() => "P1"),
|
||||
numTilesOwned: vi.fn(() => 10),
|
||||
name: vi.fn(() => "P1"),
|
||||
};
|
||||
mg.players = jest.fn(() => [player]);
|
||||
mg.numLandTiles = jest.fn(() => 100);
|
||||
mg.numTilesWithFallout = jest.fn(() => 0);
|
||||
mg.stats = jest.fn(() => ({ stats: () => ({ mocked: true }) }));
|
||||
mg.players = vi.fn(() => [player]);
|
||||
mg.numLandTiles = vi.fn(() => 100);
|
||||
mg.numTilesWithFallout = vi.fn(() => 0);
|
||||
mg.stats = vi.fn(() => ({ stats: () => ({ mocked: true }) }));
|
||||
// Advance ticks until timeElapsed (in seconds) >= maxTimerValue * 60
|
||||
// timeElapsed = (ticks - numSpawnPhaseTurns) / 10 =>
|
||||
// ticks >= numSpawnPhaseTurns + maxTimerValue * 600
|
||||
@@ -73,7 +73,7 @@ describe("WinCheckExecution", () => {
|
||||
});
|
||||
|
||||
it("should not set winner if no players", () => {
|
||||
mg.players = jest.fn(() => []);
|
||||
mg.players = vi.fn(() => []);
|
||||
winCheck.checkWinnerFFA();
|
||||
expect(mg.setWinner).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { vi, type Mocked } from "vitest";
|
||||
import { Cluster, TrainStation } from "../../../src/core/game/TrainStation";
|
||||
|
||||
const createMockStation = (id: string): jest.Mocked<TrainStation> => {
|
||||
const createMockStation = (id: string): Mocked<TrainStation> => {
|
||||
return {
|
||||
id,
|
||||
setCluster: jest.fn(),
|
||||
getCluster: jest.fn(() => null),
|
||||
setCluster: vi.fn(),
|
||||
getCluster: vi.fn(() => null),
|
||||
} as any;
|
||||
};
|
||||
|
||||
describe("Cluster tests", () => {
|
||||
let cluster: Cluster;
|
||||
let stationA: jest.Mocked<TrainStation>;
|
||||
let stationB: jest.Mocked<TrainStation>;
|
||||
let stationC: jest.Mocked<TrainStation>;
|
||||
let stationA: Mocked<TrainStation>;
|
||||
let stationB: Mocked<TrainStation>;
|
||||
let stationC: Mocked<TrainStation>;
|
||||
|
||||
beforeEach(() => {
|
||||
cluster = new Cluster();
|
||||
|
||||
@@ -67,7 +67,7 @@ describe("GameImpl", () => {
|
||||
});
|
||||
|
||||
test("Don't become traitor when betraying inactive player", async () => {
|
||||
jest.spyOn(attacker, "canSendAllianceRequest").mockReturnValue(true);
|
||||
vi.spyOn(attacker, "canSendAllianceRequest").mockReturnValue(true);
|
||||
game.addExecution(new AllianceRequestExecution(attacker, defender.id()));
|
||||
|
||||
game.executeNextTick();
|
||||
@@ -106,7 +106,7 @@ describe("GameImpl", () => {
|
||||
});
|
||||
|
||||
test("Do become traitor when betraying active player", async () => {
|
||||
jest.spyOn(attacker, "canSendAllianceRequest").mockReturnValue(true);
|
||||
vi.spyOn(attacker, "canSendAllianceRequest").mockReturnValue(true);
|
||||
game.addExecution(new AllianceRequestExecution(attacker, defender.id()));
|
||||
|
||||
game.executeNextTick();
|
||||
|
||||
@@ -13,15 +13,15 @@ const createMockStation = (unitId: number): any => {
|
||||
return {
|
||||
unit: {
|
||||
id: unitId,
|
||||
setTrainStation: jest.fn(),
|
||||
setTrainStation: vi.fn(),
|
||||
},
|
||||
tile: jest.fn(),
|
||||
neighbors: jest.fn(() => []),
|
||||
getCluster: jest.fn(() => cluster),
|
||||
setCluster: jest.fn(),
|
||||
addRailroad: jest.fn(),
|
||||
getRailroads: jest.fn(() => railroads),
|
||||
clearRailroads: jest.fn(),
|
||||
tile: vi.fn(),
|
||||
neighbors: vi.fn(() => []),
|
||||
getCluster: vi.fn(() => cluster),
|
||||
setCluster: vi.fn(),
|
||||
addRailroad: vi.fn(),
|
||||
getRailroads: vi.fn(() => railroads),
|
||||
clearRailroads: vi.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -54,18 +54,18 @@ describe("RailNetworkImpl", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
stationManager = {
|
||||
addStation: jest.fn(),
|
||||
removeStation: jest.fn(),
|
||||
findStation: jest.fn(),
|
||||
getAll: jest.fn(() => new Set()),
|
||||
addStation: vi.fn(),
|
||||
removeStation: vi.fn(),
|
||||
findStation: vi.fn(),
|
||||
getAll: vi.fn(() => new Set()),
|
||||
};
|
||||
pathService = {
|
||||
findTilePath: jest.fn(() => [0]),
|
||||
findStationsPath: jest.fn(() => [0]),
|
||||
findTilePath: vi.fn(() => [0]),
|
||||
findStationsPath: vi.fn(() => [0]),
|
||||
};
|
||||
game = {
|
||||
nearbyUnits: jest.fn(() => []),
|
||||
addExecution: jest.fn(),
|
||||
nearbyUnits: vi.fn(() => []),
|
||||
addExecution: vi.fn(),
|
||||
config: () => ({
|
||||
trainStationMaxRange: () => 80,
|
||||
trainStationMinRange: () => 10,
|
||||
@@ -86,7 +86,7 @@ describe("RailNetworkImpl", () => {
|
||||
network.connectStation(stationA);
|
||||
|
||||
const cluster = stationB.getCluster();
|
||||
cluster.addStation = jest.fn();
|
||||
cluster.addStation = vi.fn();
|
||||
expect(cluster.addStation).not.toHaveBeenCalled();
|
||||
|
||||
pathService.findTilePath.mockReturnValue(new Array(200));
|
||||
@@ -95,9 +95,9 @@ describe("RailNetworkImpl", () => {
|
||||
});
|
||||
|
||||
test("removeStation removes all neighbor links", () => {
|
||||
const neighbor = { removeNeighboringRails: jest.fn() };
|
||||
const neighbor = { removeNeighboringRails: vi.fn() };
|
||||
const station = createMockStation(1);
|
||||
station.neighbors = jest.fn(() => [neighbor]);
|
||||
station.neighbors = vi.fn(() => [neighbor]);
|
||||
stationManager.findStation.mockReturnValue(station);
|
||||
network.removeStation(station);
|
||||
expect(station.clearRailroads).toHaveBeenCalled();
|
||||
@@ -119,9 +119,9 @@ describe("RailNetworkImpl", () => {
|
||||
const cluster = new Cluster();
|
||||
const neighbor = createMockStation(1);
|
||||
const station = createMockStation(2);
|
||||
station.getCluster = jest.fn(() => cluster);
|
||||
station.neighbors = jest.fn(() => [neighbor]);
|
||||
cluster.removeStation = jest.fn();
|
||||
station.getCluster = vi.fn(() => cluster);
|
||||
station.neighbors = vi.fn(() => [neighbor]);
|
||||
cluster.removeStation = vi.fn();
|
||||
|
||||
stationManager.findStation.mockReturnValue(station);
|
||||
|
||||
@@ -150,8 +150,8 @@ describe("RailNetworkImpl", () => {
|
||||
const neighborStation = createMockStation(2);
|
||||
const cluster = new Cluster();
|
||||
cluster.addStation(neighborStation);
|
||||
neighborStation.getCluster = jest.fn(() => cluster);
|
||||
cluster.has = jest.fn(() => false);
|
||||
neighborStation.getCluster = vi.fn(() => cluster);
|
||||
cluster.has = vi.fn(() => false);
|
||||
|
||||
const neighborUnit = { unit: neighborStation.unit, distSquared: 20 };
|
||||
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
import { vi, type Mocked } from "vitest";
|
||||
import { TrainExecution } from "../../../src/core/execution/TrainExecution";
|
||||
import { Game, Player, Unit, UnitType } from "../../../src/core/game/Game";
|
||||
import { Cluster, TrainStation } from "../../../src/core/game/TrainStation";
|
||||
|
||||
jest.mock("../../../src/core/game/Game");
|
||||
jest.mock("../../../src/core/execution/TrainExecution");
|
||||
jest.mock("../../../src/core/PseudoRandom");
|
||||
vi.mock("../../../src/core/game/Game");
|
||||
vi.mock("../../../src/core/execution/TrainExecution");
|
||||
vi.mock("../../../src/core/PseudoRandom");
|
||||
|
||||
describe("TrainStation", () => {
|
||||
let game: jest.Mocked<Game>;
|
||||
let unit: jest.Mocked<Unit>;
|
||||
let player: jest.Mocked<Player>;
|
||||
let trainExecution: jest.Mocked<TrainExecution>;
|
||||
let game: Mocked<Game>;
|
||||
let unit: Mocked<Unit>;
|
||||
let player: Mocked<Player>;
|
||||
let trainExecution: Mocked<TrainExecution>;
|
||||
|
||||
beforeEach(() => {
|
||||
game = {
|
||||
ticks: jest.fn().mockReturnValue(123),
|
||||
config: jest.fn().mockReturnValue({
|
||||
ticks: vi.fn().mockReturnValue(123),
|
||||
config: vi.fn().mockReturnValue({
|
||||
trainGold: (isFriendly: boolean) =>
|
||||
isFriendly ? BigInt(1000) : BigInt(500),
|
||||
}),
|
||||
addUpdate: jest.fn(),
|
||||
addExecution: jest.fn(),
|
||||
addUpdate: vi.fn(),
|
||||
addExecution: vi.fn(),
|
||||
} as any;
|
||||
|
||||
player = {
|
||||
addGold: jest.fn(),
|
||||
addGold: vi.fn(),
|
||||
id: 1,
|
||||
canTrade: jest.fn().mockReturnValue(true),
|
||||
isFriendly: jest.fn().mockReturnValue(false),
|
||||
canTrade: vi.fn().mockReturnValue(true),
|
||||
isFriendly: vi.fn().mockReturnValue(false),
|
||||
} as any;
|
||||
|
||||
unit = {
|
||||
owner: jest.fn().mockReturnValue(player),
|
||||
level: jest.fn().mockReturnValue(1),
|
||||
tile: jest.fn().mockReturnValue({ x: 0, y: 0 }),
|
||||
type: jest.fn(),
|
||||
isActive: jest.fn().mockReturnValue(true),
|
||||
owner: vi.fn().mockReturnValue(player),
|
||||
level: vi.fn().mockReturnValue(1),
|
||||
tile: vi.fn().mockReturnValue({ x: 0, y: 0 }),
|
||||
type: vi.fn(),
|
||||
isActive: vi.fn().mockReturnValue(true),
|
||||
} as any;
|
||||
|
||||
trainExecution = {
|
||||
loadCargo: jest.fn(),
|
||||
owner: jest.fn().mockReturnValue(player),
|
||||
level: jest.fn(),
|
||||
loadCargo: vi.fn(),
|
||||
owner: vi.fn().mockReturnValue(player),
|
||||
level: vi.fn(),
|
||||
} as any;
|
||||
});
|
||||
|
||||
@@ -70,7 +71,7 @@ describe("TrainStation", () => {
|
||||
|
||||
it("checks trade availability (same owner)", () => {
|
||||
const otherUnit = {
|
||||
owner: jest.fn().mockReturnValue(unit.owner()),
|
||||
owner: vi.fn().mockReturnValue(unit.owner()),
|
||||
} as any;
|
||||
|
||||
const station = new TrainStation(game, unit);
|
||||
|
||||
Vendored
-34
@@ -1,34 +0,0 @@
|
||||
declare module "*.png" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
declare module "*.jpg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.webp" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module "*.jpeg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.svg" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.bin" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.txt" {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
declare module "*.html" {
|
||||
const content: string;
|
||||
export default content;
|
||||
}
|
||||
@@ -1,15 +1,13 @@
|
||||
import { vi } from "vitest";
|
||||
|
||||
// Mock BuildMenu to avoid importing lit and other ESM-heavy deps in this unit test
|
||||
jest.mock(
|
||||
"../src/client/graphics/layers/BuildMenu",
|
||||
() => ({
|
||||
vi.mock("../src/client/graphics/layers/BuildMenu", () => ({
|
||||
BuildMenu: class {},
|
||||
flattenedBuildTable: [],
|
||||
}),
|
||||
{ virtual: true },
|
||||
);
|
||||
}));
|
||||
|
||||
// Mock Utils to avoid touching DOM (document) during tests
|
||||
jest.mock("../src/client/Utils", () => ({
|
||||
vi.mock("../src/client/Utils", () => ({
|
||||
translateText: (k: string) => k,
|
||||
getSvgAspectRatio: async () => 1,
|
||||
}));
|
||||
@@ -57,20 +55,20 @@ const makeParams = (opts?: Partial<MenuElementParams>): MenuElementParams => {
|
||||
} as any,
|
||||
emojiTable: {} as any,
|
||||
playerActionHandler: {
|
||||
handleBreakAlliance: jest.fn(),
|
||||
handleEmbargo: jest.fn(),
|
||||
handleDonateGold: jest.fn(),
|
||||
handleDonateTroops: jest.fn(),
|
||||
handleTargetPlayer: jest.fn(),
|
||||
handleBreakAlliance: vi.fn(),
|
||||
handleEmbargo: vi.fn(),
|
||||
handleDonateGold: vi.fn(),
|
||||
handleDonateTroops: vi.fn(),
|
||||
handleTargetPlayer: vi.fn(),
|
||||
} as any,
|
||||
playerPanel: {
|
||||
show: jest.fn(),
|
||||
show: vi.fn(),
|
||||
} as any,
|
||||
chatIntegration: {
|
||||
createQuickChatMenu: jest.fn(() => []),
|
||||
createQuickChatMenu: vi.fn(() => []),
|
||||
} as any,
|
||||
eventBus: {} as any,
|
||||
closeMenu: jest.fn(),
|
||||
closeMenu: vi.fn(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
// Add global mocks or configuration here if needed
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["tests/**/*", "src/**/*"]
|
||||
}
|
||||
+9
-5
@@ -3,15 +3,16 @@
|
||||
"compilerOptions": {
|
||||
// Language and Environment
|
||||
"target": "ES2020",
|
||||
|
||||
// Modules
|
||||
"module": "ESNext",
|
||||
"rootDir": ".",
|
||||
"moduleResolution": "node",
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"resources/*": ["resources/*"]
|
||||
},
|
||||
// Emit
|
||||
"sourceMap": true,
|
||||
|
||||
// Type Checking
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"allowUnusedLabels": false,
|
||||
@@ -21,7 +22,9 @@
|
||||
"resolveJsonModule": true,
|
||||
"strictNullChecks": true,
|
||||
"useDefineForClassFields": false,
|
||||
"strictPropertyInitialization": false
|
||||
"strictPropertyInitialization": false,
|
||||
"skipLibCheck": true,
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
@@ -29,7 +32,8 @@
|
||||
"proprietary/**/*",
|
||||
"generated/**/*",
|
||||
"tests/**/*",
|
||||
"src/scripts"
|
||||
"src/scripts",
|
||||
"vite.config.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
import { execSync } from "child_process";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import { createHtmlPlugin } from "vite-plugin-html";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
|
||||
// Vite already handles these, but its good practice to define them explicitly
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
let gitCommit = process.env.GIT_COMMIT;
|
||||
|
||||
if (!gitCommit) {
|
||||
try {
|
||||
gitCommit = execSync("git rev-parse HEAD").toString().trim();
|
||||
} catch (error) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.warn("Unable to determine git commit:", error.message);
|
||||
}
|
||||
gitCommit = "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), "");
|
||||
const isProduction = mode === "production";
|
||||
|
||||
return {
|
||||
test: {
|
||||
globals: true,
|
||||
environment: "jsdom",
|
||||
setupFiles: "./tests/setup.ts",
|
||||
},
|
||||
root: "./",
|
||||
base: "/",
|
||||
publicDir: "resources", // Access static assets via import or explicit copy
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
"protobufjs/minimal": path.resolve(
|
||||
__dirname,
|
||||
"node_modules/protobufjs/minimal.js",
|
||||
),
|
||||
resources: path.resolve(__dirname, "resources"),
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
tsconfigPaths(),
|
||||
createHtmlPlugin({
|
||||
minify: isProduction,
|
||||
entry: "/src/client/Main.ts",
|
||||
template: "index.html",
|
||||
inject: {
|
||||
data: {
|
||||
// In case we need to inject variables into HTML
|
||||
},
|
||||
},
|
||||
}),
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
src: "proprietary/*",
|
||||
dest: ".",
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
||||
define: {
|
||||
"process.env.WEBSOCKET_URL": JSON.stringify(
|
||||
isProduction ? "" : "localhost:3000",
|
||||
),
|
||||
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
|
||||
"process.env.GIT_COMMIT": JSON.stringify(gitCommit),
|
||||
"process.env.STRIPE_PUBLISHABLE_KEY": JSON.stringify(
|
||||
env.STRIPE_PUBLISHABLE_KEY,
|
||||
),
|
||||
"process.env.API_DOMAIN": JSON.stringify(env.API_DOMAIN),
|
||||
// Add other process.env variables if needed, OR migrate code to import.meta.env
|
||||
},
|
||||
|
||||
build: {
|
||||
outDir: "static", // Webpack outputs to 'static', assuming we want to keep this.
|
||||
emptyOutDir: true,
|
||||
assetsDir: "assets", // Sub-directory for assets
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor: ["pixi.js", "howler", "zod", "protobufjs"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
server: {
|
||||
port: 9000,
|
||||
proxy: {
|
||||
"/socket": {
|
||||
target: "ws://localhost:3000",
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
},
|
||||
// Worker proxies
|
||||
"/w0": {
|
||||
target: "ws://localhost:3001",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/w0/, ""),
|
||||
},
|
||||
"/w1": {
|
||||
target: "ws://localhost:3002",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/w1/, ""),
|
||||
},
|
||||
"/w2": {
|
||||
target: "ws://localhost:3003",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/w2/, ""),
|
||||
},
|
||||
// API proxies
|
||||
"/api": {
|
||||
target: "http://localhost:3000",
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -1,263 +0,0 @@
|
||||
import { execSync } from "child_process";
|
||||
import CopyPlugin from "copy-webpack-plugin";
|
||||
import ESLintPlugin from "eslint-webpack-plugin";
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import webpack from "webpack";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const gitCommit =
|
||||
process.env.GIT_COMMIT ?? execSync("git rev-parse HEAD").toString().trim();
|
||||
|
||||
export default async (env, argv) => {
|
||||
const isProduction = argv.mode === "production";
|
||||
|
||||
return {
|
||||
entry: "./src/client/Main.ts",
|
||||
output: {
|
||||
publicPath: "/",
|
||||
filename: "js/[name].[contenthash].js", // Added content hash
|
||||
path: path.resolve(__dirname, "static"),
|
||||
clean: isProduction,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.bin$/,
|
||||
type: "asset/resource", // Changed from raw-loader
|
||||
generator: {
|
||||
filename: "binary/[name].[contenthash][ext]", // Added content hash
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.txt$/,
|
||||
type: "asset/source",
|
||||
},
|
||||
{
|
||||
test: /\.md$/,
|
||||
type: "asset/resource", // Changed from raw-loader
|
||||
generator: {
|
||||
filename: "text/[name].[contenthash][ext]", // Added content hash
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
"style-loader",
|
||||
{
|
||||
loader: "css-loader",
|
||||
options: {
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: "postcss-loader",
|
||||
options: {
|
||||
postcssOptions: {
|
||||
plugins: ["tailwindcss", "autoprefixer"],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(webp|png|jpe?g|gif)$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "images/[name].[contenthash][ext]", // Added content hash
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
use: ["html-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
type: "asset/resource", // Changed from asset/inline for caching
|
||||
generator: {
|
||||
filename: "images/[name].[contenthash][ext]", // Added content hash
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2|eot|ttf|otf|xml)$/,
|
||||
type: "asset/resource", // Changed from file-loader
|
||||
generator: {
|
||||
filename: "fonts/[name].[contenthash][ext]", // Added content hash and fixed path
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(mp3|wav|ogg)$/i,
|
||||
type: "asset/resource",
|
||||
generator: {
|
||||
filename: "sounds/[name].[contenthash][ext]",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js"],
|
||||
alias: {
|
||||
"protobufjs/minimal": path.resolve(
|
||||
__dirname,
|
||||
"node_modules/protobufjs/minimal.js",
|
||||
),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: "./src/client/index.html",
|
||||
filename: "index.html",
|
||||
// Add optimization for HTML
|
||||
minify: isProduction
|
||||
? {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true,
|
||||
useShortDoctype: true,
|
||||
}
|
||||
: false,
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.WEBSOCKET_URL": JSON.stringify(
|
||||
isProduction ? "" : "localhost:3000",
|
||||
),
|
||||
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
|
||||
"process.env.GIT_COMMIT": JSON.stringify(gitCommit),
|
||||
"process.env.STRIPE_PUBLISHABLE_KEY": JSON.stringify(
|
||||
process.env.STRIPE_PUBLISHABLE_KEY,
|
||||
),
|
||||
"process.env.API_DOMAIN": JSON.stringify(process.env.API_DOMAIN),
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, "resources"),
|
||||
to: path.resolve(__dirname, "static"),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, "proprietary"),
|
||||
to: path.resolve(__dirname, "static"),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
],
|
||||
options: { concurrency: 100 },
|
||||
}),
|
||||
new ESLintPlugin({
|
||||
context: __dirname,
|
||||
}),
|
||||
],
|
||||
optimization: {
|
||||
// Add optimization configuration for better caching
|
||||
runtimeChunk: "single",
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
vendor: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "vendors",
|
||||
chunks: "all",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
devServer: isProduction
|
||||
? {}
|
||||
: {
|
||||
devMiddleware: { writeToDisk: true },
|
||||
static: {
|
||||
directory: path.join(__dirname, "static"),
|
||||
},
|
||||
historyApiFallback: true,
|
||||
compress: true,
|
||||
port: 9000,
|
||||
proxy: [
|
||||
// WebSocket proxies
|
||||
{
|
||||
context: ["/socket"],
|
||||
target: "ws://localhost:3000",
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
// Worker WebSocket proxies - using direct paths without /socket suffix
|
||||
{
|
||||
context: ["/w0"],
|
||||
target: "ws://localhost:3001",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
{
|
||||
context: ["/w1"],
|
||||
target: "ws://localhost:3002",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
{
|
||||
context: ["/w2"],
|
||||
target: "ws://localhost:3003",
|
||||
ws: true,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
// Worker proxies for HTTP requests
|
||||
{
|
||||
context: ["/w0"],
|
||||
target: "http://localhost:3001",
|
||||
pathRewrite: { "^/w0": "" },
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
{
|
||||
context: ["/w1"],
|
||||
target: "http://localhost:3002",
|
||||
pathRewrite: { "^/w1": "" },
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
{
|
||||
context: ["/w2"],
|
||||
target: "http://localhost:3003",
|
||||
pathRewrite: { "^/w2": "" },
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
logLevel: "debug",
|
||||
},
|
||||
// Original API endpoints
|
||||
{
|
||||
context: [
|
||||
"/api/env",
|
||||
"/api/game",
|
||||
"/api/public_lobbies",
|
||||
"/api/join_game",
|
||||
"/api/start_game",
|
||||
"/api/create_game",
|
||||
"/api/archive_singleplayer_game",
|
||||
"/api/auth/callback",
|
||||
"/api/auth/discord",
|
||||
"/api/kick_player",
|
||||
],
|
||||
target: "http://localhost:3000",
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user