Compare commits

..

24 Commits

Author SHA1 Message Date
0f2b6c68cd chore: use some instead of find 2025-06-27 19:29:55 +02:00
4fcac6dc37 feat: duviri murmur reward tiers (#2331)
Reviewed-on: OpenWF/SpaceNinjaServer#2331
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:22:00 -07:00
d2cae012a7 chore: add operation eight claw trophies to allDecoRecipes (#2330)
Closes #2295

Reviewed-on: OpenWF/SpaceNinjaServer#2330
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:48 -07:00
b36d524953 feat: personal deco capacity costs (#2329)
Closes #2278

Reviewed-on: OpenWF/SpaceNinjaServer#2329
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:27 -07:00
abb5b8880f chore(webui): keep config in sync with multiple tabs (#2325)
Adding "wsid" to uniquely identify a given tab (by the websocket connection) so we can avoid needless refreshing on the same tab.

Closes #2316

Reviewed-on: OpenWF/SpaceNinjaServer#2325
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:05 -07:00
4895b4630b feat: credit boosters (+ daily first win) (#2324)
Daily first win is kinda weird because the client doesn't even seem to acknowledge it.

Also fixed missionCompletionCredits being added to inventory inconsistently (sometimes once, sometimes twice).

Closes #1086
Closes #2322

Reviewed-on: OpenWF/SpaceNinjaServer#2324
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:20:37 -07:00
690b872b5e chore(webui): update Chinese translation (#2328)
Reviewed-on: OpenWF/SpaceNinjaServer#2328
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-26 22:26:35 -07:00
d77fe60cd8 fix(webui): apply consistent margins (#2327)
Reviewed-on: OpenWF/SpaceNinjaServer#2327
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 22:26:22 -07:00
3cae42c7d6 fix: acrithis vendor freezing with fullyStockedVendors (#2326)
Permanent offers were not satisfying bin constraints

Reviewed-on: OpenWF/SpaceNinjaServer#2326
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 22:25:31 -07:00
bbccee0637 fix: ignore purchaseQuantity for login reward items (#2321)
cryotic amount should not be multiplied by 3000...

Reviewed-on: OpenWF/SpaceNinjaServer#2321
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:40:06 -07:00
31e24c27ad chore: ignore invalid item ids in saveLoadout (#2320)
With the 'IsNew' flag + webui delete item, this is quite easy to trigger and shouldn't prevent the other changes from going through.

Reviewed-on: OpenWF/SpaceNinjaServer#2320
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:39:46 -07:00
4acd87aae6 chore: handle CalendarProgress in updateChallengeProgress 2025-06-26 19:35:03 -07:00
d8ff601be7 fix: array out of bounds when processing CalendarProgress 2025-06-26 19:35:03 -07:00
d79e7c0274 feat(webui): world state config (#2318)
Re #2312. Will need some follow-up considerations for circuit game modes.

Reviewed-on: OpenWF/SpaceNinjaServer#2318
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:32:53 -07:00
4f1f9592b0 chore: use chokidar for configWatcherService (#2315)
It's just a lot snappier + works flawlessly under Bun.

Reviewed-on: OpenWF/SpaceNinjaServer#2315
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 15:30:01 -07:00
764cdd1ab8 feat: worldState.allTheFissures (#2313)
Closes #2294

Reviewed-on: OpenWF/SpaceNinjaServer#2313
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:32:26 -07:00
0ba641a2ac chore: update PE+ (#2311)
Closes #2309

Reviewed-on: OpenWF/SpaceNinjaServer#2311
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:31:52 -07:00
eb7b51852b fix: use exact quantity when adding gear items by StoreItem (#2310)
Closes #2304

Reviewed-on: OpenWF/SpaceNinjaServer#2310
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:31:40 -07:00
a3be376489 chore(webui): add Thalys to Incarnon List (#2299)
Closes #2298

Reviewed-on: OpenWF/SpaceNinjaServer#2299
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-06-26 11:02:40 -07:00
d94cd38120 chore(webui): update Chinese translation (#2291)
Reviewed-on: OpenWF/SpaceNinjaServer#2291
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-26 06:44:03 -07:00
8c22555904 feat(webui): add missing subsumed abilities (#2287)
Closes #1984

Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2287
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-06-25 20:24:29 -07:00
c9edef39f8 feat: claimJunctionChallengeReward (#2289)
Closes #2285

Reviewed-on: OpenWF/SpaceNinjaServer#2289
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 20:24:16 -07:00
b42182c85f fix(webui): handle existing entries for unlock all missions (#2290)
Closes #2283

Reviewed-on: OpenWF/SpaceNinjaServer#2290
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 20:22:24 -07:00
86f86d0476 chore: fully adopt tsgo
It's finally able to emit this project without issues. It doesn't yet support incremental builds, but a full build with it is faster than an incremental build with the old tsc, so we're not losing anything.
2025-06-26 05:20:29 +02:00
45 changed files with 884 additions and 292 deletions

View File

@ -34,4 +34,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `RadioLegion2Syndicate` for The Emissary - `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I - `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six - `RadioLegionSyndicate` for The Wolf of Saturn Six
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`) - `worldState.circuitGameModes` can be provided with an array of valid game modes (`Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, `Alchemy`)

View File

@ -74,6 +74,7 @@
"vallisOverride": "", "vallisOverride": "",
"duviriOverride": "", "duviriOverride": "",
"nightwaveOverride": "", "nightwaveOverride": "",
"allTheFissures": "",
"circuitGameModes": null "circuitGameModes": null
}, },
"dev": { "dev": {

84
package-lock.json generated
View File

@ -13,6 +13,8 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10", "@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.4.4",
@ -21,7 +23,7 @@
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"undici": "^7.10.0", "undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.70", "warframe-public-export-plus": "^0.5.73",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
@ -30,8 +32,6 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
"chokidar": "^4.0.3",
"eslint": "^8", "eslint": "^8",
"eslint-plugin-prettier": "^5.2.5", "eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",
@ -605,10 +605,9 @@
} }
}, },
"node_modules/@typescript/native-preview": { "node_modules/@typescript/native-preview": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-CgdgP/gmyaMThY7Fho19nDaTVryn9QV/zD/6w1KfDCn3M4Rq4WvkSc7Ob1ohc4V1XjCSIzg6Ul+HbLEc7xvV4Q==", "integrity": "sha512-7781zmsKURCHknc37H4U4la4kZduyxmmUshZLBzNhPHhV5DKo++K8MF69kxhRG3/vS4HBhozf0YI0mZMIbkSDA==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
"tsgo": "bin/tsgo.js" "tsgo": "bin/tsgo.js"
@ -617,23 +616,22 @@
"node": ">=20.6.0" "node": ">=20.6.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250523.1", "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-darwin-x64": "7.0.0-dev.20250523.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-linux-arm": "7.0.0-dev.20250523.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20250625.1",
"@typescript/native-preview-linux-arm64": "7.0.0-dev.20250523.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-linux-x64": "7.0.0-dev.20250523.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-win32-arm64": "7.0.0-dev.20250523.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20250625.1",
"@typescript/native-preview-win32-x64": "7.0.0-dev.20250523.1" "@typescript/native-preview-win32-x64": "7.0.0-dev.20250625.1"
} }
}, },
"node_modules/@typescript/native-preview-darwin-arm64": { "node_modules/@typescript/native-preview-darwin-arm64": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-arm64/-/native-preview-darwin-arm64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-oWJMPD+lfH9/dvHhPSZdTv43lfyZGrn7crytefhkiQPSwP0MIUCpnDkofGP/ML1nv0xx0pwWhH+Ein88NW3LuA==", "integrity": "sha512-JcLCql0O6+0iHIMllvax02kqpNtY1RUckGKomuO5kSbrOo9PsR+6r5MEcspfj47gwOl7AS0vrGhBCFFogF+KGw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -644,13 +642,12 @@
} }
}, },
"node_modules/@typescript/native-preview-darwin-x64": { "node_modules/@typescript/native-preview-darwin-x64": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-darwin-x64/-/native-preview-darwin-x64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-Yk8bJEsYsRKgRqYlwPvh7DPdgBMC/oPN60X0LWeuMLci65+4kyqF8Cv6K/W3ABc005cB4tYn4iR+9T6zipvrKw==", "integrity": "sha512-0vCkk3FdS92W625JyzA8Slu/0vgkeu10fRQNfgIbf+E29DKMKnwXW56WhHSdGXAivU44Mewwc589+CbsABq3Sw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -661,13 +658,12 @@
} }
}, },
"node_modules/@typescript/native-preview-linux-arm": { "node_modules/@typescript/native-preview-linux-arm": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm/-/native-preview-linux-arm-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-B+8CRIv6ebL8gzAagnJP8wml3baFV2FtFWuXYl6jlAcLGoQOh/yGdcAueZoJjJKNod4gAOl8OJoTicuC0BVIxw==", "integrity": "sha512-MumU7p+09ikH/x5IOJRV6DUj6N5/0kSlI4IsAUPtpT2WGkQdDtL2CC523/94YvOfWB1/+9r01636LVCGOJ135g==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -678,13 +674,12 @@
} }
}, },
"node_modules/@typescript/native-preview-linux-arm64": { "node_modules/@typescript/native-preview-linux-arm64": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-arm64/-/native-preview-linux-arm64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-IErNI08z9qE6mHaJaT6tM7il8j21ryH3DNVyFP4yz5FTKnkXFj1Kb4NcI41Q8w226LTQgBR8kNErVlbUWr7ywA==", "integrity": "sha512-IgnoWQSKeoeL7Y7tvlbcDQx0nidK3UWa/bbm1zJv+AfQlAGMrEMygp+ZzocmycUCYOVM0dcIbymjoiI/QRHTng==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -695,13 +690,12 @@
} }
}, },
"node_modules/@typescript/native-preview-linux-x64": { "node_modules/@typescript/native-preview-linux-x64": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-linux-x64/-/native-preview-linux-x64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-TCZtknsLUgPRaEfX9CvBZNgrHhMRZPYYZgF1Aasdv0PONv9mB8w0Xforgxoo4UFjdF5ZzOu2icgc7sKJJeu5vw==", "integrity": "sha512-6fE8piqPfzPPqmQ37ewTSbm4HW0cNqOEhfLG2F37zJd4525mefhIpWvj2iCkEHWp+BDlF2dYCbB4cY2nmfrNNw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -712,13 +706,12 @@
} }
}, },
"node_modules/@typescript/native-preview-win32-arm64": { "node_modules/@typescript/native-preview-win32-arm64": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-arm64/-/native-preview-win32-arm64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-bulwrkLEkoY4Jqeuvfz24RiVOiZZ7Rr9TblFqZAgZFZOnyXuhjM1jE8F1hnJFC5AghJe2HdLD3EKfabqlffrIw==", "integrity": "sha512-ppCkjBAFotPxL8j9Vk5cNSwMreOvAt02AMa5Hko3JQGSVA2TQCIlvTFn+SHSIWzYbzomc9j4j5WOcOR0rmAAHg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -729,13 +722,12 @@
} }
}, },
"node_modules/@typescript/native-preview-win32-x64": { "node_modules/@typescript/native-preview-win32-x64": {
"version": "7.0.0-dev.20250523.1", "version": "7.0.0-dev.20250625.1",
"resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250523.1.tgz", "resolved": "https://registry.npmjs.org/@typescript/native-preview-win32-x64/-/native-preview-win32-x64-7.0.0-dev.20250625.1.tgz",
"integrity": "sha512-ztzfO0oF/rj8xO5y3SyAcigmgvgczrqobCugEWFqiYumteWZPN2MYWcNYk2k8Y5LAgg1fN1xHIg8RRSPoo6XUg==", "integrity": "sha512-BsnJqso5MKAW4Y7fPmcamJ+EIrWOTqwLjeZP74NNFvTqCsA4RkITCw4NpLwD0lzrv9VsQcQ+bNwB8DrT+oDqoQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -1005,7 +997,6 @@
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"readdirp": "^4.0.1" "readdirp": "^4.0.1"
@ -2811,7 +2802,6 @@
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 14.18.0" "node": ">= 14.18.0"
@ -3396,9 +3386,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.70", "version": "0.5.73",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.70.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.73.tgz",
"integrity": "sha512-d5dQ/a0rakQnW9tl1HitST8439jDvEgMhkkntQIw7HmdM7s7mvIxvaYSl5wjlYawpUVfGyvGBdZVoAJ7kkQRWw==" "integrity": "sha512-v5lmaq/rNICg7WIZcosyfz92RpmrNyfW6+/Pbi9Iu8HbZH74PfaQKT6suAyC9xQn6xp8/cG3NLinqlLZovbKpw=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -5,8 +5,10 @@
"main": "index.ts", "main": "index.ts",
"scripts": { "scripts": {
"start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js", "start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui", "build": "tsgo --sourceMap && ncp static/webui build/static/webui",
"build:dev": "tsc --incremental --sourceMap", "build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
"build:dev": "tsgo --sourceMap",
"build:dev:tsc": "tsc --incremental --sourceMap",
"build-and-start": "npm run build && npm run start", "build-and-start": "npm run build && npm run start",
"build-and-start:bun": "npm run verify && npm run bun-run", "build-and-start:bun": "npm run verify && npm run bun-run",
"dev": "node scripts/dev.js", "dev": "node scripts/dev.js",
@ -25,6 +27,8 @@
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10", "@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
"json-with-bigint": "^3.4.4", "json-with-bigint": "^3.4.4",
@ -33,7 +37,7 @@
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"undici": "^7.10.0", "undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.70", "warframe-public-export-plus": "^0.5.73",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
@ -42,8 +46,6 @@
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0", "@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0", "@typescript-eslint/parser": "^8.28.0",
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
"chokidar": "^4.0.3",
"eslint": "^8", "eslint": "^8",
"eslint-plugin-prettier": "^5.2.5", "eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3", "prettier": "^3.5.3",

View File

@ -1,16 +1,12 @@
import { getAccountForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
const checkDailyMissionBonusController: RequestHandler = (_req, res) => { export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
const data = Buffer.from([ const account = await getAccountForRequest(req);
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a, const today = Math.trunc(Date.now() / 86400000) * 86400;
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, if (account.DailyFirstWinDate != today) {
0x3a, 0x31, 0x0a res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
]); } else {
res.writeHead(200, { res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
"Content-Type": "text/html", }
"Content-Length": data.length
});
res.end(data);
}; };
export { checkDailyMissionBonusController };

View File

@ -0,0 +1,35 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { RequestHandler } from "express";
import { ExportChallenges } from "warframe-public-export-plus";
export const claimJunctionChallengeRewardController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const data = getJSONfromString<IClaimJunctionChallengeRewardRequest>(String(req.body));
const challengeProgress = inventory.ChallengeProgress.find(x => x.Name == data.Challenge)!;
if (challengeProgress.ReceivedJunctionReward) {
throw new Error(`attempt to double-claim junction reward`);
}
challengeProgress.ReceivedJunctionReward = true;
inventory.ClaimedJunctionChallengeRewards ??= [];
inventory.ClaimedJunctionChallengeRewards.push(data.Challenge);
const challengeMeta = Object.entries(ExportChallenges).find(arr => arr[0].endsWith("/" + data.Challenge))![1];
const inventoryChanges = {};
for (const reward of challengeMeta.countedRewards!) {
combineInventoryChanges(
inventoryChanges,
(await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount)).InventoryChanges
);
}
await inventory.save();
res.json({
inventoryChanges: inventoryChanges // Yeah, it's "inventoryChanges" in the response here.
});
};
interface IClaimJunctionChallengeRewardRequest {
Challenge: string;
}

View File

@ -88,7 +88,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
AffiliationMods, AffiliationMods,
SyndicateXPItemReward, SyndicateXPItemReward,
ConquestCompletedMissionsCount ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion); } = await addMissionRewards(account, inventory, missionReport, firstCompletion);
if (missionReport.EndOfMatchUpload) { if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed(); inventory.RewardSeed = generateRewardSeed();

View File

@ -57,7 +57,11 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
component.DecoCapacity -= meta.capacityCost; component.DecoCapacity -= meta.capacityCost;
} }
} else { } else {
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0]; const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
if (!itemType || meta.dojoCapacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
component.DecoCapacity -= meta.dojoCapacityCost;
if (deco.Sockets !== undefined) { if (deco.Sockets !== undefined) {
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -= guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
1; 1;

View File

@ -1,7 +1,7 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountForRequest } from "@/src/services/loginService"; import { getAccountForRequest } from "@/src/services/loginService";
import { addChallenges, getInventory } from "@/src/services/inventoryService"; import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService";
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes"; import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
import { IAffiliationMods } from "@/src/types/purchaseTypes"; import { IAffiliationMods } from "@/src/types/purchaseTypes";
import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { getEntriesUnsafe } from "@/src/utils/ts-utils";
@ -25,13 +25,17 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
); );
} }
for (const [key, value] of getEntriesUnsafe(challenges)) { for (const [key, value] of getEntriesUnsafe(challenges)) {
if (value === undefined) {
logger.error(`Challenge progress update key ${key} has no value`);
continue;
}
switch (key) { switch (key) {
case "ChallengesFixVersion": case "ChallengesFixVersion":
inventory.ChallengesFixVersion = value; inventory.ChallengesFixVersion = value;
break; break;
case "SeasonChallengeHistory": case "SeasonChallengeHistory":
value!.forEach(({ challenge, id }) => { value.forEach(({ challenge, id }) => {
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge); const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
if (itemIndex !== -1) { if (itemIndex !== -1) {
inventory.SeasonChallengeHistory[itemIndex].id = id; inventory.SeasonChallengeHistory[itemIndex].id = id;
@ -41,6 +45,10 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
}); });
break; break;
case "CalendarProgress":
addCalendarProgress(inventory, value);
break;
case "ChallengeProgress": case "ChallengeProgress":
case "SeasonChallengeCompletions": case "SeasonChallengeCompletions":
case "ChallengePTS": case "ChallengePTS":
@ -63,5 +71,6 @@ interface IUpdateChallengeProgressRequest {
ChallengeProgress?: IChallengeProgress[]; ChallengeProgress?: IChallengeProgress[];
SeasonChallengeHistory?: ISeasonChallenge[]; SeasonChallengeHistory?: ISeasonChallenge[];
SeasonChallengeCompletions?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[];
CalendarProgress?: { challenge: string }[];
crossPlaySetting?: string; crossPlaySetting?: string;
} }

View File

@ -0,0 +1,24 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addRecipes } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { ExportRecipes } from "warframe-public-export-plus";
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "Recipes");
const allHelminthRecipes = Object.keys(ExportRecipes).filter(
key => ExportRecipes[key].secretIngredientAction === "SIA_WARFRAME_ABILITY"
);
const inventoryHelminthRecipes = inventory.Recipes.filter(recipe =>
recipe.ItemType.startsWith("/Lotus/Types/Recipes/AbilityOverrides/")
).map(recipe => recipe.ItemType);
const missingHelminthRecipes = allHelminthRecipes
.filter(key => !inventoryHelminthRecipes.includes(key))
.map(ItemType => ({ ItemType, ItemCount: 1 }));
addRecipes(inventory, missingHelminthRecipes);
await inventory.save();
res.end();
};

View File

@ -12,21 +12,24 @@ export const completeAllMissionsController: RequestHandler = async (req, res) =>
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const MissionRewards: IMissionReward[] = []; const MissionRewards: IMissionReward[] = [];
for (const [tag, node] of Object.entries(ExportRegions)) { for (const [tag, node] of Object.entries(ExportRegions)) {
const mission = inventory.Missions.find(x => x.Tag === tag); let mission = inventory.Missions.find(x => x.Tag == tag);
if (!mission) { if (!mission) {
mission =
inventory.Missions[
inventory.Missions.push({ inventory.Missions.push({
Completes: 1, Completes: 0,
Tier: 1, Tier: 0,
Tag: tag Tag: tag
}); }) - 1
];
}
if (mission.Completes == 0) {
mission.Completes++;
if (node.missionReward) { if (node.missionReward) {
addFixedLevelRewards(node.missionReward, inventory, MissionRewards); addFixedLevelRewards(node.missionReward, MissionRewards);
} }
} else {
if (!mission.Tier) mission.Tier = 1;
if (!mission.Completes) mission.Completes = 1;
} }
mission.Tier = 1;
} }
for (const reward of MissionRewards) { for (const reward of MissionRewards) {
await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true); await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);

View File

@ -0,0 +1,44 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWatcherService";
import { sendWsBroadcastExcept } from "@/src/services/webService";
export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
const responseData: Record<string, boolean | string | number | null> = {};
for (const id of req.body as string[]) {
const [obj, idx] = configIdToIndexable(id);
responseData[id] = obj[idx] ?? null;
}
res.json(responseData);
} else {
res.status(401).end();
}
};
export const setConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
for (const [id, value] of Object.entries(req.body as Record<string, boolean | string | number>)) {
const [obj, idx] = configIdToIndexable(id);
obj[idx] = value;
}
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
await saveConfig();
res.end();
} else {
res.status(401).end();
}
};
const configIdToIndexable = (id: string): [Record<string, boolean | string | number | undefined>, string] => {
let obj = config as unknown as Record<string, never>;
const arr = id.split(".");
while (arr.length > 1) {
obj = obj[arr[0]];
arr.splice(0, 1);
}
return [obj, arr[0]];
};

