Merge pull request #11 from evanpelle/dev

Dev
This commit is contained in:
evanpelle
2024-12-04 19:07:10 -08:00
committed by GitHub
21 changed files with 765 additions and 181 deletions
+15 -3
View File
@@ -207,11 +207,22 @@
* use mini A* for all pathfinding DONE 12/3/2024
* bugfix: gameStop not found error DONE 12/3/2024
* log stack traces & display them on screen DONE 12/3/2024
* add radiation from nuke DONE 12/4/2024
* add cities DONE 12/4/2024
* max price for units
* when player dies, don't remove atom bombs
* record and replay games for debugging purposes
* add bug report button in game
* bugfix: destroyers can't find path to dst and freeze
* record single player game stats
* add radiation from nuke
* add cities
* stop requesting lobby when playing game
* nuking an enemy and accidentally destroying a trade ship shouldn't break the alliance and make you a traitor
* you should get a notification and a reward (some money) for eliminating an enemy (perhaps take wtvr gold the enemy had)
* emojis should be displayed on top of your name not under it
* the notification for a successful trade should be shorter, example: " 70k Gold from trade with "X" "
* countries don't actually spawn with some randomness, it's always the same exact spawn
* allow longer names and allow them to be displayed in the Rank UI not be cut (many are cut for now, even for countries)
* clicking on a player's name in the rank UI should teleport you to him (pretty useful to know who's who and to locate small nations)
* create behavior tests
* create perf test
* create alternate view to show friendly & enemy units
@@ -232,4 +243,5 @@
* improve front page (make map larger?)
* REFACTOR: give terranullius an ID, game.player() returns terranullius
* REFACTOR: ocean is considered TerraNullius ?
* REFACTOR: ocean is considered TerraNullius ?
+367 -5
View File
@@ -7,6 +7,7 @@
"name": "openfront-client",
"dependencies": {
"@datastructures-js/priority-queue": "^6.3.1",
"@google-cloud/storage": "^7.14.0",
"@types/dompurify": "^3.0.5",
"@types/express": "^4.17.21",
"@types/google-protobuf": "^3.15.12",
@@ -2349,6 +2350,93 @@
"node": ">=18"
}
},
"node_modules/@google-cloud/paginator": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz",
"integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==",
"license": "Apache-2.0",
"dependencies": {
"arrify": "^2.0.0",
"extend": "^3.0.2"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@google-cloud/paginator/node_modules/arrify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@google-cloud/projectify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
"integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
"license": "Apache-2.0",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@google-cloud/promisify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
"integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
"license": "Apache-2.0",
"engines": {
"node": ">=14"
}
},
"node_modules/@google-cloud/storage": {
"version": "7.14.0",
"resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz",
"integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==",
"license": "Apache-2.0",
"dependencies": {
"@google-cloud/paginator": "^5.0.0",
"@google-cloud/projectify": "^4.0.0",
"@google-cloud/promisify": "^4.0.0",
"abort-controller": "^3.0.0",
"async-retry": "^1.3.3",
"duplexify": "^4.1.3",
"fast-xml-parser": "^4.4.1",
"gaxios": "^6.0.2",
"google-auth-library": "^9.6.3",
"html-entities": "^2.5.2",
"mime": "^3.0.0",
"p-limit": "^3.0.1",
"retry-request": "^7.0.0",
"teeny-request": "^9.0.0",
"uuid": "^8.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@google-cloud/storage/node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
"integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/@google-cloud/storage/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
@@ -3568,6 +3656,15 @@
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
"integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -3661,6 +3758,12 @@
"@types/node": "*"
}
},
"node_modules/@types/caseless": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
"integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
"license": "MIT"
},
"node_modules/@types/chai": {
"version": "4.3.20",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.20.tgz",
@@ -4208,6 +4311,18 @@
"safe-buffer": "~5.1.1"
}
},
"node_modules/@types/request": {
"version": "2.48.12",
"resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz",
"integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==",
"license": "MIT",
"dependencies": {
"@types/caseless": "*",
"@types/node": "*",
"@types/tough-cookie": "*",
"form-data": "^2.5.0"
}
},
"node_modules/@types/retry": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz",
@@ -4287,6 +4402,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/tough-cookie": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
"license": "MIT"
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
@@ -4855,6 +4976,30 @@
"dev": true,
"license": "MIT"
},
"node_modules/async-retry": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
"integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
"license": "MIT",
"dependencies": {
"retry": "0.13.1"
}
},
"node_modules/async-retry/node_modules/retry": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
@@ -5703,6 +5848,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
@@ -6610,6 +6767,15 @@
"robust-predicates": "^3.0.2"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -6780,6 +6946,32 @@
"tslib": "^2.0.3"
}
},
"node_modules/duplexify": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz",
"integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.4.1",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1",
"stream-shift": "^1.0.2"
}
},
"node_modules/duplexify/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -6872,6 +7064,15 @@
"iconv-lite": "^0.6.2"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/enhanced-resolve": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
@@ -7309,6 +7510,28 @@
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/fast-xml-parser": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz",
"integrity": "sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
},
{
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
}
],
"license": "MIT",
"dependencies": {
"strnum": "^1.0.5"
},
"bin": {
"fxparser": "src/cli/cli.js"
}
},
"node_modules/fastest-levenshtein": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz",
@@ -7550,6 +7773,41 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz",
"integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
"mime-types": "^2.1.12",
"safe-buffer": "^5.2.1"
},
"engines": {
"node": ">= 0.12"
}
},
"node_modules/form-data/node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -8041,7 +8299,6 @@
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz",
"integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -10807,7 +11064,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"dependencies": {
"wrappy": "1"
@@ -10861,7 +11117,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"yocto-queue": "^0.1.0"
@@ -11912,6 +12167,20 @@
"node": ">= 4"
}
},
"node_modules/retry-request": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
"integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
"license": "MIT",
"dependencies": {
"@types/request": "^2.48.8",
"extend": "^3.0.2",
"teeny-request": "^9.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/robust-predicates": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
@@ -12518,6 +12787,21 @@
"node": ">= 0.8"
}
},
"node_modules/stream-events": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
"integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
"license": "MIT",
"dependencies": {
"stubs": "^3.0.0"
}
},
"node_modules/stream-shift": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
"integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -12648,6 +12932,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strnum": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
"license": "MIT"
},
"node_modules/strtok3": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz",
@@ -12665,6 +12955,12 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/stubs": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
"integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
"license": "MIT"
},
"node_modules/style-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz",
@@ -12774,6 +13070,74 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"license": "ISC"
},
"node_modules/teeny-request": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
"integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
"license": "Apache-2.0",
"dependencies": {
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.9",
"stream-events": "^1.0.5",
"uuid": "^9.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/teeny-request/node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"license": "MIT",
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/teeny-request/node_modules/http-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
"integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
"license": "MIT",
"dependencies": {
"@tootallnate/once": "2",
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/teeny-request/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"license": "MIT",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/teeny-request/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/terser": {
"version": "5.36.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz",
@@ -14198,7 +14562,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true,
"license": "ISC"
},
"node_modules/write-file-atomic": {
@@ -14394,7 +14757,6 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
+1
View File
@@ -55,6 +55,7 @@
},
"dependencies": {
"@datastructures-js/priority-queue": "^6.3.1",
"@google-cloud/storage": "^7.14.0",
"@types/dompurify": "^3.0.5",
"@types/express": "^4.17.21",
"@types/google-protobuf": "^3.15.12",
Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
width="800px"
height="800px"
viewBox="0 0 24 24"
version="1.1"
id="svg4"
sodipodi:docname="CityIconWhite.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs8" />
<sodipodi:namedview
id="namedview6"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="0.295"
inkscape:cx="401.69492"
inkscape:cy="400"
inkscape:window-width="1536"
inkscape:window-height="987"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg4" />
<path
d="M13,9a1,1,0,0,0-1-1H3A1,1,0,0,0,2,9V22H13ZM6,20H4V18H6Zm0-4H4V14H6Zm0-4H4V10H6Zm5,8H8V18h3Zm0-4H8V14h3Zm0-4H8V10h3Zm3.5-6H6V3A1,1,0,0,1,7,2H17a1,1,0,0,1,1,1v7H15V6.5A.5.5,0,0,0,14.5,6ZM22,13v9H19.5V18h-2v4H15V13a1,1,0,0,1,1-1h5A1,1,0,0,1,22,13Z"
id="path2" />
<path
style="fill:#ffffff;stroke-width:3.38983"
d="M 67.79661,511.52542 V 290.84746 l 11.621631,-11.62163 11.621632,-11.62163 161.555287,0.94366 161.55528,0.94367 8.17902,9.51106 8.17901,9.51106 V 510.35852 732.20339 H 249.15254 67.79661 Z M 200,633.89831 V 600 h -33.89831 -33.8983 v 33.89831 33.8983 H 166.10169 200 Z m 169.49153,0 V 600 h -52.54238 -52.54237 v 33.89831 33.8983 h 52.54237 52.54238 z M 200,500 v -35.59322 h -33.89831 -33.8983 V 500 535.59322 H 166.10169 200 Z m 169.49153,0 V 464.40678 H 316.94915 264.40678 V 500 535.59322 h 52.54237 52.54238 z M 200,366.10169 v -33.8983 h -33.89831 -33.8983 V 366.10169 400 H 166.10169 200 Z m 169.49153,0 v -33.8983 H 316.94915 264.40678 V 366.10169 400 h 52.54237 52.54238 z"
id="path183"
transform="scale(0.03)" />
<path
style="fill:#ffffff;stroke-width:3.38983"
d="m 501.69492,270.16949 c 0,-43.72881 -1.20034,-63.23423 -4.0678,-66.10169 C 494.58828,201.02896 456.35566,200 346.48184,200 H 199.40436 l 1.14528,-53.55002 c 1.24728,-58.31948 4.26404,-68.716423 21.88222,-75.414844 11.34097,-4.31183 343.79529,-4.31183 355.13628,0 4.68488,1.781189 11.26725,7.431255 14.62745,12.555701 5.54217,8.452001 6.20319,20.427403 7.11855,128.964863 l 1.00901,119.64769 h -49.3141 -49.31413 z"
id="path185"
transform="scale(0.03)" />
<path
style="fill:#ffffff;stroke-width:3.38983"
d="M 501.69492,578.01958 V 423.83577 l 9.89142,-11.07043 9.89143,-11.07042 h 92.80003 c 83.69792,0 93.71458,0.59923 102.12428,6.10948 5.12834,3.36022 10.78159,9.94258 12.56278,14.62746 1.88177,4.94941 3.23853,71.62228 3.23853,159.14475 V 732.20339 H 691.52542 650.84746 V 666.10169 600 h -33.89831 -33.8983 v 66.10169 66.1017 h -40.67797 -40.67796 z"
id="path187"
transform="scale(0.03)" />
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -8,6 +8,7 @@ import { EventBus } from "../../../core/EventBus";
import anchorIcon from '../../../../resources/images/AnchorIcon.png';
import missileSiloIcon from '../../../../resources/images/MissileSiloUnit.png';
import shieldIcon from '../../../../resources/images/ShieldIcon.png';
import cityIcon from '../../../../resources/images/CityIcon.png';
interface UnitRenderConfig {
icon: string;
@@ -38,6 +39,11 @@ export class StructureLayer implements Layer {
icon: shieldIcon,
borderRadius: 8,
territoryRadius: 6
},
[UnitType.City]: {
icon: cityIcon,
borderRadius: 8,
territoryRadius: 6
}
};
@@ -75,6 +75,10 @@ export class TerritoryLayer implements Layer {
}
paintTerritory(tile: Tile) {
if (tile.hasFallout()) {
this.paintCell(tile.cell(), this.theme.falloutColor(), 150)
return
}
if (!tile.hasOwner()) {
this.clearCell(tile.cell())
return
@@ -11,6 +11,7 @@ import missileSiloIcon from '../../../../../resources/images/MissileSiloIconWhit
import goldCoinIcon from '../../../../../resources/images/GoldCoinIcon.svg';
import portIcon from '../../../../../resources/images/PortIcon.svg';
import shieldIcon from '../../../../../resources/images/ShieldIconWhite.svg';
import cityIcon from '../../../../../resources/images/CityIconWhite.svg';
import { renderNumber } from '../../Utils';
import { ContextMenuEvent } from '../../../InputHandler';
@@ -27,7 +28,8 @@ const buildTable: BuildItemDisplay[][] = [
{ unitType: UnitType.Battleship, icon: battleshipIcon },
{ unitType: UnitType.Port, icon: portIcon },
{ unitType: UnitType.MissileSilo, icon: missileSiloIcon },
{ unitType: UnitType.DefensePost, icon: shieldIcon }
{ unitType: UnitType.DefensePost, icon: shieldIcon },
{ unitType: UnitType.City, icon: cityIcon }
]
];
+5
View File
@@ -46,6 +46,7 @@ export interface Config {
}
attackAmount(attacker: Player, defender: Player | TerraNullius): number
maxPopulation(player: Player): number
cityPopulationIncrease(): number
boatAttackAmount(attacker: Player, defender: Player | TerraNullius): number
boatMaxDistance(): number
boatMaxNumber(): number
@@ -62,6 +63,9 @@ export interface Config {
tradeShipSpawnRate(): number
defensePostRange(): number
defensePostDefenseBonus(): number
falloutDefenseModifier(): number
maxUnitCost(): number
gameStorageBucketName(): string
}
export interface Theme {
@@ -71,6 +75,7 @@ export interface Theme {
defendedBorderColor(playerInfo: PlayerInfo): Colord;
terrainColor(tile: Tile): Colord;
backgroundColor(): Colord;
falloutColor(): Colord
font(): string;
}
+86 -54
View File
@@ -7,6 +7,22 @@ import { pastelTheme } from "./PastelTheme";
export class DefaultConfig implements Config {
maxUnitCost(): number {
return 99_999_999
}
cityPopulationIncrease(): number {
return 250_000
}
falloutDefenseModifier(): number {
return 2
}
gameStorageBucketName(): string {
return "openfront-games"
}
defensePostRange(): number {
return 30
}
@@ -23,61 +39,73 @@ export class DefaultConfig implements Config {
tradeShipSpawnRate(): number {
return 500
}
unitInfo(type: UnitType): UnitInfo {
switch (type) {
case UnitType.TransportShip:
return {
cost: () => 0,
territoryBound: false
}
case UnitType.Destroyer:
return {
cost: (p: Player) => (p.units(UnitType.Destroyer).length + 1) * 250_000,
territoryBound: false
}
case UnitType.Battleship:
return {
cost: (p: Player) => (p.units(UnitType.Battleship).length + 1) * 500_000,
territoryBound: false
}
case UnitType.Shell:
return {
cost: (p: Player) => 0,
territoryBound: false
}
case UnitType.Port:
return {
cost: (p: Player) => Math.pow(2, p.units(UnitType.Port).length) * 250_000,
territoryBound: true
}
case UnitType.AtomBomb:
return {
cost: () => 1_000_000,
territoryBound: false
}
case UnitType.HydrogenBomb:
return {
cost: () => 5_000_000,
territoryBound: false
}
case UnitType.TradeShip:
return {
cost: () => 0,
territoryBound: false
}
case UnitType.MissileSilo:
return {
cost: () => 1_000_000,
territoryBound: true
}
case UnitType.DefensePost:
return {
cost: (p: Player) => Math.pow(2, p.units(UnitType.Port).length) * 100_000,
territoryBound: true
}
default:
assertNever(type)
const fn = () => {
switch (type) {
case UnitType.TransportShip:
return {
cost: () => 0,
territoryBound: false
}
case UnitType.Destroyer:
return {
cost: (p: Player) => (p.units(UnitType.Destroyer).length + 1) * 250_000,
territoryBound: false
}
case UnitType.Battleship:
return {
cost: (p: Player) => (p.units(UnitType.Battleship).length + 1) * 500_000,
territoryBound: false
}
case UnitType.Shell:
return {
cost: (p: Player) => 0,
territoryBound: false
}
case UnitType.Port:
return {
cost: (p: Player) => Math.pow(2, p.units(UnitType.Port).length) * 250_000,
territoryBound: true
}
case UnitType.AtomBomb:
return {
cost: () => 1_000_000,
territoryBound: false
}
case UnitType.HydrogenBomb:
return {
cost: () => 5_000_000,
territoryBound: false
}
case UnitType.TradeShip:
return {
cost: () => 0,
territoryBound: false
}
case UnitType.MissileSilo:
return {
cost: () => 1_000_000,
territoryBound: true
}
case UnitType.DefensePost:
return {
cost: (p: Player) => Math.pow(2, p.units(UnitType.Port).length) * 100_000,
territoryBound: true
}
case UnitType.City:
return {
cost: (p: Player) => Math.pow(2, p.units(UnitType.Port).length) * 250_000,
territoryBound: true
}
default:
assertNever(type)
}
}
const ui = fn()
const oldCost = ui.cost
ui.cost = (p: Player) => Math.min(this.maxUnitCost(), oldCost(p))
return ui
}
defaultDonationAmount(sender: Player): number {
return Math.floor(sender.troops() / 3)
@@ -148,6 +176,10 @@ export class DefaultConfig implements Config {
}
mag *= tileToConquer.defenseBonus(attacker)
speed *= tileToConquer.defenseBonus(attacker)
if (tileToConquer.hasFallout()) {
mag *= this.falloutDefenseModifier()
speed *= this.falloutDefenseModifier()
}
if (attacker.isPlayer() && defender.isPlayer()) {
if (attacker.type() == PlayerType.Human && defender.type() == PlayerType.Bot) {
@@ -208,7 +240,7 @@ export class DefaultConfig implements Config {
if (player.type() == PlayerType.Bot) {
return maxPop
}
return maxPop * 2
return maxPop * 2 + player.units(UnitType.City).length * this.cityPopulationIncrease()
}
populationIncreaseRate(player: Player): number {
+4 -1
View File
@@ -5,9 +5,12 @@ export const devConfig = new class extends DefaultConfig {
unitInfo(type: UnitType): UnitInfo {
const info = super.unitInfo(type)
const oldCost = info.cost
info.cost = (p: Player) => oldCost(p) / 100000
info.cost = (p: Player) => oldCost(p) / 1000
return info
}
maxUnitCost(): number {
return 10000
}
percentageTilesOwnedToWin(): number {
return 95
+121 -111
View File
@@ -1,123 +1,129 @@
import {Colord, colord, random} from "colord";
import {PlayerID, PlayerInfo, TerrainType, Tile} from "../game/Game";
import {Theme} from "./Config";
import {time} from "console";
import {PseudoRandom} from "../PseudoRandom";
import {simpleHash} from "../Util";
import { Colord, colord, random } from "colord";
import { PlayerID, PlayerInfo, TerrainType, Tile } from "../game/Game";
import { Theme } from "./Config";
import { time } from "console";
import { PseudoRandom } from "../PseudoRandom";
import { simpleHash } from "../Util";
export const pastelTheme = new class implements Theme {
private rand = new PseudoRandom(123)
private background = colord({r: 60, g: 60, b: 60});
private land = colord({r: 194, g: 193, b: 148});
private shore = colord({r: 204, g: 203, b: 158});
private water = colord({r: 75, g: 142, b: 190});
private shorelineWater = colord({r: 100, g: 143, b: 255});
private background = colord({ r: 60, g: 60, b: 60 });
private land = colord({ r: 194, g: 193, b: 148 });
private shore = colord({ r: 204, g: 203, b: 158 });
private falloutColors = [
colord({ r: 120, g: 255, b: 71 }), // Original color
colord({ r: 130, g: 255, b: 85 }), // Slightly lighter
colord({ r: 110, g: 245, b: 65 }), // Slightly darker
colord({ r: 125, g: 255, b: 75 }), // Warmer tint
colord({ r: 115, g: 250, b: 68 }) // Cooler tint
];
private water = colord({ r: 75, g: 142, b: 190 });
private shorelineWater = colord({ r: 100, g: 143, b: 255 });
private territoryColors: Colord[] = [
colord({r: 230, g: 100, b: 100}), // Bright Red
colord({r: 100, g: 180, b: 230}), // Sky Blue
colord({r: 230, g: 180, b: 80}), // Golden Yellow
colord({r: 180, g: 100, b: 230}), // Purple
colord({r: 80, g: 200, b: 120}), // Emerald Green
colord({r: 230, g: 130, b: 180}), // Pink
colord({r: 100, g: 160, b: 80}), // Olive Green
colord({r: 230, g: 150, b: 100}), // Peach
colord({r: 80, g: 130, b: 190}), // Navy Blue
colord({r: 210, g: 210, b: 100}), // Lime Yellow
colord({r: 190, g: 100, b: 130}), // Maroon
colord({r: 100, g: 210, b: 210}), // Turquoise
colord({r: 210, g: 140, b: 80}), // Light Orange
colord({r: 150, g: 110, b: 190}), // Lavender
colord({r: 180, g: 210, b: 120}), // Light Green
colord({r: 210, g: 100, b: 160}), // Hot Pink
colord({r: 100, g: 140, b: 110}), // Sea Green
colord({r: 230, g: 180, b: 180}), // Light Pink
colord({r: 120, g: 120, b: 190}), // Periwinkle
colord({r: 190, g: 170, b: 100}), // Sand
colord({r: 100, g: 180, b: 160}), // Aquamarine
colord({r: 210, g: 160, b: 200}), // Orchid
colord({r: 170, g: 190, b: 100}), // Yellow Green
colord({r: 100, g: 130, b: 150}), // Steel Blue
colord({r: 230, g: 140, b: 140}), // Salmon
colord({r: 140, g: 180, b: 220}), // Light Blue
colord({r: 200, g: 160, b: 110}), // Tan
colord({r: 180, g: 130, b: 180}), // Plum
colord({r: 130, g: 200, b: 130}), // Light Sea Green
colord({r: 220, g: 120, b: 120}), // Coral
colord({r: 120, g: 160, b: 200}), // Cornflower Blue
colord({r: 200, g: 200, b: 140}), // Khaki
colord({r: 160, g: 120, b: 160}), // Purple Gray
colord({r: 140, g: 180, b: 140}), // Dark Sea Green
colord({r: 200, g: 130, b: 110}), // Dark Salmon
colord({r: 130, g: 170, b: 190}), // Cadet Blue
colord({r: 190, g: 180, b: 160}), // Tan Gray
colord({r: 170, g: 140, b: 190}), // Medium Purple
colord({r: 160, g: 190, b: 160}), // Pale Green
colord({r: 190, g: 150, b: 130}), // Rosy Brown
colord({r: 140, g: 150, b: 180}), // Light Slate Gray
colord({r: 180, g: 170, b: 140}), // Dark Khaki
colord({r: 150, g: 130, b: 150}), // Thistle
colord({r: 170, g: 190, b: 180}), // Pale Blue Green
colord({r: 190, g: 140, b: 150}), // Puce
colord({r: 130, g: 180, b: 170}), // Medium Aquamarine
colord({r: 180, g: 160, b: 180}), // Mauve
colord({r: 160, g: 180, b: 140}), // Dark Olive Green
colord({r: 170, g: 150, b: 170}), // Dusty Rose
colord({r: 100, g: 180, b: 230}), // Sky Blue
colord({r: 230, g: 180, b: 80}), // Golden Yellow
colord({r: 180, g: 100, b: 230}), // Purple
colord({r: 80, g: 200, b: 120}), // Emerald Green
colord({r: 230, g: 130, b: 180}), // Pink
colord({r: 100, g: 160, b: 80}), // Olive Green
colord({r: 230, g: 150, b: 100}), // Peach
colord({r: 80, g: 130, b: 190}), // Navy Blue
colord({r: 210, g: 210, b: 100}), // Lime Yellow
colord({r: 190, g: 100, b: 130}), // Maroon
colord({r: 100, g: 210, b: 210}), // Turquoise
colord({r: 210, g: 140, b: 80}), // Light Orange
colord({r: 150, g: 110, b: 190}), // Lavender
colord({r: 180, g: 210, b: 120}), // Light Green
colord({r: 210, g: 100, b: 160}), // Hot Pink
colord({r: 100, g: 140, b: 110}), // Sea Green
colord({r: 230, g: 180, b: 180}), // Light Pink
colord({r: 120, g: 120, b: 190}), // Periwinkle
colord({r: 190, g: 170, b: 100}), // Sand
colord({r: 100, g: 180, b: 160}), // Aquamarine
colord({r: 210, g: 160, b: 200}), // Orchid
colord({r: 170, g: 190, b: 100}), // Yellow Green
colord({r: 100, g: 130, b: 150}), // Steel Blue
colord({r: 230, g: 140, b: 140}), // Salmon
colord({r: 140, g: 180, b: 220}), // Light Blue
colord({r: 200, g: 160, b: 110}), // Tan
colord({r: 180, g: 130, b: 180}), // Plum
colord({r: 130, g: 200, b: 130}), // Light Sea Green
colord({r: 220, g: 120, b: 120}), // Coral
colord({r: 120, g: 160, b: 200}), // Cornflower Blue
colord({r: 200, g: 200, b: 140}), // Khaki
colord({r: 160, g: 120, b: 160}), // Purple Gray
colord({r: 140, g: 180, b: 140}), // Dark Sea Green
colord({r: 200, g: 130, b: 110}), // Dark Salmon
colord({r: 130, g: 170, b: 190}), // Cadet Blue
colord({r: 190, g: 180, b: 160}), // Tan Gray
colord({r: 170, g: 140, b: 190}), // Medium Purple
colord({r: 160, g: 190, b: 160}), // Pale Green
colord({r: 190, g: 150, b: 130}), // Rosy Brown
colord({r: 140, g: 150, b: 180}), // Light Slate Gray
colord({r: 180, g: 170, b: 140}), // Dark Khaki
colord({r: 150, g: 130, b: 150}), // Thistle
colord({r: 170, g: 190, b: 180}), // Pale Blue Green
colord({r: 190, g: 140, b: 150}), // Puce
colord({r: 130, g: 180, b: 170}), // Medium Aquamarine
colord({r: 180, g: 160, b: 180}), // Mauve
colord({r: 160, g: 180, b: 140}), // Dark Olive Green
colord({r: 170, g: 150, b: 170}) // Dusty Rose
colord({ r: 230, g: 100, b: 100 }), // Bright Red
colord({ r: 100, g: 180, b: 230 }), // Sky Blue
colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
colord({ r: 180, g: 100, b: 230 }), // Purple
colord({ r: 80, g: 200, b: 120 }), // Emerald Green
colord({ r: 230, g: 130, b: 180 }), // Pink
colord({ r: 100, g: 160, b: 80 }), // Olive Green
colord({ r: 230, g: 150, b: 100 }), // Peach
colord({ r: 80, g: 130, b: 190 }), // Navy Blue
colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
colord({ r: 190, g: 100, b: 130 }), // Maroon
colord({ r: 100, g: 210, b: 210 }), // Turquoise
colord({ r: 210, g: 140, b: 80 }), // Light Orange
colord({ r: 150, g: 110, b: 190 }), // Lavender
colord({ r: 180, g: 210, b: 120 }), // Light Green
colord({ r: 210, g: 100, b: 160 }), // Hot Pink
colord({ r: 100, g: 140, b: 110 }), // Sea Green
colord({ r: 230, g: 180, b: 180 }), // Light Pink
colord({ r: 120, g: 120, b: 190 }), // Periwinkle
colord({ r: 190, g: 170, b: 100 }), // Sand
colord({ r: 100, g: 180, b: 160 }), // Aquamarine
colord({ r: 210, g: 160, b: 200 }), // Orchid
colord({ r: 170, g: 190, b: 100 }), // Yellow Green
colord({ r: 100, g: 130, b: 150 }), // Steel Blue
colord({ r: 230, g: 140, b: 140 }), // Salmon
colord({ r: 140, g: 180, b: 220 }), // Light Blue
colord({ r: 200, g: 160, b: 110 }), // Tan
colord({ r: 180, g: 130, b: 180 }), // Plum
colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
colord({ r: 220, g: 120, b: 120 }), // Coral
colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
colord({ r: 200, g: 200, b: 140 }), // Khaki
colord({ r: 160, g: 120, b: 160 }), // Purple Gray
colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
colord({ r: 190, g: 180, b: 160 }), // Tan Gray
colord({ r: 170, g: 140, b: 190 }), // Medium Purple
colord({ r: 160, g: 190, b: 160 }), // Pale Green
colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
colord({ r: 150, g: 130, b: 150 }), // Thistle
colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
colord({ r: 190, g: 140, b: 150 }), // Puce
colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
colord({ r: 180, g: 160, b: 180 }), // Mauve
colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
colord({ r: 170, g: 150, b: 170 }), // Dusty Rose
colord({ r: 100, g: 180, b: 230 }), // Sky Blue
colord({ r: 230, g: 180, b: 80 }), // Golden Yellow
colord({ r: 180, g: 100, b: 230 }), // Purple
colord({ r: 80, g: 200, b: 120 }), // Emerald Green
colord({ r: 230, g: 130, b: 180 }), // Pink
colord({ r: 100, g: 160, b: 80 }), // Olive Green
colord({ r: 230, g: 150, b: 100 }), // Peach
colord({ r: 80, g: 130, b: 190 }), // Navy Blue
colord({ r: 210, g: 210, b: 100 }), // Lime Yellow
colord({ r: 190, g: 100, b: 130 }), // Maroon
colord({ r: 100, g: 210, b: 210 }), // Turquoise
colord({ r: 210, g: 140, b: 80 }), // Light Orange
colord({ r: 150, g: 110, b: 190 }), // Lavender
colord({ r: 180, g: 210, b: 120 }), // Light Green
colord({ r: 210, g: 100, b: 160 }), // Hot Pink
colord({ r: 100, g: 140, b: 110 }), // Sea Green
colord({ r: 230, g: 180, b: 180 }), // Light Pink
colord({ r: 120, g: 120, b: 190 }), // Periwinkle
colord({ r: 190, g: 170, b: 100 }), // Sand
colord({ r: 100, g: 180, b: 160 }), // Aquamarine
colord({ r: 210, g: 160, b: 200 }), // Orchid
colord({ r: 170, g: 190, b: 100 }), // Yellow Green
colord({ r: 100, g: 130, b: 150 }), // Steel Blue
colord({ r: 230, g: 140, b: 140 }), // Salmon
colord({ r: 140, g: 180, b: 220 }), // Light Blue
colord({ r: 200, g: 160, b: 110 }), // Tan
colord({ r: 180, g: 130, b: 180 }), // Plum
colord({ r: 130, g: 200, b: 130 }), // Light Sea Green
colord({ r: 220, g: 120, b: 120 }), // Coral
colord({ r: 120, g: 160, b: 200 }), // Cornflower Blue
colord({ r: 200, g: 200, b: 140 }), // Khaki
colord({ r: 160, g: 120, b: 160 }), // Purple Gray
colord({ r: 140, g: 180, b: 140 }), // Dark Sea Green
colord({ r: 200, g: 130, b: 110 }), // Dark Salmon
colord({ r: 130, g: 170, b: 190 }), // Cadet Blue
colord({ r: 190, g: 180, b: 160 }), // Tan Gray
colord({ r: 170, g: 140, b: 190 }), // Medium Purple
colord({ r: 160, g: 190, b: 160 }), // Pale Green
colord({ r: 190, g: 150, b: 130 }), // Rosy Brown
colord({ r: 140, g: 150, b: 180 }), // Light Slate Gray
colord({ r: 180, g: 170, b: 140 }), // Dark Khaki
colord({ r: 150, g: 130, b: 150 }), // Thistle
colord({ r: 170, g: 190, b: 180 }), // Pale Blue Green
colord({ r: 190, g: 140, b: 150 }), // Puce
colord({ r: 130, g: 180, b: 170 }), // Medium Aquamarine
colord({ r: 180, g: 160, b: 180 }), // Mauve
colord({ r: 160, g: 180, b: 140 }), // Dark Olive Green
colord({ r: 170, g: 150, b: 170 }) // Dusty Rose
];
playerInfoColor(id: PlayerID): Colord {
return colord({r: 50, g: 50, b: 50})
return colord({ r: 50, g: 50, b: 50 })
}
territoryColor(playerInfo: PlayerInfo): Colord {
@@ -186,6 +192,10 @@ export const pastelTheme = new class implements Theme {
return this.background;
}
falloutColor(): Colord {
return this.rand.randElement(this.falloutColors)
}
font(): string {
return "Overpass, sans-serif";
}
+48
View File
@@ -0,0 +1,48 @@
import { Cell, DefenseBonus, Execution, MutableGame, MutablePlayer, MutableUnit, PlayerID, Tile, UnitType } from "../game/Game";
import { bfs, dist } from "../Util";
export class CityExecution implements Execution {
private player: MutablePlayer
private mg: MutableGame
private city: MutableUnit
private tile: Tile
private active: boolean = true
constructor(private ownerId: PlayerID, private cell: Cell) { }
init(mg: MutableGame, ticks: number): void {
this.mg = mg
this.tile = mg.tile(this.cell)
this.player = mg.player(this.ownerId)
}
tick(ticks: number): void {
if (this.city == null) {
const spawnTile = this.player.canBuild(UnitType.City, this.tile)
if (spawnTile == false) {
console.warn('cannot build Defense Post')
this.active = false
return
}
this.city = this.player.buildUnit(UnitType.City, 0, spawnTile)
}
if (!this.city.isActive()) {
this.active = false
return
}
}
owner(): MutablePlayer {
return null
}
isActive(): boolean {
return this.active
}
activeDuringSpawnPhase(): boolean {
return false
}
}
+3
View File
@@ -23,6 +23,7 @@ import { BattleshipExecution } from "./BattleshipExecution";
import { PathFinder } from "../pathfinding/PathFinding";
import { WorkerClient } from "../worker/WorkerClient";
import { DefensePostExecution } from "./DefensePostExecution";
import { CityExecution } from "./CityExecution";
@@ -100,6 +101,8 @@ export class Executor {
return new MissileSiloExecution(intent.player, new Cell(intent.x, intent.y))
case UnitType.DefensePost:
return new DefensePostExecution(intent.player, new Cell(intent.x, intent.y))
case UnitType.City:
return new CityExecution(intent.player, new Cell(intent.x, intent.y))
default:
throw Error(`unit type ${intent.unit} not supported`)
}
+3
View File
@@ -83,6 +83,9 @@ export class NukeExecution implements Execution {
mp.removeTroops(2 * ratio[mp.id()])
others.add(mp)
}
if (tile.isLand()) {
this.mg.addFallout(tile)
}
}
for (const other of others) {
const alliance = this.player.allianceWith(other)
+4 -1
View File
@@ -39,7 +39,8 @@ export enum UnitType {
HydrogenBomb = "Hydrogen Bomb",
TradeShip = "Trade Ship",
MissileSilo = "Missile Silo",
DefensePost = "Defense Post"
DefensePost = "Defense Post",
City = "City"
}
export class Nation {
@@ -174,6 +175,7 @@ export interface Tile extends SearchNode {
defenseBonuses(): DefenseBonus[]
// defense bonus against this player
defenseBonus(player: Player): number
hasFallout(): boolean
}
export interface Unit {
@@ -314,6 +316,7 @@ export interface MutableGame extends Game {
units(...types: UnitType[]): MutableUnit[]
addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus
removeTileDefenseBonus(bonus: DefenseBonus): void
addFallout(tile: Tile)
}
export class TileEvent implements GameEvent {
+10
View File
@@ -65,6 +65,15 @@ export class GameImpl implements MutableGame {
})
}
addFallout(tile: Tile) {
const ti = tile as TileImpl
if (tile.hasOwner()) {
throw Error(`cannot set fallout, tile ${tile} has owner`)
}
ti._hasFallout = true
this.eventBus.emit(new TileEvent(tile))
}
addTileDefenseBonus(tile: Tile, unit: Unit, amount: number): DefenseBonus {
const df = { unit: unit, tile: tile, amount: amount };
(tile as TileImpl)._defenseBonuses.push(df)
@@ -322,6 +331,7 @@ export class GameImpl implements MutableGame {
tileImpl._owner = owner
owner._tiles.set(tile.cell().toString(), tile)
this.updateBorders(tile)
tileImpl._hasFallout = false
this.eventBus.emit(new TileEvent(tile))
}
+2
View File
@@ -372,6 +372,8 @@ export class PlayerImpl implements MutablePlayer {
return this.transportShipSpawn(targetTile)
case UnitType.TradeShip:
return this.tradeShipSpawn(targetTile)
case UnitType.City:
return this.landBasedStructureSpawn(targetTile)
default:
assertNever(unitType)
}
+6
View File
@@ -13,6 +13,8 @@ export class TileImpl implements Tile {
public _defenseBonuses: DefenseBonus[] = []
public _hasFallout = false
constructor(
private readonly gs: GameImpl,
public _owner: PlayerImpl | TerraNulliusImpl,
@@ -20,6 +22,10 @@ export class TileImpl implements Tile {
private readonly _terrain: TerrainTileImpl
) { }
hasFallout(): boolean {
return this._hasFallout
}
terrainType(): TerrainType {
return this._terrain.type
}
+1 -3
View File
@@ -74,9 +74,7 @@ export class GameManager {
active.filter(g => !g.hasStarted() && g.isPublic).forEach(g => {
g.start()
})
finished.forEach(g => {
g.endGame()
})
finished.map(g => g.endGame()); // Fire and forget
this.games = [...lobbies, ...active]
}
}
+20 -2
View File
@@ -3,7 +3,9 @@ import { Config } from "../core/configuration/Config";
import { Client } from "./Client";
import WebSocket from 'ws';
import { slog } from "./StructuredLog";
import { Storage } from '@google-cloud/storage';
const storage = new Storage();
export enum GamePhase {
Lobby = 'LOBBY',
@@ -14,7 +16,7 @@ export enum GamePhase {
export class GameServer {
private maxGameDuration = 60 * 60 * 1000 // 1 hour
private maxGameDuration = 2 * 60 * 60 * 1000 // 2 hours
private turns: Turn[] = []
private intents: Intent[] = []
@@ -135,7 +137,7 @@ export class GameServer {
})
}
endGame() {
async endGame() {
// Close all WebSocket connections
clearInterval(this.endTurnIntervalID);
this.clients.forEach(client => {
@@ -144,6 +146,22 @@ export class GameServer {
client.ws.close();
}
});
try {
if (this.turns.length > 100 && this.clients.length > 0) {
const bucket = storage.bucket(this.config.gameStorageBucketName());
const file = bucket.file(this.id);
const game = {
id: this.id,
date: new Date().toISOString().split('T')[0],
turns: this.turns
}
await file.save(JSON.stringify(game), {
contentType: 'application/json'
});
}
} catch (error) {
console.log('error writing game to gcs: ' + error)
}
}
phase(): GamePhase {