This commit is contained in:
evanpelle
2025-12-17 08:38:10 -08:00
parent 58c7cdd46f
commit 03778a722d
126 changed files with 6514 additions and 1010 deletions
+93 -33
View File
@@ -43,6 +43,7 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-typescript": "^7.24.7",
"@bufbuild/protobuf": "^2.10.2",
"@datastructures-js/priority-queue": "^6.3.3",
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
@@ -105,6 +106,7 @@
"style-loader": "^4.0.0",
"tailwindcss": "^3.4.17",
"ts-loader": "^9.5.2",
"ts-proto": "^2.8.3",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"typescript": "^5.7.2",
@@ -1049,7 +1051,6 @@
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
@@ -2803,6 +2804,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/@bufbuild/protobuf": {
"version": "2.10.2",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.10.2.tgz",
"integrity": "sha512-uFsRXwIGyu+r6AMdz+XijIIZJYpoWeYzILt5yZ2d3mCjQrWUTVpVD9WL/jZAbvp+Ed04rOhrsk7FiTcEDseB5A==",
"dev": true,
"license": "(Apache-2.0 AND BSD-3-Clause)"
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
@@ -2922,7 +2930,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
},
@@ -2946,7 +2953,6 @@
}
],
"license": "MIT",
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5101,7 +5107,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
"integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -6420,6 +6425,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6436,6 +6442,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6452,6 +6459,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6468,6 +6476,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6484,6 +6493,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6500,6 +6510,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6516,6 +6527,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6532,6 +6544,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6548,6 +6561,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6564,6 +6578,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=10"
}
@@ -6599,6 +6614,7 @@
"integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==",
"devOptional": true,
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@swc/counter": "^0.1.3"
}
@@ -7253,7 +7269,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.32.tgz",
"integrity": "sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==",
"license": "MIT",
"peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -7488,7 +7503,6 @@
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.34.1",
"@typescript-eslint/types": "8.34.1",
@@ -8259,7 +8273,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -8318,7 +8331,6 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -8884,7 +8896,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
@@ -9063,7 +9074,6 @@
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"node-addon-api": "^7.0.0",
"prebuild-install": "^7.1.1"
@@ -9079,13 +9089,25 @@
"dev": true,
"license": "MIT"
},
"node_modules/case-anything": {
"version": "2.1.13",
"resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz",
"integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.13"
},
"funding": {
"url": "https://github.com/sponsors/mesqueeb"
}
},
"node_modules/chai": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
"integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"assertion-error": "^2.0.1",
"check-error": "^2.1.1",
@@ -9690,7 +9712,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -10278,7 +10299,6 @@
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
"dev": true,
"license": "ISC",
"peer": true,
"engines": {
"node": ">=12"
}
@@ -10739,6 +10759,29 @@
"url": "https://dotenvx.com"
}
},
"node_modules/dprint-node": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz",
"integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==",
"dev": true,
"license": "MIT",
"dependencies": {
"detect-libc": "^1.0.3"
}
},
"node_modules/dprint-node/node_modules/detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"detect-libc": "bin/detect-libc.js"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -11021,7 +11064,6 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -11191,7 +11233,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -11550,7 +11591,6 @@
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
@@ -12451,7 +12491,6 @@
"integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/html-minifier-terser": "^6.0.0",
"html-minifier-terser": "^6.0.2",
@@ -14981,7 +15020,6 @@
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"cssstyle": "^4.2.1",
"data-urls": "^5.0.0",
@@ -16695,7 +16733,6 @@
"integrity": "sha512-dyuThzncsgEgJZnvd/A/5x6IkUERbK+phXqUQrI+0C6WE+8xqGH5VChRTLecemhgZF0kQ+gZOM3tJTX9937xpg==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@pixi/colord": "^2.9.6",
"@types/css-font-loading-module": "^0.0.12",
@@ -16753,7 +16790,6 @@
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -17092,7 +17128,6 @@
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -18362,7 +18397,6 @@
"integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==",
"dev": true,
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"@sinonjs/commons": "^3.0.1",
"@sinonjs/fake-timers": "^13.0.5",
@@ -19075,7 +19109,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -19307,7 +19340,6 @@
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -19495,7 +19527,6 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -19549,6 +19580,42 @@
"node": ">=0.3.1"
}
},
"node_modules/ts-poet": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz",
"integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"dprint-node": "^1.0.8"
}
},
"node_modules/ts-proto": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.8.3.tgz",
"integrity": "sha512-TdXInqG+61pj/TvORqITWjvjTTsL1EZxwX49iEj89+xFAcqPT8tjChpAGQXzfcF4MJwvNiuoCEbBOKqVf3ds3g==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0",
"case-anything": "^2.1.13",
"ts-poet": "^6.12.0",
"ts-proto-descriptors": "2.0.0"
},
"bin": {
"protoc-gen-ts_proto": "protoc-gen-ts_proto"
}
},
"node_modules/ts-proto-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz",
"integrity": "sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==",
"dev": true,
"license": "ISC",
"dependencies": {
"@bufbuild/protobuf": "^2.0.0"
}
},
"node_modules/tsconfig-paths": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
@@ -19578,8 +19645,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD",
"peer": true
"license": "0BSD"
},
"node_modules/tsx": {
"version": "4.20.3",
@@ -19668,7 +19734,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -19963,7 +20028,6 @@
"integrity": "sha512-QaNKAvGCDRh3wW1dsDjeMdDXwZm2vqq3zn6Pvq4rHOEOGSaUMgOOjG2Y9ZbIGzpfkJk9ZYTHpDqgDfeBDcnLaw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -20013,7 +20077,6 @@
"integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.6.1",
"@webpack-cli/configtest": "^3.0.1",
@@ -20097,7 +20160,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -20213,7 +20275,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -20306,7 +20367,6 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
+4 -1
View File
@@ -18,7 +18,8 @@
"lint": "eslint",
"lint:fix": "eslint --fix",
"prepare": "husky",
"gen-maps": "cd map-generator && go run . && npm run format"
"gen-maps": "cd map-generator && go run . && npm run format",
"gen-proto": "npx protoc --plugin=./node_modules/.bin/protoc-gen-ts_proto --ts_proto_out=. src/core/game/GameUpdates.proto --ts_proto_opt=unrecognizedEnum=false"
},
"lint-staged": {
"**/*": [
@@ -30,6 +31,7 @@
"@babel/core": "^7.25.2",
"@babel/preset-env": "^7.25.3",
"@babel/preset-typescript": "^7.24.7",
"@bufbuild/protobuf": "^2.10.2",
"@datastructures-js/priority-queue": "^6.3.3",
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
@@ -92,6 +94,7 @@
"style-loader": "^4.0.0",
"tailwindcss": "^3.4.17",
"ts-loader": "^9.5.2",
"ts-proto": "^2.8.3",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.17.0",
"typescript": "^5.7.2",
+13 -7
View File
@@ -1,6 +1,7 @@
import { translateText } from "../client/Utils";
import { EventBus } from "../core/EventBus";
import {
AllPlayersStats,
ClientID,
GameID,
GameRecord,
@@ -8,18 +9,19 @@ import {
PlayerCosmeticRefs,
PlayerRecord,
ServerMessage,
Winner,
} from "../core/Schemas";
import { createPartialGameRecord, replacer } from "../core/Util";
import { ServerConfig } from "../core/configuration/Config";
import { getConfig } from "../core/configuration/ConfigLoader";
import { PlayerActions, UnitType } from "../core/game/Game";
import { PlayerActions } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { GameMapLoader } from "../core/game/GameMapLoader";
import {
ErrorUpdate,
GameUpdateType,
GameUpdateViewData,
HashUpdate,
UnitType,
WinUpdate,
} from "../core/game/GameUpdates";
import { GameView, PlayerView } from "../core/game/GameView";
@@ -241,18 +243,20 @@ export class ClientGameRunner {
if (this.myPlayer === null) {
return;
}
const stats = JSON.parse(update.stats) as AllPlayersStats;
const players: PlayerRecord[] = [
{
persistentID: getPersistentID(),
username: this.lobby.playerName,
clientID: this.lobby.clientID,
stats: update.allPlayersStats[this.lobby.clientID],
stats: stats[this.lobby.clientID],
},
];
if (this.lobby.gameStartInfo === undefined) {
throw new Error("missing gameStartInfo");
}
const winner = JSON.parse(update.winner) as Winner;
const record = createPartialGameRecord(
this.lobby.gameStartInfo.gameID,
this.lobby.gameStartInfo.config,
@@ -261,7 +265,7 @@ export class ClientGameRunner {
[],
startTime(),
Date.now(),
update.winner,
winner,
this.lobby.gameStartInfo.lobbyCreatedAt,
);
endGame(record);
@@ -310,8 +314,10 @@ export class ClientGameRunner {
return;
}
this.transport.turnComplete();
gu.updates[GameUpdateType.Hash].forEach((hu: HashUpdate) => {
this.eventBus.emit(new SendHashEvent(hu.tick, hu.hash));
gu.updates[GameUpdateType.Hash].updates.forEach((update) => {
this.eventBus.emit(
new SendHashEvent(update.hash!.tick, update.hash!.hash),
);
});
this.gameView.update(gu);
this.renderer.tick();
@@ -324,7 +330,7 @@ export class ClientGameRunner {
// Reset tick delay for next measurement
this.currentTickDelay = undefined;
if (gu.updates[GameUpdateType.Win].length > 0) {
if (gu.updates[GameUpdateType.Win]?.updates.length > 0) {
this.saveGame(gu.updates[GameUpdateType.Win][0]);
}
});
+1 -1
View File
@@ -12,9 +12,9 @@ import {
HumansVsNations,
Quads,
Trios,
UnitType,
mapCategories,
} from "../core/game/Game";
import { UnitType } from "../core/game/GameUpdates";
import { UserSettings } from "../core/game/UserSettings";
import {
ClientInfo,
+1 -1
View File
@@ -1,5 +1,5 @@
import { EventBus, GameEvent } from "../core/EventBus";
import { UnitType } from "../core/game/Game";
import { UnitType } from "../core/game/GameUpdates";
import { UnitView } from "../core/game/GameView";
import { UserSettings } from "../core/game/UserSettings";
import { UIState } from "./graphics/UIState";
+1 -1
View File
@@ -12,9 +12,9 @@ import {
HumansVsNations,
Quads,
Trios,
UnitType,
mapCategories,
} from "../core/game/Game";
import { UnitType } from "../core/game/GameUpdates";
import { UserSettings } from "../core/game/UserSettings";
import { TeamCountConfig } from "../core/Schemas";
import { generateID } from "../core/Util";
+2 -8
View File
@@ -1,14 +1,8 @@
import { z } from "zod";
import { EventBus, GameEvent } from "../core/EventBus";
import {
AllPlayers,
GameType,
Gold,
PlayerID,
Tick,
UnitType,
} from "../core/game/Game";
import { AllPlayers, GameType, Gold, PlayerID, Tick } from "../core/game/Game";
import { TileRef } from "../core/game/GameMap";
import { UnitType } from "../core/game/GameUpdates";
import { PlayerView } from "../core/game/GameView";
import {
AllPlayersStats,
+1 -1
View File
@@ -1,5 +1,5 @@
import IntlMessageFormat from "intl-messageformat";
import { MessageType } from "../core/game/Game";
import { MessageType } from "../core/game/GameUpdates";
import { LangSelector } from "./LangSelector";
export function renderDuration(totalSeconds: number): string {
+1 -1
View File
@@ -8,11 +8,11 @@ import {
GameMode,
HumansVsNations,
PlayerInfo,
PlayerType,
Quads,
Team,
Trios,
} from "../../core/game/Game";
import { PlayerType } from "../../core/game/GameUpdates";
import { assignTeamsLobbyPreview } from "../../core/game/TeamAssignment";
import { ClientInfo, TeamCountConfig } from "../../core/Schemas";
import { translateText } from "../Utils";
+2 -1
View File
@@ -1,4 +1,5 @@
import { Cell, Game, NameViewData, Player } from "../../core/game/Game";
import { Cell, Game, Player } from "../../core/game/Game";
import { NameViewData } from "../../core/game/GameUpdates";
import { calculateBoundingBox } from "../../core/Util";
export interface Point {
+3 -4
View File
@@ -11,7 +11,7 @@ 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 { nukeTypes } from "../../core/game/Game";
import { GameView, PlayerView } from "../../core/game/GameView";
export type PlayerIconId =
@@ -114,15 +114,14 @@ export function getPlayerIcons(
.outgoingEmojis()
.filter(
(emoji) =>
emoji.recipientID === AllPlayers ||
emoji.recipientID === myPlayer?.smallID(),
emoji.allPlayers ?? emoji.recipientId === myPlayer?.smallID(),
);
if (emojis.length > 0) {
icons.push({
id: "emoji",
kind: "emoji",
text: emojis[0].message,
text: emojis[0].emoji,
});
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ 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 { TrainType, UnitType } from "../../core/game/GameUpdates";
import { UnitView } from "../../core/game/GameView";
// Can't reuse TrainType because "loaded" is not a type, just an attribute
+1 -1
View File
@@ -1,4 +1,4 @@
import { UnitType } from "../../core/game/Game";
import { UnitType } from "../../core/game/GameUpdates";
export interface UIState {
attackRatio: number;
+2 -2
View File
@@ -1,5 +1,5 @@
import { ConquestUpdate } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { renderNumber } from "../../Utils";
import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader";
import { Fx, FxType } from "./Fx";
@@ -17,7 +17,7 @@ export function conquestFxFactory(
game: GameView,
): Fx[] {
const conquestFx: Fx[] = [];
const conquered = game.player(conquest.conqueredId);
const conquered = game.playerBySmallID(conquest.conqueredId) as PlayerView;
const x = conquered.nameLocation().x;
const y = conquered.nameLocation().y;
+7 -7
View File
@@ -108,8 +108,8 @@ export class AlertFrame extends LitElement implements Layer {
// Check for BrokeAllianceUpdate events
this.game
.updatesSinceLastTick()
?.[GameUpdateType.BrokeAlliance]?.forEach((update) => {
this.onBrokeAllianceUpdate(update as BrokeAllianceUpdate);
?.[GameUpdateType.BrokeAlliance]?.updates.forEach((update) => {
this.onBrokeAllianceUpdate(update.brokeAlliance!);
});
// Check for new incoming attacks
@@ -125,7 +125,7 @@ export class AlertFrame extends LitElement implements Layer {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return;
const betrayed = this.game.playerBySmallID(update.betrayedID);
const betrayed = this.game.playerBySmallID(update.betrayedId);
// Only trigger alert if the current player is the betrayed one
if (betrayed === myPlayer) {
@@ -154,8 +154,8 @@ export class AlertFrame extends LitElement implements Layer {
// Track when we attack other players (not terra nullius)
for (const attack of outgoingAttacks) {
// Only track attacks on players (targetID !== 0 means it's a player, not unclaimed land)
if (attack.targetID !== 0 && !attack.retreating) {
const existingTick = this.outgoingAttackTicks.get(attack.targetID);
if (attack.targetId !== 0 && !attack.retreating) {
const existingTick = this.outgoingAttackTicks.get(attack.targetId);
// Only update timestamp if:
// 1. This is a new attack (not in map yet), OR
@@ -164,7 +164,7 @@ export class AlertFrame extends LitElement implements Layer {
existingTick === undefined ||
currentTick - existingTick >= RETALIATION_WINDOW_TICKS
) {
this.outgoingAttackTicks.set(attack.targetID, currentTick);
this.outgoingAttackTicks.set(attack.targetId, currentTick);
}
}
}
@@ -199,7 +199,7 @@ export class AlertFrame extends LitElement implements Layer {
// Only alert for non-retreating attacks
if (!attack.retreating && !this.seenAttackIds.has(attack.id)) {
// Check if this is a retaliation (we attacked them recently)
const ourAttackTick = this.outgoingAttackTicks.get(attack.attackerID);
const ourAttackTick = this.outgoingAttackTicks.get(attack.attackerId);
const isRetaliation =
ourAttackTick !== undefined &&
currentTick - ourAttackTick < RETALIATION_WINDOW_TICKS;
+2 -6
View File
@@ -13,13 +13,9 @@ import samlauncherIcon from "../../../../resources/images/SamLauncherIconWhite.s
import shieldIcon from "../../../../resources/images/ShieldIconWhite.svg";
import { translateText } from "../../../client/Utils";
import { EventBus } from "../../../core/EventBus";
import {
BuildableUnit,
Gold,
PlayerActions,
UnitType,
} from "../../../core/game/Game";
import { BuildableUnit, Gold, PlayerActions } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import {
CloseViewEvent,
+22 -27
View File
@@ -3,10 +3,10 @@ import { customElement, state } from "lit/decorators.js";
import { DirectiveResult } from "lit/directive.js";
import { unsafeHTML, UnsafeHTMLDirective } from "lit/directives/unsafe-html.js";
import { EventBus } from "../../../core/EventBus";
import { MessageType } from "../../../core/game/Game";
import {
DisplayMessageUpdate,
GameUpdateType,
MessageType,
} from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { onlyImages } from "../../../core/Util";
@@ -57,8 +57,8 @@ export class ChatDisplay extends LitElement implements Layer {
if (event.messageType !== MessageType.CHAT) return;
const myPlayer = this.game.myPlayer();
if (
event.playerID !== null &&
(!myPlayer || myPlayer.smallID() !== event.playerID)
event.playerId !== null &&
(!myPlayer || myPlayer.smallID() !== event.playerId)
) {
return;
}
@@ -76,31 +76,26 @@ export class ChatDisplay extends LitElement implements Layer {
tick() {
// this.active = true;
const updates = this.game.updatesSinceLastTick();
if (updates === null) return;
const messages = updates[GameUpdateType.DisplayEvent] as
| DisplayMessageUpdate[]
| undefined;
if (messages) {
for (const msg of messages) {
if (msg.messageType === MessageType.CHAT) {
const myPlayer = this.game.myPlayer();
if (
msg.playerID !== null &&
(!myPlayer || myPlayer.smallID() !== msg.playerID)
) {
continue;
}
this.chatEvents = [
...this.chatEvents,
{
description: msg.message,
unsafeDescription: true,
createdAt: this.game.ticks(),
},
];
for (const update of updates?.[GameUpdateType.DisplayEvent]?.updates ??
[]) {
const msg = update.displayMessage!;
if (msg.messageType === MessageType.CHAT) {
const myPlayer = this.game.myPlayer();
if (
msg.playerId !== null &&
(!myPlayer || myPlayer.smallID() !== msg.playerId)
) {
continue;
}
this.chatEvents = [
...this.chatEvents,
{
description: msg.message,
unsafeDescription: true,
createdAt: this.game.ticks(),
},
];
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { LitElement, html } from "lit";
import { customElement, query } from "lit/decorators.js";
import { PlayerType } from "../../../core/game/Game";
import { PlayerType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import quickChatData from "../../../../resources/QuickChat.json";
+56 -58
View File
@@ -8,15 +8,7 @@ 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,
getMessageCategory,
MessageCategory,
MessageType,
PlayerType,
Tick,
UnitType,
} from "../../../core/game/Game";
import { AllPlayers, getMessageCategory, Tick } from "../../../core/game/Game";
import {
AllianceExpiredUpdate,
AllianceRequestReplyUpdate,
@@ -27,8 +19,12 @@ import {
DisplayMessageUpdate,
EmojiUpdate,
GameUpdateType,
MessageCategory,
MessageType,
PlayerType,
TargetPlayerUpdate,
UnitIncomingUpdate,
UnitType,
} from "../../../core/game/GameUpdates";
import {
CancelAttackIntentEvent,
@@ -96,7 +92,7 @@ export class EventsDisplay extends LitElement implements Layer {
[MessageCategory.NUKE, false],
[MessageCategory.TRADE, false],
[MessageCategory.ALLIANCE, false],
[MessageCategory.CHAT, false],
[MessageCategory.CHAT_CATEGORY, false],
]);
@query(".events-container")
@@ -228,7 +224,7 @@ export class EventsDisplay extends LitElement implements Layer {
const updates = this.game.updatesSinceLastTick();
if (updates) {
for (const [ut, fn] of this.updateMap) {
updates[ut]?.forEach(fn as (event: unknown) => void);
updates[ut]?.updates.forEach(fn as (event: unknown) => void);
}
}
@@ -253,17 +249,17 @@ export class EventsDisplay extends LitElement implements Layer {
// Update attacks
this.incomingAttacks = myPlayer.incomingAttacks().filter((a) => {
const t = (this.game.playerBySmallID(a.attackerID) as PlayerView).type();
const t = (this.game.playerBySmallID(a.attackerId) as PlayerView).type();
return t !== PlayerType.Bot;
});
this.outgoingAttacks = myPlayer
.outgoingAttacks()
.filter((a) => a.targetID !== 0);
.filter((a) => a.targetId !== 0);
this.outgoingLandAttacks = myPlayer
.outgoingAttacks()
.filter((a) => a.targetID === 0);
.filter((a) => a.targetId === 0);
this.outgoingBoats = myPlayer
.units()
@@ -362,15 +358,15 @@ export class EventsDisplay extends LitElement implements Layer {
onDisplayMessageEvent(event: DisplayMessageUpdate) {
const myPlayer = this.game.myPlayer();
if (
event.playerID !== null &&
(!myPlayer || myPlayer.smallID() !== event.playerID)
event.playerId !== null &&
(!myPlayer || myPlayer.smallID() !== event.playerId)
) {
return;
}
if (event.goldAmount !== undefined) {
const hasChanged = this.latestGoldAmount !== event.goldAmount;
this.latestGoldAmount = event.goldAmount;
const hasChanged = this.latestGoldAmount !== BigInt(event.goldAmount);
this.latestGoldAmount = BigInt(event.goldAmount);
if (this.goldAmountTimeoutId !== null) {
clearTimeout(this.goldAmountTimeoutId);
@@ -408,9 +404,9 @@ export class EventsDisplay extends LitElement implements Layer {
onDisplayChatEvent(event: DisplayChatMessageUpdate) {
const myPlayer = this.game.myPlayer();
if (
event.playerID === null ||
event.playerId === null ||
!myPlayer ||
myPlayer.smallID() !== event.playerID
myPlayer.smallID() !== event.playerId
) {
return;
}
@@ -452,15 +448,15 @@ export class EventsDisplay extends LitElement implements Layer {
onAllianceRequestEvent(update: AllianceRequestUpdate) {
const myPlayer = this.game.myPlayer();
if (!myPlayer || update.recipientID !== myPlayer.smallID()) {
if (!myPlayer || update.recipientId !== myPlayer.smallID()) {
return;
}
const requestor = this.game.playerBySmallID(
update.requestorID,
update.requestorId,
) as PlayerView;
const recipient = this.game.playerBySmallID(
update.recipientID,
update.recipientId,
) as PlayerView;
this.addEvent({
@@ -500,7 +496,7 @@ export class EventsDisplay extends LitElement implements Layer {
// Recipient sent a separate request, so they became allied without the recipient responding.
return requestor.isAlliedWith(recipient);
},
focusID: update.requestorID,
focusID: update.requestorId,
});
}
@@ -510,25 +506,25 @@ export class EventsDisplay extends LitElement implements Layer {
return;
}
// myPlayer can deny alliances without clicking on the button
if (update.request.recipientID === myPlayer.smallID()) {
if (update.request!.recipientId === myPlayer.smallID()) {
// Remove alliance requests whose requestors are the same as the reply's requestor
// Noop unless the request was denied through other means (e.g attacking the requestor)
this.events = this.events.filter(
(event) =>
!(
event.type === MessageType.ALLIANCE_REQUEST &&
event.focusID === update.request.requestorID
event.focusID === update.request!.requestorId
),
);
this.requestUpdate();
return;
}
if (update.request.requestorID !== myPlayer.smallID()) {
if (update.request!.requestorId !== myPlayer.smallID()) {
return;
}
const recipient = this.game.playerBySmallID(
update.request.recipientID,
update.request!.recipientId,
) as PlayerView;
this.addEvent({
description: translateText("events_display.alliance_request_status", {
@@ -542,7 +538,7 @@ export class EventsDisplay extends LitElement implements Layer {
: MessageType.ALLIANCE_REJECTED,
highlight: true,
createdAt: this.game.ticks(),
focusID: update.request.recipientID,
focusID: update.request!.recipientId,
});
}
@@ -550,8 +546,8 @@ export class EventsDisplay extends LitElement implements Layer {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return;
const betrayed = this.game.playerBySmallID(update.betrayedID) as PlayerView;
const traitor = this.game.playerBySmallID(update.traitorID) as PlayerView;
const betrayed = this.game.playerBySmallID(update.betrayedId) as PlayerView;
const traitor = this.game.playerBySmallID(update.traitorId) as PlayerView;
if (betrayed.isDisconnected()) return; // Do not send the message if betraying a disconnected player
@@ -579,7 +575,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
createdAt: this.game.ticks(),
focusID: update.betrayedID,
focusID: update.betrayedId,
});
} else if (betrayed === myPlayer) {
const buttons = [
@@ -597,7 +593,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.ALLIANCE_BROKEN,
highlight: true,
createdAt: this.game.ticks(),
focusID: update.traitorID,
focusID: update.traitorId,
buttons,
});
}
@@ -608,10 +604,10 @@ export class EventsDisplay extends LitElement implements Layer {
if (!myPlayer) return;
const otherID =
update.player1ID === myPlayer.smallID()
? update.player2ID
: update.player2ID === myPlayer.smallID()
? update.player1ID
update.player1Id === myPlayer.smallID()
? update.player2Id
: update.player2Id === myPlayer.smallID()
? update.player1Id
: null;
if (otherID === null) return;
const other = this.game.playerBySmallID(otherID) as PlayerView;
@@ -629,11 +625,11 @@ export class EventsDisplay extends LitElement implements Layer {
}
onTargetPlayerEvent(event: TargetPlayerUpdate) {
const other = this.game.playerBySmallID(event.playerID) as PlayerView;
const other = this.game.playerBySmallID(event.playerId) as PlayerView;
const myPlayer = this.game.myPlayer() as PlayerView;
if (!myPlayer || !myPlayer.isFriendly(other)) return;
const target = this.game.playerBySmallID(event.targetID) as PlayerView;
const target = this.game.playerBySmallID(event.targetId) as PlayerView;
this.addEvent({
description: translateText("events_display.attack_request", {
@@ -643,7 +639,7 @@ export class EventsDisplay extends LitElement implements Layer {
type: MessageType.ATTACK_REQUEST,
highlight: true,
createdAt: this.game.ticks(),
focusID: event.targetID,
focusID: event.targetId,
});
}
@@ -677,28 +673,27 @@ export class EventsDisplay extends LitElement implements Layer {
const myPlayer = this.game.myPlayer();
if (!myPlayer) return;
const recipient =
update.emoji.recipientID === AllPlayers
? AllPlayers
: this.game.playerBySmallID(update.emoji.recipientID);
const recipient = update.emoji!.allPlayers
? AllPlayers
: this.game.playerBySmallID(update.emoji!.recipientId!);
const sender = this.game.playerBySmallID(
update.emoji.senderID,
update.emoji!.senderId,
) as PlayerView;
if (recipient === myPlayer) {
this.addEvent({
description: `${sender.displayName()}: ${update.emoji.message}`,
description: `${sender.displayName()}: ${update.emoji!.emoji}`,
unsafeDescription: true,
type: MessageType.CHAT,
highlight: true,
createdAt: this.game.ticks(),
focusID: update.emoji.senderID,
focusID: update.emoji!.senderId,
});
} else if (sender === myPlayer && recipient !== AllPlayers) {
this.addEvent({
description: translateText("events_display.sent_emoji", {
name: (recipient as PlayerView).displayName(),
emoji: update.emoji.message,
emoji: update.emoji!.emoji,
}),
unsafeDescription: true,
type: MessageType.CHAT,
@@ -712,11 +707,11 @@ export class EventsDisplay extends LitElement implements Layer {
onUnitIncomingEvent(event: UnitIncomingUpdate) {
const myPlayer = this.game.myPlayer();
if (!myPlayer || myPlayer.smallID() !== event.playerID) {
if (!myPlayer || myPlayer.smallID() !== event.playerId) {
return;
}
const unitView = this.game.unit(event.unitID);
const unitView = this.game.unit(event.unitId);
this.addEvent({
description: event.message,
@@ -737,27 +732,27 @@ export class EventsDisplay extends LitElement implements Layer {
}
private async attackWarningOnClick(attack: AttackUpdate) {
const playerView = this.game.playerBySmallID(attack.attackerID);
const playerView = this.game.playerBySmallID(attack.attackerId);
if (playerView !== undefined) {
if (playerView instanceof PlayerView) {
const averagePosition = await playerView.attackAveragePosition(
attack.attackerID,
attack.attackerId,
attack.id,
);
if (averagePosition === null) {
this.emitGoToPlayerEvent(attack.attackerID);
this.emitGoToPlayerEvent(attack.attackerId);
} else {
this.emitGoToPositionEvent(averagePosition.x, averagePosition.y);
}
}
} else {
this.emitGoToPlayerEvent(attack.attackerID);
this.emitGoToPlayerEvent(attack.attackerId);
}
}
private handleRetaliate(attack: AttackUpdate) {
const attacker = this.game.playerBySmallID(attack.attackerID) as PlayerView;
const attacker = this.game.playerBySmallID(attack.attackerId) as PlayerView;
if (!attacker) return;
const myPlayer = this.game.myPlayer();
@@ -780,7 +775,7 @@ export class EventsDisplay extends LitElement implements Layer {
${renderTroops(attack.troops)}
${(
this.game.playerBySmallID(
attack.attackerID,
attack.attackerId,
) as PlayerView
)?.name()}
${attack.retreating
@@ -822,7 +817,7 @@ export class EventsDisplay extends LitElement implements Layer {
${renderTroops(attack.troops)}
${(
this.game.playerBySmallID(
attack.targetID,
attack.targetId,
) as PlayerView
)?.name()}
`,
@@ -1032,7 +1027,10 @@ export class EventsDisplay extends LitElement implements Layer {
allianceIcon,
MessageCategory.ALLIANCE,
)}
${this.renderToggleButton(chatIcon, MessageCategory.CHAT)}
${this.renderToggleButton(
chatIcon,
MessageCategory.CHAT_CATEGORY,
)}
</div>
<div class="flex items-center gap-3">
${this.latestGoldAmount !== null
+12 -10
View File
@@ -1,10 +1,10 @@
import { Theme } from "../../../core/configuration/Config";
import { UnitType } from "../../../core/game/Game";
import {
BonusEventUpdate,
ConquestUpdate,
GameUpdateType,
RailroadUpdate,
UnitType,
} from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import SoundManager, { SoundEffect } from "../../sound/SoundManager";
@@ -45,29 +45,31 @@ export class FxLayer implements Layer {
this.manageBoatTargetFx();
this.game
.updatesSinceLastTick()
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
?.[GameUpdateType.Unit]?.updates.map((update) =>
this.game.unit(update.unit!.id),
)
?.forEach((unitView) => {
if (unitView === undefined) return;
this.onUnitEvent(unitView);
});
this.game
.updatesSinceLastTick()
?.[GameUpdateType.BonusEvent]?.forEach((bonusEvent) => {
if (bonusEvent === undefined) return;
this.onBonusEvent(bonusEvent);
?.[GameUpdateType.BonusEvent]?.updates.forEach((update) => {
if (update === undefined) return;
this.onBonusEvent(update.bonusEvent!);
});
this.game
.updatesSinceLastTick()
?.[GameUpdateType.RailroadEvent]?.forEach((update) => {
?.[GameUpdateType.RailroadEvent]?.updates.forEach((update) => {
if (update === undefined) return;
this.onRailroadEvent(update);
this.onRailroadEvent(update.railroad!);
});
this.game
.updatesSinceLastTick()
?.[GameUpdateType.ConquestEvent]?.forEach((update) => {
?.[GameUpdateType.ConquestEvent]?.updates.forEach((update) => {
if (update === undefined) return;
this.onConquestEvent(update);
this.onConquestEvent(update.conquest!);
});
}
@@ -249,7 +251,7 @@ export class FxLayer implements Layer {
onConquestEvent(conquest: ConquestUpdate) {
// Only display fx for the current player
const conqueror = this.game.player(conquest.conquerorId);
const conqueror = this.game.playerBySmallID(conquest.conquerorId);
if (conqueror !== this.game.myPlayer()) {
return;
}
@@ -54,7 +54,8 @@ export class GameRightSidebar extends LitElement implements Layer {
// Timer logic
const updates = this.game.updatesSinceLastTick();
if (updates) {
this.hasWinner = this.hasWinner || updates[GameUpdateType.Win].length > 0;
this.hasWinner =
this.hasWinner || updates[GameUpdateType.Win]?.updates.length > 0;
}
const maxTimerValue = this.game.config().gameConfig().maxTimerValue;
if (maxTimerValue !== undefined) {
@@ -1,6 +1,6 @@
import { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import { ParabolaPathFinder } from "../../../core/pathfinding/PathFinding";
import { GhostStructureChangedEvent, MouseMoveEvent } from "../../InputHandler";
@@ -11,15 +11,13 @@ 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 {
PlayerProfile,
PlayerType,
Relation,
Unit,
UnitType,
} from "../../../core/game/Game";
import { PlayerProfile, Relation, Unit } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { AllianceView } from "../../../core/game/GameUpdates";
import {
AllianceView,
PlayerType,
UnitType,
} from "../../../core/game/GameUpdates";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { ContextMenuEvent, MouseMoveEvent } from "../../InputHandler";
import {
+1 -1
View File
@@ -15,10 +15,10 @@ import {
AllPlayers,
PlayerActions,
PlayerProfile,
PlayerType,
Relation,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { PlayerType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { Emoji, flattenedEmojiTable } from "../../../core/Util";
import { actionButton } from "../../components/ui/ActionButton";
@@ -1,6 +1,7 @@
import { Config } from "../../../core/configuration/Config";
import { AllPlayers, PlayerActions, UnitType } from "../../../core/game/Game";
import { AllPlayers, PlayerActions } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { Emoji, flattenedEmojiTable } from "../../../core/Util";
import { renderNumber, translateText } from "../../Utils";
+2 -2
View File
@@ -45,9 +45,9 @@ export class RailroadLayer implements Layer {
tick() {
const updates = this.game.updatesSinceLastTick();
const railUpdates =
updates !== null ? updates[GameUpdateType.RailroadEvent] : [];
updates !== null ? updates[GameUpdateType.RailroadEvent].updates : [];
for (const rail of railUpdates) {
this.handleRailroadRendering(rail);
this.handleRailroadRendering(rail.railroad!);
}
}
+12 -12
View File
@@ -1,21 +1,21 @@
import { RailType } from "../../../core/game/GameUpdates";
const railTypeToFunctionMap: Record<RailType, () => number[][]> = {
[RailType.TOP_RIGHT]: topRightRailroadCornerRects,
[RailType.BOTTOM_LEFT]: bottomLeftRailroadCornerRects,
[RailType.TOP_LEFT]: topLeftRailroadCornerRects,
[RailType.BOTTOM_RIGHT]: bottomRightRailroadCornerRects,
[RailType.HORIZONTAL]: horizontalRailroadRects,
[RailType.VERTICAL]: verticalRailroadRects,
[RailType.topRight]: topRightRailroadCornerRects,
[RailType.bottomLeft]: bottomLeftRailroadCornerRects,
[RailType.topLeft]: topLeftRailroadCornerRects,
[RailType.bottomRight]: bottomRightRailroadCornerRects,
[RailType.horizontal]: horizontalRailroadRects,
[RailType.vertical]: verticalRailroadRects,
};
const railTypeToBridgeFunctionMap: Record<RailType, () => number[][]> = {
[RailType.TOP_RIGHT]: topRightBridgeCornerRects,
[RailType.BOTTOM_LEFT]: bottomLeftBridgeCornerRects,
[RailType.TOP_LEFT]: topLeftBridgeCornerRects,
[RailType.BOTTOM_RIGHT]: bottomRightBridgeCornerRects,
[RailType.HORIZONTAL]: horizontalBridge,
[RailType.VERTICAL]: verticalBridge,
[RailType.topRight]: topRightBridgeCornerRects,
[RailType.bottomLeft]: bottomLeftBridgeCornerRects,
[RailType.topLeft]: topLeftBridgeCornerRects,
[RailType.bottomRight]: bottomRightBridgeCornerRects,
[RailType.horizontal]: horizontalBridge,
[RailType.vertical]: verticalBridge,
};
export function getRailroadRects(type: RailType): number[][] {
+3 -3
View File
@@ -1,6 +1,5 @@
import type { EventBus } from "../../../core/EventBus";
import { UnitType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameUpdateType, UnitType } from "../../../core/game/GameUpdates";
import type { GameView, PlayerView } from "../../../core/game/GameView";
import { ToggleStructureEvent } from "../../InputHandler";
import { TransformHandler } from "../TransformHandler";
@@ -76,7 +75,8 @@ export class SAMRadiusLayer implements Layer {
if (unitUpdates) {
let hasChanges = false;
for (const update of unitUpdates) {
for (const u of unitUpdates?.updates ?? []) {
const update = u.unit!;
const unit = this.game.unit(update.id);
if (unit && unit.type() === UnitType.SAMLauncher) {
const wasTracked = this.samLaunchers.has(update.id);
@@ -1,6 +1,7 @@
import * as PIXI from "pixi.js";
import { Theme } from "../../../core/configuration/Config";
import { Cell, UnitType } from "../../../core/game/Game";
import { Cell } from "../../../core/game/Game";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView, UnitView } from "../../../core/game/GameView";
import { TransformHandler } from "../TransformHandler";
@@ -10,10 +10,9 @@ import {
Cell,
PlayerActions,
PlayerID,
UnitType,
} from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameUpdateType, UnitType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import {
GhostStructureChangedEvent,
@@ -178,7 +177,9 @@ export class StructureIconsLayer implements Layer {
tick() {
this.game
.updatesSinceLastTick()
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
?.[GameUpdateType.Unit]?.updates.map((unit) =>
this.game.unit(unit.unit!.id),
)
?.forEach((unitView) => {
if (unitView === undefined) return;
+12 -8
View File
@@ -10,9 +10,9 @@ 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 { AllUnitTypes, Cell } from "../../../core/game/Game";
import { euclDistFN, isometricDistFN } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameUpdateType, UnitType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
const underConstructionColor = colord("rgb(150,150,150)");
@@ -32,7 +32,7 @@ interface UnitRenderConfig {
export class StructureLayer implements Layer {
private canvas: HTMLCanvasElement;
private context: CanvasRenderingContext2D;
private unitIcons: Map<string, HTMLImageElement> = new Map();
private unitIcons: Map<UnitType, HTMLImageElement> = new Map();
private theme: Theme;
private tempCanvas: HTMLCanvasElement;
private tempContext: CanvasRenderingContext2D;
@@ -84,7 +84,7 @@ export class StructureLayer implements Layer {
this.loadIconData();
}
private loadIcon(unitType: string, config: UnitRenderConfig) {
private loadIcon(unitType: UnitType, config: UnitRenderConfig) {
const image = new Image();
image.src = config.icon;
image.onload = () => {
@@ -99,8 +99,11 @@ export class StructureLayer implements Layer {
}
private loadIconData() {
Object.entries(this.unitConfigs).forEach(([unitType, config]) => {
this.loadIcon(unitType, config);
AllUnitTypes.forEach((unitType) => {
const config = this.unitConfigs[unitType];
if (config) {
this.loadIcon(unitType, this.unitConfigs[unitType]!);
}
});
}
@@ -110,9 +113,10 @@ export class StructureLayer implements Layer {
tick() {
const updates = this.game.updatesSinceLastTick();
const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : [];
const unitUpdates =
updates !== null ? (updates[GameUpdateType.Unit]?.updates ?? []) : [];
for (const u of unitUpdates) {
const unit = this.game.unit(u.id);
const unit = this.game.unit(u.unit!.id);
if (unit === undefined) continue;
this.handleUnitRendering(unit);
}
+2 -1
View File
@@ -1,7 +1,8 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { EventBus } from "../../../core/EventBus";
import { GameMode, Team, UnitType } from "../../../core/game/Game";
import { GameMode, Team } from "../../../core/game/Game";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { renderNumber, translateText } from "../../Utils";
import { Layer } from "./Layer";
+39 -32
View File
@@ -2,15 +2,13 @@ import { PriorityQueue } from "@datastructures-js/priority-queue";
import { Colord } from "colord";
import { Theme } from "../../../core/configuration/Config";
import { EventBus } from "../../../core/EventBus";
import {
Cell,
ColoredTeams,
PlayerType,
Team,
UnitType,
} from "../../../core/game/Game";
import { Cell, ColoredTeams, Team } from "../../../core/game/Game";
import { euclDistFN, TileRef } from "../../../core/game/GameMap";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import {
GameUpdateType,
PlayerType,
UnitType,
} from "../../../core/game/GameUpdates";
import { GameView, PlayerView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { PseudoRandom } from "../../../core/PseudoRandom";
@@ -87,8 +85,8 @@ export class TerritoryLayer implements Layer {
this.game.recentlyUpdatedTiles().forEach((t) => this.enqueueTile(t));
const updates = this.game.updatesSinceLastTick();
const unitUpdates = updates !== null ? updates[GameUpdateType.Unit] : [];
unitUpdates.forEach((update) => {
updates[GameUpdateType.Unit]?.updates.forEach((u) => {
const update = u.unit!;
if (update.unitType === UnitType.DefensePost) {
// Only update borders if the defense post is not under construction
if (update.underConstruction) {
@@ -101,8 +99,8 @@ export class TerritoryLayer implements Layer {
.forEach((t) => {
if (
this.game.isBorder(t) &&
(this.game.ownerID(t) === update.ownerID ||
this.game.ownerID(t) === update.lastOwnerID)
(this.game.ownerID(t) === update.ownerId ||
this.game.ownerID(t) === update.lastOwnerId)
) {
this.enqueueTile(t);
}
@@ -113,33 +111,42 @@ export class TerritoryLayer implements Layer {
// Detect alliance mutations
const myPlayer = this.game.myPlayer();
if (myPlayer) {
updates?.[GameUpdateType.BrokeAlliance]?.forEach((update) => {
const territory = this.game.playerBySmallID(update.betrayedID);
updates?.[GameUpdateType.BrokeAlliance]?.updates.forEach((update) => {
const brokeAllianceUpdate = update.brokeAlliance!;
const territory = this.game.playerBySmallID(
brokeAllianceUpdate.betrayedId,
);
if (territory && territory instanceof PlayerView) {
this.redrawBorder(territory);
}
});
updates?.[GameUpdateType.AllianceRequestReply]?.forEach((update) => {
if (
update.accepted &&
(update.request.requestorID === myPlayer.smallID() ||
update.request.recipientID === myPlayer.smallID())
) {
const territoryId =
update.request.requestorID === myPlayer.smallID()
? update.request.recipientID
: update.request.requestorID;
const territory = this.game.playerBySmallID(territoryId);
if (territory && territory instanceof PlayerView) {
this.redrawBorder(territory);
updates?.[GameUpdateType.AllianceRequestReply]?.updates.forEach(
(update) => {
const au = update.allianceRequestReply!;
if (
au.accepted &&
(au.request!.requestorId === myPlayer.smallID() ||
au.request!.recipientId === myPlayer.smallID())
) {
const territoryId =
au.request!.requestorId === myPlayer.smallID()
? au.request!.recipientId
: au.request!.requestorId;
const territory = this.game.playerBySmallID(territoryId);
if (territory && territory instanceof PlayerView) {
this.redrawBorder(territory);
}
}
}
});
updates?.[GameUpdateType.EmbargoEvent]?.forEach((update) => {
const player = this.game.playerBySmallID(update.playerID) as PlayerView;
},
);
updates?.[GameUpdateType.EmbargoEvent]?.updates.forEach((update) => {
const embargoUpdate = update.embargo!;
const player = this.game.playerBySmallID(
embargoUpdate.playerId,
) as PlayerView;
const embargoed = this.game.playerBySmallID(
update.embargoedID,
embargoUpdate.embargoedId,
) as PlayerView;
if (
+4 -3
View File
@@ -1,8 +1,7 @@
import { Colord } from "colord";
import { EventBus } from "../../../core/EventBus";
import { Theme } from "../../../core/configuration/Config";
import { UnitType } from "../../../core/game/Game";
import { GameUpdateType } from "../../../core/game/GameUpdates";
import { GameUpdateType, UnitType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { UserSettings } from "../../../core/game/UserSettings";
import { UnitSelectionEvent } from "../../InputHandler";
@@ -71,7 +70,9 @@ export class UILayer implements Layer {
this.game
.updatesSinceLastTick()
?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id))
?.[GameUpdateType.Unit]?.updates.map((unit) =>
this.game.unit(unit.unit!.id),
)
?.forEach((unitView) => {
if (unitView === undefined) return;
this.onUnitEvent(unitView);
+2 -1
View File
@@ -11,7 +11,8 @@ 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 { Gold, PlayerActions } from "../../../core/game/Game";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView } from "../../../core/game/GameView";
import {
GhostStructureChangedEvent,
+2 -2
View File
@@ -1,8 +1,8 @@
import { colord, Colord } from "colord";
import { EventBus } from "../../../core/EventBus";
import { Theme } from "../../../core/configuration/Config";
import { UnitType } from "../../../core/game/Game";
import { TileRef } from "../../../core/game/GameMap";
import { UnitType } from "../../../core/game/GameUpdates";
import { GameView, UnitView } from "../../../core/game/GameView";
import { BezenhamLine } from "../../../core/utilities/Line";
import {
@@ -67,7 +67,7 @@ export class UnitLayer implements Layer {
tick() {
const unitIds = this.game
.updatesSinceLastTick()
?.[GameUpdateType.Unit]?.map((unit) => unit.id);
?.[GameUpdateType.Unit]?.updates.map((unit) => unit.unit!.id);
this.updateUnitsSprites(unitIds ?? []);
}
+9 -4
View File
@@ -315,12 +315,17 @@ export class WinModal extends LitElement implements Layer {
this.show();
}
const updates = this.game.updatesSinceLastTick();
const winUpdates = updates !== null ? updates[GameUpdateType.Win] : [];
winUpdates.forEach((wu) => {
const winUpdates =
updates !== null ? updates[GameUpdateType.Win].updates : [];
winUpdates.forEach((update) => {
const wu = update.win!;
if (wu.winner === undefined) {
// ...
} else if (wu.winner[0] === "team") {
this.eventBus.emit(new SendWinnerEvent(wu.winner, wu.allPlayersStats));
this.eventBus.emit(
new SendWinnerEvent(JSON.parse(wu.winner), JSON.parse(wu.stats)),
);
if (wu.winner[1] === this.game.myPlayer()?.team()) {
this._title = translateText("win_modal.your_team");
this.isWin = true;
@@ -337,7 +342,7 @@ export class WinModal extends LitElement implements Layer {
const winnerClient = winner.clientID();
if (winnerClient !== null) {
this.eventBus.emit(
new SendWinnerEvent(["player", winnerClient], wu.allPlayersStats),
new SendWinnerEvent(["player", winnerClient], JSON.parse(wu.stats)),
);
}
if (
@@ -1,6 +1,6 @@
// renderUnitTypeOptions.ts
import { html, TemplateResult } from "lit";
import { UnitType } from "../../core/game/Game";
import { UnitType } from "../../core/game/GameUpdates";
import { translateText } from "../Utils";
export interface UnitTypeRenderContext {
+5 -20
View File
@@ -7,8 +7,6 @@ import {
Attack,
Cell,
Game,
GameUpdates,
NameViewData,
Nation,
Player,
PlayerActions,
@@ -16,15 +14,15 @@ import {
PlayerID,
PlayerInfo,
PlayerProfile,
PlayerType,
} from "./game/Game";
import { createGame } from "./game/GameImpl";
import { TileRef } from "./game/GameMap";
import { GameMapLoader } from "./game/GameMapLoader";
import {
ErrorUpdate,
GameUpdateType,
GameUpdateViewData,
NameViewData,
PlayerType,
} from "./game/GameUpdates";
import { loadTerrainMap as loadGameMap } from "./game/TerrainMapLoader";
import { PseudoRandom } from "./PseudoRandom";
@@ -134,17 +132,14 @@ export class GameRunner {
);
this.currTurn++;
let updates: GameUpdates;
let tickExecutionDuration: number = 0;
let updates: GameUpdateViewData;
try {
const startTime = performance.now();
updates = this.game.executeNextTick();
const endTime = performance.now();
tickExecutionDuration = endTime - startTime;
} catch (error: unknown) {
if (error instanceof Error) {
console.error("Game tick error:", error.message);
console.error("Stack trace:", error.stack);
this.callBack({
errMsg: error.message,
stack: error.stack,
@@ -173,17 +168,7 @@ export class GameRunner {
});
}
// Many tiles are updated to pack it into an array
const packedTileUpdates = updates[GameUpdateType.Tile].map((u) => u.update);
updates[GameUpdateType.Tile] = [];
this.callBack({
tick: this.game.ticks(),
packedTileUpdates: new BigUint64Array(packedTileUpdates),
updates: updates,
playerNameViewData: this.playerViewData,
tickExecutionDuration: tickExecutionDuration,
});
this.callBack(updates);
this.isExecuting = false;
}
+1 -1
View File
@@ -17,8 +17,8 @@ import {
HumansVsNations,
Quads,
Trios,
UnitType,
} from "./game/Game";
import { UnitType } from "./game/GameUpdates";
import { PlayerStatsSchema } from "./StatsSchemas";
import { flattenedEmojiTable } from "./Util";
+1 -1
View File
@@ -1,5 +1,5 @@
import { z } from "zod";
import { UnitType } from "./game/Game";
import { UnitType } from "./game/GameUpdates";
export const bombUnits = ["abomb", "hbomb", "mirv", "mirvw"] as const;
export const BombUnitSchema = z.enum(bombUnits);
+3 -2
View File
@@ -16,6 +16,7 @@ import {
BOT_NAME_PREFIXES,
BOT_NAME_SUFFIXES,
} from "./execution/utils/BotNames";
import { PlayerType } from "./game/GameUpdates";
export function manhattanDistWrapped(
c1: Cell,
@@ -290,10 +291,10 @@ export function withinInt(num: bigint, min: bigint, max: bigint): bigint {
export function createRandomName(
name: string,
playerType: string,
playerType: PlayerType,
): string | null {
let randomName: string | null = null;
if (playerType === "HUMAN") {
if (playerType === PlayerType.Human) {
const hash = simpleHash(name);
const prefixIndex = hash % BOT_NAME_PREFIXES.length;
const suffixIndex =
+1 -1
View File
@@ -12,9 +12,9 @@ import {
TerraNullius,
Tick,
UnitInfo,
UnitType,
} from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { UnitType } from "../game/GameUpdates";
import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
+1 -2
View File
@@ -11,16 +11,15 @@ import {
HumansVsNations,
Player,
PlayerInfo,
PlayerType,
Quads,
TerrainType,
TerraNullius,
Tick,
Trios,
UnitInfo,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PlayerType, UnitType } from "../game/GameUpdates";
import { PlayerView } from "../game/GameView";
import { UserSettings } from "../game/UserSettings";
import { GameConfig, GameID, TeamCountConfig } from "../Schemas";
+2 -1
View File
@@ -1,7 +1,8 @@
import { Colord, colord, LabaColor } from "colord";
import { PseudoRandom } from "../PseudoRandom";
import { PlayerType, Team, TerrainType } from "../game/Game";
import { Team, TerrainType } from "../game/Game";
import { GameMap, TileRef } from "../game/GameMap";
import { PlayerType } from "../game/GameUpdates";
import { PlayerView } from "../game/GameView";
import { ColorAllocator } from "./ColorAllocator";
import { botColors, fallbackColors, humanColors, nationColors } from "./Colors";
+1 -2
View File
@@ -3,14 +3,13 @@ import {
Attack,
Execution,
Game,
MessageType,
Player,
PlayerID,
PlayerType,
TerrainType,
TerraNullius,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, PlayerType } from "../game/GameUpdates";
import { PseudoRandom } from "../PseudoRandom";
import { FlatBinaryHeap } from "./utils/FlatBinaryHeap"; // adjust path if needed
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, Player, UnitType } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
import { UnitType } from "../game/GameUpdates";
export class BoatRetreatExecution implements Execution {
private active = true;
+2 -1
View File
@@ -1,5 +1,6 @@
import { Game, PlayerInfo, PlayerType } from "../game/Game";
import { Game, PlayerInfo } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PlayerType } from "../game/GameUpdates";
import { PseudoRandom } from "../PseudoRandom";
import { GameID } from "../Schemas";
import { simpleHash } from "../Util";
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, Unit, UnitType } from "../game/Game";
import { Execution, Game, Unit } from "../game/Game";
import { UnitType } from "../game/GameUpdates";
import { TrainStationExecution } from "./TrainStationExecution";
export class CityExecution implements Execution {
+2 -1
View File
@@ -1,5 +1,6 @@
import { Execution, Game, Player, Tick, Unit, UnitType } from "../game/Game";
import { Execution, Game, Player, Tick, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { UnitType } from "../game/GameUpdates";
import { CityExecution } from "./CityExecution";
import { DefensePostExecution } from "./DefensePostExecution";
import { FactoryExecution } from "./FactoryExecution";
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, MessageType, Player, Unit } from "../game/Game";
import { Execution, Game, Player, Unit } from "../game/Game";
import { MessageType } from "../game/GameUpdates";
export class DeleteUnitExecution implements Execution {
private active: boolean = true;
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, Player, PlayerType } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
import { PlayerType } from "../game/GameUpdates";
export class EmbargoAllExecution implements Execution {
constructor(
+2 -8
View File
@@ -1,11 +1,5 @@
import {
AllPlayers,
Execution,
Game,
Player,
PlayerID,
PlayerType,
} from "../game/Game";
import { AllPlayers, Execution, Game, Player, PlayerID } from "../game/Game";
import { PlayerType } from "../game/GameUpdates";
import { flattenedEmojiTable } from "../Util";
export class EmojiExecution implements Execution {
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, Unit, UnitType } from "../game/Game";
import { Execution, Game, Unit } from "../game/Game";
import { UnitType } from "../game/GameUpdates";
import { TrainStationExecution } from "./TrainStationExecution";
export class FactoryExecution implements Execution {
+1 -2
View File
@@ -7,14 +7,13 @@ import {
Nation,
Player,
PlayerID,
PlayerType,
Relation,
TerrainType,
Tick,
Unit,
UnitType,
} from "../game/Game";
import { TileRef, euclDistFN } from "../game/GameMap";
import { PlayerType, UnitType } from "../game/GameUpdates";
import { PseudoRandom } from "../PseudoRandom";
import { GameID } from "../Schemas";
import { boundingBoxTiles, calculateBoundingBox, simpleHash } from "../Util";
+2 -9
View File
@@ -1,13 +1,6 @@
import {
Execution,
Game,
MessageType,
Player,
TerraNullius,
Unit,
UnitType,
} from "../game/Game";
import { Execution, Game, Player, TerraNullius, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, UnitType } from "../game/GameUpdates";
import { ParabolaPathFinder } from "../pathfinding/PathFinding";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
+2 -1
View File
@@ -1,5 +1,6 @@
import { Execution, Game, Player, UnitType } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { UnitType } from "../game/GameUpdates";
export class MoveWarshipExecution implements Execution {
constructor(
+1 -2
View File
@@ -2,14 +2,13 @@ import {
Execution,
Game,
isStructureType,
MessageType,
Player,
TerraNullius,
TrajectoryTile,
Unit,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, UnitType } from "../game/GameUpdates";
import { ParabolaPathFinder } from "../pathfinding/PathFinding";
import { PseudoRandom } from "../PseudoRandom";
import { NukeType } from "../StatsSchemas";
+2 -1
View File
@@ -1,6 +1,7 @@
import { Config } from "../configuration/Config";
import { Execution, Game, Player, UnitType } from "../game/Game";
import { Execution, Game, Player } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { UnitType } from "../game/GameUpdates";
import { calculateBoundingBox, getMode, inscribed, simpleHash } from "../Util";
interface ClusterTraversalState {
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, Unit, UnitType } from "../game/Game";
import { Execution, Game, Unit } from "../game/Game";
import { UnitType } from "../game/GameUpdates";
import { PseudoRandom } from "../PseudoRandom";
import { TradeShipExecution } from "./TradeShipExecution";
import { TrainStationExecution } from "./TrainStationExecution";
+21 -19
View File
@@ -27,7 +27,7 @@ export class RailroadExecution implements Execution {
railType:
tiles.length > 0
? this.computeExtremityDirection(tiles[0], tiles[1])
: RailType.VERTICAL,
: RailType.vertical,
});
for (let i = 1; i < tiles.length - 1; i++) {
const direction = this.computeDirection(
@@ -45,7 +45,7 @@ export class RailroadExecution implements Execution {
tiles[tiles.length - 1],
tiles[tiles.length - 2],
)
: RailType.VERTICAL,
: RailType.vertical,
});
}
@@ -58,14 +58,14 @@ export class RailroadExecution implements Execution {
const dx = nextX - x;
const dy = nextY - y;
if (dx === 0 && dy === 0) return RailType.VERTICAL; // No movement
if (dx === 0 && dy === 0) return RailType.vertical; // No movement
if (dx === 0) {
return RailType.VERTICAL;
return RailType.vertical;
} else if (dy === 0) {
return RailType.HORIZONTAL;
return RailType.horizontal;
}
return RailType.VERTICAL;
return RailType.vertical;
}
private computeDirection(
@@ -90,25 +90,25 @@ export class RailroadExecution implements Execution {
// Straight line
if (dx1 === dx2 && dy1 === dy2) {
if (dx1 !== 0) return RailType.HORIZONTAL;
if (dy1 !== 0) return RailType.VERTICAL;
if (dx1 !== 0) return RailType.horizontal;
if (dy1 !== 0) return RailType.vertical;
}
// Turn (corner) cases
if ((dx1 === 0 && dx2 !== 0) || (dx1 !== 0 && dx2 === 0)) {
// Now figure out which type of corner
if (dx1 === 0 && dx2 === 1 && dy1 === -1) return RailType.BOTTOM_RIGHT;
if (dx1 === 0 && dx2 === -1 && dy1 === -1) return RailType.BOTTOM_LEFT;
if (dx1 === 0 && dx2 === 1 && dy1 === 1) return RailType.TOP_RIGHT;
if (dx1 === 0 && dx2 === -1 && dy1 === 1) return RailType.TOP_LEFT;
if (dx1 === 0 && dx2 === 1 && dy1 === -1) return RailType.bottomRight;
if (dx1 === 0 && dx2 === -1 && dy1 === -1) return RailType.bottomLeft;
if (dx1 === 0 && dx2 === 1 && dy1 === 1) return RailType.topRight;
if (dx1 === 0 && dx2 === -1 && dy1 === 1) return RailType.topLeft;
if (dx1 === 1 && dx2 === 0 && dy2 === -1) return RailType.TOP_LEFT;
if (dx1 === -1 && dx2 === 0 && dy2 === -1) return RailType.TOP_RIGHT;
if (dx1 === 1 && dx2 === 0 && dy2 === 1) return RailType.BOTTOM_LEFT;
if (dx1 === -1 && dx2 === 0 && dy2 === 1) return RailType.BOTTOM_RIGHT;
if (dx1 === 1 && dx2 === 0 && dy2 === -1) return RailType.topLeft;
if (dx1 === -1 && dx2 === 0 && dy2 === -1) return RailType.topRight;
if (dx1 === 1 && dx2 === 0 && dy2 === 1) return RailType.bottomLeft;
if (dx1 === -1 && dx2 === 0 && dy2 === 1) return RailType.bottomRight;
}
console.warn(`Invalid rail segment: ${dx1}:${dy1}, ${dx2}:${dy2}`);
return RailType.VERTICAL;
return RailType.vertical;
}
tick(ticks: number): void {
@@ -144,8 +144,10 @@ export class RailroadExecution implements Execution {
if (updatedRailTiles) {
this.mg.addUpdate({
type: GameUpdateType.RailroadEvent,
isActive: true,
railTiles: updatedRailTiles,
railroad: {
isActive: true,
railTiles: updatedRailTiles,
},
});
}
}
+2 -9
View File
@@ -1,13 +1,6 @@
import {
Execution,
Game,
isUnit,
MessageType,
Player,
Unit,
UnitType,
} from "../game/Game";
import { Execution, Game, isUnit, Player, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, UnitType } from "../game/GameUpdates";
import { PseudoRandom } from "../PseudoRandom";
import { SAMMissileExecution } from "./SAMMissileExecution";
+2 -8
View File
@@ -1,12 +1,6 @@
import {
Execution,
Game,
MessageType,
Player,
Unit,
UnitType,
} from "../game/Game";
import { Execution, Game, Player, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, UnitType } from "../game/GameUpdates";
import { AirPathFinder } from "../pathfinding/PathFinding";
import { PseudoRandom } from "../PseudoRandom";
import { NukeType } from "../StatsSchemas";
+2 -1
View File
@@ -1,5 +1,6 @@
import { Execution, Game, Player, Unit, UnitType } from "../game/Game";
import { Execution, Game, Player, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { UnitType } from "../game/GameUpdates";
import { AirPathFinder } from "../pathfinding/PathFinding";
import { PseudoRandom } from "../PseudoRandom";
+2 -1
View File
@@ -1,5 +1,6 @@
import { Execution, Game, Player, PlayerInfo, PlayerType } from "../game/Game";
import { Execution, Game, Player, PlayerInfo } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { PlayerType } from "../game/GameUpdates";
import { BotExecution } from "./BotExecution";
import { PlayerExecution } from "./PlayerExecution";
import { getSpawnTiles } from "./Util";
+2 -8
View File
@@ -1,13 +1,7 @@
import { renderNumber } from "../../client/Utils";
import {
Execution,
Game,
MessageType,
Player,
Unit,
UnitType,
} from "../game/Game";
import { Execution, Game, Player, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, UnitType } from "../game/GameUpdates";
import { PathFindResultType } from "../pathfinding/AStar";
import { PathFinder } from "../pathfinding/PathFinding";
import { distSortUnit } from "../Util";
+2 -8
View File
@@ -1,12 +1,6 @@
import {
Execution,
Game,
Player,
TrainType,
Unit,
UnitType,
} from "../game/Game";
import { Execution, Game, Player, Unit } from "../game/Game";
import { TileRef } from "../game/GameMap";
import { TrainType, UnitType } from "../game/GameUpdates";
import { RailNetwork } from "../game/RailNetwork";
import { getOrientedRailroad, OrientedRailroad } from "../game/Railroad";
import { TrainStation } from "../game/TrainStation";
+2 -1
View File
@@ -1,4 +1,5 @@
import { Execution, Game, Unit, UnitType } from "../game/Game";
import { Execution, Game, Unit } from "../game/Game";
import { UnitType } from "../game/GameUpdates";
import { TrainStation } from "../game/TrainStation";
import { PseudoRandom } from "../PseudoRandom";
import { TrainExecution } from "./TrainExecution";
+1 -2
View File
@@ -2,14 +2,13 @@ import { renderTroops } from "../../client/Utils";
import {
Execution,
Game,
MessageType,
Player,
PlayerID,
TerraNullius,
Unit,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { MessageType, UnitType } from "../game/GameUpdates";
import { targetTransportTile } from "../game/TransportShipUtils";
import { PathFindResultType } from "../pathfinding/AStar";
import { PathFinder } from "../pathfinding/PathFinding";
+1 -1
View File
@@ -5,9 +5,9 @@ import {
OwnerComp,
Unit,
UnitParams,
UnitType,
} from "../game/Game";
import { TileRef } from "../game/GameMap";
import { UnitType } from "../game/GameUpdates";
import { PathFindResultType } from "../pathfinding/AStar";
import { PathFinder } from "../pathfinding/PathFinding";
import { PseudoRandom } from "../PseudoRandom";
@@ -1,10 +1,5 @@
import {
Execution,
Game,
MessageType,
Player,
PlayerID,
} from "../../game/Game";
import { Execution, Game, Player, PlayerID } from "../../game/Game";
import { MessageType } from "../../game/GameUpdates";
export class AllianceExtensionExecution implements Execution {
constructor(
@@ -1,5 +1,6 @@
import { Game, Player, Relation, UnitType } from "../../game/Game";
import { Game, Player, Relation } from "../../game/Game";
import { TileRef } from "../../game/GameMap";
import { UnitType } from "../../game/GameUpdates";
import { closestTile, closestTwoTiles } from "../Util";
export function structureSpawnTileValue(
+2 -7
View File
@@ -1,10 +1,5 @@
import {
Difficulty,
Game,
Player,
PlayerType,
Relation,
} from "../../game/Game";
import { Difficulty, Game, Player, Relation } from "../../game/Game";
import { PlayerType } from "../../game/GameUpdates";
import { PseudoRandom } from "../../PseudoRandom";
import { AllianceExtensionExecution } from "../alliance/AllianceExtensionExecution";
import { AllianceRequestExecution } from "../alliance/AllianceRequestExecution";
+1 -1
View File
@@ -2,11 +2,11 @@ import {
Difficulty,
Game,
Player,
PlayerType,
Relation,
TerraNullius,
Tick,
} from "../../game/Game";
import { PlayerType } from "../../game/GameUpdates";
import { PseudoRandom } from "../../PseudoRandom";
import {
boundingBoxCenter,
+2 -1
View File
@@ -1,5 +1,6 @@
import { Game, PlayerType } from "../../game/Game";
import { Game } from "../../game/Game";
import { TileRef } from "../../game/GameMap";
import { PlayerType } from "../../game/GameUpdates";
import { PseudoRandom } from "../../PseudoRandom";
import { GameID } from "../../Schemas";
import { simpleHash } from "../../Util";
+7 -5
View File
@@ -1,6 +1,6 @@
import { AllianceRequest, Player, Tick } from "./Game";
import { GameImpl } from "./GameImpl";
import { AllianceRequestUpdate, GameUpdateType } from "./GameUpdates";
import { GameUpdate, GameUpdateType } from "./GameUpdates";
export class AllianceRequestImpl implements AllianceRequest {
private status_: "pending" | "accepted" | "rejected" = "pending";
@@ -37,12 +37,14 @@ export class AllianceRequestImpl implements AllianceRequest {
this.game.rejectAllianceRequest(this);
}
toUpdate(): AllianceRequestUpdate {
toUpdate(): GameUpdate {
return {
type: GameUpdateType.AllianceRequest,
requestorID: this.requestor_.smallID(),
recipientID: this.recipient_.smallID(),
createdAt: this.tickCreated,
allianceRequest: {
requestorId: this.requestor_.smallID(),
recipientId: this.recipient_.smallID(),
createdAt: this.tickCreated,
},
};
}
}
+16 -96
View File
@@ -3,9 +3,14 @@ import { AllPlayersStats, ClientID } from "../Schemas";
import { getClanTag } from "../Util";
import { GameMap, TileRef } from "./GameMap";
import {
EmojiMessage,
GameUpdate,
GameUpdateType,
PlayerUpdate,
GameUpdateViewData,
MessageCategory,
MessageType,
PlayerType,
TrainType,
UnitType,
UnitUpdate,
} from "./GameUpdates";
import { RailNetwork } from "./RailNetwork";
@@ -25,14 +30,9 @@ export type Gold = bigint;
export const AllPlayers = "AllPlayers" as const;
// export type GameUpdates = Record<GameUpdateType, GameUpdate[]>;
// Create a type that maps GameUpdateType to its corresponding update type
type UpdateTypeMap<T extends GameUpdateType> = Extract<GameUpdate, { type: T }>;
// Then use it to create the record type
export type GameUpdates = {
[K in GameUpdateType]: UpdateTypeMap<K>[];
};
export const AllUnitTypes = Object.values(UnitType).filter(
(v): v is UnitType => typeof v === "number",
) as UnitType[];
export interface MapPos {
x: number;
@@ -188,30 +188,6 @@ export interface UnitInfo {
experimental?: boolean;
}
export enum UnitType {
TransportShip = "Transport",
Warship = "Warship",
Shell = "Shell",
SAMMissile = "SAMMissile",
Port = "Port",
AtomBomb = "Atom Bomb",
HydrogenBomb = "Hydrogen Bomb",
TradeShip = "Trade Ship",
MissileSilo = "Missile Silo",
DefensePost = "Defense Post",
SAMLauncher = "SAM Launcher",
City = "City",
MIRV = "MIRV",
MIRVWarhead = "MIRV Warhead",
Train = "Train",
Factory = "Factory",
}
export enum TrainType {
Engine = "Engine",
Carriage = "Carriage",
}
const _structureTypes: ReadonlySet<UnitType> = new Set([
UnitType.City,
UnitType.DefensePost,
@@ -290,7 +266,7 @@ export interface UnitParamsMap {
}
// Type helper to get params type for a specific unit type
export type UnitParams<T extends UnitType> = UnitParamsMap[T];
export type UnitParams<T extends keyof UnitParamsMap> = UnitParamsMap[T];
export type AllUnitParams = UnitParamsMap[keyof UnitParamsMap];
@@ -347,12 +323,6 @@ export enum TerrainType {
Ocean,
}
export enum PlayerType {
Bot = "BOT",
Human = "HUMAN",
FakeHuman = "FAKEHUMAN",
}
export interface Execution {
isActive(): boolean;
activeDuringSpawnPhase(): boolean;
@@ -579,7 +549,7 @@ export interface Player {
buildUnit<T extends UnitType>(
type: T,
spawnTile: TileRef,
params: UnitParams<T>,
params: UnitParams<keyof UnitParamsMap>,
): Unit;
// Returns the existing unit that can be upgraded,
@@ -660,7 +630,7 @@ export interface Player {
executeRetreat(attackID: string): void;
// Misc
toUpdate(): PlayerUpdate;
toUpdate(): GameUpdate;
playerProfile(): PlayerProfile;
// WARNING: this operation is expensive.
bestTransportShipSpawn(tile: TileRef): TileRef | false;
@@ -704,7 +674,7 @@ export interface Game extends GameMap {
// Game State
ticks(): Tick;
inSpawnPhase(): boolean;
executeNextTick(): GameUpdates;
executeNextTick(): GameUpdateViewData;
setWinner(winner: Player | Team, allPlayersStats: AllPlayersStats): void;
config(): Config;
@@ -799,52 +769,8 @@ export interface PlayerInteraction {
allianceExpiresAt?: Tick;
}
export interface EmojiMessage {
message: string;
senderID: number;
recipientID: number | typeof AllPlayers;
createdAt: Tick;
}
export enum MessageType {
ATTACK_FAILED,
ATTACK_CANCELLED,
ATTACK_REQUEST,
CONQUERED_PLAYER,
MIRV_INBOUND,
NUKE_INBOUND,
HYDROGEN_BOMB_INBOUND,
NAVAL_INVASION_INBOUND,
SAM_MISS,
SAM_HIT,
CAPTURED_ENEMY_UNIT,
UNIT_CAPTURED_BY_ENEMY,
UNIT_DESTROYED,
ALLIANCE_ACCEPTED,
ALLIANCE_REJECTED,
ALLIANCE_REQUEST,
ALLIANCE_BROKEN,
ALLIANCE_EXPIRED,
SENT_GOLD_TO_PLAYER,
RECEIVED_GOLD_FROM_PLAYER,
RECEIVED_GOLD_FROM_TRADE,
SENT_TROOPS_TO_PLAYER,
RECEIVED_TROOPS_FROM_PLAYER,
CHAT,
RENEW_ALLIANCE,
}
// Message categories used for filtering events in the EventsDisplay
export enum MessageCategory {
ATTACK = "ATTACK",
NUKE = "NUKE",
ALLIANCE = "ALLIANCE",
TRADE = "TRADE",
CHAT = "CHAT",
}
// Ensures that all message types are included in a category
export const MESSAGE_TYPE_CATEGORIES: Record<MessageType, MessageCategory> = {
export const MESSAGE_TYPE_CATEGORIES: Record<number, MessageCategory> = {
[MessageType.ATTACK_FAILED]: MessageCategory.ATTACK,
[MessageType.ATTACK_CANCELLED]: MessageCategory.ATTACK,
[MessageType.ATTACK_REQUEST]: MessageCategory.ATTACK,
@@ -869,7 +795,7 @@ export const MESSAGE_TYPE_CATEGORIES: Record<MessageType, MessageCategory> = {
[MessageType.RECEIVED_GOLD_FROM_TRADE]: MessageCategory.TRADE,
[MessageType.SENT_TROOPS_TO_PLAYER]: MessageCategory.TRADE,
[MessageType.RECEIVED_TROOPS_FROM_PLAYER]: MessageCategory.TRADE,
[MessageType.CHAT]: MessageCategory.CHAT,
[MessageType.CHAT]: MessageCategory.CHAT_CATEGORY,
} as const;
/**
@@ -878,9 +804,3 @@ export const MESSAGE_TYPE_CATEGORIES: Record<MessageType, MessageCategory> = {
export function getMessageCategory(messageType: MessageType): MessageCategory {
return MESSAGE_TYPE_CATEGORIES[messageType];
}
export interface NameViewData {
x: number;
y: number;
size: number;
}
+105 -60
View File
@@ -10,19 +10,15 @@ import {
Cell,
ColoredTeams,
Duos,
EmojiMessage,
Execution,
Game,
GameMode,
GameUpdates,
HumansVsNations,
MessageType,
MutableAlliance,
Nation,
Player,
PlayerID,
PlayerInfo,
PlayerType,
Quads,
Team,
TerrainType,
@@ -30,10 +26,18 @@ import {
Trios,
Unit,
UnitInfo,
UnitType,
} from "./Game";
import { GameMap, TileRef, TileUpdate } from "./GameMap";
import { GameUpdate, GameUpdateType } from "./GameUpdates";
import {
EmojiMessage,
GameUpdate,
GameUpdates,
GameUpdateType,
GameUpdateViewData,
MessageType,
PlayerType,
UnitType,
} from "./GameUpdates";
import { PlayerImpl } from "./PlayerImpl";
import { RailNetwork } from "./RailNetwork";
import { createRailNetwork } from "./RailNetworkImpl";
@@ -75,7 +79,7 @@ export class GameImpl implements Game {
private nextPlayerID = 1;
private _nextUnitID = 1;
private updates: GameUpdates = createGameUpdatesMap();
private gameViewData = createGameUpdateViewData();
private unitGrid: UnitGrid;
private playerTeams: Team[];
@@ -197,12 +201,17 @@ export class GameImpl implements Game {
map(): GameMap {
return this._map;
}
miniMap(): GameMap {
return this.miniGameMap;
}
addTileUpdate(tile: TileRef) {
this.gameViewData.tileUpdates.push(Number(this.toTileUpdate(tile)));
}
addUpdate(update: GameUpdate) {
(this.updates[update.type] as GameUpdate[]).push(update);
this.gameViewData.updates[update.type].updates.push(update);
}
nextUnitID(): number {
@@ -219,10 +228,7 @@ export class GameImpl implements Game {
return;
}
this._map.setFallout(tile, value);
this.addUpdate({
type: GameUpdateType.Tile,
update: this.toTileUpdate(tile),
});
this.addTileUpdate(tile);
}
units(...types: UnitType[]): Unit[] {
@@ -311,8 +317,14 @@ export class GameImpl implements Game {
this.addUpdate({
type: GameUpdateType.AllianceRequestReply,
request: request.toUpdate(),
accepted: true,
allianceRequestReply: {
request: {
requestorId: requestor.smallID(),
recipientId: recipient.smallID(),
createdAt: request.createdAt(),
},
accepted: true,
},
});
}
@@ -325,8 +337,14 @@ export class GameImpl implements Game {
);
this.addUpdate({
type: GameUpdateType.AllianceRequestReply,
request: request.toUpdate(),
accepted: false,
allianceRequestReply: {
request: {
requestorId: request.requestor().smallID(),
recipientId: request.recipient().smallID(),
createdAt: request.createdAt(),
},
accepted: false,
},
});
}
@@ -345,8 +363,8 @@ export class GameImpl implements Game {
return this._ticks;
}
executeNextTick(): GameUpdates {
this.updates = createGameUpdatesMap();
executeNextTick(): GameUpdateViewData {
this.gameViewData = createGameUpdateViewData();
this.execs.forEach((e) => {
if (
(!this.inSpawnPhase() || e.activeDuringSpawnPhase()) &&
@@ -377,12 +395,14 @@ export class GameImpl implements Game {
if (this.ticks() % 10 === 0) {
this.addUpdate({
type: GameUpdateType.Hash,
tick: this.ticks(),
hash: this.hash(),
hash: {
tick: this.ticks(),
hash: this.hash(),
},
});
}
this._ticks++;
return this.updates;
return this.gameViewData;
}
private hash(): number {
@@ -556,10 +576,7 @@ export class GameImpl implements Game {
owner._lastTileChange = this._ticks;
this.updateBorders(tile);
this._map.setFallout(tile, false);
this.addUpdate({
type: GameUpdateType.Tile,
update: this.toTileUpdate(tile),
});
this.addTileUpdate(tile);
}
relinquish(tile: TileRef) {
@@ -577,10 +594,7 @@ export class GameImpl implements Game {
this._map.setOwnerID(tile, 0);
this.updateBorders(tile);
this.addUpdate({
type: GameUpdateType.Tile,
update: this.toTileUpdate(tile),
});
this.addTileUpdate(tile);
}
private updateBorders(tile: TileRef) {
@@ -616,8 +630,10 @@ export class GameImpl implements Game {
target(targeter: Player, target: Player) {
this.addUpdate({
type: GameUpdateType.TargetPlayer,
playerID: targeter.smallID(),
targetID: target.smallID(),
targetPlayer: {
playerId: targeter.smallID(),
targetId: target.smallID(),
},
});
}
@@ -647,8 +663,10 @@ export class GameImpl implements Game {
this.alliances_ = this.alliances_.filter((a) => a !== alliances[0]);
this.addUpdate({
type: GameUpdateType.BrokeAlliance,
traitorID: breaker.smallID(),
betrayedID: other.smallID(),
brokeAlliance: {
traitorId: breaker.smallID(),
betrayedId: other.smallID(),
},
});
}
@@ -666,23 +684,29 @@ export class GameImpl implements Game {
this.alliances_ = this.alliances_.filter((a) => a !== alliances[0]);
this.addUpdate({
type: GameUpdateType.AllianceExpired,
player1ID: alliance.requestor().smallID(),
player2ID: alliance.recipient().smallID(),
allianceExpired: {
player1Id: alliance.requestor().smallID(),
player2Id: alliance.recipient().smallID(),
},
});
}
sendEmojiUpdate(msg: EmojiMessage): void {
this.addUpdate({
type: GameUpdateType.Emoji,
emoji: msg,
emoji: {
emoji: msg,
},
});
}
setWinner(winner: Player | Team, allPlayersStats: AllPlayersStats): void {
this.addUpdate({
type: GameUpdateType.Win,
winner: this.makeWinner(winner),
allPlayersStats,
win: {
winner: JSON.stringify(this.makeWinner(winner)),
stats: JSON.stringify(allPlayersStats),
},
});
}
@@ -718,7 +742,7 @@ export class GameImpl implements Game {
type: MessageType,
playerID: PlayerID | null,
goldAmount?: bigint,
params?: Record<string, string | number>,
params?: Record<string, string>,
): void {
let id: number | null = null;
if (playerID !== null) {
@@ -726,11 +750,13 @@ export class GameImpl implements Game {
}
this.addUpdate({
type: GameUpdateType.DisplayEvent,
messageType: type,
message: message,
playerID: id,
goldAmount: goldAmount,
params: params,
displayMessage: {
message: message,
messageType: type,
playerId: id ?? undefined,
goldAmount: Number(goldAmount),
params: params ?? {},
},
});
}
@@ -748,12 +774,14 @@ export class GameImpl implements Game {
}
this.addUpdate({
type: GameUpdateType.DisplayChatEvent,
key: message,
category: category,
target: target,
playerID: id,
isFrom,
recipient: recipient,
displayChatMessage: {
key: message,
category: category,
target: target,
playerId: id ?? undefined,
isFrom: isFrom,
recipient: recipient,
},
});
}
@@ -767,10 +795,12 @@ export class GameImpl implements Game {
this.addUpdate({
type: GameUpdateType.UnitIncoming,
unitID: unitID,
message: message,
messageType: type,
playerID: id,
unitIncoming: {
unitId: unitID,
message: message,
messageType: type,
playerId: id,
},
});
}
@@ -964,9 +994,11 @@ export class GameImpl implements Game {
conquered.removeGold(gold);
this.addUpdate({
type: GameUpdateType.ConquestEvent,
conquerorId: conqueror.id(),
conqueredId: conquered.id(),
gold,
conquest: {
conquerorId: conqueror.smallID(),
conqueredId: conquered.smallID(),
gold: Number(gold),
},
});
// Record stats
@@ -975,12 +1007,25 @@ export class GameImpl implements Game {
}
// Or a more dynamic approach that will catch new enum values:
const createGameUpdatesMap = (): GameUpdates => {
const map = {} as GameUpdates;
const createGameUpdatesMap = (): Record<GameUpdateType, GameUpdates> => {
const map = {} as Record<GameUpdateType, GameUpdates>;
Object.values(GameUpdateType)
.filter((key) => !isNaN(Number(key))) // Filter out reverse mappings
.forEach((key) => {
map[key as GameUpdateType] = [];
map[key as GameUpdateType] = {
type: key as GameUpdateType,
updates: [],
};
});
return map;
};
const createGameUpdateViewData = (): GameUpdateViewData => {
return {
tick: 0,
updates: createGameUpdatesMap(),
tileUpdates: [],
playerNameViewData: {},
tickExecutionDuration: undefined,
};
};
+337
View File
@@ -0,0 +1,337 @@
syntax = "proto3";
package game;
enum PlayerType {
Bot = 0;
Human = 1;
FakeHuman = 2;
}
enum UnitType {
TransportShip = 0;
Warship = 1;
Shell = 2;
SAMMissile = 3;
Port = 4;
AtomBomb = 5;
HydrogenBomb = 6;
TradeShip = 7;
MissileSilo = 8;
DefensePost = 9;
SAMLauncher = 10;
City = 11;
MIRV = 12;
MIRVWarhead = 13;
Train = 14;
Factory = 15;
}
enum TrainType {
Engine = 0;
Carriage = 1;
}
message NameViewData {
int32 x = 1;
int32 y = 2;
int32 size = 3;
}
message EmojiMessage {
int32 sender_id = 1;
string emoji = 2;
oneof recipient {
bool allPlayers = 3;
int32 recipient_id = 4;
}
int32 createdAt = 5;
}
// Enums
enum GameUpdateType {
Tile = 0;
Unit = 1;
Player = 2;
DisplayEvent = 3;
DisplayChatEvent = 4;
AllianceRequest = 5;
AllianceRequestReply = 6;
BrokeAlliance = 7;
AllianceExpired = 8;
AllianceExtension = 9;
TargetPlayer = 10;
Emoji = 11;
Win = 12;
Hash = 13;
UnitIncoming = 14;
BonusEvent = 15;
RailroadEvent = 16;
ConquestEvent = 17;
EmbargoEvent = 18;
}
enum MessageType {
messageTypeUnspecified = 0;
ATTACK_FAILED = 1;
ATTACK_CANCELLED = 2;
ATTACK_REQUEST = 3;
CONQUERED_PLAYER = 4;
MIRV_INBOUND = 5;
NUKE_INBOUND = 6;
HYDROGEN_BOMB_INBOUND = 7;
NAVAL_INVASION_INBOUND = 8;
SAM_MISS = 9;
SAM_HIT = 10;
CAPTURED_ENEMY_UNIT = 11;
UNIT_CAPTURED_BY_ENEMY = 12;
UNIT_DESTROYED = 13;
ALLIANCE_ACCEPTED = 14;
ALLIANCE_REJECTED = 15;
ALLIANCE_REQUEST = 16;
ALLIANCE_BROKEN = 17;
ALLIANCE_EXPIRED = 18;
SENT_GOLD_TO_PLAYER = 19;
RECEIVED_GOLD_FROM_PLAYER = 20;
RECEIVED_GOLD_FROM_TRADE = 21;
SENT_TROOPS_TO_PLAYER = 22;
RECEIVED_TROOPS_FROM_PLAYER = 23;
CHAT = 24;
RENEW_ALLIANCE = 25;
}
enum MessageCategory {
messageCategoryUnspecified = 0;
ATTACK = 1;
NUKE = 2;
ALLIANCE = 3;
TRADE = 4;
CHAT_CATEGORY = 5;
}
enum RailType {
vertical = 0;
horizontal = 1;
topLeft = 2;
topRight = 3;
bottomLeft = 4;
bottomRight = 5;
}
// Update messages
message BonusEventUpdate {
string player = 1;
int64 tile = 2; // TileRef encoded as int64
int64 gold = 3;
int64 troops = 4;
}
message RailTile {
int64 tile = 1; // TileRef encoded as int64
RailType rail_type = 2;
}
message RailroadUpdate {
bool is_active = 1;
repeated RailTile rail_tiles = 2;
}
message ConquestUpdate {
int32 conqueror_id = 1;
int32 conquered_id = 2;
int64 gold = 3;
}
message UnitUpdate {
GameUpdateType type = 22;
UnitType unit_type = 1;
int32 troops = 2;
int32 id = 3;
int32 owner_id = 4;
optional int32 last_owner_id = 5;
int64 pos = 6; // TileRef encoded as int64
int64 last_pos = 7; // TileRef encoded as int64
bool is_active = 8;
bool reached_target = 9;
bool retreating = 10;
bool targetable = 11;
oneof marked_for_deletion_value {
int32 marked_for_deletion = 12;
}
optional int32 target_unit_id = 13; // Only for trade ships
optional int64 target_tile = 14; // TileRef encoded as int64, only for nukes
optional int32 health = 15;
optional bool under_construction = 16;
repeated int32 missile_timer_queue = 17;
int32 level = 18;
bool has_train_station = 19;
optional TrainType train_type = 20; // Only for trains
optional bool loaded = 21; // Only for trains
}
message AttackUpdate {
int32 attacker_id = 1;
int32 target_id = 2;
int32 troops = 3;
string id = 4;
bool retreating = 5;
}
message AllianceView {
int32 id = 1;
string other = 2;
int32 created_at = 3;
int32 expires_at = 4;
bool has_extension_request = 5;
}
message PlayerUpdate {
optional NameViewData name_view_data = 1;
optional string client_id = 2;
string name = 3;
string display_name = 4;
string id = 5;
optional string team = 6;
int32 small_id = 7;
PlayerType player_type = 8;
bool is_alive = 9;
bool is_disconnected = 10;
int32 tiles_owned = 11;
uint64 gold = 12;
int32 troops = 13;
repeated int32 allies = 14;
repeated string embargoes = 15;
bool is_traitor = 16;
optional int32 traitor_remaining_ticks = 17;
repeated int32 targets = 18;
repeated EmojiMessage outgoing_emojis = 19;
repeated AttackUpdate outgoing_attacks = 20;
repeated AttackUpdate incoming_attacks = 21;
repeated string outgoing_alliance_requests = 22;
repeated AllianceView alliances = 23;
bool has_spawned = 24;
int32 betrayals = 25;
int32 last_delete_unit_tick = 26;
}
message AllianceRequestUpdate {
int32 requestor_id = 1;
int32 recipient_id = 2;
int32 created_at = 3;
}
message AllianceRequestReplyUpdate {
AllianceRequestUpdate request = 1;
bool accepted = 2;
}
message BrokeAllianceUpdate {
int32 traitor_id = 1;
int32 betrayed_id = 2;
}
message AllianceExpiredUpdate {
int32 player1_id = 1;
int32 player2_id = 2;
}
message AllianceExtensionUpdate {
string player_id = 1;
int32 alliance_id = 2;
}
message TargetPlayerUpdate {
int32 player_id = 1;
int32 target_id = 2;
}
message EmojiUpdate {
EmojiMessage emoji = 1;
}
message DisplayMessageUpdate {
string message = 1;
MessageType message_type = 2;
optional int64 gold_amount = 3;
optional int64 player_id = 4;
map<string, string> params = 5;
}
message DisplayChatMessageUpdate {
string key = 1;
string category = 2;
optional string target = 3;
optional int32 player_id = 4;
bool is_from = 5;
string recipient = 6;
}
message WinUpdate {
string winner = 1;
string stats = 2;
}
message HashUpdate {
int32 tick = 1;
int32 hash = 2;
}
message UnitIncomingUpdate {
int32 unit_id = 1;
string message = 2;
MessageType message_type = 3;
int32 player_id = 4;
}
message EmbargoUpdate {
enum EmbargoEvent {
start = 0;
stop = 1;
}
EmbargoEvent event = 1;
int32 player_id = 2;
int32 embargoed_id = 3;
}
// Main GameUpdate message using oneof for polymorphism
message GameUpdate {
GameUpdateType type = 1;
oneof update {
UnitUpdate unit = 3;
PlayerUpdate player = 4;
AllianceRequestUpdate alliance_request = 5;
AllianceRequestReplyUpdate alliance_request_reply = 6;
BrokeAllianceUpdate broke_alliance = 7;
AllianceExpiredUpdate alliance_expired = 8;
DisplayMessageUpdate display_message = 9;
DisplayChatMessageUpdate display_chat_message = 10;
TargetPlayerUpdate target_player = 11;
EmojiUpdate emoji = 12;
WinUpdate win = 13;
HashUpdate hash = 14;
UnitIncomingUpdate unit_incoming = 15;
AllianceExtensionUpdate alliance_extension = 16;
BonusEventUpdate bonus_event = 17;
RailroadUpdate railroad = 18;
ConquestUpdate conquest = 19;
EmbargoUpdate embargo = 20;
}
}
message GameUpdates {
GameUpdateType type = 1;
repeated GameUpdate updates = 2;
}
message GameUpdateViewData {
int32 tick = 1;
map<int32, GameUpdates> updates = 2;
repeated int64 tile_updates = 3;
map<string, NameViewData> player_name_view_data = 4;
optional double tick_execution_duration = 5;
}
message ErrorUpdate {
string err_msg = 1;
optional string stack = 2;
}
+5388 -161
View File
File diff suppressed because it is too large Load Diff
+48 -45
View File
@@ -8,30 +8,30 @@ import { createRandomName } from "../Util";
import { WorkerClient } from "../worker/WorkerClient";
import {
Cell,
EmojiMessage,
GameUpdates,
Gold,
NameViewData,
PlayerActions,
PlayerBorderTiles,
PlayerID,
PlayerProfile,
PlayerType,
Team,
TerrainType,
TerraNullius,
Tick,
TrainType,
UnitInfo,
UnitType,
} from "./Game";
import { GameMap, TileRef, TileUpdate } from "./GameMap";
import {
AllianceView,
AttackUpdate,
EmojiMessage,
GameUpdates,
GameUpdateType,
GameUpdateViewData,
NameViewData,
PlayerType,
PlayerUpdate,
TrainType,
UnitType,
UnitUpdate,
} from "./GameUpdates";
import { TerrainMapData } from "./TerrainMapLoader";
@@ -88,7 +88,7 @@ export class UnitView {
}
markedForDeletion(): number | false {
return this.data.markedForDeletion;
return this.data.markedForDeletion ?? false;
}
type(): UnitType {
@@ -107,7 +107,7 @@ export class UnitView {
return this.data.pos;
}
owner(): PlayerView {
return this.gameView.playerBySmallID(this.data.ownerID)! as PlayerView;
return this.gameView.playerBySmallID(this.data.ownerId)! as PlayerView;
}
isActive(): boolean {
return this.data.isActive;
@@ -194,7 +194,7 @@ export class PlayerView {
public nameData: NameViewData,
public cosmetics: PlayerCosmetics,
) {
if (data.clientID === game.myClientID()) {
if (data.clientId === game.myClientID()) {
this.anonymousName = this.data.name;
} else {
this.anonymousName = createRandomName(
@@ -239,7 +239,7 @@ export class PlayerView {
.structureColors(this._territoryColor);
const maybeFocusedBorderColor =
this.game.myClientID() === this.data.clientID
this.game.myClientID() === this.data.clientId
? this.game.config().theme().focusedBorderColor()
: defaultBorderColor;
@@ -327,7 +327,7 @@ export class PlayerView {
}
smallID(): number {
return this.data.smallID;
return this.data.smallId;
}
name(): string {
@@ -342,7 +342,7 @@ export class PlayerView {
}
clientID(): ClientID | null {
return this.data.clientID;
return this.data.clientId ?? null;
}
id(): PlayerID {
return this.data.id;
@@ -373,7 +373,7 @@ export class PlayerView {
);
}
gold(): Gold {
return this.data.gold;
return BigInt(this.data.gold);
}
troops(): number {
@@ -412,7 +412,7 @@ export class PlayerView {
}
hasEmbargoAgainst(other: PlayerView): boolean {
return this.data.embargoes.has(other.id());
return this.data.embargoes.some((id) => id === other.id());
}
hasEmbargo(other: PlayerView): boolean {
@@ -506,8 +506,8 @@ export class GameView implements GameMap {
return this._map.isOnEdgeOfMap(ref);
}
public updatesSinceLastTick(): GameUpdates | null {
return this.lastUpdate?.updates ?? null;
public updatesSinceLastTick(): { [key: number]: GameUpdates } {
return this.lastUpdate!.updates;
}
public update(gu: GameUpdateViewData) {
@@ -517,34 +517,36 @@ export class GameView implements GameMap {
this.lastUpdate = gu;
this.updatedTiles = [];
this.lastUpdate.packedTileUpdates.forEach((tu) => {
this.updatedTiles.push(this.updateTile(tu));
this.lastUpdate.tileUpdates.forEach((tu) => {
this.updatedTiles.push(this.updateTile(BigInt(tu)));
});
if (gu.updates === null) {
throw new Error("lastUpdate.updates not initialized");
}
gu.updates[GameUpdateType.Player].forEach((pu) => {
this.smallIDToID.set(pu.smallID, pu.id);
const player = this._players.get(pu.id);
if (player !== undefined) {
player.data = pu;
player.nameData = gu.playerNameViewData[pu.id];
} else {
this._players.set(
pu.id,
new PlayerView(
this,
pu,
gu.playerNameViewData[pu.id],
// First check human by clientID, then check nation by name.
this._cosmetics.get(pu.clientID ?? "") ??
this._cosmetics.get(pu.name) ??
{},
),
);
}
});
gu.updates[GameUpdateType.Player].updates
.map((pu) => pu.player!)
.forEach((pu) => {
this.smallIDToID.set(pu.smallId, pu.id);
const player = this._players.get(pu.id);
if (player !== undefined) {
player.data = pu;
player.nameData = gu.playerNameViewData[pu.id];
} else {
this._players.set(
pu.id,
new PlayerView(
this,
pu,
gu.playerNameViewData[pu.id],
// First check human by clientID, then check nation by name.
this._cosmetics.get(pu.clientId ?? "") ??
this._cosmetics.get(pu.name) ??
{},
),
);
}
});
this._myPlayer ??= this.playerByClientID(this._myClientID);
@@ -552,16 +554,17 @@ export class GameView implements GameMap {
unit._wasUpdated = false;
unit.lastPos = unit.lastPos.slice(-1);
}
gu.updates[GameUpdateType.Unit].forEach((update) => {
let unit = this._units.get(update.id);
gu.updates[GameUpdateType.Unit].updates.forEach((update) => {
const ud = update.unit!;
let unit = this._units.get(ud.id);
if (unit !== undefined) {
unit.update(update);
unit.update(ud);
} else {
unit = new UnitView(this, update);
this._units.set(update.id, unit);
unit = new UnitView(this, ud);
this._units.set(ud.id, unit);
this.unitGrid.addUnit(unit);
}
if (!update.isActive) {
if (!ud.isActive) {
this.unitGrid.removeUnit(unit);
} else if (unit.tile() !== unit.lastTile()) {
this.unitGrid.updateUnitCell(unit);
+91 -81
View File
@@ -14,35 +14,38 @@ import {
Alliance,
AllianceRequest,
AllPlayers,
AllUnitTypes,
Attack,
BuildableUnit,
Cell,
ColoredTeams,
Embargo,
EmojiMessage,
Gold,
MessageType,
MutableAlliance,
Player,
PlayerID,
PlayerInfo,
PlayerProfile,
PlayerType,
Relation,
Team,
TerraNullius,
Tick,
Unit,
UnitParams,
UnitType,
UnitParamsMap,
} from "./Game";
import { GameImpl } from "./GameImpl";
import { andFN, manhattanDistFN, TileRef } from "./GameMap";
import {
AllianceView,
AttackUpdate,
EmbargoUpdate_EmbargoEvent,
EmojiMessage,
GameUpdate,
GameUpdateType,
PlayerUpdate,
MessageType,
PlayerType,
UnitType,
} from "./GameUpdates";
import {
bestShoreDeploymentSource,
@@ -119,66 +122,66 @@ export class PlayerImpl implements Player {
largestClusterBoundingBox: { min: Cell; max: Cell } | null;
toUpdate(): PlayerUpdate {
const outgoingAllianceRequests = this.outgoingAllianceRequests().map((ar) =>
ar.recipient().id(),
);
toUpdate(): GameUpdate {
return {
type: GameUpdateType.Player,
clientID: this.clientID(),
name: this.name(),
displayName: this.displayName(),
id: this.id(),
team: this.team() ?? undefined,
smallID: this.smallID(),
playerType: this.type(),
isAlive: this.isAlive(),
isDisconnected: this.isDisconnected(),
tilesOwned: this.numTilesOwned(),
gold: this._gold,
troops: this.troops(),
allies: this.alliances().map((a) => a.other(this).smallID()),
embargoes: new Set([...this.embargoes.keys()].map((p) => p.toString())),
isTraitor: this.isTraitor(),
traitorRemainingTicks: this.getTraitorRemainingTicks(),
targets: this.targets().map((p) => p.smallID()),
outgoingEmojis: this.outgoingEmojis(),
outgoingAttacks: this._outgoingAttacks.map((a) => {
return {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
} satisfies AttackUpdate;
}),
incomingAttacks: this._incomingAttacks.map((a) => {
return {
attackerID: a.attacker().smallID(),
targetID: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
} satisfies AttackUpdate;
}),
outgoingAllianceRequests: outgoingAllianceRequests,
alliances: this.alliances().map(
(a) =>
({
player: {
clientId: this.clientID() ?? undefined,
name: this.name(),
displayName: this.displayName(),
id: this.id(),
team: this.team() ?? undefined,
smallId: this.smallID(),
playerType: this.type(),
isAlive: this.isAlive(),
isDisconnected: this.isDisconnected(),
tilesOwned: this.numTilesOwned(),
gold: Number(this._gold),
troops: this.troops(),
allies: this.alliances().map((a) => a.other(this).smallID()),
embargoes: [...this.embargoes.keys()].map((p) => p.toString()),
isTraitor: this.isTraitor(),
traitorRemainingTicks: this.getTraitorRemainingTicks(),
targets: this.targets().map((p) => p.smallID()),
outgoingEmojis: this.outgoingEmojis(),
outgoingAttacks: this._outgoingAttacks.map((a) => {
return {
attackerId: a.attacker().smallID(),
targetId: a.target().smallID(),
troops: a.troops(),
id: a.id(),
other: a.other(this).id(),
createdAt: a.createdAt(),
expiresAt: a.expiresAt(),
hasExtensionRequest:
a.expiresAt() <=
this.mg.ticks() +
this.mg.config().allianceExtensionPromptOffset(),
}) satisfies AllianceView,
),
hasSpawned: this.hasSpawned(),
betrayals: this._betrayalCount,
lastDeleteUnitTick: this.lastDeleteUnitTick,
retreating: a.retreating(),
} satisfies AttackUpdate;
}),
incomingAttacks: this._incomingAttacks.map((a) => {
return {
attackerId: a.attacker().smallID(),
targetId: a.target().smallID(),
troops: a.troops(),
id: a.id(),
retreating: a.retreating(),
} satisfies AttackUpdate;
}),
outgoingAllianceRequests: this.outgoingAllianceRequests().map((ar) =>
ar.recipient().id(),
),
alliances: this.alliances().map(
(a) =>
({
id: a.id(),
other: a.other(this).id(),
createdAt: a.createdAt(),
expiresAt: a.expiresAt(),
hasExtensionRequest:
a.expiresAt() <=
this.mg.ticks() +
this.mg.config().allianceExtensionPromptOffset(),
}) satisfies AllianceView,
),
hasSpawned: this.hasSpawned(),
betrayals: this._betrayalCount,
lastDeleteUnitTick: this.lastDeleteUnitTick,
},
};
}
@@ -553,9 +556,10 @@ export class PlayerImpl implements Player {
throw Error(`Cannot send emoji to oneself: ${this}`);
}
const msg: EmojiMessage = {
message: emoji,
senderID: this.smallID(),
recipientID: recipient === AllPlayers ? recipient : recipient.smallID(),
emoji: emoji,
senderId: this.smallID(),
allPlayers: recipient === AllPlayers,
recipientId: recipient === AllPlayers ? undefined : recipient.smallID(),
createdAt: this.mg.ticks(),
};
this.outgoingEmojis_.push(msg);
@@ -578,9 +582,9 @@ export class PlayerImpl implements Player {
}
const recipientID =
recipient === AllPlayers ? AllPlayers : recipient.smallID();
const prevMsgs = this.outgoingEmojis_.filter(
(msg) => msg.recipientID === recipientID,
);
const prevMsgs = this.outgoingEmojis_.filter((msg) => {
return msg.allPlayers ?? msg.recipientId === recipientID;
});
for (const msg of prevMsgs) {
if (
this.mg.ticks() - msg.createdAt <
@@ -740,9 +744,11 @@ export class PlayerImpl implements Player {
this.mg.addUpdate({
type: GameUpdateType.EmbargoEvent,
event: "start",
playerID: this.smallID(),
embargoedID: other.smallID(),
embargo: {
playerId: this.smallID(),
embargoedId: other.smallID(),
event: EmbargoUpdate_EmbargoEvent.start,
},
});
this.embargoes.set(other.id(), {
@@ -756,9 +762,11 @@ export class PlayerImpl implements Player {
this.embargoes.delete(other.id());
this.mg.addUpdate({
type: GameUpdateType.EmbargoEvent,
event: "stop",
playerID: this.smallID(),
embargoedID: other.smallID(),
embargo: {
playerId: this.smallID(),
embargoedId: other.smallID(),
event: EmbargoUpdate_EmbargoEvent.stop,
},
});
}
@@ -808,10 +816,12 @@ export class PlayerImpl implements Player {
if (tile) {
this.mg.addUpdate({
type: GameUpdateType.BonusEvent,
player: this.id(),
tile,
gold: Number(toAdd),
troops: 0,
bonusEvent: {
player: this.id(),
tile,
gold: Number(toAdd),
troops: 0,
},
});
}
}
@@ -855,7 +865,7 @@ export class PlayerImpl implements Player {
buildUnit<T extends UnitType>(
type: T,
spawnTile: TileRef,
params: UnitParams<T>,
params: UnitParams<keyof UnitParamsMap>,
): Unit {
if (this.mg.config().isUnitDisabled(type)) {
throw new Error(
@@ -930,7 +940,7 @@ export class PlayerImpl implements Player {
public buildableUnits(tile: TileRef | null): BuildableUnit[] {
const validTiles = tile !== null ? this.validStructureSpawnTiles(tile) : [];
return Object.values(UnitType).map((u) => {
return AllUnitTypes.map((u) => {
let canUpgrade: number | false = false;
if (!this.mg.inSpawnPhase()) {
const existingUnit = tile !== null && this.findUnitToUpgrade(u, tile);
@@ -1076,7 +1086,7 @@ export class PlayerImpl implements Player {
}
const searchRadius = 15;
const searchRadiusSquared = searchRadius ** 2;
const types = Object.values(UnitType).filter((unitTypeValue) => {
const types = AllUnitTypes.filter((unitTypeValue) => {
return this.mg.config().unitInfo(unitTypeValue).territoryBound;
});
+2 -1
View File
@@ -2,8 +2,9 @@ import { RailroadExecution } from "../execution/RailroadExecution";
import { PathFindResultType } from "../pathfinding/AStar";
import { MiniAStar } from "../pathfinding/MiniAStar";
import { SerialAStar } from "../pathfinding/SerialAStar";
import { Game, Unit, UnitType } from "./Game";
import { Game, Unit } from "./Game";
import { TileRef } from "./GameMap";
import { UnitType } from "./GameUpdates";
import { RailNetwork } from "./RailNetwork";
import { Railroad } from "./Railroad";
import { Cluster, TrainStation, TrainStationMapAdapter } from "./TrainStation";
+5 -3
View File
@@ -13,12 +13,14 @@ export class Railroad {
delete(game: Game) {
const railTiles: RailTile[] = this.tiles.map((tile) => ({
tile,
railType: RailType.VERTICAL,
railType: RailType.vertical,
}));
game.addUpdate({
type: GameUpdateType.RailroadEvent,
isActive: false,
railTiles,
railroad: {
isActive: false,
railTiles,
},
});
this.from.removeRailroad(this);
this.to.removeRailroad(this);
+2 -1
View File
@@ -26,7 +26,8 @@ import {
unitTypeToBombUnit,
unitTypeToOtherUnit,
} from "../StatsSchemas";
import { Player, TerraNullius, UnitType } from "./Game";
import { Player, TerraNullius } from "./Game";
import { UnitType } from "./GameUpdates";
import { Stats } from "./Stats";
type BigIntLike = bigint | number;
+2 -1
View File
@@ -1,6 +1,7 @@
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
import { PlayerInfo, PlayerType, Team } from "./Game";
import { PlayerInfo, Team } from "./Game";
import { PlayerType } from "./GameUpdates";
export function assignTeams(
players: PlayerInfo[],
+7 -5
View File
@@ -1,9 +1,9 @@
import { TrainExecution } from "../execution/TrainExecution";
import { GraphAdapter } from "../pathfinding/SerialAStar";
import { PseudoRandom } from "../PseudoRandom";
import { Game, Player, Unit, UnitType } from "./Game";
import { Game, Player, Unit } from "./Game";
import { TileRef } from "./GameMap";
import { GameUpdateType, RailTile, RailType } from "./GameUpdates";
import { GameUpdateType, RailTile, RailType, UnitType } from "./GameUpdates";
import { Railroad } from "./Railroad";
/**
@@ -115,12 +115,14 @@ export class TrainStation {
if (toRemove) {
const railTiles: RailTile[] = toRemove.tiles.map((tile) => ({
tile,
railType: RailType.VERTICAL,
railType: RailType.vertical,
}));
this.mg.addUpdate({
type: GameUpdateType.RailroadEvent,
isActive: false,
railTiles,
railroad: {
isActive: false,
railTiles,
},
});
this.removeRailroad(toRemove);
}
+2 -1
View File
@@ -1,7 +1,8 @@
import { PathFindResultType } from "../pathfinding/AStar";
import { MiniAStar } from "../pathfinding/MiniAStar";
import { Game, Player, UnitType } from "./Game";
import { Game, Player } from "./Game";
import { andFN, GameMap, manhattanDistFN, TileRef } from "./GameMap";
import { UnitType } from "./GameUpdates";
export function canBuildTransportShip(
game: Game,
+2 -1
View File
@@ -1,5 +1,6 @@
import { PlayerID, Unit, UnitType } from "./Game";
import { PlayerID, Unit } from "./Game";
import { GameMap, TileRef } from "./GameMap";
import { UnitType } from "./GameUpdates";
import { UnitView } from "./GameView";
export type UnitPredicate = (value: {
+12 -9
View File
@@ -1,18 +1,21 @@
import { simpleHash, toInt, withinInt } from "../Util";
import { toInt, withinInt } from "../Util";
import {
AllUnitParams,
MessageType,
Player,
Tick,
TrainType,
TrajectoryTile,
Unit,
UnitInfo,
UnitType,
} from "./Game";
import { GameImpl } from "./GameImpl";
import { TileRef } from "./GameMap";
import { GameUpdateType, UnitUpdate } from "./GameUpdates";
import {
GameUpdateType,
MessageType,
TrainType,
UnitType,
UnitUpdate,
} from "./GameUpdates";
import { PlayerImpl } from "./PlayerImpl";
export class UnitImpl implements Unit {
@@ -123,13 +126,13 @@ export class UnitImpl implements Unit {
unitType: this._type,
id: this._id,
troops: this._troops,
ownerID: this._owner.smallID(),
lastOwnerID: this._lastOwner?.smallID(),
ownerId: this._owner.smallID(),
lastOwnerId: this._lastOwner?.smallID(),
isActive: this._active,
reachedTarget: this._reachedTarget,
retreating: this._retreating,
pos: this._tile,
markedForDeletion: this._deletionAt ?? false,
markedForDeletion: this._deletionAt ?? undefined,
targetable: this._targetable,
lastPos: this._lastTile,
health: this.hasHealth() ? Number(this._health) : undefined,
@@ -325,7 +328,7 @@ export class UnitImpl implements Unit {
}
hash(): number {
return this.tile() + simpleHash(this.type()) * this._id;
return this.tile() + this.type() * this._id;
}
toString(): string {
+1 -1
View File
@@ -4,9 +4,9 @@ import {
Game,
Player,
PlayerInfo,
PlayerType,
Tick,
} from "../src/core/game/Game";
import { PlayerType } from "../src/core/game/GameUpdates";
import { PseudoRandom } from "../src/core/PseudoRandom";
import { setup } from "./util/Setup";
+2 -1
View File
@@ -1,7 +1,8 @@
import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution";
import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution";
import { DonateGoldExecution } from "../src/core/execution/DonateGoldExecution";
import { Game, Player, PlayerType } from "../src/core/game/Game";
import { Game, Player } from "../src/core/game/Game";
import { PlayerType } from "../src/core/game/GameUpdates";
import { playerInfo, setup } from "./util/Setup";
let game: Game;
+2 -1
View File
@@ -1,7 +1,8 @@
import { AllianceExtensionExecution } from "../src/core/execution/alliance/AllianceExtensionExecution";
import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution";
import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution";
import { Game, MessageType, Player, PlayerType } from "../src/core/game/Game";
import { Game, Player } from "../src/core/game/Game";
import { MessageType, PlayerType } from "../src/core/game/GameUpdates";
import { playerInfo, setup } from "./util/Setup";
let game: Game;
+2 -1
View File
@@ -1,7 +1,8 @@
import { AllianceRequestExecution } from "../src/core/execution/alliance/AllianceRequestExecution";
import { AllianceRequestReplyExecution } from "../src/core/execution/alliance/AllianceRequestReplyExecution";
import { NukeExecution } from "../src/core/execution/NukeExecution";
import { Game, Player, PlayerType, UnitType } from "../src/core/game/Game";
import { Game, Player } from "../src/core/game/Game";
import { PlayerType, UnitType } from "../src/core/game/GameUpdates";
import { playerInfo, setup } from "./util/Setup";
import { constructionExecution } from "./util/utils";
+2 -7
View File
@@ -1,14 +1,9 @@
import { AttackExecution } from "../src/core/execution/AttackExecution";
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
import { TransportShipExecution } from "../src/core/execution/TransportShipExecution";
import {
Game,
Player,
PlayerInfo,
PlayerType,
UnitType,
} from "../src/core/game/Game";
import { Game, Player, PlayerInfo } from "../src/core/game/Game";
import { TileRef } from "../src/core/game/GameMap";
import { PlayerType, UnitType } from "../src/core/game/GameUpdates";
import { setup } from "./util/Setup";
import { TestConfig } from "./util/TestConfig";
import { constructionExecution } from "./util/utils";
+2 -1
View File
@@ -1,6 +1,7 @@
import { AttackExecution } from "../src/core/execution/AttackExecution";
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
import { Game, Player, PlayerInfo, PlayerType } from "../src/core/game/Game";
import { Game, Player, PlayerInfo } from "../src/core/game/Game";
import { PlayerType } from "../src/core/game/GameUpdates";
import { GOLD_INDEX_WAR, GOLD_INDEX_WORK } from "../src/core/StatsSchemas";
import { setup } from "./util/Setup";
+2 -1
View File
@@ -1,5 +1,6 @@
import { BotBehavior } from "../src/core/execution/utils/BotBehavior";
import { Game, Player, PlayerInfo, PlayerType } from "../src/core/game/Game";
import { Game, Player, PlayerInfo } from "../src/core/game/Game";
import { PlayerType } from "../src/core/game/GameUpdates";
import { PseudoRandom } from "../src/core/PseudoRandom";
import { setup } from "./util/Setup";
+2 -8
View File
@@ -1,14 +1,8 @@
import { DeleteUnitExecution } from "../src/core/execution/DeleteUnitExecution";
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
import {
Game,
Player,
PlayerInfo,
PlayerType,
Unit,
UnitType,
} from "../src/core/game/Game";
import { Game, Player, PlayerInfo, Unit } from "../src/core/game/Game";
import { TileRef } from "../src/core/game/GameMap";
import { PlayerType, UnitType } from "../src/core/game/GameUpdates";
import { setup } from "./util/Setup";
import { executeTicks } from "./util/utils";
+4 -10
View File
@@ -3,14 +3,8 @@ import { MarkDisconnectedExecution } from "../src/core/execution/MarkDisconnecte
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
import { TransportShipExecution } from "../src/core/execution/TransportShipExecution";
import { WarshipExecution } from "../src/core/execution/WarshipExecution";
import {
Game,
GameMode,
Player,
PlayerInfo,
PlayerType,
UnitType,
} from "../src/core/game/Game";
import { Game, GameMode, Player, PlayerInfo } from "../src/core/game/Game";
import { PlayerType, UnitType } from "../src/core/game/GameUpdates";
import { toInt } from "../src/core/Util";
import { setup } from "./util/Setup";
import { UseRealAttackLogic } from "./util/TestConfig";
@@ -72,7 +66,7 @@ describe("Disconnected", () => {
test("should include disconnected state in player update", () => {
player1.markDisconnected(true);
const update = player1.toUpdate();
expect(update.isDisconnected).toBe(true);
expect(update.player!.isDisconnected).toBe(true);
});
});
@@ -146,7 +140,7 @@ describe("Disconnected", () => {
player1.markDisconnected(true);
executeTicks(game, 3);
const update = player1.toUpdate();
expect(update.isDisconnected).toBe(true);
expect(update.player!.isDisconnected).toBe(true);
});
});
+2 -1
View File
@@ -1,7 +1,8 @@
import { DonateGoldExecution } from "../src/core/execution/DonateGoldExecution";
import { DonateTroopsExecution } from "../src/core/execution/DonateTroopExecution";
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
import { PlayerInfo, PlayerType } from "../src/core/game/Game";
import { PlayerInfo } from "../src/core/game/Game";
import { PlayerType } from "../src/core/game/GameUpdates";
import { setup } from "./util/Setup";
describe("Donate troops to an ally", () => {

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