View File

@ -1,14 +0,0 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
const getConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
res.json(config);
} else {
res.status(401).end();
}
};
export { getConfigDataController };

View File

@ -23,9 +23,9 @@ export const setBoosterController: RequestHandler = async (req, res) => {
res.status(400).send("Invalid ItemType provided."); res.status(400).send("Invalid ItemType provided.");
return; return;
} }
const now = Math.floor(Date.now() / 1000); const now = Math.trunc(Date.now() / 1000);
for (const { ItemType, ExpiryDate } of requests) { for (const { ItemType, ExpiryDate } of requests) {
if (ExpiryDate < now) { if (ExpiryDate <= now) {
// remove expired boosters // remove expired boosters
const index = boosters.findIndex(item => item.ItemType === ItemType); const index = boosters.findIndex(item => item.ItemType === ItemType);
if (index !== -1) { if (index !== -1) {

View File

@ -1,21 +0,0 @@
import { RequestHandler } from "express";
import { saveConfig } from "@/src/services/configWatcherService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { config, IConfig } from "@/src/services/configService";
export const updateConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
const data = req.body as IUpdateConfigDataRequest;
config[data.key] = data.value;
await saveConfig();
res.end();
} else {
res.status(401).end();
}
};
interface IUpdateConfigDataRequest {
key: keyof IConfig;
value: never;
}

View File

@ -483,8 +483,9 @@ personalGoalProgressSchema.set("toJSON", {
const challengeProgressSchema = new Schema<IChallengeProgress>( const challengeProgressSchema = new Schema<IChallengeProgress>(
{ {
Progress: Number, Progress: Number,
Name: String, Completed: { type: [String], default: undefined },
Completed: [String] ReceivedJunctionReward: Boolean,
Name: { type: String, required: true }
}, },
{ _id: false } { _id: false }
); );
@ -1777,7 +1778,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined }, BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined }, LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined },
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined } HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined },
ClaimedJunctionChallengeRewards: { type: [String], default: undefined }
}, },
{ timestamps: { createdAt: "Created", updatedAt: false } } { timestamps: { createdAt: "Created", updatedAt: false } }
); );

