Merge branch 'openfrontio:main' into add-quick-chat

This commit is contained in:
Aotumuri
2025-04-03 12:25:14 +09:00
committed by GitHub
63 changed files with 3155 additions and 1583 deletions
+9
View File
@@ -0,0 +1,9 @@
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
+18
View File
@@ -0,0 +1,18 @@
name: ESLint Check
on:
pull_request:
push:
branches: [main]
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npx eslint --format gha
+20
View File
@@ -1,13 +1,33 @@
import { includeIgnoreFile } from "@eslint/compat";
import pluginJs from "@eslint/js";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import globals from "globals";
import path from "node:path";
import { fileURLToPath } from "node:url";
import tseslint from "typescript-eslint";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const gitignorePath = path.resolve(__dirname, ".gitignore");
/** @type {import('eslint').Linter.Config[]} */
export default [
includeIgnoreFile(gitignorePath),
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: { ...globals.browser, ...globals.node } } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
{
rules: {
// Disable rules that would fail. The failures should be fixed, and the entries here removed.
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-require-imports": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-unused-vars": "off",
"no-case-declarations": "off",
"no-useless-escape": "off",
},
},
];
+360 -221
View File
@@ -64,6 +64,7 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-typescript": "^7.24.7",
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3",
@@ -85,6 +86,8 @@
"css-loader": "^7.1.2",
"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",
@@ -1061,9 +1064,9 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.0.tgz",
"integrity": "sha512-INCKxTtbXtcNbUZ3YXutwMpEleqttcswhAdee7dhuoVrD2cnuc3PqtERBtxkX5nziX9vnBL8WXmSGwv8CuPV6g==",
"version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1413,27 +1416,27 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.25.9",
"@babel/types": "^7.26.0"
"@babel/template": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.26.1",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.1.tgz",
"integrity": "sha512-reoQYNiAJreZNsJzyrDNzFQ+IQ5JFiIzAHJg9bn94S3l+4++J7RsIhNMoB+lgP/9tpmiAQqspv+xfdxTSzREOw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.0"
"@babel/types": "^7.27.0"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -2800,9 +2803,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2813,15 +2816,15 @@
}
},
"node_modules/@babel/template": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.25.9",
"@babel/parser": "^7.25.9",
"@babel/types": "^7.25.9"
"@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.27.0",
"@babel/types": "^7.27.0"
},
"engines": {
"node": ">=6.9.0"
@@ -2857,9 +2860,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2937,32 +2940,26 @@
}
},
"node_modules/@discordjs/builders": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.9.0.tgz",
"integrity": "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.10.1.tgz",
"integrity": "sha512-OWo1fY4ztL1/M/DUyRPShB4d/EzVfuUvPTRRHRIt/YxBrUYSz0a+JicD5F5zHFoNs2oTuWavxCOVFV1UljHTng==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/formatters": "^0.5.0",
"@discordjs/formatters": "^0.6.0",
"@discordjs/util": "^1.1.1",
"@sapphire/shapeshift": "^4.0.0",
"discord-api-types": "0.37.97",
"discord-api-types": "^0.37.119",
"fast-deep-equal": "^3.1.3",
"ts-mixer": "^6.0.4",
"tslib": "^2.6.3"
},
"engines": {
"node": ">=18"
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/builders/node_modules/discord-api-types": {
"version": "0.37.97",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.97.tgz",
"integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==",
"license": "MIT"
},
"node_modules/@discordjs/collection": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz",
@@ -2973,30 +2970,24 @@
}
},
"node_modules/@discordjs/formatters": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.5.0.tgz",
"integrity": "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.0.tgz",
"integrity": "sha512-YIruKw4UILt/ivO4uISmrGq2GdMY6EkoTtD0oS0GvkJFRZbTSdPhzYiUILbJ/QslsvC9H9nTgGgnarnIl4jMfw==",
"license": "Apache-2.0",
"dependencies": {
"discord-api-types": "0.37.97"
"discord-api-types": "^0.37.114"
},
"engines": {
"node": ">=18"
"node": ">=16.11.0"
},
"funding": {
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/formatters/node_modules/discord-api-types": {
"version": "0.37.97",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.97.tgz",
"integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==",
"license": "MIT"
},
"node_modules/@discordjs/rest": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.0.tgz",
"integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==",
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.3.tgz",
"integrity": "sha512-+SO4RKvWsM+y8uFHgYQrcTl/3+cY02uQOH7/7bKbVZsTfrfpoE62o5p+mmV+s7FVhTX82/kQUGGbu4YlV60RtA==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.1",
@@ -3004,10 +2995,10 @@
"@sapphire/async-queue": "^1.5.3",
"@sapphire/snowflake": "^3.5.3",
"@vladfrangu/async_event_emitter": "^2.4.6",
"discord-api-types": "0.37.97",
"discord-api-types": "^0.37.119",
"magic-bytes.js": "^1.10.0",
"tslib": "^2.6.3",
"undici": "6.19.8"
"undici": "6.21.1"
},
"engines": {
"node": ">=18"
@@ -3028,12 +3019,6 @@
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/rest/node_modules/discord-api-types": {
"version": "0.37.97",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.97.tgz",
"integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==",
"license": "MIT"
},
"node_modules/@discordjs/util": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz",
@@ -3047,20 +3032,20 @@
}
},
"node_modules/@discordjs/ws": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz",
"integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.1.tgz",
"integrity": "sha512-PBvenhZG56a6tMWF/f4P6f4GxZKJTBG95n7aiGSPTnodmz4N5g60t79rSIAq7ywMbv8A4jFtexMruH+oe51aQQ==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/collection": "^2.1.0",
"@discordjs/rest": "^2.3.0",
"@discordjs/rest": "^2.4.3",
"@discordjs/util": "^1.1.0",
"@sapphire/async-queue": "^1.5.2",
"@types/ws": "^8.5.10",
"@vladfrangu/async_event_emitter": "^2.2.4",
"discord-api-types": "0.37.83",
"discord-api-types": "^0.37.119",
"tslib": "^2.6.2",
"ws": "^8.16.0"
"ws": "^8.17.0"
},
"engines": {
"node": ">=16.11.0"
@@ -3081,12 +3066,6 @@
"url": "https://github.com/discordjs/discord.js?sponsor"
}
},
"node_modules/@discordjs/ws/node_modules/discord-api-types": {
"version": "0.37.83",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz",
"integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==",
"license": "MIT"
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -3106,9 +3085,9 @@
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
"integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz",
"integrity": "sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==",
"cpu": [
"ppc64"
],
@@ -3123,9 +3102,9 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
"integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.2.tgz",
"integrity": "sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==",
"cpu": [
"arm"
],
@@ -3140,9 +3119,9 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
"integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.2.tgz",
"integrity": "sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==",
"cpu": [
"arm64"
],
@@ -3157,9 +3136,9 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
"integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.2.tgz",
"integrity": "sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==",
"cpu": [
"x64"
],
@@ -3174,9 +3153,9 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
"integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.2.tgz",
"integrity": "sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==",
"cpu": [
"arm64"
],
@@ -3191,9 +3170,9 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
"integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.2.tgz",
"integrity": "sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==",
"cpu": [
"x64"
],
@@ -3208,9 +3187,9 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
"integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.2.tgz",
"integrity": "sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==",
"cpu": [
"arm64"
],
@@ -3225,9 +3204,9 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
"integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.2.tgz",
"integrity": "sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==",
"cpu": [
"x64"
],
@@ -3242,9 +3221,9 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
"integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.2.tgz",
"integrity": "sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==",
"cpu": [
"arm"
],
@@ -3259,9 +3238,9 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
"integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.2.tgz",
"integrity": "sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==",
"cpu": [
"arm64"
],
@@ -3276,9 +3255,9 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
"integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.2.tgz",
"integrity": "sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==",
"cpu": [
"ia32"
],
@@ -3293,9 +3272,9 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
"integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.2.tgz",
"integrity": "sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==",
"cpu": [
"loong64"
],
@@ -3310,9 +3289,9 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
"integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.2.tgz",
"integrity": "sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==",
"cpu": [
"mips64el"
],
@@ -3327,9 +3306,9 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
"integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.2.tgz",
"integrity": "sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==",
"cpu": [
"ppc64"
],
@@ -3344,9 +3323,9 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
"integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.2.tgz",
"integrity": "sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==",
"cpu": [
"riscv64"
],
@@ -3361,9 +3340,9 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
"integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.2.tgz",
"integrity": "sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==",
"cpu": [
"s390x"
],
@@ -3378,9 +3357,9 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
"integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.2.tgz",
"integrity": "sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==",
"cpu": [
"x64"
],
@@ -3394,10 +3373,27 @@
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.2.tgz",
"integrity": "sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
"integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.2.tgz",
"integrity": "sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==",
"cpu": [
"x64"
],
@@ -3412,9 +3408,9 @@
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
"integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.2.tgz",
"integrity": "sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==",
"cpu": [
"arm64"
],
@@ -3429,9 +3425,9 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
"integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.2.tgz",
"integrity": "sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==",
"cpu": [
"x64"
],
@@ -3446,9 +3442,9 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
"integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.2.tgz",
"integrity": "sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==",
"cpu": [
"x64"
],
@@ -3463,9 +3459,9 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
"integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.2.tgz",
"integrity": "sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==",
"cpu": [
"arm64"
],
@@ -3480,9 +3476,9 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
"integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.2.tgz",
"integrity": "sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==",
"cpu": [
"ia32"
],
@@ -3497,9 +3493,9 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
"integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.2.tgz",
"integrity": "sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==",
"cpu": [
"x64"
],
@@ -3555,6 +3551,24 @@
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/compat": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.7.tgz",
"integrity": "sha512-xvv7hJE32yhegJ8xNAnb62ggiAwTYHBpUCWhRxEj/ksvgDJuSXfoDkBcRYaYNFiJ+jH0IE3K16hd+xXzhBgNbg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"peerDependencies": {
"eslint": "^9.10.0"
},
"peerDependenciesMeta": {
"eslint": {
"optional": true
}
}
},
"node_modules/@eslint/config-array": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz",
@@ -6608,6 +6622,17 @@
"@types/trusted-types": "*"
}
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -9890,29 +9915,29 @@
}
},
"node_modules/discord-api-types": {
"version": "0.37.100",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.100.tgz",
"integrity": "sha512-a8zvUI0GYYwDtScfRd/TtaNBDTXwP5DiDVX7K5OmE+DRT57gBqKnwtOC5Ol8z0mRW8KQfETIgiB8U0YZ9NXiCA==",
"version": "0.37.119",
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.119.tgz",
"integrity": "sha512-WasbGFXEB+VQWXlo6IpW3oUv73Yuau1Ig4AZF/m13tXcTKnMpc/mHjpztIlz4+BM9FG9BHQkEXiPto3bKduQUg==",
"license": "MIT"
},
"node_modules/discord.js": {
"version": "14.16.3",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.16.3.tgz",
"integrity": "sha512-EPCWE9OkA9DnFFNrO7Kl1WHHDYFXu3CNVFJg63bfU7hVtjZGyhShwZtSBImINQRWxWP2tgo2XI+QhdXx28r0aA==",
"version": "14.18.0",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.18.0.tgz",
"integrity": "sha512-SvU5kVUvwunQhN2/+0t55QW/1EHfB1lp0TtLZUSXVHDmyHTrdOj5LRKdR0zLcybaA15F+NtdWuWmGOX9lE+CAw==",
"license": "Apache-2.0",
"dependencies": {
"@discordjs/builders": "^1.9.0",
"@discordjs/builders": "^1.10.1",
"@discordjs/collection": "1.5.3",
"@discordjs/formatters": "^0.5.0",
"@discordjs/rest": "^2.4.0",
"@discordjs/formatters": "^0.6.0",
"@discordjs/rest": "^2.4.3",
"@discordjs/util": "^1.1.1",
"@discordjs/ws": "1.1.1",
"@discordjs/ws": "^1.2.1",
"@sapphire/snowflake": "3.5.3",
"discord-api-types": "0.37.100",
"discord-api-types": "^0.37.119",
"fast-deep-equal": "3.1.3",
"lodash.snakecase": "4.1.1",
"tslib": "^2.6.3",
"undici": "6.19.8"
"undici": "6.21.1"
},
"engines": {
"node": ">=18"
@@ -10005,10 +10030,13 @@
}
},
"node_modules/dompurify": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.7.tgz",
"integrity": "sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==",
"license": "(MPL-2.0 OR Apache-2.0)"
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.4.tgz",
"integrity": "sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/domutils": {
"version": "2.8.0",
@@ -10305,9 +10333,9 @@
}
},
"node_modules/esbuild": {
"version": "0.23.1",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
"integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
"version": "0.25.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.2.tgz",
"integrity": "sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
@@ -10318,30 +10346,31 @@
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.23.1",
"@esbuild/android-arm": "0.23.1",
"@esbuild/android-arm64": "0.23.1",
"@esbuild/android-x64": "0.23.1",
"@esbuild/darwin-arm64": "0.23.1",
"@esbuild/darwin-x64": "0.23.1",
"@esbuild/freebsd-arm64": "0.23.1",
"@esbuild/freebsd-x64": "0.23.1",
"@esbuild/linux-arm": "0.23.1",
"@esbuild/linux-arm64": "0.23.1",
"@esbuild/linux-ia32": "0.23.1",
"@esbuild/linux-loong64": "0.23.1",
"@esbuild/linux-mips64el": "0.23.1",
"@esbuild/linux-ppc64": "0.23.1",
"@esbuild/linux-riscv64": "0.23.1",
"@esbuild/linux-s390x": "0.23.1",
"@esbuild/linux-x64": "0.23.1",
"@esbuild/netbsd-x64": "0.23.1",
"@esbuild/openbsd-arm64": "0.23.1",
"@esbuild/openbsd-x64": "0.23.1",
"@esbuild/sunos-x64": "0.23.1",
"@esbuild/win32-arm64": "0.23.1",
"@esbuild/win32-ia32": "0.23.1",
"@esbuild/win32-x64": "0.23.1"
"@esbuild/aix-ppc64": "0.25.2",
"@esbuild/android-arm": "0.25.2",
"@esbuild/android-arm64": "0.25.2",
"@esbuild/android-x64": "0.25.2",
"@esbuild/darwin-arm64": "0.25.2",
"@esbuild/darwin-x64": "0.25.2",
"@esbuild/freebsd-arm64": "0.25.2",
"@esbuild/freebsd-x64": "0.25.2",
"@esbuild/linux-arm": "0.25.2",
"@esbuild/linux-arm64": "0.25.2",
"@esbuild/linux-ia32": "0.25.2",
"@esbuild/linux-loong64": "0.25.2",
"@esbuild/linux-mips64el": "0.25.2",
"@esbuild/linux-ppc64": "0.25.2",
"@esbuild/linux-riscv64": "0.25.2",
"@esbuild/linux-s390x": "0.25.2",
"@esbuild/linux-x64": "0.25.2",
"@esbuild/netbsd-arm64": "0.25.2",
"@esbuild/netbsd-x64": "0.25.2",
"@esbuild/openbsd-arm64": "0.25.2",
"@esbuild/openbsd-x64": "0.25.2",
"@esbuild/sunos-x64": "0.25.2",
"@esbuild/win32-arm64": "0.25.2",
"@esbuild/win32-ia32": "0.25.2",
"@esbuild/win32-x64": "0.25.2"
}
},
"node_modules/escalade": {
@@ -10446,6 +10475,42 @@
"eslint": ">=7.0.0"
}
},
"node_modules/eslint-formatter-gha": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/eslint-formatter-gha/-/eslint-formatter-gha-1.5.2.tgz",
"integrity": "sha512-1TY8AYbrIP9DCcbydYW467nTP67eW79bT+oVKdUehO3WPMP8pn2oOZVkDc8yDcWf35+t2Li1elY5Dh6jTjJ3/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"eslint-formatter-json": "^8.40.0",
"eslint-formatter-stylish": "^8.40.0"
}
},
"node_modules/eslint-formatter-json": {
"version": "8.40.0",
"resolved": "https://registry.npmjs.org/eslint-formatter-json/-/eslint-formatter-json-8.40.0.tgz",
"integrity": "sha512-0bXo4At1EoEU23gFfN7wcDeqRXDHLJnvDOuQKD3Q6FkBlk7L2oVNPYg/sciIWdYrUnCBcKuMit3IWXkdSfzChg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/eslint-formatter-stylish": {
"version": "8.40.0",
"resolved": "https://registry.npmjs.org/eslint-formatter-stylish/-/eslint-formatter-stylish-8.40.0.tgz",
"integrity": "sha512-blbD5ZSQnjNEUaG38VCO4WG9nfDQWE8/IOmt8DFRHXUIfZikaIXmsQTdWNFk0/e0j7RgIVRza86MpsJ+aHgFLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"strip-ansi": "^6.0.1",
"text-table": "^0.2.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
@@ -10472,6 +10537,88 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-webpack-plugin": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-5.0.0.tgz",
"integrity": "sha512-iDhXf2r55KO1UhMfpus8oGp93wdNF+934q5kEkwa7qn3BH9f51QEC11xQidt+8jfqRnEYYZa2/8lhac7U/vqWw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint": "^9.6.1",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"schema-utils": "^4.3.0"
},
"engines": {
"node": ">= 18.12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"eslint": "^8.0.0 || ^9.0.0",
"webpack": "^5.0.0"
}
},
"node_modules/eslint-webpack-plugin/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/eslint-webpack-plugin/node_modules/ajv-keywords": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.3"
},
"peerDependencies": {
"ajv": "^8.8.2"
}
},
"node_modules/eslint-webpack-plugin/node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true,
"license": "MIT"
},
"node_modules/eslint-webpack-plugin/node_modules/schema-utils": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz",
"integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.9",
"ajv": "^8.9.0",
"ajv-formats": "^2.1.1",
"ajv-keywords": "^5.1.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/eslint/node_modules/eslint-scope": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz",
@@ -10766,9 +10913,9 @@
"license": "Apache-2.0"
},
"node_modules/express": {
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"version": "4.21.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"dependencies": {
"accepts": "~1.3.8",
@@ -10790,7 +10937,7 @@
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.10",
"path-to-regexp": "0.1.12",
"proxy-addr": "~2.0.7",
"qs": "6.13.0",
"range-parser": "~1.2.1",
@@ -10805,6 +10952,10 @@
},
"engines": {
"node": ">= 0.10.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/express-rate-limit": {
@@ -14666,9 +14817,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
@@ -15400,9 +15551,9 @@
"license": "ISC"
},
"node_modules/path-to-regexp": {
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/path-type": {
@@ -15959,25 +16110,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/postgres-array": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz",
@@ -18193,6 +18325,13 @@
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
"license": "MIT"
},
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true,
"license": "MIT"
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
@@ -18560,13 +18699,13 @@
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.19.1",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz",
"integrity": "sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==",
"version": "4.19.3",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.3.tgz",
"integrity": "sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.23.0",
"esbuild": "~0.25.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
@@ -18684,9 +18823,9 @@
}
},
"node_modules/undici": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz",
"integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==",
"version": "6.21.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz",
"integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==",
"license": "MIT",
"engines": {
"node": ">=18.17"
+4
View File
@@ -17,6 +17,7 @@
},
"lint-staged": {
"**/*": [
"eslint --fix",
"prettier --ignore-unknown --write"
]
},
@@ -24,6 +25,7 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-typescript": "^7.24.7",
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
"@types/chai": "^4.3.17",
"@types/d3": "^7.4.3",
@@ -45,6 +47,8 @@
"css-loader": "^7.1.2",
"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",
+4 -1
View File
@@ -1,5 +1,6 @@
{
"main": {
"title": "OpenFront (ALPHA)",
"join_discord": "Join the Discord!",
"create_lobby": "Create Lobby",
"join_lobby": "Join Lobby",
@@ -111,7 +112,9 @@
"random": "Random",
"iceland": "Iceland",
"pangaea": "Pangaea",
"japan": "Japan and Neighbors"
"japan": "Japan and Neighbors",
"twoseas": "Between Two Seas",
"knownworld": "Known World"
},
"private_lobby": {
"title": "Join Private Lobby",
File diff suppressed because one or more lines are too long
+69 -57
View File
@@ -1,126 +1,138 @@
{
"name": "Japan",
"width": 2000,
"height": 2000,
"width": 1562,
"height": 1646,
"nations": [
{
"coordinates": [1340, 468],
"coordinates": [1151, 709],
"name": "Hokkaido",
"strength": 3,
"strength": 1,
"flag": "jp"
},
{
"coordinates": [1220, 830],
"coordinates": [1005, 843],
"name": "Tohoku",
"strength": 3,
"strength": 1,
"flag": "jp"
},
{
"coordinates": [1000, 1150],
"coordinates": [858, 1045],
"name": "Chubu",
"strength": 3,
"strength": 1,
"flag": "jp"
},
{
"coordinates": [1150, 1170],
"coordinates": [948, 1037],
"name": "Kanto",
"strength": 3,
"strength": 1,
"flag": "jp"
},
{
"coordinates": [860, 1260],
"name": "Kinki",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [670, 1250],
"name": "Chugoku",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [700, 1350],
"name": "Shikoku",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [510, 1440],
"name": "Kyushu",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [300, 1150],
"name": "South Korea",
"coordinates": [1162, 154],
"name": "Sakhalin",
"strength": 2,
"flag": ""
},
{
"coordinates": [571, 1116],
"name": "Chugoku",
"strength": 1,
"flag": "jp"
},
{
"coordinates": [8612, 1183],
"name": "Shikoku",
"strength": 2,
"flag": "jp"
},
{
"coordinates": [450, 1234],
"name": "Kyushu",
"strength": 2,
"flag": "jp"
},
{
"coordinates": [274, 1058],
"name": "South Korea",
"strength": 3,
"flag": "kr"
},
{
"coordinates": [220, 880],
"coordinates": [160, 841],
"name": "North Korea",
"strength": 2,
"strength": 1,
"flag": "kp"
},
{
"coordinates": [50, 50],
"coordinates": [15, 795],
"name": "China",
"strength": 2,
"strength": 3,
"flag": "cn"
},
{
"coordinates": [900, 200],
"coordinates": [230, 75],
"name": "Russia",
"strength": 2,
"strength": 3,
"flag": "ru"
},
{
"coordinates": [1030, 970],
"coordinates": [898, 923],
"name": "Sado Island",
"strength": 2,
"strength": 1,
"flag": "jp"
},
{
"coordinates": [670, 1130],
"coordinates": [592, 1031],
"name": "Oki Islands",
"strength": 2,
"strength": 1,
"flag": "jp"
},
{
"coordinates": [300, 1930],
"name": "Okinawa",
"coordinates": [741, 1108],
"name": "Kyoto",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [1190, 680],
"name": "Tsugaru Strait",
"coordinates": [219, 972],
"name": "Seoul",
"strength": 3,
"flag": "jp"
"flag": "kr"
},
{
"coordinates": [1130, 1190],
"coordinates": [976, 1073],
"name": "Tokyo",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [1060, 1190],
"coordinates": [911, 1079],
"name": "Mount Fuji",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [1500, 420],
"coordinates": [1298, 564],
"name": "Shiretoko Peninsula",
"strength": 1,
"flag": "jp"
},
{
"coordinates": [435, 1311],
"name": "Sakurajima",
"strength": 1,
"flag": "jp"
},
{
"coordinates": [275, 1614],
"name": "Okinawa",
"strength": 3,
"flag": "jp"
},
{
"coordinates": [490, 1530],
"name": "Sakurajima",
"strength": 3,
"coordinates": [731, 1115],
"name": "Kinki",
"strength": 1,
"flag": "jp"
}
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 386 KiB

After

Width:  |  Height:  |  Size: 9.8 MiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long
+362
View File
@@ -0,0 +1,362 @@
{
"name": "Known_World",
"width": 2652,
"height": 1522,
"nations": [
{
"coordinates": [297, 84],
"name": "Free Folk",
"strength": 2
},
{
"coordinates": [352, 160],
"name": "Nights Watch",
"strength": 2
},
{
"coordinates": [174, 227],
"name": "House Mormont",
"strength": 1
},
{
"coordinates": [504, 186],
"name": "House Magnar",
"strength": 1
},
{
"coordinates": [381, 249],
"name": "House Umber",
"strength": 1
},
{
"coordinates": [494, 295],
"name": "House Karstark",
"strength": 1
},
{
"coordinates": [383, 343],
"name": "House Bolton",
"strength": 2
},
{
"coordinates": [374, 495],
"name": "House locke",
"strength": 2
},
{
"coordinates": [275, 377],
"name": "House Stark",
"strength": 3
},
{
"coordinates": [113, 411],
"name": "House Ryswell",
"strength": 1
},
{
"coordinates": [248, 546],
"name": "House Reed",
"strength": 1
},
{
"coordinates": [116, 570],
"name": "House Flint's Finger",
"strength": 1
},
{
"coordinates": [256, 628],
"name": "House Frey",
"strength": 2
},
{
"coordinates": [535, 700],
"name": "House Royce",
"strength": 1
},
{
"coordinates": [396, 687],
"name": "House Arryn",
"strength": 3
},
{
"coordinates": [416, 623],
"name": "House Corbray",
"strength": 2
},
{
"coordinates": [246, 746],
"name": "Hous Tully",
"strength": 3
},
{
"coordinates": [104, 830],
"name": "House Lannister",
"strength": 3
},
{
"coordinates": [89, 697],
"name": "House Greyjoy",
"strength": 2
},
{
"coordinates": [128, 729],
"name": "House Banefort",
"strength": 1
},
{
"coordinates": [325, 767],
"name": "Harrenhall",
"strength": 1
},
{
"coordinates": [190, 927],
"name": "House Rowan",
"strength": 1
},
{
"coordinates": [383, 864],
"name": "Kings Landing",
"strength": 3
},
{
"coordinates": [475, 820],
"name": "Dragonstone",
"strength": 2
},
{
"coordinates": [333, 902],
"name": "House Meadows",
"strength": 2
},
{
"coordinates": [169, 1006],
"name": "House Tyrell",
"strength": 3
},
{
"coordinates": [301, 1030],
"name": "House Selmy",
"strength": 1
},
{
"coordinates": [456, 975],
"name": "House Baratheon",
"strength": 3
},
{
"coordinates": [523, 963],
"name": "House Tarth",
"strength": 1
},
{
"coordinates": [514, 1156],
"name": "House Martell",
"strength": 3
},
{
"coordinates": [403, 1189],
"name": "House Vaith",
"strength": 1
},
{
"coordinates": [330, 1122],
"name": "House Yronwood",
"strength": 2
},
{
"coordinates": [251, 1158],
"name": "House Qorgyle",
"strength": 1
},
{
"coordinates": [188, 1148],
"name": "House Dayne",
"strength": 1
},
{
"coordinates": [108, 1090],
"name": "House Hightower",
"strength": 2
},
{
"coordinates": [176, 1045],
"name": "House Tarly",
"strength": 1
},
{
"coordinates": [80, 900],
"name": "House Craekham",
"strength": 1
},
{
"coordinates": [183, 808],
"name": "House Brax",
"strength": 1
},
{
"coordinates": [460, 748],
"name": "House Redfort",
"strength": 1
},
{
"coordinates": [651, 630],
"name": "Free City Of Bravos",
"strength": 3
},
{
"coordinates": [658, 871],
"name": "Free City Of Pentos",
"strength": 2
},
{
"coordinates": [702, 1049],
"name": "Free City Of Myr",
"strength": 2
},
{
"coordinates": [603, 1042],
"name": "Free City Of Tyrosh",
"strength": 2
},
{
"coordinates": [933, 1177],
"name": "Free City Of Volantis",
"strength": 3
},
{
"coordinates": [974, 871],
"name": "Free City Of Qohor",
"strength": 2
},
{
"coordinates": [894, 779],
"name": "Free City Of Norvos",
"strength": 2
},
{
"coordinates": [1157, 770],
"name": "Samor",
"strength": 1
},
{
"coordinates": [1119, 1163],
"name": "Mantarys",
"strength": 2
},
{
"coordinates": [1184, 1170],
"name": "Tolos",
"strength": 2
},
{
"coordinates": [1312, 664],
"name": "Omber",
"strength": 1
},
{
"coordinates": [1610, 779],
"name": "Vaes Dothrak",
"strength": 3
},
{
"coordinates": [1651, 500],
"name": "New Ibbish",
"strength": 2
},
{
"coordinates": [1381, 1093],
"name": "Meereen",
"strength": 2
},
{
"coordinates": [1343, 1134],
"name": "Yunkai",
"strength": 2
},
{
"coordinates": [1319, 1230],
"name": "Astaphor",
"strength": 2
},
{
"coordinates": [1606, 1310],
"name": "Port Yhos",
"strength": 1
},
{
"coordinates": [1555, 1110],
"name": "Lhazar",
"strength": 1
},
{
"coordinates": [1832, 1054],
"name": "Zabhad",
"strength": 3
},
{
"coordinates": [1796, 1300],
"name": "Qarth",
"strength": 3
},
{
"coordinates": [1753, 1483],
"name": "Vahar",
"strength": 2
},
{
"coordinates": [1779, 1414],
"name": "Faros",
"strength": 2
},
{
"coordinates": [2107, 1315],
"name": "Yi Ti",
"strength": 3
},
{
"coordinates": [2233, 1462],
"name": "Leng",
"strength": 2
},
{
"coordinates": [2312, 1363],
"name": "Jinqi",
"strength": 2
},
{
"coordinates": [2563, 1474],
"name": "Shadow Lands",
"strength": 2
},
{
"coordinates": [2536, 647],
"name": "Mossovy",
"strength": 2
},
{
"coordinates": [2151, 847],
"name": "Jogos Nhai",
"strength": 3
},
{
"coordinates": [1415, 1348],
"name": "Ghis",
"strength": 2
},
{
"coordinates": [1362, 1397],
"name": "Ghaen",
"strength": 2
},
{
"coordinates": [1849, 1450],
"name": "Great Moraq",
"strength": 3
},
{
"coordinates": [2351, 782],
"name": "N'Ghai",
"strength": 2
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 MiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+1329 -821
View File
File diff suppressed because one or more lines are too long
+53 -53
View File
@@ -1,216 +1,216 @@
{
"name": "MENA",
"width": 2000,
"height": 1000,
"width": 2200,
"height": 964,
"nations": [
{
"coordinates": [200, 100],
"coordinates": [257, 82],
"name": "Spain",
"strength": 1,
"strength": 3,
"flag": "es"
},
{
"coordinates": [50, 135],
"coordinates": [142, 134],
"name": "Portugal",
"strength": 1,
"strength": 2,
"flag": "pt"
},
{
"coordinates": [125, 375],
"coordinates": [142, 348],
"name": "Morocco",
"strength": 1,
"flag": "ma"
},
{
"coordinates": [425, 300],
"coordinates": [490, 296],
"name": "Algeria",
"strength": 1,
"flag": "dz"
},
{
"coordinates": [600, 275],
"coordinates": [675, 225],
"name": "Tunisia",
"strength": 1,
"flag": "tn"
},
{
"coordinates": [750, 450],
"coordinates": [825, 352],
"name": "Libyan Arab Jamahiriya",
"strength": 1,
"flag": "ly"
},
{
"coordinates": [1100, 450],
"coordinates": [1280, 389],
"name": "Egypt",
"strength": 1,
"strength": 2,
"flag": "eg"
},
{
"coordinates": [1333, 333],
"coordinates": [1444, 341],
"name": "Israel",
"strength": 1,
"strength": 3,
"flag": "il"
},
{
"coordinates": [1338, 388],
"coordinates": [1409, 372],
"name": "Palestinian Territory, Occupied",
"strength": 1,
"flag": "ps"
},
{
"coordinates": [1370, 325],
"coordinates": [1460, 294],
"name": "Lebanon",
"strength": 1,
"flag": "lb"
},
{
"coordinates": [1228, 728],
"coordinates": [1259, 761],
"name": "Sudan",
"strength": 1,
"flag": "sd"
},
{
"coordinates": [1450, 275],
"coordinates": [1500, 221],
"name": "Syrian Arab Republic",
"strength": 1,
"flag": "sy"
},
{
"coordinates": [1600, 300],
"coordinates": [1743, 303],
"name": "Iraq",
"strength": 1,
"strength": 2,
"flag": "iq"
},
{
"coordinates": [1550, 600],
"coordinates": [1729, 534],
"name": "Saudi Arabia",
"strength": 1,
"strength": 3,
"flag": "sa"
},
{
"coordinates": [1700, 850],
"coordinates": [1726, 884],
"name": "Yemen",
"strength": 1,
"flag": "ye"
},
{
"coordinates": [1950, 725],
"coordinates": [2134, 654],
"name": "Oman",
"strength": 1,
"flag": "om"
},
{
"coordinates": [1860, 620],
"coordinates": [2025, 576],
"name": "United Arab Emirates",
"strength": 1,
"flag": "ae"
},
{
"coordinates": [1730, 580],
"coordinates": [1924, 538],
"name": "Qatar",
"strength": 1,
"flag": "qa"
},
{
"coordinates": [1900, 350],
"name": "Iran, Islamic Republic Of",
"strength": 1,
"coordinates": [1948, 333],
"name": "Islamic Republic of Iran",
"strength": 3,
"flag": "ir"
},
{
"coordinates": [1300, 150],
"name": "Turkey",
"strength": 1,
"coordinates": [1313, 127],
"name": "Turkiye",
"strength": 3,
"flag": "tr"
},
{
"coordinates": [675, 50],
"coordinates": [776, 39],
"name": "Italy",
"strength": 1,
"strength": 2,
"flag": "it"
},
{
"coordinates": [950, 125],
"coordinates": [1087, 145],
"name": "Greece",
"strength": 1,
"strength": 2,
"flag": "gr"
},
{
"coordinates": [1000, 25],
"coordinates": [1181, 20],
"name": "Bulgaria",
"strength": 1,
"flag": "bg"
},
{
"coordinates": [1980, 45],
"coordinates": [2156, 18],
"name": "Uzbekistan",
"strength": 1,
"flag": "uz"
},
{
"coordinates": [1400, 400],
"coordinates": [1480, 342],
"name": "Jordan",
"strength": 1,
"flag": "jo"
},
{
"coordinates": [750, 930],
"coordinates": [1050, 725],
"name": "Chad",
"strength": 1,
"strength": 2,
"flag": "td"
},
{
"coordinates": [500, 900],
"coordinates": [767, 734],
"name": "Niger",
"strength": 1,
"strength": 2,
"flag": "ne"
},
{
"coordinates": [230, 940],
"coordinates": [408, 553],
"name": "Mali",
"strength": 1,
"flag": "ml"
},
{
"coordinates": [40, 830],
"name": "Mauritania",
"coordinates": [98, 55],
"name": "2",
"strength": 1,
"flag": "mr"
},
{
"coordinates": [1490, 980],
"coordinates": [1562, 828],
"name": "Eritrea",
"strength": 1,
"flag": "er"
},
{
"coordinates": [1630, 460],
"coordinates": [1811, 414],
"name": "Kuwait",
"strength": 1,
"flag": "kw"
},
{
"coordinates": [1550, 25],
"coordinates": [1667, 30],
"name": "Georgia",
"strength": 1,
"flag": "ge"
},
{
"coordinates": [1640, 100],
"coordinates": [1848, 116],
"name": "Azerbaijan",
"strength": 1,
"flag": "az"
},
{
"coordinates": [1226, 280],
"coordinates": [1385, 248],
"name": "Cyprus",
"strength": 1,
"flag": "cy"
},
{
"coordinates": [1800, 40],
"coordinates": [1994, 19],
"name": "Kazakhstan",
"strength": 1,
"strength": 2,
"flag": "kz"
}
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 6.1 MiB

File diff suppressed because one or more lines are too long
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long
+91
View File
@@ -0,0 +1,91 @@
{
"name": "TwoSeas",
"width": 1778,
"height": 1062,
"nations": [
{
"coordinates": [40, 674],
"name": "Bulgaria",
"strength": 2,
"flag": "bg"
},
{
"coordinates": [354, 820],
"name": "Republic of Turkiye",
"strength": 3,
"flag": "tr"
},
{
"coordinates": [105, 474],
"name": "Romania",
"strength": 2,
"flag": "ro"
},
{
"coordinates": [172, 382],
"name": "Moldova",
"strength": 2,
"flag": "md"
},
{
"coordinates": [465, 296],
"name": "Ukraine",
"strength": 3,
"flag": "ua"
},
{
"coordinates": [980, 24],
"name": "Russian Federation",
"strength": 3,
"flag": "ru"
},
{
"coordinates": [1003, 674],
"name": "Georgia",
"strength": 1,
"flag": "ge"
},
{
"coordinates": [1131, 804],
"name": "Republic of Armenia",
"strength": 1,
"flag": "am"
},
{
"coordinates": [856, 1044],
"name": "Syrian Arab Republic",
"strength": 2,
"flag": "sy"
},
{
"coordinates": [1355, 1022],
"name": "Islamic Republic of Iran",
"strength": 3,
"flag": "ir"
},
{
"coordinates": [1355, 18],
"name": "Republic of Azerbaijan",
"strength": 1,
"flag": "az"
},
{
"coordinates": [334, 18],
"name": "Belarus",
"strength": 1,
"flag": "by"
},
{
"coordinates": [1689, 467],
"name": "Kazakhstan",
"strength": 1,
"flag": "kz"
},
{
"coordinates": [1727, 831],
"name": "Turkmenistan",
"strength": 1,
"flag": "tm"
}
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 MiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+37 -10
View File
@@ -25,7 +25,7 @@ import { loadTerrainMap } from "../core/game/TerrainMapLoader";
import { UserSettings } from "../core/game/UserSettings";
import { WorkerClient } from "../core/worker/WorkerClient";
import { InputHandler, MouseMoveEvent, MouseUpEvent } from "./InputHandler";
import { LocalPersistantStats } from "./LocalPersistantStats";
import { endGame, startGame, startTime } from "./LocalPersistantStats";
import { getPersistentIDFromCookie } from "./Main";
import {
SendAttackIntentEvent,
@@ -36,6 +36,7 @@ import {
import { createCanvas } from "./Utils";
import { createRenderer, GameRenderer } from "./graphics/GameRenderer";
export // Is this function needed?
function distSortUnitWorld(tile: TileRef, game: GameView) {
return (a: Unit | UnitView, b: Unit | UnitView) => {
return (
@@ -69,10 +70,7 @@ export function joinLobby(
);
const userSettings: UserSettings = new UserSettings();
LocalPersistantStats.startGame(
lobbyConfig.gameID,
lobbyConfig.gameStartInfo?.config,
);
startGame(lobbyConfig.gameID, lobbyConfig.gameStartInfo?.config);
const transport = new Transport(lobbyConfig, eventBus);
@@ -158,8 +156,9 @@ export class ClientGameRunner {
private hasJoined = false;
private lastMousePosition: { x: number; y: number } | null = null;
private mouseHoverTimer: number | null = null;
private readonly HOVER_DELAY = 200;
private lastMessageTime: number = 0;
private connectionCheckInterval: NodeJS.Timeout | null = null;
constructor(
private lobby: LobbyConfig,
@@ -169,7 +168,9 @@ export class ClientGameRunner {
private transport: Transport,
private worker: WorkerClient,
private gameView: GameView,
) {}
) {
this.lastMessageTime = Date.now();
}
private saveGame(update: WinUpdate) {
const players: PlayerRecord[] = [
@@ -195,18 +196,25 @@ export class ClientGameRunner {
players,
// Not saving turns locally
[],
LocalPersistantStats.startTime(),
startTime(),
Date.now(),
winner,
update.winnerType,
update.allPlayersStats,
);
LocalPersistantStats.endGame(record);
endGame(record);
}
public start() {
consolex.log("starting client game");
this.isActive = true;
this.lastMessageTime = Date.now();
setTimeout(() => {
this.connectionCheckInterval = setInterval(
() => this.onConnectionCheck(),
1000,
);
}, 20000);
this.eventBus.on(MouseUpEvent, (e) => this.inputEvent(e));
this.eventBus.on(MouseMoveEvent, (e) => this.onMouseMove(e));
@@ -245,6 +253,7 @@ export class ClientGameRunner {
this.transport.joinGame(this.turnsSeen);
};
const onmessage = (message: ServerMessage) => {
this.lastMessageTime = Date.now();
if (message.type == "start") {
this.hasJoined = true;
consolex.log("starting game!");
@@ -296,6 +305,10 @@ export class ClientGameRunner {
this.worker.cleanup();
this.isActive = false;
this.transport.leaveGame(saveFullGame);
if (this.connectionCheckInterval) {
clearInterval(this.connectionCheckInterval);
this.connectionCheckInterval = null;
}
}
private inputEvent(event: MouseUpEvent) {
@@ -391,6 +404,20 @@ export class ClientGameRunner {
}
}
}
private onConnectionCheck() {
if (this.transport.isLocal) {
return;
}
const timeSinceLastMessage = Date.now() - this.lastMessageTime;
if (timeSinceLastMessage > 5000) {
console.log(
`No message from server for ${timeSinceLastMessage} ms, reconnecting`,
);
this.lastMessageTime = Date.now();
this.transport.reconnect();
}
}
}
function showErrorModal(
+2 -2
View File
@@ -3,7 +3,7 @@ import { customElement, property } from "lit/decorators.js";
declare global {
interface Window {
adsbygoogle: any[];
adsbygoogle: unknown[];
}
}
@@ -89,7 +89,7 @@ const isElectron = () => {
if (
typeof window !== "undefined" &&
typeof window.process === "object" &&
// @ts-ignore
// @ts-expect-error hidden
window.process.type === "renderer"
) {
return true;
+2
View File
@@ -432,6 +432,7 @@ export class HostLobbyModal extends LitElement {
} as GameConfig),
},
);
return response;
}
private getRandomMap(): GameMapType {
@@ -460,6 +461,7 @@ export class HostLobbyModal extends LitElement {
},
},
);
return response;
}
private async copyToClipboard() {
+1 -1
View File
@@ -93,7 +93,7 @@ export class InputHandler {
private alternateView = false;
private moveInterval: any = null;
private moveInterval: NodeJS.Timeout = null;
private activeKeys = new Set<string>();
private readonly PAN_SPEED = 5;
+35 -33
View File
@@ -20,10 +20,12 @@ const translations = {
es: esTranslations,
};
type Translation = Partial<(typeof translations)[keyof typeof translations]>;
@customElement("lang-selector")
export class LangSelector extends LitElement {
@state() public translations: any = {};
@state() private defaultTranslations: any = {};
@state() public translations: Translation = {};
@state() private defaultTranslations = {};
@state() private currentLang: string = "en";
createRenderRoot() {
@@ -44,10 +46,10 @@ export class LangSelector extends LitElement {
this.translations = await this.loadLanguage(userLang);
this.currentLang = userLang;
this.applyTranslation(this.translations);
this.applyTranslation();
}
private async loadLanguage(lang: string): Promise<any> {
private async loadLanguage(lang: string): Promise<Translation> {
try {
const translation = translations[lang as keyof typeof translations];
if (!translation) throw new Error(`Language file not found: ${lang}`);
@@ -58,7 +60,7 @@ export class LangSelector extends LitElement {
}
}
private applyTranslation(translations: any) {
private applyTranslation() {
const components = [
"single-player-modal",
"host-lobby-modal",
@@ -75,24 +77,14 @@ export class LangSelector extends LitElement {
"public-lobby",
];
document.title = translations.main?.title || document.title;
const main = this.translations.main;
if (main && "title" in main) {
document.title = main.title;
}
document.querySelectorAll("[data-i18n]").forEach((element) => {
const key = element.getAttribute("data-i18n");
const keys = key.split(".");
let text = translations;
for (const k of keys) {
text = text?.[k];
if (!text) break;
}
if (!text && this.defaultTranslations) {
let fallback = this.defaultTranslations;
for (const k of keys) {
fallback = fallback?.[k];
if (!fallback) break;
}
text = fallback;
}
const text = this.translateText(key);
if (text) {
element.innerHTML = text;
} else {
@@ -101,7 +93,7 @@ export class LangSelector extends LitElement {
});
components.forEach((tagName) => {
const el = document.querySelector(tagName) as any;
const el = document.querySelector(tagName) as LitElement;
if (el && typeof el.requestUpdate === "function") {
el.requestUpdate();
} else {
@@ -117,19 +109,13 @@ export class LangSelector extends LitElement {
params: Record<string, string | number> = {},
): string {
const keys = key.split(".");
let text: any = this.translations;
for (const k of keys) {
text = text?.[k];
if (!text) break;
let text = findTranslation(keys, this.translations);
if (!text && this.defaultTranslations) {
text = findTranslation(keys, this.defaultTranslations);
}
if (!text && this.defaultTranslations) {
text = this.defaultTranslations;
for (const k of keys) {
text = text?.[k];
if (!text) return key;
}
if (text == null || typeof text !== "string") {
return null;
}
for (const [param, value] of Object.entries(params)) {
@@ -143,7 +129,7 @@ export class LangSelector extends LitElement {
localStorage.setItem("lang", lang);
this.translations = await this.loadLanguage(lang);
this.currentLang = lang;
this.applyTranslation(this.translations);
this.applyTranslation();
}
render() {
@@ -178,3 +164,19 @@ export class LangSelector extends LitElement {
`;
}
}
function findTranslation(
keys: string[],
translations: Translation,
): string | null {
let ptr: unknown = translations;
for (const k of keys) {
ptr = ptr?.[k];
if (!ptr) break;
}
if (ptr && typeof ptr === "string") {
return ptr;
} else {
return null;
}
}
+46 -48
View File
@@ -9,53 +9,51 @@ export interface LocalStatsData {
};
}
export namespace LocalPersistantStats {
let _startTime: number;
let _startTime: number;
function getStats(): LocalStatsData {
const statsStr = localStorage.getItem("game-records");
return statsStr ? JSON.parse(statsStr) : {};
}
function save(stats: LocalStatsData) {
// To execute asynchronously
setTimeout(
() => localStorage.setItem("game-records", JSON.stringify(stats)),
0,
);
}
// The user can quit the game anytime so better save the lobby as soon as the
// game starts.
export function startGame(id: GameID, lobby: GameConfig) {
if (typeof localStorage === "undefined") {
return;
}
_startTime = Date.now();
const stats = getStats();
stats[id] = { lobby };
save(stats);
}
export function startTime() {
return _startTime;
}
export function endGame(gameRecord: GameRecord) {
if (typeof localStorage === "undefined") {
return;
}
const stats = getStats();
const gameStat = stats[gameRecord.id];
if (!gameStat) {
consolex.log("LocalPersistantStats: game not found");
return;
}
gameStat.gameRecord = gameRecord;
save(stats);
}
function getStats(): LocalStatsData {
const statsStr = localStorage.getItem("game-records");
return statsStr ? JSON.parse(statsStr) : {};
}
function save(stats: LocalStatsData) {
// To execute asynchronously
setTimeout(
() => localStorage.setItem("game-records", JSON.stringify(stats)),
0,
);
}
// The user can quit the game anytime so better save the lobby as soon as the
// game starts.
export function startGame(id: GameID, lobby: GameConfig) {
if (typeof localStorage === "undefined") {
return;
}
_startTime = Date.now();
const stats = getStats();
stats[id] = { lobby };
save(stats);
}
export function startTime() {
return _startTime;
}
export function endGame(gameRecord: GameRecord) {
if (typeof localStorage === "undefined") {
return;
}
const stats = getStats();
const gameStat = stats[gameRecord.id];
if (!gameStat) {
consolex.log("LocalPersistantStats: game not found");
return;
}
gameStat.gameRecord = gameRecord;
save(stats);
}
+2 -2
View File
@@ -86,7 +86,7 @@ class Client {
"google-ad",
) as NodeListOf<GoogleAdElement>;
window.addEventListener("beforeunload", (event) => {
window.addEventListener("beforeunload", () => {
consolex.log("Browser is closing");
if (this.gameStop != null) {
this.gameStop();
@@ -221,7 +221,7 @@ class Client {
);
}
private async handleLeaveLobby(event: CustomEvent) {
private async handleLeaveLobby(/* event: CustomEvent */) {
if (this.gameStop == null) {
return;
}
+12 -11
View File
@@ -2,6 +2,7 @@ import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
import { consolex } from "../core/Consolex";
import { GameMode } from "../core/game/Game";
import { GameID, GameInfo } from "../core/Schemas";
import { generateID } from "../core/Util";
import { JoinLobbyEvent } from "./Main";
@@ -115,26 +116,26 @@ export class PublicLobby extends LitElement {
style="border: 1px solid rgba(255, 255, 255, 0.5)"
/>
<div
class="w-full flex flex-col md:flex-row items-center justify-center gap-4"
class="w-full flex flex-col md:flex-row items-center justify-center md:justify-evenly"
>
<div class="flex flex-col items-start">
<div class="text-md font-medium text-blue-100">
<div class="flex flex-col items-center">
<div class="text-md font-medium text-blue-100 mb-4">
<!-- ${lobby.gameConfig.gameMap} -->
${translateText(
`map.${lobby.gameConfig.gameMap.toLowerCase().replace(/\s+/g, "")}`,
)}
</div>
</div>
<div class="flex flex-col items-start">
<div class="text-md font-medium text-blue-100">
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
${translateText("public_lobby.waiting")}
${lobby.gameConfig.gameMode == GameMode.Team
? translateText("game_mode.teams")
: translateText("game_mode.ffa")}
</div>
</div>
<div class="flex items-center">
<div
class="min-w-20 text-sm font-medium px-2 py-1 bg-white/10 rounded-xl text-blue-100 text-center"
>
<div class="flex flex-col items-center">
<div class="text-md font-medium text-blue-100 mb-2">
${lobby.numClients} / ${lobby.gameConfig.maxPlayers}
</div>
<div class="text-md font-medium text-blue-100">
${timeDisplay}
</div>
</div>
+8 -5
View File
@@ -158,8 +158,7 @@ export class Transport {
private onmessage: (msg: ServerMessage) => void;
private pingInterval: number | null = null;
private isLocal: boolean;
public readonly isLocal: boolean;
constructor(
private lobbyConfig: LobbyConfig,
private eventBus: EventBus,
@@ -267,7 +266,7 @@ export class Transport {
onmessage: (message: ServerMessage) => void,
) {
this.startPing();
this.maybeKillSocket();
this.killExistingSocket();
const wsHost = window.location.host;
const wsProtocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const workerPath = this.lobbyConfig.serverConfig.workerPath(
@@ -304,11 +303,15 @@ export class Transport {
);
if (event.code != 1000) {
console.log(`reconnecting`);
this.connect(onconnect, onmessage);
this.reconnect();
}
};
}
public reconnect() {
this.connect(this.onconnect, this.onmessage);
}
private onSendLogEvent(event: SendLogEvent) {
this.sendMsg(
JSON.stringify(
@@ -586,7 +589,7 @@ export class Transport {
}
}
private maybeKillSocket(): void {
private killExistingSocket(): void {
if (this.socket == null) {
return;
}
+2
View File
@@ -21,6 +21,8 @@ export const MapDescription: Record<keyof typeof GameMapType, string> = {
Australia: "Australia",
Iceland: "Iceland",
Japan: "Japan",
TwoSeas: "Between Two Seas",
KnownWorld: "Known World",
};
@customElement("map-display")
-1
View File
@@ -40,7 +40,6 @@ export function createRenderer(
const startingModal = document.querySelector(
"game-starting-modal",
) as GameStartingModal;
startingModal instanceof GameStartingModal;
startingModal.hide();
// TODO maybe append this to dcoument instead of querying for them?
+11 -10
View File
@@ -2,16 +2,17 @@ import { LitElement, css, html } from "lit";
import { customElement, state } from "lit/decorators.js";
const emojiTable: string[][] = [
["😀", "😱", "🤡", "😡", "🥺"],
["😈", "👏", "🥉", "🥈", "🥇"],
["🤙", "🥰", "😇", "😊", "🔥"],
["💪", "🏳️", "💀", "😭", "🫡"],
["🤦‍♂️", "👎", "👍", "🥱", "💔"],
["😎", "❤️", "💰", "🤝", "🖕"],
["💥", "🆘", "🕊️", "", ""],
["", "", "↗️", "⬆️", "↘️"],
["", "", "", "☢️", "⚠️"],
["😭", "😞", "👋", "🐀", ""],
["😀", "😊", "🥰", "😇", "😎"],
["😞", "🥺", "😭", "😱", "😡"],
["😈", "🤡", "🖕", "🥱", "🤦‍♂️"],
["👋", "👏", "🤌", "💪", "🫡"],
["👍", "👎", "", "🐔", "🐀"],
["🤝", "🆘", "🕊️", "🏳️", ""],
["🔥", "💥", "💀", "", ""],
["", "", "↗️", "👑", "🥇"],
["", "🎯", "➡️", "🥈", "🥉"],
["↙️", "⬇️", "↘️", "❤️", "💔"],
["💰", "⚓", "⛵", "🏡", "🛡️"],
];
@customElement("emoji-table")
+44 -17
View File
@@ -61,6 +61,7 @@ export class EventsDisplay extends LitElement implements Layer {
private events: Event[] = [];
@state() private incomingAttacks: AttackUpdate[] = [];
@state() private outgoingAttacks: AttackUpdate[] = [];
@state() private outgoingLandAttacks: AttackUpdate[] = [];
@state() private outgoingBoats: UnitView[] = [];
@state() private _hidden: boolean = false;
@state() private newEvents: number = 0;
@@ -134,6 +135,10 @@ export class EventsDisplay extends LitElement implements Layer {
.outgoingAttacks()
.filter((a) => a.targetID != 0);
this.outgoingLandAttacks = myPlayer
.outgoingAttacks()
.filter((a) => a.targetID == 0);
this.outgoingBoats = myPlayer
.units()
.filter((u) => u.type() === UnitType.TransportShip);
@@ -396,14 +401,7 @@ export class EventsDisplay extends LitElement implements Layer {
: event.description;
}
private renderAttacks() {
if (
this.incomingAttacks.length === 0 &&
this.outgoingAttacks.length === 0
) {
return html``;
}
private renderIncomingAttacks() {
return html`
${this.incomingAttacks.length > 0
? html`
@@ -431,6 +429,11 @@ export class EventsDisplay extends LitElement implements Layer {
</tr>
`
: ""}
`;
}
private renderOutgoingAttacks() {
return html`
${this.outgoingAttacks.length > 0
? html`
<tr class="border-t border-gray-700">
@@ -467,6 +470,37 @@ export class EventsDisplay extends LitElement implements Layer {
`;
}
private renderOutgoingLandAttacks() {
return html`
${this.outgoingLandAttacks.length > 0
? html`
<tr class="border-t border-gray-700">
<td class="lg:p-3 p-1 text-left text-gray-400">
${this.outgoingLandAttacks.map(
(landAttack) => html`
<button translate="no" class="ml-2">
${renderTroops(landAttack.troops)} Wilderness
</button>
${!landAttack.retreating
? html`<button
${landAttack.retreating ? "disabled" : ""}
@click=${() => {
this.emitCancelAttackIntent(landAttack.id);
}}
>
</button>`
: "(retreating...)"}
`,
)}
</td>
</tr>
`
: ""}
`;
}
private renderBoats() {
if (this.outgoingBoats.length === 0) {
return html``;
@@ -497,14 +531,6 @@ export class EventsDisplay extends LitElement implements Layer {
}
render() {
if (
this.events.length === 0 &&
this.incomingAttacks.length === 0 &&
this.outgoingAttacks.length === 0 &&
this.outgoingBoats.length === 0
) {
return html``;
}
this.events.sort((a, b) => {
const aPrior = a.priority ?? 100000;
const bPrior = b.priority ?? 100000;
@@ -602,7 +628,8 @@ export class EventsDisplay extends LitElement implements Layer {
</tr>
`,
)}
${this.renderAttacks()} ${this.renderBoats()}
${this.renderIncomingAttacks()} ${this.renderOutgoingAttacks()}
${this.renderOutgoingLandAttacks()} ${this.renderBoats()}
</tbody>
</table>
</div>
+14
View File
@@ -106,6 +106,11 @@ export class OptionsMenu extends LitElement implements Layer {
this.eventBus.emit(new RefreshGraphicsEvent());
}
private onToggleFocusLockedButtonClick() {
this.userSettings.toggleFocusLocked();
this.requestUpdate();
}
private onToggleLeftClickOpensMenu() {
this.userSettings.toggleLeftClickOpenMenu();
}
@@ -200,6 +205,15 @@ export class OptionsMenu extends LitElement implements Layer {
? "Opens menu"
: "Attack"),
})}
${button({
onClick: this.onToggleFocusLockedButtonClick,
title: "Lock Focus",
children:
"🗺: " +
(this.userSettings.focusLocked()
? "Focus locked"
: "Hover focus"),
})}
</div>
</div>
`;
+37 -8
View File
@@ -1,34 +1,63 @@
import { blue, red } from "../../../core/configuration/Colors";
import { GameMode, TeamName } from "../../../core/game/Game";
import { GameView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
import { Layer } from "./Layer";
export class SpawnTimer implements Layer {
private ratio = 0;
private leftColor = "rgba(0, 128, 255, 0.7)";
private rightColor = "rgba(0, 0, 0, 0.5)";
constructor(
private game: GameView,
private transformHandler: TransformHandler,
) {}
init() {}
tick() {}
tick() {
if (this.game.inSpawnPhase()) {
this.ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns();
return;
}
if (this.game.config().gameConfig().gameMode != GameMode.Team) {
this.ratio = 0;
return;
}
const numBlueTiles = this.game
.players()
.filter((p) => p.teamName() == TeamName.Blue)
.reduce((acc, p) => acc + p.numTilesOwned(), 0);
const numRedTiles = this.game
.players()
.filter((p) => p.teamName() == TeamName.Red)
.reduce((acc, p) => acc + p.numTilesOwned(), 0);
this.ratio = numBlueTiles / (numBlueTiles + numRedTiles);
this.leftColor = blue.toRgbString();
this.rightColor = red.toRgbString();
}
shouldTransform(): boolean {
return false;
}
renderLayer(context: CanvasRenderingContext2D) {
if (!this.game.inSpawnPhase()) {
if (this.ratio == 0) {
return;
}
const barHeight = 15;
const barHeight = 10;
const barBackgroundWidth = this.transformHandler.width();
const ratio = this.game.ticks() / this.game.config().numSpawnPhaseTurns();
// Draw bar background
context.fillStyle = "rgba(0, 0, 0, 0.5)";
context.fillStyle = this.rightColor;
context.fillRect(0, 0, barBackgroundWidth, barHeight);
context.fillStyle = "rgba(0, 128, 255, 0.7)";
context.fillRect(0, 0, barBackgroundWidth * ratio, barHeight);
context.fillStyle = this.leftColor;
context.fillRect(0, 0, barBackgroundWidth * this.ratio, barHeight);
}
}
+1 -1
View File
@@ -216,7 +216,7 @@ export class StructureLayer implements Layer {
private handleUnitRendering(unit: UnitView) {
const unitType = unit.constructionType() ?? unit.type();
let iconType = unitType;
const iconType = unitType;
if (!this.isUnitTypeSupported(unitType)) return;
const config = this.unitConfigs[unitType];
+4 -3
View File
@@ -12,11 +12,12 @@ import { Layer } from "./Layer";
// Add this at the top of your file
declare global {
interface Window {
adsbygoogle: any[];
adsbygoogle: unknown[];
}
}
// Add this at the top of your file
declare let adsbygoogle: any[];
declare let adsbygoogle: unknown[];
@customElement("win-modal")
export class WinModal extends LitElement implements Layer {
@@ -257,7 +258,7 @@ export class WinModal extends LitElement implements Layer {
});
}
renderLayer(context: CanvasRenderingContext2D) {}
renderLayer(/* context: CanvasRenderingContext2D */) {}
shouldTransform(): boolean {
return false;
+5
View File
@@ -328,6 +328,11 @@ label.option-card:hover {
center / cover;
}
#helpModal .sam-launcher-icon {
mask: url("../../resources/images/SamLauncherIconWhite.svg") no-repeat
center / cover;
}
#helpModal .atom-bomb-icon {
mask: url("../../resources/images/NukeIconWhite.svg") no-repeat center / cover;
}
+6
View File
@@ -7,12 +7,14 @@ import europe from "../../../resources/maps/EuropeThumb.webp";
import gatewayToTheAtlantic from "../../../resources/maps/GatewayToTheAtlanticThumb.webp";
import iceland from "../../../resources/maps/IcelandThumb.webp";
import japan from "../../../resources/maps/JapanThumb.webp";
import knownworld from "../../../resources/maps/KnownWorldThumb.webp";
import mars from "../../../resources/maps/MarsThumb.webp";
import mena from "../../../resources/maps/MenaThumb.webp";
import northAmerica from "../../../resources/maps/NorthAmericaThumb.webp";
import oceania from "../../../resources/maps/OceaniaThumb.webp";
import pangaea from "../../../resources/maps/PangaeaThumb.webp";
import southAmerica from "../../../resources/maps/SouthAmericaThumb.webp";
import twoSeas from "../../../resources/maps/TwoSeasThumb.webp";
import world from "../../../resources/maps/WorldMapThumb.webp";
import { GameMapType } from "../../core/game/Game";
@@ -51,6 +53,10 @@ export function getMapsImage(map: GameMapType): string {
return iceland;
case GameMapType.Japan:
return japan;
case GameMapType.TwoSeas:
return twoSeas;
case GameMapType.KnownWorld:
return knownworld;
default:
return "";
}
+51 -8
View File
@@ -2,6 +2,7 @@ import {
Difficulty,
Game,
GameMapType,
GameMode,
GameType,
Gold,
Player,
@@ -58,15 +59,54 @@ export abstract class DefaultServerConfig implements ServerConfig {
return 60 * 1000;
}
lobbyMaxPlayers(map: GameMapType): number {
if (map == GameMapType.World) {
return Math.random() < 0.3 ? 150 : 60;
}
// Maps with ~4 mil pixels
if (
[GameMapType.Mars, GameMapType.Africa, GameMapType.BlackSea].includes(map)
[
GameMapType.GatewayToTheAtlantic,
GameMapType.SouthAmerica,
GameMapType.NorthAmerica,
GameMapType.Africa,
GameMapType.Europe,
].includes(map)
) {
return Math.random() < 0.3 ? 70 : 50;
return Math.random() < 0.2 ? 150 : 70;
}
return Math.random() < 0.3 ? 60 : 40;
// Maps with ~2.5 - ~3.5 mil pixels
if (
[
GameMapType.Australia,
GameMapType.Iceland,
GameMapType.Britannia,
GameMapType.Asia,
].includes(map)
) {
return Math.random() < 0.2 ? 100 : 50;
}
// Maps with ~2 mil pixels
if (
[
GameMapType.Mena,
GameMapType.Mars,
GameMapType.Oceania,
GameMapType.Japan, // Japan at this level because its 2/3 water
].includes(map)
) {
return Math.random() < 0.2 ? 70 : 40;
}
// Maps smaller than ~2 mil pixels
if (
[GameMapType.TwoSeas, GameMapType.BlackSea, GameMapType.Pangaea].includes(
map,
)
) {
return Math.random() < 0.2 ? 60 : 35;
}
// world belongs with the ~2 mils, but these amounts never made sense so I assume the insanity is intended.
if (map == GameMapType.World) {
return Math.random() < 0.2 ? 150 : 60;
}
// default return for non specified map
return Math.random() < 0.2 ? 85 : 45;
}
workerIndex(gameID: GameID): number {
return simpleHash(gameID) % this.numWorkers();
@@ -240,7 +280,7 @@ export class DefaultConfig implements Config {
cost: (p: Player) =>
p.type() == PlayerType.Human && this.infiniteGold()
? 0
: 20_000_000,
: 25_000_000,
territoryBound: false,
};
case UnitType.MIRVWarhead:
@@ -294,7 +334,7 @@ export class DefaultConfig implements Config {
p.type() == PlayerType.Human && this.infiniteGold()
? 0
: Math.min(
1_000_000,
2_000_000,
Math.pow(
2,
p.unitsIncludingConstruction(UnitType.City).length,
@@ -337,6 +377,9 @@ export class DefaultConfig implements Config {
return 600 * 10; // 10 minutes.
}
percentageTilesOwnedToWin(): number {
if (this._gameConfig.gameMode == GameMode.Team) {
return 95;
}
return 80;
}
boatMaxNumber(): number {
+5 -1
View File
@@ -190,7 +190,11 @@ export class AttackExecution implements Execution {
tick(ticks: number) {
if (this.attack.retreated()) {
this.retreat(malusForRetreat);
if (this.attack.target().isPlayer()) {
this.retreat(malusForRetreat);
} else {
this.retreat();
}
this.active = false;
return;
}
+4 -2
View File
@@ -60,6 +60,8 @@ export enum GameMapType {
Australia = "Australia",
Iceland = "Iceland",
Japan = "Japan",
TwoSeas = "Between Two Seas",
KnownWorld = "Known World",
}
export enum GameType {
@@ -135,8 +137,8 @@ export class Cell {
private strRepr: string;
constructor(
public readonly x,
public readonly y,
public readonly x: number,
public readonly y: number,
) {
this.strRepr = `Cell[${this.x},${this.y}]`;
}
+2 -2
View File
@@ -141,7 +141,7 @@ export class GameImpl implements Game {
}
addUpdate(update: GameUpdate) {
(this.updates[update.type] as any[]).push(update);
(this.updates[update.type] as GameUpdate[]).push(update);
}
nextUnitID(): number {
@@ -383,7 +383,7 @@ export class GameImpl implements Game {
}
playerByClientID(id: ClientID): Player | null {
for (const [pID, player] of this._players) {
for (const [, player] of this._players) {
if (player.clientID() == id) {
return player;
}
+8
View File
@@ -32,6 +32,9 @@ import {
} from "./GameUpdates";
import { TerraNulliusImpl } from "./TerraNulliusImpl";
import { UnitGrid } from "./UnitGrid";
import { UserSettings } from "./UserSettings";
const userSettings: UserSettings = new UserSettings();
export class UnitView {
public _wasUpdated = true;
@@ -384,6 +387,10 @@ export class GameView implements GameMap {
throw Error(`player id ${id} not found`);
}
players(): PlayerView[] {
return Array.from(this._players.values());
}
playerBySmallID(id: number): PlayerView | TerraNullius {
if (id == 0) {
return new TerraNulliusImpl();
@@ -542,6 +549,7 @@ export class GameView implements GameMap {
}
focusedPlayer(): PlayerView | null {
if (userSettings.focusLocked()) return this.myPlayer();
return this._focusedPlayer;
}
setFocusedPlayer(player: PlayerView | null): void {
+2 -1
View File
@@ -92,6 +92,7 @@ export class PlayerImpl implements Player {
public _incomingAttacks: Attack[] = [];
public _outgoingAttacks: Attack[] = [];
public _outgoingLandAttacks: Attack[] = [];
constructor(
private mg: GameImpl,
@@ -1003,7 +1004,7 @@ export class PlayerImpl implements Player {
// It's a probability list, so if an element appears twice it's because it's
// twice more likely to be picked later.
tradingPorts(port: Unit): Unit[] {
let ports = this.mg
const ports = this.mg
.players()
.filter((p) => p != port.owner() && p.canTrade(port.owner()))
.flatMap((p) => p.units(UnitType.Port))
+2
View File
@@ -39,6 +39,8 @@ const MAP_FILE_NAMES: Record<GameMapType, string> = {
[GameMapType.Australia]: "Australia",
[GameMapType.Iceland]: "Iceland",
[GameMapType.Japan]: "Japan",
[GameMapType.TwoSeas]: "TwoSeas",
[GameMapType.KnownWorld]: "KnownWorld",
};
class GameMapLoader {
+8
View File
@@ -24,10 +24,18 @@ export class UserSettings {
return this.get("settings.leftClickOpensMenu", false);
}
focusLocked() {
return this.get("settings.focusLocked", false);
}
toggleLeftClickOpenMenu() {
this.set("settings.leftClickOpensMenu", !this.leftClickOpensMenu());
}
toggleFocusLocked() {
this.set("settings.focusLocked", !this.focusLocked());
}
toggleEmojis() {
this.set("settings.emojis", !this.emojis());
}
-4
View File
@@ -32,7 +32,3 @@ declare module "*.html" {
const content: string;
export default content;
}
declare module "*.json" {
const value: any;
export default value;
}
+43 -13
View File
@@ -120,19 +120,49 @@ function processShore(map: Terrain[][]): Coord[] {
}
function processDistToLand(shorelineWaters: Coord[], map: Terrain[][]) {
console.log("Setting Water tiles magnitude = distance from land");
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
const tile = map[x][y];
if (tile.type == TerrainType.Water) {
if (shorelineWaters.some((coord) => coord.x == x && coord.y == y)) {
tile.magnitude = 0;
} else {
const dist = shorelineWaters.map(
(coord) => Math.abs(x - coord.x) + Math.abs(y - coord.y),
);
tile.magnitude = Math.min(...dist);
}
console.log(
"Setting Water tiles magnitude = Manhattan distance from nearest land",
);
const width = map.length;
const height = map[0].length;
const visited = Array.from({ length: width }, () =>
Array(height).fill(false),
);
const queue: { x: number; y: number; dist: number }[] = [];
for (const { x, y } of shorelineWaters) {
queue.push({ x, y, dist: 0 });
visited[x][y] = true;
map[x][y].magnitude = 0;
}
const directions = [
{ dx: 0, dy: 1 },
{ dx: 1, dy: 0 },
{ dx: 0, dy: -1 },
{ dx: -1, dy: 0 },
];
while (queue.length > 0) {
const { x, y, dist } = queue.shift()!;
for (const { dx, dy } of directions) {
const nx = x + dx;
const ny = y + dy;
if (
nx >= 0 &&
ny >= 0 &&
nx < width &&
ny < height &&
!visited[nx][ny] &&
map[nx][ny].type === TerrainType.Water
) {
visited[nx][ny] = true;
map[nx][ny].magnitude = dist + 1;
queue.push({ x: nx, y: ny, dist: dist + 1 });
}
}
}
+3
View File
@@ -19,6 +19,9 @@ const maps = [
"Australia",
"Pangaea",
"Iceland",
"TwoSeas",
"Japan",
"KnownWorld",
];
const removeSmall = true;
+7 -7
View File
@@ -456,7 +456,7 @@ export class GameServer {
const lastHashTurn = this.turns.length - 10;
let { mostCommonHash, outOfSyncClients } =
const { mostCommonHash, outOfSyncClients } =
this.findOutOfSyncClients(lastHashTurn);
if (outOfSyncClients.length == 0) {
@@ -464,11 +464,6 @@ export class GameServer {
return;
}
if (outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)) {
// If half clients out of sync assume all are out of sync.
outOfSyncClients = this.activeClients;
}
const serverDesync = ServerDesyncSchema.safeParse({
type: "desync",
turn: lastHashTurn,
@@ -519,7 +514,7 @@ export class GameServer {
}
// Create a list of clients whose hash doesn't match the most common one
const outOfSyncClients: Client[] = [];
let outOfSyncClients: Client[] = [];
for (const client of this.activeClients) {
if (client.hashes.has(turnNumber)) {
@@ -530,6 +525,11 @@ export class GameServer {
}
}
// If half clients out of sync assume all are out of sync.
if (outOfSyncClients.length >= Math.floor(this.activeClients.length / 2)) {
outOfSyncClients = this.activeClients;
}
return {
mostCommonHash,
outOfSyncClients,
+5 -5
View File
@@ -16,7 +16,7 @@ export interface Gatekeeper {
// The wrapper for request handlers with optional rate limiting
httpHandler: (
limiterType: LimiterType,
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
) => (req: Request, res: Response, next: NextFunction) => Promise<void>;
// The wrapper for WebSocket message handlers with rate limiting
@@ -67,8 +67,8 @@ async function getGatekeeper(): Promise<Gatekeeper> {
// Use dynamic import for ES modules
// Using a type assertion to avoid TypeScript errors for optional modules
const module = await import(
"./gatekeeper/RealGatekeeper.js" as any
).catch(() => import("./gatekeeper/RealGatekeeper.js" as any));
"./gatekeeper/RealGatekeeper.js" as string
).catch(() => import("./gatekeeper/RealGatekeeper.js" as string));
if (!module || !module.RealGatekeeper) {
console.log(
@@ -95,7 +95,7 @@ export class GatekeeperWrapper implements Gatekeeper {
httpHandler(
limiterType: LimiterType,
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
@@ -129,7 +129,7 @@ export class NoOpGatekeeper implements Gatekeeper {
// Simple pass-through with no rate limiting
httpHandler(
limiterType: LimiterType,
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>,
fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>,
) {
return async (req: Request, res: Response, next: NextFunction) => {
try {
+9 -6
View File
@@ -5,7 +5,7 @@ import http from "http";
import path from "path";
import { fileURLToPath } from "url";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
import { Difficulty, GameMapType, GameType } from "../core/game/Game";
import { Difficulty, GameMapType, GameMode, GameType } from "../core/game/Game";
import { PseudoRandom } from "../core/PseudoRandom";
import { GameConfig, GameInfo } from "../core/Schemas";
import { generateID } from "../core/Util";
@@ -237,6 +237,7 @@ async function schedulePublicGame() {
instantBuild: false,
disableNPCs: false,
disableNukes: false,
gameMode: Math.random() < 0.7 ? GameMode.FFA : GameMode.Team,
bots: 400,
} as GameConfig;
@@ -280,20 +281,22 @@ function getNextMap(): GameMapType {
}
const frequency = {
World: 2,
World: 1,
Europe: 3,
Mena: 2,
NorthAmerica: 3,
BlackSea: 2,
Pangaea: 2,
NorthAmerica: 2,
BlackSea: 1,
Pangaea: 1,
Africa: 2,
Asia: 1,
Mars: 1,
Britannia: 2,
GatewayToTheAtlantic: 3,
GatewayToTheAtlantic: 2,
Australia: 2,
Iceland: 2,
SouthAmerica: 3,
Japan: 3,
TwoSeas: 3,
};
Object.keys(GameMapType).forEach((key) => {
+3 -3
View File
@@ -50,7 +50,7 @@ export function setupMetricsServer() {
} else if (line.trim() && !line.startsWith("#")) {
// Add worker label to each metric line and collect for later
const processedLine = line.replace(
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9\.e+-]+.*)/,
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9.e+-]+.*)/,
(match, metricName, existingLabels, valueAndRest) => {
if (existingLabels) {
return `${metricName}{${existingLabels},worker="master"}${valueAndRest}`;
@@ -108,7 +108,7 @@ export function setupMetricsServer() {
// Process and collect actual metric values
try {
const processedLine = line.replace(
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9\.e+-]+.*)/,
/^([a-z][a-z0-9_]*)(?:{([^}]*)})?(\s+[0-9.e+-]+.*)/,
(match, metricName, existingLabels, valueAndRest) => {
if (existingLabels) {
return `${metricName}{${existingLabels},worker="worker-${i}"}${valueAndRest}`;
@@ -122,7 +122,7 @@ export function setupMetricsServer() {
if (processedLine !== line) {
allMetricValues.push(processedLine);
} else if (
line.match(/^[a-z][a-z0-9_]*(?:{[^}]*})?\s+[0-9\.e+-]+.*/)
line.match(/^[a-z][a-z0-9_]*(?:{[^}]*})?\s+[0-9.e+-]+.*/)
) {
// This looks like a metric line but didn't match our regex, try a more general approach
const parts = line.split(/({|\s+)/);
+7 -1
View File
@@ -3,7 +3,13 @@ import { ClientID, GameID, LogSeverity } from "../core/Schemas";
export interface slogMsg {
logKey: string;
msg: string;
data?: any;
data?: {
stack?: unknown;
clientID?: unknown;
clientIP?: unknown;
gameID?: unknown;
isRejoin?: unknown;
};
severity?: LogSeverity;
gameID?: GameID;
clientID?: ClientID;
+1
View File
@@ -8,6 +8,7 @@
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"useDefineForClassFields": false
},
"include": [
+4
View File
@@ -1,4 +1,5 @@
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";
@@ -129,6 +130,9 @@ export default async (env, argv) => {
],
options: { concurrency: 100 },
}),
new ESLintPlugin({
context: __dirname,
}),
],
optimization: {
// Add optimization configuration for better caching