View File

@ -25,7 +25,8 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
LastLogin: { type: Date, default: 0 }, LastLogin: { type: Date, default: 0 },
LatestEventMessageDate: { type: Date, default: 0 }, LatestEventMessageDate: { type: Date, default: 0 },
LastLoginRewardDate: { type: Number, default: 0 }, LastLoginRewardDate: { type: Number, default: 0 },
LoginDays: { type: Number, default: 1 } LoginDays: { type: Number, default: 1 },
DailyFirstWinDate: { type: Number, default: 0 }
}, },
opts opts
); );

View File

@ -19,6 +19,7 @@ import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootCo
import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController"; import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController"; import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController"; import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
import { claimJunctionChallengeRewardController } from "@/src/controllers/api/claimJunctionChallengeRewardController";
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController"; import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
@ -237,6 +238,7 @@ apiRouter.post("/artifacts.php", artifactsController);
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController); apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
apiRouter.post("/changeDojoRoot.php", changeDojoRootController); apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController); apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
apiRouter.post("/claimJunctionChallengeReward.php", claimJunctionChallengeRewardController);
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController); apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController); apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?) apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)

View File

@ -13,6 +13,7 @@ import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAl
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController"; import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController"; import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController";
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController"; import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController"; import { createMessageController } from "@/src/controllers/custom/createMessageController";
@ -24,8 +25,7 @@ import { manageQuestsController } from "@/src/controllers/custom/manageQuestsCon
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController"; import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { setBoosterController } from "@/src/controllers/custom/setBoosterController"; import { setBoosterController } from "@/src/controllers/custom/setBoosterController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { getConfigController, setConfigController } from "@/src/controllers/custom/configController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
const customRouter = express.Router(); const customRouter = express.Router();
@ -42,6 +42,7 @@ customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController); customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController); customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController); customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController); customRouter.post("/createMessage", createMessageController);
@ -53,7 +54,7 @@ customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController); customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController); customRouter.post("/setBooster", setBoosterController);
customRouter.get("/config", getConfigDataController); customRouter.post("/getConfig", getConfigController);
customRouter.post("/config", updateConfigDataController); customRouter.post("/setConfig", setConfigController);
export { customRouter }; export { customRouter };

View File

@ -81,6 +81,7 @@ export interface IConfig {
vallisOverride?: string; vallisOverride?: string;
duviriOverride?: string; duviriOverride?: string;
nightwaveOverride?: string; nightwaveOverride?: string;
allTheFissures?: string;
circuitGameModes?: string[]; circuitGameModes?: string[];
}; };
dev?: { dev?: {

View File

@ -1,4 +1,4 @@
import fs from "fs"; import chokidar from "chokidar";
import fsPromises from "fs/promises"; import fsPromises from "fs/promises";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { config, configPath, loadConfig } from "./configService"; import { config, configPath, loadConfig } from "./configService";
@ -6,12 +6,7 @@ import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./w
import { Inbox } from "../models/inboxModel"; import { Inbox } from "../models/inboxModel";
let amnesia = false; let amnesia = false;
fs.watchFile(configPath, (now, then) => { chokidar.watch(configPath).on("change", () => {
// https://github.com/oven-sh/bun/issues/20542
if (process.versions.bun && now.mtimeMs == then.mtimeMs) {
return;
}
if (amnesia) { if (amnesia) {
amnesia = false; amnesia = false;
} else { } else {

View File

@ -349,7 +349,8 @@ export const removeDojoDeco = (
component.DecoCapacity! += meta.capacityCost; component.DecoCapacity! += meta.capacityCost;
} }
} else { } else {
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0]; const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
component.DecoCapacity! += meta.dojoCapacityCost!;
if (deco.Sockets !== undefined) { if (deco.Sockets !== undefined) {
addVaultFusionTreasures(guild, [ addVaultFusionTreasures(guild, [
{ {

View File

@ -499,6 +499,7 @@ export const addItem = async (
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity. // - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
if (!exactQuantity) { if (!exactQuantity) {
quantity *= ExportGear[typeName].purchaseQuantity ?? 1; quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
logger.debug(`non-exact acquisition of ${typeName}; factored quantity is ${quantity}`);
} }
const consumablesChanges = [ const consumablesChanges = [
{ {
@ -1831,6 +1832,15 @@ export const addChallenges = (
return affiliationMods; return affiliationMods;
}; };
export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value: { challenge: string }[]): void => {
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
};
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => { export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
const { Missions } = inventory; const { Missions } = inventory;
const itemIndex = Missions.findIndex(item => item.Tag === Tag); const itemIndex = Missions.findIndex(item => item.Tag === Tag);

View File

@ -144,7 +144,8 @@ export const claimLoginReward = async (
case "RT_STORE_ITEM": case "RT_STORE_ITEM":
case "RT_RECIPE": case "RT_RECIPE":
case "RT_RANDOM_RECIPE": case "RT_RANDOM_RECIPE":
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount)).InventoryChanges; return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount, undefined, true))
.InventoryChanges;
case "RT_CREDITS": case "RT_CREDITS":
return updateCurrency(inventory, -reward.Amount, false); return updateCurrency(inventory, -reward.Amount, false);

View File

@ -14,6 +14,7 @@ import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/servi
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { import {
addBooster, addBooster,
addCalendarProgress,
addChallenges, addChallenges,
addConsumables, addConsumables,
addCrewShipAmmo, addCrewShipAmmo,
@ -33,10 +34,8 @@ import {
addSkin, addSkin,
addStanding, addStanding,
applyClientEquipmentUpdates, applyClientEquipmentUpdates,
checkCalendarChallengeCompletion,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed, generateRewardSeed,
getCalendarProgress,
getDialogue, getDialogue,
giveNemesisPetRecipe, giveNemesisPetRecipe,
giveNemesisWeaponRecipe, giveNemesisWeaponRecipe,
@ -235,7 +234,7 @@ export const addMissionInventoryUpdates = async (
} }
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) { for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
if (value === undefined) { if (value === undefined) {
logger.error(`Inventory update key ${key} has no value `); logger.error(`Inventory update key ${key} has no value`);
continue; continue;
} }
switch (key) { switch (key) {
@ -671,12 +670,7 @@ export const addMissionInventoryUpdates = async (
break; break;
} }
case "CalendarProgress": { case "CalendarProgress": {
const calendarProgress = getCalendarProgress(inventory); addCalendarProgress(inventory, value);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
x => x.events[0].challenge == value[value.length - 1].challenge
);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
break; break;
} }
case "duviriCaveOffers": { case "duviriCaveOffers": {
@ -968,6 +962,7 @@ const droptableAliases: Record<string, string> = {
//TODO: return type of partial missioninventoryupdate response //TODO: return type of partial missioninventoryupdate response
export const addMissionRewards = async ( export const addMissionRewards = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
{ {
wagerTier: wagerTier, wagerTier: wagerTier,
@ -1015,13 +1010,17 @@ export const addMissionRewards = async (
const fixedLevelRewards = getLevelKeyRewards(levelKeyName); const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`); //logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
if (fixedLevelRewards.levelKeyRewards) { if (fixedLevelRewards.levelKeyRewards) {
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo); missionCompletionCredits += addFixedLevelRewards(
fixedLevelRewards.levelKeyRewards,
MissionRewards,
rewardInfo
);
} }
if (fixedLevelRewards.levelKeyRewards2) { if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) { for (const reward of fixedLevelRewards.levelKeyRewards2) {
//quest stage completion credit rewards //quest stage completion credit rewards
if (reward.rewardType == "RT_CREDITS") { if (reward.rewardType == "RT_CREDITS") {
missionCompletionCredits += reward.amount; // will be added to inventory in addCredits missionCompletionCredits += reward.amount;
continue; continue;
} }
MissionRewards.push({ MissionRewards.push({
@ -1050,12 +1049,11 @@ export const addMissionRewards = async (
) { ) {
const levelCreditReward = getLevelCreditRewards(node); const levelCreditReward = getLevelCreditRewards(node);
missionCompletionCredits += levelCreditReward; missionCompletionCredits += levelCreditReward;
inventory.RegularCredits += levelCreditReward;
logger.debug(`levelCreditReward ${levelCreditReward}`); logger.debug(`levelCreditReward ${levelCreditReward}`);
} }
if (node.missionReward) { if (node.missionReward) {
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo); missionCompletionCredits += addFixedLevelRewards(node.missionReward, MissionRewards, rewardInfo);
} }
if (rewardInfo.sortieTag == "Mission1") { if (rewardInfo.sortieTag == "Mission1") {
@ -1165,7 +1163,9 @@ export const addMissionRewards = async (
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges); combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
} }
const credits = addCredits(inventory, { inventory.RegularCredits += missionCompletionCredits;
const credits = await addCredits(account, inventory, {
missionCompletionCredits, missionCompletionCredits,
missionDropCredits: creditDrops ?? 0, missionDropCredits: creditDrops ?? 0,
rngRewardCredits: inventoryChanges.RegularCredits ?? 0 rngRewardCredits: inventoryChanges.RegularCredits ?? 0
@ -1388,48 +1388,61 @@ export const addMissionRewards = async (
}; };
}; };
//creditBonus is not entirely accurate. export const addCredits = async (
//TODO: consider ActiveBoosters account: TAccountDocument,
export const addCredits = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
{ {
missionDropCredits, missionDropCredits,
missionCompletionCredits, missionCompletionCredits,
rngRewardCredits rngRewardCredits
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number } }: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
): IMissionCredits => { ): Promise<IMissionCredits> => {
const hasDailyCreditBonus = true;
const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
const finalCredits: IMissionCredits = { const finalCredits: IMissionCredits = {
MissionCredits: [missionDropCredits, missionDropCredits], MissionCredits: [missionDropCredits, missionDropCredits],
CreditBonus: [missionCompletionCredits, missionCompletionCredits], CreditsBonus: [missionCompletionCredits, missionCompletionCredits],
TotalCredits: [totalCredits, totalCredits] TotalCredits: [0, 0]
}; };
if (hasDailyCreditBonus) { const today = Math.trunc(Date.now() / 86400000) * 86400;
if (account.DailyFirstWinDate != today) {
account.DailyFirstWinDate = today;
await account.save();
logger.debug(`daily first win, doubling missionCompletionCredits (${missionCompletionCredits})`);
finalCredits.DailyMissionBonus = true;
inventory.RegularCredits += missionCompletionCredits; inventory.RegularCredits += missionCompletionCredits;
finalCredits.CreditBonus[1] *= 2; finalCredits.CreditsBonus[1] *= 2;
finalCredits.MissionCredits[1] *= 2;
finalCredits.TotalCredits[1] *= 2;
} }
if (!hasDailyCreditBonus) { const totalCredits = finalCredits.MissionCredits[1] + finalCredits.CreditsBonus[1] + rngRewardCredits;
return finalCredits; finalCredits.TotalCredits = [totalCredits, totalCredits];
if (config.worldState?.creditBoost) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
} }
return { ...finalCredits, DailyMissionBonus: true }; const now = Math.trunc(Date.now() / 1000); // TOVERIFY: Should we maybe subtract mission time as to apply credit boosters that expired during mission?
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBooster")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
return finalCredits;
}; };
export const addFixedLevelRewards = ( export const addFixedLevelRewards = (
rewards: IMissionRewardExternal, rewards: IMissionRewardExternal,
inventory: TInventoryDatabaseDocument,
MissionRewards: IMissionReward[], MissionRewards: IMissionReward[],
rewardInfo?: IRewardInfo rewardInfo?: IRewardInfo
): number => { ): number => {
let missionBonusCredits = 0; let missionBonusCredits = 0;
if (rewards.credits) { if (rewards.credits) {
missionBonusCredits += rewards.credits; missionBonusCredits += rewards.credits;
inventory.RegularCredits += rewards.credits;
} }
if (rewards.items) { if (rewards.items) {
for (const item of rewards.items) { for (const item of rewards.items) {
@ -1603,6 +1616,27 @@ function getRandomMissionDrops(
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards" ? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards" : "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
]; ];
} else if (RewardInfo.T == 17) {
if (mission?.Tier == 1) {
logger.warn(`non-steel path duviri murmur tier used on steel path?!`);
}
/*if (operation eight claw is active) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
ItemCount: 10
});
}*/
rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"];
} else if (RewardInfo.T == 19) {
/*if (operation eight claw is active) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
ItemCount: 15
});
}*/
rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
];
} else if (RewardInfo.T == 70) { } else if (RewardInfo.T == 70) {
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path. // Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
drops.push({ drops.push({

View File

@ -371,18 +371,28 @@ export const handleStoreItemAcquisition = async (
} else { } else {
const storeCategory = getStoreItemCategory(storeItemName); const storeCategory = getStoreItemCategory(storeItemName);
const internalName = fromStoreItem(storeItemName); const internalName = fromStoreItem(storeItemName);
logger.debug(`store category ${storeCategory}`);
if (!ignorePurchaseQuantity) { if (!ignorePurchaseQuantity) {
if (internalName in ExportGear) { if (internalName in ExportGear) {
quantity *= ExportGear[internalName].purchaseQuantity || 1; quantity *= ExportGear[internalName].purchaseQuantity || 1;
logger.debug(`factored quantity is ${quantity}`);
} else if (internalName in ExportResources) { } else if (internalName in ExportResources) {
quantity *= ExportResources[internalName].purchaseQuantity || 1; quantity *= ExportResources[internalName].purchaseQuantity || 1;
logger.debug(`factored quantity is ${quantity}`);
} }
} }
logger.debug(`store category ${storeCategory}`);
switch (storeCategory) { switch (storeCategory) {
default: { default: {
purchaseResponse = { purchaseResponse = {
InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed) InventoryChanges: await addItem(
inventory,
internalName,
quantity,
premiumPurchase,
seed,
undefined,
true
)
}; };
break; break;
} }
@ -524,7 +534,9 @@ const handleTypesPurchase = async (
logger.debug(`type category ${typeCategory}`); logger.debug(`type category ${typeCategory}`);
switch (typeCategory) { switch (typeCategory) {
default: default:
return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) }; return {
InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed, undefined, true)
};
case "BoosterPacks": case "BoosterPacks":
return handleBoosterPackPurchase(typesName, inventory, quantity); return handleBoosterPackPurchase(typesName, inventory, quantity);
case "SlotItems": case "SlotItems":

View File

@ -331,7 +331,7 @@ export const giveKeyChainMissionReward = async (
const fixedLevelRewards = getLevelKeyRewards(missionName); const fixedLevelRewards = getLevelKeyRewards(missionName);
if (fixedLevelRewards.levelKeyRewards) { if (fixedLevelRewards.levelKeyRewards) {
const missionRewards: { StoreItem: string; ItemCount: number }[] = []; const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards); inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards);
for (const reward of missionRewards) { for (const reward of missionRewards) {
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount); await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);

View File

@ -149,7 +149,8 @@ export const handleInventoryItemConfigChange = async (
} else { } else {
const inventoryItem = inventory.WeaponSkins.id(itemId); const inventoryItem = inventory.WeaponSkins.id(itemId);
if (!inventoryItem) { if (!inventoryItem) {
throw new Error(`inventory item WeaponSkins not found with id ${itemId}`); logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
continue;
} }
if ("Favorite" in itemConfigEntries) { if ("Favorite" in itemConfigEntries) {
inventoryItem.Favorite = itemConfigEntries.Favorite; inventoryItem.Favorite = itemConfigEntries.Favorite;
@ -177,7 +178,8 @@ export const handleInventoryItemConfigChange = async (
const inventoryItem = inventory[equipmentName].id(itemId); const inventoryItem = inventory[equipmentName].id(itemId);
if (!inventoryItem) { if (!inventoryItem) {
throw new Error(`inventory item ${equipmentName} not found with id ${itemId}`); logger.warn(`inventory item ${equipmentName} not found with id ${itemId}`);
continue;
} }
for (const [configId, config] of Object.entries(itemConfigEntries)) { for (const [configId, config] of Object.entries(itemConfigEntries)) {

View File

@ -281,6 +281,10 @@ const generateVendorManifest = (
offersToAdd.push(item); offersToAdd.push(item);
++offset; ++offset;
} }
if (missingItemsPerBin[item.bin]) {
missingItemsPerBin[item.bin] -= 1;
numOffersThatNeedToMatchABin -= 1;
}
} else { } else {
numCountedOffers += 1 + item.duplicates; numCountedOffers += 1 + item.duplicates;
} }

View File

@ -59,7 +59,12 @@ export const handleSetShipDecorations = async (
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room); const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
if (!roomToPlaceIn) { if (!roomToPlaceIn) {
throw new Error("room not found"); throw new Error(`unknown room: ${placedDecoration.Room}`);
}
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!;
if (!itemType || meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
} }
if (placedDecoration.MoveId) { if (placedDecoration.MoveId) {
@ -83,7 +88,7 @@ export const handleSetShipDecorations = async (
OldRoom: placedDecoration.OldRoom, OldRoom: placedDecoration.OldRoom,
NewRoom: placedDecoration.Room, NewRoom: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment, IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal MaxCapacityIncrease: 0
}; };
} }
@ -96,6 +101,7 @@ export const handleSetShipDecorations = async (
} }
oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId }); oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId });
oldRoom.MaxCapacity += meta.capacityCost;
const newDecoration = { const newDecoration = {
Type: placedDecoration.Type, Type: placedDecoration.Type,
@ -108,12 +114,14 @@ export const handleSetShipDecorations = async (
//the new room is still roomToPlaceIn //the new room is still roomToPlaceIn
roomToPlaceIn.PlacedDecos.push(newDecoration); roomToPlaceIn.PlacedDecos.push(newDecoration);
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
await personalRooms.save(); await personalRooms.save();
return { return {
OldRoom: placedDecoration.OldRoom, OldRoom: placedDecoration.OldRoom,
NewRoom: placedDecoration.Room, NewRoom: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment, IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal MaxCapacityIncrease: -meta.capacityCost
}; };
} }
@ -121,11 +129,11 @@ export const handleSetShipDecorations = async (
const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId)); const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId));
const deco = roomToPlaceIn.PlacedDecos[decoIndex]; const deco = roomToPlaceIn.PlacedDecos[decoIndex];
roomToPlaceIn.PlacedDecos.splice(decoIndex, 1); roomToPlaceIn.PlacedDecos.splice(decoIndex, 1);
roomToPlaceIn.MaxCapacity += meta.capacityCost;
await personalRooms.save(); await personalRooms.save();
if (!config.unlockAllShipDecorations) { if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
if (deco.Sockets !== undefined) { if (deco.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]); addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
} else { } else {
@ -138,24 +146,20 @@ export const handleSetShipDecorations = async (
DecoId: placedDecoration.RemoveId, DecoId: placedDecoration.RemoveId,
Room: placedDecoration.Room, Room: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment, IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 MaxCapacityIncrease: 0 // Client already implies the capacity being refunded.
}; };
} else { }
if (!config.unlockAllShipDecorations) { if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0]; const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) { if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [ addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }
]);
} else { } else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]); addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
} }
await inventory.save(); await inventory.save();
} }
}
// TODO: handle capacity
//place decoration //place decoration
const decoId = new Types.ObjectId(); const decoId = new Types.ObjectId();
@ -167,10 +171,16 @@ export const handleSetShipDecorations = async (
Sockets: placedDecoration.Sockets, Sockets: placedDecoration.Sockets,
_id: decoId _id: decoId
}); });
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
await personalRooms.save(); await personalRooms.save();
return { DecoId: decoId.toString(), Room: placedDecoration.Room, IsApartment: placedDecoration.IsApartment }; return {
DecoId: decoId.toString(),
Room: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: -meta.capacityCost
};
}; };
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => { export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {

View File

@ -136,7 +136,10 @@ export const stopWebServer = async (): Promise<void> => {
await Promise.all(promises); await Promise.all(promises);
}; };
let lastWsid: number = 0;
interface IWsCustomData extends ws { interface IWsCustomData extends ws {
id?: number;
accountId?: string; accountId?: string;
} }
@ -150,6 +153,7 @@ interface IWsMsgFromClient {
} }
interface IWsMsgToClient { interface IWsMsgToClient {
//wsid?: number;
reload?: boolean; reload?: boolean;
ports?: { ports?: {
http: number | undefined; http: number | undefined;
@ -174,6 +178,10 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
ws.close(); ws.close();
return; return;
} }
(ws as IWsCustomData).id = ++lastWsid;
ws.send(JSON.stringify({ wsid: lastWsid }));
// eslint-disable-next-line @typescript-eslint/no-misused-promises // eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => { ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient; const data = JSON.parse(String(msg)) as IWsMsgFromClient;
@ -268,3 +276,21 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
} }
} }
}; };
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
};

View File

@ -372,7 +372,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
hardWeekly: syndicate.weeklyChallenges!.filter(x => hardWeekly: syndicate.weeklyChallenges!.filter(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/") x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
), ),
hasWeeklyPermanent: !!syndicate.weeklyChallenges!.find(x => hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent") x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
) )
}; };
@ -1524,6 +1524,25 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}; };
export const populateFissures = async (worldState: IWorldState): Promise<void> => { export const populateFissures = async (worldState: IWorldState): Promise<void> => {
if (config.worldState?.allTheFissures) {
let i = 0;
for (const [tier, nodes] of Object.entries(fissureMissions)) {
for (const node of nodes) {
const meta = ExportRegions[node];
worldState.ActiveMissions.push({
_id: { $oid: (i++).toString().padStart(8, "0") + "8e0c70ba050f1eb7" },
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: { $date: { $numberLong: "1000000000000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Node: node,
MissionType: eMissionType[meta.missionIndex].tag,
Modifier: tier,
Hard: config.worldState.allTheFissures == "hard"
});
}
}
} else {
const fissures = await Fissure.find({}); const fissures = await Fissure.find({});
for (const fissure of fissures) { for (const fissure of fissures) {
const meta = ExportRegions[fissure.Node]; const meta = ExportRegions[fissure.Node];
@ -1539,6 +1558,7 @@ export const populateFissures = async (worldState: IWorldState): Promise<void> =
Hard: fissure.Hard Hard: fissure.Hard
}); });
} }
}
}; };
export const idToBountyCycle = (id: string): number => { export const idToBountyCycle = (id: string): number => {

View File

@ -380,6 +380,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
LockedWeaponGroup?: ILockedWeaponGroupClient; LockedWeaponGroup?: ILockedWeaponGroupClient;
HubNpcCustomizations?: IHubNpcCustomization[]; HubNpcCustomizations?: IHubNpcCustomization[];
Ship?: IOrbiter; // U22 and below, response only Ship?: IOrbiter; // U22 and below, response only
ClaimedJunctionChallengeRewards?: string[]; // U39
} }
export interface IAffiliation { export interface IAffiliation {
@ -448,8 +449,9 @@ export interface IVendorPurchaseHistoryEntryDatabase {
export interface IChallengeProgress { export interface IChallengeProgress {
Progress: number; Progress: number;
Name: string;
Completed?: string[]; Completed?: string[];
ReceivedJunctionReward?: boolean; // U39
Name: string;
} }
export interface ICollectibleEntry { export interface ICollectibleEntry {

View File

@ -25,6 +25,7 @@ export interface IDatabaseAccount extends IDatabaseAccountRequiredFields {
LatestEventMessageDate: Date; LatestEventMessageDate: Date;
LastLoginRewardDate: number; LastLoginRewardDate: number;
LoginDays: number; LoginDays: number;
DailyFirstWinDate: number;
} }
// Includes virtual ID // Includes virtual ID

View File

@ -17,9 +17,9 @@ export interface IMissionReward {
} }
export interface IMissionCredits { export interface IMissionCredits {
MissionCredits: number[]; MissionCredits: [number, number];
CreditBonus: number[]; CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus
TotalCredits: number[]; TotalCredits: [number, number];
DailyMissionBonus?: boolean; DailyMissionBonus?: boolean;
} }

View File

@ -20,6 +20,10 @@
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe", "/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe",

View File

@ -45,5 +45,6 @@
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon", "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol", "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon", "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon" "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
] ]

View File

@ -98,9 +98,9 @@
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-md-3"> <div class="col-md-3">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="currency_RegularCredits"></h5> <h5 class="card-header" data-loc="currency_RegularCredits"></h5>
<div class="card-body"> <div class="card-body">
<p class="card-text" id="RegularCredits-owned"></p> <p class="card-text" id="RegularCredits-owned"></p>
@ -112,7 +112,7 @@
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="currency_PremiumCredits"></h5> <h5 class="card-header" data-loc="currency_PremiumCredits"></h5>
<div class="card-body"> <div class="card-body">
<p class="card-text" id="PremiumCredits-owned"></p> <p class="card-text" id="PremiumCredits-owned"></p>
@ -124,7 +124,7 @@
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="currency_FusionPoints"></h5> <h5 class="card-header" data-loc="currency_FusionPoints"></h5>
<div class="card-body"> <div class="card-body">
<p class="card-text" id="FusionPoints-owned"></p> <p class="card-text" id="FusionPoints-owned"></p>
@ -136,7 +136,7 @@
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="currency_PrimeTokens"></h5> <h5 class="card-header" data-loc="currency_PrimeTokens"></h5>
<div class="card-body"> <div class="card-body">
<p class="card-text" id="PrimeTokens-owned"></p> <p class="card-text" id="PrimeTokens-owned"></p>
@ -148,9 +148,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_suits"></h5> <h5 class="card-header" data-loc="inventory_suits"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
@ -164,7 +164,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_longGuns"></h5> <h5 class="card-header" data-loc="inventory_longGuns"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
@ -183,9 +183,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_pistols"></h5> <h5 class="card-header" data-loc="inventory_pistols"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
@ -204,7 +204,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_melee"></h5> <h5 class="card-header" data-loc="inventory_melee"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
@ -223,9 +223,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceSuits"></h5> <h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
@ -239,7 +239,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceGuns"></h5> <h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
@ -253,9 +253,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceMelee"></h5> <h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
@ -269,7 +269,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_mechSuits"></h5> <h5 class="card-header" data-loc="inventory_mechSuits"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
@ -283,9 +283,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinels"></h5> <h5 class="card-header" data-loc="inventory_sentinels"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
@ -299,7 +299,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_moaPets"></h5> <h5 class="card-header" data-loc="inventory_moaPets"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
@ -325,9 +325,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_kubrowPets"></h5> <h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
@ -349,7 +349,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5> <h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
@ -363,9 +363,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_operatorAmps"></h5> <h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
@ -384,7 +384,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_hoverboards"></h5> <h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('HoverBoards');return false;"> <form class="input-group mb-3" onsubmit="doAcquireModularEquipment('HoverBoards');return false;">
@ -401,9 +401,9 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_evolutionProgress"></h5> <h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;"> <form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
@ -417,7 +417,7 @@
</div> </div>
</div> </div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_Boosters"></h5> <h5 class="card-header" data-loc="inventory_Boosters"></h5>
<div class="card-body overflow-auto"> <div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;"> <form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
@ -431,7 +431,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
@ -458,7 +458,7 @@
<div id="powersuit-route" data-route="~ /webui/powersuit/(.+)" data-title="Inventory | OpenWF WebUI"> <div id="powersuit-route" data-route="~ /webui/powersuit/(.+)" data-title="Inventory | OpenWF WebUI">
<h3 class="mb-0"></h3> <h3 class="mb-0"></h3>
<p class="text-body-secondary"></p> <p class="text-body-secondary"></p>
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5> <h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
<div class="card-body"> <div class="card-body">
<p> <p>
@ -498,7 +498,7 @@
<a href="riven-tool/" target="_blank" data-loc="mods_fingerprintHelp"></a> <a href="riven-tool/" target="_blank" data-loc="mods_fingerprintHelp"></a>
</form> </form>
</div> </div>
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="mods_rivens"></h5> <h5 class="card-header" data-loc="mods_rivens"></h5>
<div class="card-body"> <div class="card-body">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -521,7 +521,7 @@
</table> </table>
</div> </div>
</div> </div>
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body d-flex flex-wrap gap-2"> <div class="card-body d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_addMissingUnrankedMods"></button> <button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_addMissingUnrankedMods"></button>
@ -535,7 +535,7 @@
<div data-route="/webui/quests" data-title="Quests | OpenWF WebUI"> <div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="quests_list"></h5> <h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body"> <div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
@ -549,7 +549,7 @@
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
<div class="d-flex flex-wrap gap-2"> <div class="d-flex flex-wrap gap-2">
@ -565,13 +565,13 @@
<div data-route="/webui/cheats, /webui/settings" data-title="Cheats | OpenWF WebUI"> <div data-route="/webui/cheats, /webui/settings" data-title="Cheats | OpenWF WebUI">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<div class="card mb-3"> <div class="card">
<h5 class="card-header" data-loc="cheats_server"></h5> <h5 class="card-header" data-loc="cheats_server"></h5>
<div class="card-body"> <div class="card-body">
<div id="server-settings-no-perms" class="d-none"> <div class="d-none config-admin-hide">
<p class="card-text" data-loc="cheats_administratorRequirement"></p> <p class="card-text" data-loc="cheats_administratorRequirement"></p>
</div> </div>
<div id="server-settings" class="d-none"> <div class="d-none config-admin-show config-form">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="skipTutorial" /> <input class="form-check-input" type="checkbox" id="skipTutorial" />
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label> <label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
@ -794,6 +794,7 @@
<button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button> <button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
<button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button> <button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
<button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button> <button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(addMissingHelminthRecipes);" data-loc="cheats_addMissingSubsumedAbilities"></button>
<button class="btn btn-primary" onclick="doIntrinsicsUnlockAll();" data-loc="cheats_intrinsicsUnlockAll"></button> <button class="btn btn-primary" onclick="doIntrinsicsUnlockAll();" data-loc="cheats_intrinsicsUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button> <button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
</div> </div>
@ -806,6 +807,93 @@
</form> </form>
</div> </div>
</div> </div>
<div class="card d-none config-admin-show config-form">
<h5 class="card-header" data-loc="worldState"></h5>
<div class="card-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.creditBoost" />
<label class="form-check-label" for="worldState.creditBoost" data-loc="worldState_creditBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.affinityBoost" />
<label class="form-check-label" for="worldState.affinityBoost" data-loc="worldState_affinityBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.resourceBoost" />
<label class="form-check-label" for="worldState.resourceBoost" data-loc="worldState_resourceBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.starDays" />
<label class="form-check-label" for="worldState.starDays" data-loc="worldState_starDays"></label>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_galleonOfGhouls"></label>
<select class="form-control" id="worldState.galleonOfGhouls">
<option value="0" data-loc="disabled"></option>
<option value="1" data-loc="worldState_we1"></option>
<option value="2" data-loc="worldState_we2"></option>
<option value="3" data-loc="worldState_we3"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride">
<option value="" data-loc="disabled"></option>
<option value="day" data-loc="worldState_day"></option>
<option value="night" data-loc="worldState_night"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_vallisOverride"></label>
<select class="form-control" id="worldState.vallisOverride">
<option value="" data-loc="disabled"></option>
<option value="warm" data-loc="worldState_warm"></option>
<option value="cold" data-loc="worldState_cold"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_duviriOverride"></label>
<select class="form-control" id="worldState.duviriOverride">
<option value="" data-loc="disabled"></option>
<option value="joy" data-loc="worldState_joy"></option>
<option value="anger" data-loc="worldState_anger"></option>
<option value="envy" data-loc="worldState_envy"></option>
<option value="sorrow" data-loc="worldState_sorrow"></option>
<option value="fear" data-loc="worldState_fear"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_nightwaveOverride"></label>
<select class="form-control" id="worldState.nightwaveOverride">
<option value="" data-loc="disabled"></option>
<option value="RadioLegionIntermission13Syndicate" data-loc="worldState_RadioLegionIntermission13Syndicate"></option>
<option value="RadioLegionIntermission12Syndicate" data-loc="worldState_RadioLegionIntermission12Syndicate"></option>
<option value="RadioLegionIntermission11Syndicate" data-loc="worldState_RadioLegionIntermission11Syndicate"></option>
<option value="RadioLegionIntermission10Syndicate" data-loc="worldState_RadioLegionIntermission10Syndicate"></option>
<option value="RadioLegionIntermission9Syndicate" data-loc="worldState_RadioLegionIntermission9Syndicate"></option>
<option value="RadioLegionIntermission8Syndicate" data-loc="worldState_RadioLegionIntermission8Syndicate"></option>
<option value="RadioLegionIntermission7Syndicate" data-loc="worldState_RadioLegionIntermission7Syndicate"></option>
<option value="RadioLegionIntermission6Syndicate" data-loc="worldState_RadioLegionIntermission6Syndicate"></option>
<option value="RadioLegionIntermission5Syndicate" data-loc="worldState_RadioLegionIntermission5Syndicate"></option>
<option value="RadioLegionIntermission4Syndicate" data-loc="worldState_RadioLegionIntermission4Syndicate"></option>
<option value="RadioLegionIntermission3Syndicate" data-loc="worldState_RadioLegionIntermission3Syndicate"></option>
<option value="RadioLegion3Syndicate" data-loc="worldState_RadioLegion3Syndicate"></option>
<option value="RadioLegionIntermission2Syndicate" data-loc="worldState_RadioLegionIntermission2Syndicate"></option>
<option value="RadioLegion2Syndicate" data-loc="worldState_RadioLegion2Syndicate"></option>
<option value="RadioLegionIntermissionSyndicate" data-loc="worldState_RadioLegionIntermissionSyndicate"></option>
<option value="RadioLegionSyndicate" data-loc="worldState_RadioLegionSyndicate"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_fissures"></label>
<select class="form-control" id="worldState.allTheFissures">
<option value="" data-loc="normal"></option>
<option value="normal" data-loc="worldState_allAtOnceNormal"></option>
<option value="hard" data-loc="worldState_allAtOnceSteelPath"></option>
</select>
</div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -10,7 +10,8 @@
let auth_pending = false, let auth_pending = false,
did_initial_auth = false, did_initial_auth = false,
ws_is_open = false; ws_is_open = false,
wsid = 0;
const sendAuth = isRegister => { const sendAuth = isRegister => {
if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) { if (ws_is_open && localStorage.getItem("email") && localStorage.getItem("password")) {
auth_pending = true; auth_pending = true;
@ -34,6 +35,9 @@ function openWebSocket() {
}; };
window.ws.onmessage = e => { window.ws.onmessage = e => {
const msg = JSON.parse(e.data); const msg = JSON.parse(e.data);
if ("wsid" in msg) {
wsid = msg.wsid;
}
if ("reload" in msg) { if ("reload" in msg) {
setTimeout(() => { setTimeout(() => {
getWebSocket().then(() => { getWebSocket().then(() => {
@ -248,7 +252,8 @@ const permanentEvolutionWeapons = new Set([
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon", "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol", "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon", "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon" "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
]); ]);
let uniqueLevelCaps = {}; let uniqueLevelCaps = {};
@ -1449,6 +1454,11 @@ function addMissingEquipment(categories) {
} }
} }
async function addMissingHelminthRecipes() {
await revalidateAuthz();
await fetch("/custom/addMissingHelminthBlueprints?" + window.authz);
}
function addMissingEvolutionProgress() { function addMissingEvolutionProgress() {
const requests = []; const requests = [];
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => { document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
@ -1841,16 +1851,28 @@ function doAcquireMod() {
} }
} }
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id); const uiConfigs = [...$(".config-form input[id], .config-form select[id]")].map(x => x.id);
for (const id of uiConfigs) { for (const id of uiConfigs) {
const elm = document.getElementById(id); const elm = document.getElementById(id);
if (elm.type == "checkbox") { if (elm.tagName == "SELECT") {
elm.onchange = function () {
let value = this.value;
if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
$.post({
url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json",
data: JSON.stringify({ [id]: value })
});
};
} else if (elm.type == "checkbox") {
elm.onchange = function () { elm.onchange = function () {
$.post({ $.post({
url: "/custom/config?" + window.authz, url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ key: id, value: this.checked }) data: JSON.stringify({ [id]: this.checked })
}).then(() => { }).then(() => {
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) { if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) {
updateInventory(); updateInventory();
@ -1863,9 +1885,9 @@ for (const id of uiConfigs) {
function doSaveConfig(id) { function doSaveConfig(id) {
const elm = document.getElementById(id); const elm = document.getElementById(id);
$.post({ $.post({
url: "/custom/config?" + window.authz, url: "/custom/setConfig?" + window.authz + "&wsid=" + wsid,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ key: id, value: parseInt(elm.value) }) data: JSON.stringify({ [id]: parseInt(elm.value) })
}); });
} }
@ -1876,12 +1898,15 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
interval = setInterval(() => { interval = setInterval(() => {
if (window.authz) { if (window.authz) {
clearInterval(interval); clearInterval(interval);
fetch("/custom/config?" + window.authz).then(async res => { $.post({
if (res.status == 200) { url: "/custom/getConfig?" + window.authz,
contentType: "application/json",
data: JSON.stringify(uiConfigs)
})
.done(json => {
//window.is_admin = true; //window.is_admin = true;
$("#server-settings-no-perms").addClass("d-none"); $(".config-admin-hide").addClass("d-none");
$("#server-settings").removeClass("d-none"); $(".config-admin-show").removeClass("d-none");
res.json().then(json =>
Object.entries(json).forEach(entry => { Object.entries(json).forEach(entry => {
const [key, value] = entry; const [key, value] = entry;
var x = document.getElementById(`${key}`); var x = document.getElementById(`${key}`);
@ -1892,10 +1917,10 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
x.setAttribute("value", `${value}`); x.setAttribute("value", `${value}`);
} }
} }
});
}) })
); .fail(res => {
} else { if (res.responseText == "Log-in expired") {
if ((await res.text()) == "Log-in expired") {
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/cheats") { if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats"); single.loadRoute("/webui/cheats");
@ -1903,9 +1928,8 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
}); });
} else { } else {
//window.is_admin = false; //window.is_admin = false;
$("#server-settings-no-perms").removeClass("d-none"); $(".config-admin-hide").removeClass("d-none");
$("#server-settings").addClass("d-none"); $(".config-admin-show").addClass("d-none");
}
} }
}); });
} }
@ -2269,14 +2293,13 @@ function doAcquireBoosters() {
const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days
setBooster(uniqueName, ExpiryDate, () => { setBooster(uniqueName, ExpiryDate, () => {
$("#acquire-type-Boosters").val(""); $("#acquire-type-Boosters").val("");
updateInventory();
}); });
} }
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) { function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value); console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value);
// cast local datetime string to unix timestamp // cast local datetime string to unix timestamp
const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000; const ExpiryDate = Math.trunc(new Date(ExpiryDateInput.value).getTime() / 1000);
if (isNaN(ExpiryDate)) { if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus(); ExpiryDateInput.addClass("is-invalid").focus();
return false; return false;

View File

@ -181,10 +181,56 @@ dict = {
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`, cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
cheats_helminthUnlockAll: `Helminth vollständig aufleveln`, cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`, cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`,
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`, cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`, cheats_changeButton: `Ändern`,
cheats_none: `Keines`, cheats_none: `Keines`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`, import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
import_submit: `Absenden`, import_submit: `Absenden`,
import_samples: `[UNTRANSLATED] Samples:`, import_samples: `[UNTRANSLATED] Samples:`,

View File

@ -180,10 +180,56 @@ dict = {
cheats_account: `Account`, cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Unlock All Focus Schools`, cheats_unlockAllFocusSchools: `Unlock All Focus Schools`,
cheats_helminthUnlockAll: `Fully Level Up Helminth`, cheats_helminthUnlockAll: `Fully Level Up Helminth`,
cheats_addMissingSubsumedAbilities: `Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`, cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`,
cheats_changeSupportedSyndicate: `Supported syndicate`, cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`, cheats_changeButton: `Change`,
cheats_none: `None`, cheats_none: `None`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,
worldState_affinityBoost: `Affinity Boost`,
worldState_resourceBoost: `Resource Boost`,
worldState_starDays: `Star Days`,
worldState_galleonOfGhouls: `Galleon of Ghouls`,
disabled: `Disabled`,
worldState_we1: `Weekend 1`,
worldState_we2: `Weekend 2`,
worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `Eidolon Override`,
worldState_day: `Day`,
worldState_night: `Night`,
worldState_vallisOverride: `Orb Vallis Override`,
worldState_warm: `Warm`,
worldState_cold: `Cold`,
worldState_duviriOverride: `Duviri Override`,
worldState_joy: `Joy`,
worldState_anger: `Anger`,
worldState_envy: `Envy`,
worldState_sorrow: `Sorrow`,
worldState_fear: `Fear`,
worldState_nightwaveOverride: `Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `Intermission III`,
worldState_RadioLegion3Syndicate: `Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `Intermission II`,
worldState_RadioLegion2Syndicate: `The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `Intermission I`,
worldState_RadioLegionSyndicate: `The Wolf of Saturn Six`,
worldState_fissures: `Fissures`,
normal: `Normal`,
worldState_allAtOnceNormal: `All At Once, Normal`,
worldState_allAtOnceSteelPath: `All At Once, Steel Path`,
import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.`, import_importNote: `You can provide a full or partial inventory response (client respresentation) here. All fields that are supported by the importer <b>will be overwritten</b> in your account.`,
import_submit: `Submit`, import_submit: `Submit`,
import_samples: `Samples:`, import_samples: `Samples:`,

View File

@ -181,10 +181,56 @@ dict = {
cheats_account: `Cuenta`, cheats_account: `Cuenta`,
cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`, cheats_unlockAllFocusSchools: `Desbloquear todas las escuelas de enfoque`,
cheats_helminthUnlockAll: `Subir al máximo el Helminto`, cheats_helminthUnlockAll: `Subir al máximo el Helminto`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`, cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
cheats_changeSupportedSyndicate: `Sindicatos disponibles`, cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`, cheats_changeButton: `Cambiar`,
cheats_none: `Ninguno`, cheats_none: `Ninguno`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador <b>serán sobrescritos</b> en tu cuenta.`, import_importNote: `Puedes proporcionar una respuesta de inventario completa o parcial (representación del cliente) aquí. Todos los campos compatibles con el importador <b>serán sobrescritos</b> en tu cuenta.`,
import_submit: `Enviar`, import_submit: `Enviar`,
import_samples: `Muestras:`, import_samples: `Muestras:`,

View File

@ -181,10 +181,56 @@ dict = {
cheats_account: `Compte`, cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
cheats_helminthUnlockAll: `Helminth niveau max`, cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_intrinsicsUnlockAll: `Inhérences niveau max`, cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_changeSupportedSyndicate: `Allégeance`, cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`, cheats_changeButton: `Changer`,
cheats_none: `Aucun`, cheats_none: `Aucun`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`, import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
import_submit: `Soumettre`, import_submit: `Soumettre`,
import_samples: `Echantillons :`, import_samples: `Echantillons :`,

View File

@ -181,10 +181,56 @@ dict = {
cheats_account: `Аккаунт`, cheats_account: `Аккаунт`,
cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`, cheats_unlockAllFocusSchools: `Разблокировать все школы фокуса`,
cheats_helminthUnlockAll: `Полностью улучшить Гельминта`, cheats_helminthUnlockAll: `Полностью улучшить Гельминта`,
cheats_addMissingSubsumedAbilities: `Добавить отсутствующие поглощённые способности`,
cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`, cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`,
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`, cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`, cheats_changeButton: `Изменить`,
cheats_none: `Отсутствует`, cheats_none: `Отсутствует`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`, import_importNote: `Вы можете загрузить полный или частичный ответ инвентаря (клиентское представление) здесь. Все поддерживаемые поля <b>будут перезаписаны</b> в вашем аккаунте.`,
import_submit: `Отправить`, import_submit: `Отправить`,
import_samples: `[UNTRANSLATED] Samples:`, import_samples: `[UNTRANSLATED] Samples:`,

View File

@ -1,4 +1,4 @@
// Chinese translation by meb154 & bishan178 // Chinese translation by meb154, bishan178 & Corvus
dict = { dict = {
general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`, general_inventoryUpdateNote: `注意:此处所做的更改只有在游戏同步仓库后才会生效。您可以通过访问星图来触发仓库更新。`,
general_addButton: `添加`, general_addButton: `添加`,
@ -74,9 +74,9 @@ dict = {
inventory_longGuns: `主要武器`, inventory_longGuns: `主要武器`,
inventory_pistols: `次要武器`, inventory_pistols: `次要武器`,
inventory_melee: `近战武器`, inventory_melee: `近战武器`,
inventory_spaceSuits: `Archwings`, inventory_spaceSuits: `载具`,
inventory_spaceGuns: `Archwing主武器`, inventory_spaceGuns: `载具主武器`,
inventory_spaceMelee: `Archwing近战武器`, inventory_spaceMelee: `载具近战武器`,
inventory_mechSuits: `殁世机甲`, inventory_mechSuits: `殁世机甲`,
inventory_sentinels: `守护`, inventory_sentinels: `守护`,
inventory_sentinelWeapons: `守护武器`, inventory_sentinelWeapons: `守护武器`,
@ -88,15 +88,15 @@ dict = {
inventory_Boosters: `加成器`, inventory_Boosters: `加成器`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失Archwing`, inventory_bulkAddSpaceSuits: `添加缺失载具`,
inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, inventory_bulkAddSpaceWeapons: `添加缺失载具武器`,
inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinels: `添加缺失守护`,
inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, inventory_bulkRankUpSpaceWeapons: `所有载具武器升满级`,
inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源最大等级`,
@ -181,10 +181,56 @@ dict = {
cheats_account: `账户`, cheats_account: `账户`,
cheats_unlockAllFocusSchools: `解锁所有专精学派`, cheats_unlockAllFocusSchools: `解锁所有专精学派`,
cheats_helminthUnlockAll: `完全升级Helminth`, cheats_helminthUnlockAll: `完全升级Helminth`,
cheats_addMissingSubsumedAbilities: `添加Helminth未汲取的战甲技能`,
cheats_intrinsicsUnlockAll: `所有内源之力最大等级`, cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_changeSupportedSyndicate: `支持的集团`, cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`, cheats_changeButton: `更改`,
cheats_none: ``, cheats_none: ``,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,
worldState_affinityBoost: `经验加成`,
worldState_resourceBoost: `资源加成`,
worldState_starDays: `活动:星日`,
worldState_galleonOfGhouls: `战术警报:尸鬼的帆船战舰`,
disabled: `关闭/取消配置`,
worldState_we1: `活动阶段:第一周`,
worldState_we2: `活动阶段:第二周`,
worldState_we3: `活动阶段:第三周`,
worldState_eidolonOverride: `夜灵平原/魔胎之境状态`,
worldState_day: `白昼/FASS`,
worldState_night: `黑夜/VOME`,
worldState_vallisOverride: `奥布山谷状态`,
worldState_warm: `温暖`,
worldState_cold: `寒冷`,
worldState_duviriOverride: `双衍王镜状态`,
worldState_joy: `喜悦`,
worldState_anger: `愤怒`,
worldState_envy: `嫉妒`,
worldState_sorrow: `悲伤`,
worldState_fear: `恐惧`,
worldState_nightwaveOverride: `午夜电波系列`,
worldState_RadioLegionIntermission13Syndicate: `诺拉的混选VOL.9`,
worldState_RadioLegionIntermission12Syndicate: `诺拉的混选VOL.8`,
worldState_RadioLegionIntermission11Syndicate: `诺拉的混选VOL.7`,
worldState_RadioLegionIntermission10Syndicate: `诺拉的混选VOL.6`,
worldState_RadioLegionIntermission9Syndicate: `诺拉的混选VOL.5`,
worldState_RadioLegionIntermission8Syndicate: `诺拉的混选VOL.4`,
worldState_RadioLegionIntermission7Syndicate: `诺拉的混选VOL.3`,
worldState_RadioLegionIntermission6Syndicate: `诺拉的混选VOL.2`,
worldState_RadioLegionIntermission5Syndicate: `诺拉的混选VOL.1`,
worldState_RadioLegionIntermission4Syndicate: `诺拉的精选`,
worldState_RadioLegionIntermission3Syndicate: `间歇III`,
worldState_RadioLegion3Syndicate: `系列3 — 玻璃匠`,
worldState_RadioLegionIntermission2Syndicate: `间歇II`,
worldState_RadioLegion2Syndicate: `系列2 — 使徒`,
worldState_RadioLegionIntermissionSyndicate: `间歇I`,
worldState_RadioLegionSyndicate: `系列1 — 土星六号之狼`,
worldState_fissures: `虚空裂缝难度设定`,
normal: `正常`,
worldState_allAtOnceNormal: `全部开启(普通)`,
worldState_allAtOnceSteelPath: `全部开启(钢铁之路)`,
import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`, import_importNote: `您可以在此处提供完整或部分库存响应(客户端表示)。支持的所有字段<b>将被覆盖</b>到您的账户中。`,
import_submit: `提交`, import_submit: `提交`,
import_samples: `示例:`, import_samples: `示例:`,