Compare commits

..

6 Commits
main ... main

Author SHA1 Message Date
84f081312b feat: fullyStockedVendors cheat (#2246)
Reviewed-on: OpenWF/SpaceNinjaServer#2246
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:55:44 -07:00
7ca7147b78 fix(docker): install node-gyp deps to fix arm64 build 2025-06-22 15:52:37 +02:00
558af66965 chore: verify that httpsPort has actually been bound by us (#2243)
I'm not the biggest fan of this, but it's a semi-regular problem and this should help affected users quickly discover it.

Reviewed-on: OpenWF/SpaceNinjaServer#2243
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:41:43 -07:00
d7b9fb1ab5 fix(webui): once awake stage 2 not consistently showing up in-game (#2244)
Fixes #2040

Reviewed-on: OpenWF/SpaceNinjaServer#2244
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:41:06 -07:00
bf12f90c88 chore: replace unlockAllMissions config with an account cheats button (#2241)
This way, mission completion rewards are given. This is especially import for junction rewards like quest keys (Closes #2229).

Reviewed-on: OpenWF/SpaceNinjaServer#2241
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:37:17 -07:00
6dd9b42f40 feat(webui): update inventory when in-game changes are made (#2239)
A bit of a rough initial implementation, but already works pretty well.

Closes #2224

Reviewed-on: OpenWF/SpaceNinjaServer#2239
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:36:47 -07:00
26 changed files with 467 additions and 138 deletions

View File

@ -1,6 +1,6 @@
FROM node:24-alpine3.21 FROM node:24-alpine3.21
RUN apk add --no-cache bash jq RUN apk add --no-cache bash jq python3 make gcc alpine-sdk
COPY . /app COPY . /app
WORKDIR /app WORKDIR /app

View File

@ -13,7 +13,6 @@
"skipTutorial": false, "skipTutorial": false,
"skipAllDialogue": false, "skipAllDialogue": false,
"unlockAllScans": false, "unlockAllScans": false,
"unlockAllMissions": false,
"infiniteCredits": false, "infiniteCredits": false,
"infinitePlatinum": false, "infinitePlatinum": false,
"infiniteEndo": false, "infiniteEndo": false,
@ -42,6 +41,7 @@
"noVendorPurchaseLimits": false, "noVendorPurchaseLimits": false,
"noDeathMarks": false, "noDeathMarks": false,
"noKimCooldowns": false, "noKimCooldowns": false,
"fullyStockedVendors": false,
"syndicateMissionsRepeatable": false, "syndicateMissionsRepeatable": false,
"unlockAllProfitTakerStages": false, "unlockAllProfitTakerStages": false,
"instantFinishRivenChallenge": false, "instantFinishRivenChallenge": false,

204
package-lock.json generated
View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@types/express": "^5", "@types/express": "^5",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
@ -21,6 +22,7 @@
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.68", "warframe-public-export-plus": "^0.5.68",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"websocket": "^1.0.35",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2" "ws": "^8.18.2"
@ -382,6 +384,15 @@
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/websocket": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz",
"integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/whatwg-url": { "node_modules/@types/whatwg-url": {
"version": "11.0.5", "version": "11.0.5",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
@ -910,6 +921,19 @@
"node": ">=16.20.1" "node": ">=16.20.1"
} }
}, },
"node_modules/bufferutil": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz",
"integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/bytes": { "node_modules/bytes": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -1128,6 +1152,19 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/d": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
"license": "ISC",
"dependencies": {
"es5-ext": "^0.10.64",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
@ -1239,6 +1276,46 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/es5-ext": {
"version": "0.10.64",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"esniff": "^2.0.1",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"license": "MIT",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-symbol": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
"license": "ISC",
"dependencies": {
"d": "^1.0.2",
"ext": "^1.7.0"
},
"engines": {
"node": ">=0.12"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -1400,6 +1477,21 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/esniff": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
"license": "ISC",
"dependencies": {
"d": "^1.0.1",
"es5-ext": "^0.10.62",
"event-emitter": "^0.3.5",
"type": "^2.7.2"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/espree": { "node_modules/espree": {
"version": "9.6.1", "version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
@ -1473,6 +1565,16 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/event-emitter": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
"license": "MIT",
"dependencies": {
"d": "1",
"es5-ext": "~0.10.14"
}
},
"node_modules/express": { "node_modules/express": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
@ -1515,6 +1617,15 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"license": "ISC",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -2044,6 +2155,12 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"license": "MIT"
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -2449,6 +2566,23 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
"license": "ISC"
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/object-hash": { "node_modules/object-hash": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
@ -3239,6 +3373,12 @@
"dev": true, "dev": true,
"license": "0BSD" "license": "0BSD"
}, },
"node_modules/type": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
"license": "ISC"
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -3279,6 +3419,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"license": "MIT",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.8.3", "version": "5.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
@ -3317,6 +3466,19 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -3351,6 +3513,38 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/websocket": {
"version": "1.0.35",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz",
"integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==",
"license": "Apache-2.0",
"dependencies": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.63",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/websocket/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/websocket/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/whatwg-url": { "node_modules/whatwg-url": {
"version": "14.2.0", "version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
@ -3471,6 +3665,16 @@
} }
} }
}, },
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
"license": "MIT",
"engines": {
"node": ">=0.10.32"
}
},
"node_modules/yocto-queue": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -20,6 +20,7 @@
"dependencies": { "dependencies": {
"@types/express": "^5", "@types/express": "^5",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
@ -30,6 +31,7 @@
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.68", "warframe-public-export-plus": "^0.5.68",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"websocket": "^1.0.35",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2" "ws": "^8.18.2"

View File

@ -6,13 +6,7 @@ import allDialogue from "@/static/fixed_responses/allDialogue.json";
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes"; import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes"; import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
ExportCustoms,
ExportFlavour,
ExportRegions,
ExportResources,
ExportVirtuals
} from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
import { import {
addMiscItems, addMiscItems,
@ -22,7 +16,7 @@ import {
generateRewardSeed generateRewardSeed
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers"; import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { getPersonalRooms } from "@/src/services/personalRoomsService";
@ -167,18 +161,6 @@ export const getInventoryResponse = async (
} }
} }
if (config.unlockAllMissions) {
inventoryResponse.Missions = [];
for (const tag of Object.keys(ExportRegions)) {
inventoryResponse.Missions.push({
Completes: 1,
Tier: 1,
Tag: tag
});
}
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
}
if (config.unlockAllShipDecorations) { if (config.unlockAllShipDecorations) {
inventoryResponse.ShipDecorations = []; inventoryResponse.ShipDecorations = [];
for (const [uniqueName, item] of Object.entries(ExportResources)) { for (const [uniqueName, item] of Object.entries(ExportResources)) {
@ -362,12 +344,6 @@ const allEudicoHeistJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour" "/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
]; ];
const addString = (arr: string[], str: string): void => {
if (arr.indexOf(str) == -1) {
arr.push(str);
}
};
const getExpRequiredForMr = (rank: number): number => { const getExpRequiredForMr = (rank: number): number => {
if (rank <= 30) { if (rank <= 30) {
return 2500 * rank * rank; return 2500 * rank * rank;

View File

@ -7,6 +7,7 @@ import { generateRewardSeed, getInventory } from "@/src/services/inventoryServic
import { getInventoryResponse } from "./inventoryController"; import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes"; import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
import { sendWsBroadcastTo } from "@/src/services/webService";
/* /*
**** INPUT **** **** INPUT ****
@ -76,6 +77,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
InventoryJson: JSON.stringify(inventoryResponse), InventoryJson: JSON.stringify(inventoryResponse),
MissionRewards: [] MissionRewards: []
}); });
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
return; return;
} }
@ -106,6 +108,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
AffiliationMods, AffiliationMods,
ConquestCompletedMissionsCount ConquestCompletedMissionsCount
} satisfies IMissionInventoryUpdateResponse); } satisfies IMissionInventoryUpdateResponse);
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
}; };
/* /*

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes"; import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { sendWsBroadcastTo } from "@/src/services/webService";
interface INameWeaponRequest { interface INameWeaponRequest {
ItemName: string; ItemName: string;
@ -27,4 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
res.json({ res.json({
InventoryChanges: currencyChanges InventoryChanges: currencyChanges
}); });
sendWsBroadcastTo(accountId, { update_inventory: true });
}; };

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPurchaseRequest } from "@/src/types/purchaseTypes"; import { IPurchaseRequest } from "@/src/types/purchaseTypes";
import { handlePurchase } from "@/src/services/purchaseService"; import { handlePurchase } from "@/src/services/purchaseService";
import { getInventory } from "@/src/services/inventoryService"; import { getInventory } from "@/src/services/inventoryService";
import { sendWsBroadcastTo } from "@/src/services/webService";
export const purchaseController: RequestHandler = async (req, res) => { export const purchaseController: RequestHandler = async (req, res) => {
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest; const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
@ -11,4 +12,5 @@ export const purchaseController: RequestHandler = async (req, res) => {
const response = await handlePurchase(purchaseRequest, inventory); const response = await handlePurchase(purchaseRequest, inventory);
await inventory.save(); await inventory.save();
res.json(response); res.json(response);
sendWsBroadcastTo(accountId, { update_inventory: true });
}; };

View File

@ -1,6 +1,7 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, updateCurrency } from "@/src/services/inventoryService"; import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/webService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
@ -22,6 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => {
...data, ...data,
inventoryChanges: inventoryChanges inventoryChanges: inventoryChanges
}); });
sendWsBroadcastTo(accountId, { update_inventory: true });
}; };
interface IRenamePetRequest { interface IRenamePetRequest {

View File

@ -15,6 +15,7 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { sendWsBroadcastTo } from "@/src/services/webService";
export const sellController: RequestHandler = async (req, res) => { export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest; const payload = JSON.parse(String(req.body)) as ISellRequest;
@ -279,6 +280,7 @@ export const sellController: RequestHandler = async (req, res) => {
res.json({ res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges" inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
}); });
sendWsBroadcastTo(accountId, { update_inventory: true });
}; };
interface ISellRequest { interface ISellRequest {

View File

@ -0,0 +1,34 @@
import { addString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IMissionReward } from "@/src/types/missionTypes";
import { RequestHandler } from "express";
import { ExportRegions } from "warframe-public-export-plus";
export const completeAllMissionsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const MissionRewards: IMissionReward[] = [];
for (const [tag, node] of Object.entries(ExportRegions)) {
if (!inventory.Missions.find(x => x.Tag == tag)) {
inventory.Missions.push({
Completes: 1,
Tier: 1,
Tag: tag
});
if (node.missionReward) {
console.log(node.missionReward);
addFixedLevelRewards(node.missionReward, inventory, MissionRewards);
}
}
}
for (const reward of MissionRewards) {
await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);
}
addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
await inventory.save();
res.end();
};

View File

@ -128,7 +128,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
await completeQuest(inventory, questKey.ItemType); await completeQuest(inventory, questKey.ItemType);
} else { } else {
const progress = { const progress = {
c: questManifest.chainStages![currentStage].key ? -1 : 0, c: 0,
i: false, i: false,
m: false, m: false,
b: [] b: []

View File

@ -54,3 +54,9 @@ export const regexEscape = (str: string): string => {
str = str.split("}").join("\\}"); str = str.split("}").join("\\}");
return str; return str;
}; };
export const addString = (arr: string[], str: string): void => {
if (arr.indexOf(str) == -1) {
arr.push(str);
}
};

View File

@ -12,6 +12,7 @@ import { ircDroppedController } from "@/src/controllers/custom/ircDroppedControl
import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController"; import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController";
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 { 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";
@ -40,6 +41,7 @@ customRouter.get("/ircDropped", ircDroppedController);
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); 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.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController); customRouter.post("/createMessage", createMessageController);

View File

@ -19,7 +19,6 @@ export interface IConfig {
skipTutorial?: boolean; skipTutorial?: boolean;
skipAllDialogue?: boolean; skipAllDialogue?: boolean;
unlockAllScans?: boolean; unlockAllScans?: boolean;
unlockAllMissions?: boolean;
infiniteCredits?: boolean; infiniteCredits?: boolean;
infinitePlatinum?: boolean; infinitePlatinum?: boolean;
infiniteEndo?: boolean; infiniteEndo?: boolean;
@ -49,6 +48,7 @@ export interface IConfig {
noVendorPurchaseLimits?: boolean; noVendorPurchaseLimits?: boolean;
noDeathMarks?: boolean; noDeathMarks?: boolean;
noKimCooldowns?: boolean; noKimCooldowns?: boolean;
fullyStockedVendors?: boolean;
syndicateMissionsRepeatable?: boolean; syndicateMissionsRepeatable?: boolean;
unlockAllProfitTakerStages?: boolean; unlockAllProfitTakerStages?: boolean;
instantFinishRivenChallenge?: boolean; instantFinishRivenChallenge?: boolean;

View File

@ -1367,7 +1367,7 @@ export const addFixedLevelRewards = (
if (rewards.countedItems) { if (rewards.countedItems) {
for (const item of rewards.countedItems) { for (const item of rewards.countedItems) {
MissionRewards.push({ MissionRewards.push({
StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`, StoreItem: toStoreItem(item.ItemType),
ItemCount: item.ItemCount ItemCount: item.ItemCount
}); });
} }

View File

@ -6,6 +6,7 @@ import { mixSeeds, SRng } from "@/src/services/rngService";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes"; import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus"; import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
import { config } from "./configService";
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> { interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleOffset?: number; cycleOffset?: number;
@ -59,20 +60,23 @@ const getCycleDuration = (manifest: IVendor): number => {
return dur * unixTimesInMs.hour; return dur * unixTimesInMs.hour;
}; };
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => { export const getVendorManifestByTypeName = (typeName: string, fullStock?: boolean): IVendorManifest | undefined => {
for (const vendorInfo of generatableVendors) { for (const vendorInfo of generatableVendors) {
if (vendorInfo.TypeName == typeName) { if (vendorInfo.TypeName == typeName) {
return generateVendorManifest(vendorInfo); return generateVendorManifest(vendorInfo, fullStock ?? config.fullyStockedVendors);
} }
} }
if (typeName in ExportVendors) { if (typeName in ExportVendors) {
const manifest = ExportVendors[typeName]; const manifest = ExportVendors[typeName];
return generateVendorManifest({ return generateVendorManifest(
{
_id: { $oid: getVendorOid(typeName) }, _id: { $oid: getVendorOid(typeName) },
TypeName: typeName, TypeName: typeName,
RandomSeedType: manifest.randomSeedType, RandomSeedType: manifest.randomSeedType,
cycleDuration: getCycleDuration(manifest) cycleDuration: getCycleDuration(manifest)
}); },
fullStock ?? config.fullyStockedVendors
);
} }
return undefined; return undefined;
}; };
@ -80,18 +84,21 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifest |
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => { export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
for (const vendorInfo of generatableVendors) { for (const vendorInfo of generatableVendors) {
if (vendorInfo._id.$oid == oid) { if (vendorInfo._id.$oid == oid) {
return generateVendorManifest(vendorInfo); return generateVendorManifest(vendorInfo, config.fullyStockedVendors);
} }
} }
for (const [typeName, manifest] of Object.entries(ExportVendors)) { for (const [typeName, manifest] of Object.entries(ExportVendors)) {
const typeNameOid = getVendorOid(typeName); const typeNameOid = getVendorOid(typeName);
if (typeNameOid == oid) { if (typeNameOid == oid) {
return generateVendorManifest({ return generateVendorManifest(
{
_id: { $oid: typeNameOid }, _id: { $oid: typeNameOid },
TypeName: typeName, TypeName: typeName,
RandomSeedType: manifest.randomSeedType, RandomSeedType: manifest.randomSeedType,
cycleDuration: getCycleDuration(manifest) cycleDuration: getCycleDuration(manifest)
}); },
config.fullyStockedVendors
);
} }
} }
return undefined; return undefined;
@ -169,9 +176,26 @@ const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => {
} }
}; };
let vendorManifestsUsingFullStock = false;
const vendorManifestCache: Record<string, IVendorManifest> = {}; const vendorManifestCache: Record<string, IVendorManifest> = {};
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => { const clearVendorCache = (): void => {
for (const k of Object.keys(vendorManifestCache)) {
delete vendorManifestCache[k];
}
};
const generateVendorManifest = (
vendorInfo: IGeneratableVendorInfo,
fullStock: boolean | undefined
): IVendorManifest => {
fullStock ??= config.fullyStockedVendors;
fullStock ??= false;
if (vendorManifestsUsingFullStock != fullStock) {
vendorManifestsUsingFullStock = fullStock;
clearVendorCache();
}
if (!(vendorInfo.TypeName in vendorManifestCache)) { if (!(vendorInfo.TypeName in vendorManifestCache)) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo; const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
@ -208,7 +232,20 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration); const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration);
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex)); const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
const offersToAdd: IVendorOffer[] = []; const offersToAdd: IVendorOffer[] = [];
if (!manifest.isOneBinPerCycle) { if (manifest.isOneBinPerCycle) {
if (fullStock) {
for (const rawItem of manifest.items) {
offersToAdd.push(rawItem);
}
} else {
const binThisCycle = cycleIndex % 2; // Note: May want to check the actual number of bins, but this is only used for coda weapons right now.
for (const rawItem of manifest.items) {
if (rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem);
}
}
}
} else {
// Compute vendor requirements, subtracting existing offers // Compute vendor requirements, subtracting existing offers
const remainingItemCapacity: Record<TOfferId, number> = {}; const remainingItemCapacity: Record<TOfferId, number> = {};
const missingItemsPerBin: Record<number, number> = {}; const missingItemsPerBin: Record<number, number> = {};
@ -254,7 +291,9 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
manifest.numItems && manifest.numItems &&
(manifest.numItems.minValue != manifest.numItems.maxValue || (manifest.numItems.minValue != manifest.numItems.maxValue ||
manifest.numItems.minValue != numCountedOffers); manifest.numItems.minValue != numCountedOffers);
const numItemsTarget = manifest.numItems const numItemsTarget = fullStock
? numUncountedOffers + numCountedOffers
: manifest.numItems
? numUncountedOffers + ? numUncountedOffers +
(useRng (useRng
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue) ? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
@ -282,13 +321,6 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
i = 0; i = 0;
} }
} }
} else {
const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
for (const rawItem of manifest.items) {
if (rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem);
}
}
} }
const cycleStart = cycleOffset + cycleIndex * cycleDuration; const cycleStart = cycleOffset + cycleIndex * cycleDuration;
for (const rawItem of offersToAdd) { for (const rawItem of offersToAdd) {
@ -387,8 +419,13 @@ if (args.dev) {
logger.warn(`getCycleDuration self test failed`); logger.warn(`getCycleDuration self test failed`);
} }
const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")! for (let i = 0; i != 2; ++i) {
.VendorInfo.ItemManifest; const fullStock = !!i;
const ads = getVendorManifestByTypeName(
"/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
fullStock
)!.VendorInfo.ItemManifest;
if ( if (
ads.length != 5 || ads.length != 5 ||
ads[0].Bin != "BIN_4" || ads[0].Bin != "BIN_4" ||
@ -400,8 +437,10 @@ if (args.dev) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
} }
const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")! const pall = getVendorManifestByTypeName(
.VendorInfo.ItemManifest; "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
fullStock
)!.VendorInfo.ItemManifest;
if ( if (
pall.length != 5 || pall.length != 5 ||
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" || pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
@ -412,9 +451,12 @@ if (args.dev) {
) { ) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
} }
}
const cms = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest")! const cms = getVendorManifestByTypeName(
.VendorInfo.ItemManifest; "/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest",
false
)!.VendorInfo.ItemManifest;
if ( if (
cms.length != 9 || cms.length != 9 ||
cms[0].Bin != "BIN_2" || cms[0].Bin != "BIN_2" ||
@ -426,13 +468,15 @@ if (args.dev) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`);
} }
const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")! const temple = getVendorManifestByTypeName(
.VendorInfo.ItemManifest; "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
false
)!.VendorInfo.ItemManifest;
if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) { if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`); logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`);
} }
const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest")! const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", false)!
.VendorInfo.ItemManifest; .VendorInfo.ItemManifest;
if ( if (
nakak.length != 10 || nakak.length != 10 ||

View File

@ -10,6 +10,7 @@ import { Account } from "../models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService"; import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService";
import { IDatabaseAccountJson } from "../types/loginTypes"; import { IDatabaseAccountJson } from "../types/loginTypes";
import { HydratedDocument } from "mongoose"; import { HydratedDocument } from "mongoose";
import websocket from "websocket";
let httpServer: http.Server | undefined; let httpServer: http.Server | undefined;
let httpsServer: https.Server | undefined; let httpsServer: https.Server | undefined;
@ -44,6 +45,37 @@ export const startWebServer = (): void => {
logger.info( logger.info(
"Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort) "Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
); );
void runWsSelfTest("wss", httpsPort).then(ok => {
if (!ok) {
logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`);
if (process.platform == "win32") {
logger.warn(
`You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess`
);
}
}
});
});
});
};
const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise<boolean> => {
return new Promise(resolve => {
const client = new websocket.client({ tlsOptions: { rejectUnauthorized: false } });
client.connect(`${protocol}://localhost:${port}/custom/selftest`);
client.on("connect", connection => {
connection.on("message", msg => {
if (msg.type == "utf8" && msg.utf8Data == "SpaceNinjaServer") {
resolve(true);
}
});
connection.on("close", () => {
resolve(false);
});
});
client.on("connectFailed", () => {
resolve(false);
}); });
}); });
}; };
@ -125,9 +157,15 @@ interface IWsMsgToClient {
isRegister: boolean; isRegister: boolean;
}; };
logged_out?: boolean; logged_out?: boolean;
update_inventory?: boolean;
} }
const wsOnConnect = (ws: ws, _req: http.IncomingMessage): void => { const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
if (req.url == "/custom/selftest") {
ws.send("SpaceNinjaServer");
ws.close();
return;
}
// 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;

View File

@ -452,9 +452,6 @@
<button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button> <button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button> <button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -587,10 +584,6 @@
<input class="form-check-input" type="checkbox" id="unlockAllScans" /> <input class="form-check-input" type="checkbox" id="unlockAllScans" />
<label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label> <label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
</div> </div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllMissions" />
<label class="form-check-label" for="unlockAllMissions" data-loc="cheats_unlockAllMissions"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteCredits" /> <input class="form-check-input" type="checkbox" id="infiniteCredits" />
<label class="form-check-label" for="infiniteCredits" data-loc="cheats_infiniteCredits"></label> <label class="form-check-label" for="infiniteCredits" data-loc="cheats_infiniteCredits"></label>
@ -707,6 +700,10 @@
<input class="form-check-input" type="checkbox" id="noKimCooldowns" /> <input class="form-check-input" type="checkbox" id="noKimCooldowns" />
<label class="form-check-label" for="noKimCooldowns" data-loc="cheats_noKimCooldowns"></label> <label class="form-check-label" for="noKimCooldowns" data-loc="cheats_noKimCooldowns"></label>
</div> </div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
<label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="syndicateMissionsRepeatable" /> <input class="form-check-input" type="checkbox" id="syndicateMissionsRepeatable" />
<label class="form-check-label" for="syndicateMissionsRepeatable" data-loc="cheats_syndicateMissionsRepeatable"></label> <label class="form-check-label" for="syndicateMissionsRepeatable" data-loc="cheats_syndicateMissionsRepeatable"></label>
@ -782,9 +779,11 @@
<h5 class="card-header" data-loc="cheats_account"></h5> <h5 class="card-header" data-loc="cheats_account"></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">
<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="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>
</div> </div>
<form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;"> <form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label> <label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>

View File

@ -79,6 +79,9 @@ function openWebSocket() {
if ("logged_out" in msg) { if ("logged_out" in msg) {
sendAuth(); sendAuth();
} }
if ("update_inventory" in msg) {
updateInventory();
}
}; };
window.ws.onclose = function () { window.ws.onclose = function () {
ws_is_open = false; ws_is_open = false;
@ -111,11 +114,9 @@ function doLogin() {
window.registerSubmit = false; window.registerSubmit = false;
} }
function revalidateAuthz(succ_cb) { async function revalidateAuthz() {
getWebSocket().then(() => { await getWebSocket();
// We have a websocket connection, so authz should be good. // We have a websocket connection, so authz should be good.
succ_cb();
});
} }
function logout() { function logout() {
@ -135,7 +136,7 @@ function doLogout() {
function renameAccount() { function renameAccount() {
const newname = window.prompt(loc("code_changeNameConfirm")); const newname = window.prompt(loc("code_changeNameConfirm"));
if (newname) { if (newname) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => { fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => {
$(".displayname").text(newname); $(".displayname").text(newname);
updateLocElements(); updateLocElements();
@ -146,7 +147,7 @@ function renameAccount() {
function deleteAccount() { function deleteAccount() {
if (window.confirm(loc("code_deleteAccountConfirm"))) { if (window.confirm(loc("code_deleteAccountConfirm"))) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
fetch("/custom/deleteAccount?" + window.authz).then(() => { fetch("/custom/deleteAccount?" + window.authz).then(() => {
logout(); logout();
single.loadRoute("/webui/"); // Show login screen single.loadRoute("/webui/"); // Show login screen
@ -646,7 +647,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
revalidateAuthz(() => { revalidateAuthz().then(() => {
const promises = []; const promises = [];
if (item.XP < maxXP) { if (item.XP < maxXP) {
promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP)); promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP));
@ -1236,7 +1237,7 @@ function doAcquireEquipment(category) {
.focus(); .focus();
return; return;
} }
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.post({ const req = $.post({
url: "/custom/addItems?" + window.authz, url: "/custom/addItems?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -1363,7 +1364,7 @@ function doAcquireModularEquipment(category, WeaponType) {
} }
}); });
if (category == "KubrowPets") Parts.unshift(WeaponType); if (category == "KubrowPets") Parts.unshift(WeaponType);
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.post({ const req = $.post({
url: "/api/modularWeaponCrafting.php?" + window.authz, url: "/api/modularWeaponCrafting.php?" + window.authz,
contentType: "application/octet-stream", contentType: "application/octet-stream",
@ -1416,7 +1417,7 @@ $("input[list]").on("input", function () {
}); });
function dispatchAddItemsRequestsBatch(requests) { function dispatchAddItemsRequestsBatch(requests) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.post({ const req = $.post({
url: "/custom/addItems?" + window.authz, url: "/custom/addItems?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -1460,7 +1461,7 @@ function addMissingEvolutionProgress() {
} }
function maxRankAllEvolutions() { function maxRankAllEvolutions() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => { req.done(data => {
const requests = []; const requests = [];
@ -1484,7 +1485,7 @@ function maxRankAllEvolutions() {
} }
function maxRankAllEquipment(categories) { function maxRankAllEquipment(categories) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => { req.done(data => {
window.itemListPromise.then(itemMap => { window.itemListPromise.then(itemMap => {
@ -1558,7 +1559,7 @@ function addGearExp(category, oid, xp) {
} }
function sendBatchGearExp(data) { function sendBatchGearExp(data) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/addXp?" + window.authz, url: "/custom/addXp?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -1571,7 +1572,7 @@ function sendBatchGearExp(data) {
} }
function renameGear(category, oid, name) { function renameGear(category, oid, name) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
if (category == "KubrowPets") { if (category == "KubrowPets") {
$.post({ $.post({
url: "/api/renamePet.php?" + window.authz + "&webui=1", url: "/api/renamePet.php?" + window.authz + "&webui=1",
@ -1599,7 +1600,7 @@ function renameGear(category, oid, name) {
function disposeOfGear(category, oid) { function disposeOfGear(category, oid) {
if (category == "KubrowPets") { if (category == "KubrowPets") {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/releasePet.php?" + window.authz, url: "/api/releasePet.php?" + window.authz,
contentType: "application/octet-stream", contentType: "application/octet-stream",
@ -1621,7 +1622,7 @@ function disposeOfGear(category, oid) {
Count: 0 Count: 0
} }
]; ];
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
@ -1643,7 +1644,7 @@ function disposeOfItems(category, type, count) {
Count: count Count: count
} }
]; ];
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/sell.php?" + window.authz, url: "/api/sell.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
@ -1653,7 +1654,7 @@ function disposeOfItems(category, type, count) {
} }
function gildEquipment(category, oid) { function gildEquipment(category, oid) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category, url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category,
contentType: "application/octet-stream", contentType: "application/octet-stream",
@ -1667,7 +1668,7 @@ function gildEquipment(category, oid) {
} }
function maturePet(oid, revert) { function maturePet(oid, revert) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/maturePet.php?" + window.authz, url: "/api/maturePet.php?" + window.authz,
contentType: "application/octet-stream", contentType: "application/octet-stream",
@ -1682,7 +1683,7 @@ function maturePet(oid, revert) {
} }
function setEvolutionProgress(requests) { function setEvolutionProgress(requests) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.post({ const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz, url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -1702,7 +1703,7 @@ function doAcquireMiscItems() {
} }
const count = parseInt($("#miscitem-count").val()); const count = parseInt($("#miscitem-count").val());
if (count != 0) { if (count != 0) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/addItems?" + window.authz, url: "/custom/addItems?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -1743,7 +1744,7 @@ function doAcquireRiven() {
return; return;
} }
const uniqueName = "/Lotus/Upgrades/Mods/Randomized/" + $("#addriven-type").val(); const uniqueName = "/Lotus/Upgrades/Mods/Randomized/" + $("#addriven-type").val();
revalidateAuthz(() => { revalidateAuthz().then(() => {
// Add riven type to inventory // Add riven type to inventory
$.post({ $.post({
url: "/custom/addItems?" + window.authz, url: "/custom/addItems?" + window.authz,
@ -1790,7 +1791,7 @@ $("#addriven-fingerprint").on("input", () => {
}); });
function setFingerprint(ItemType, ItemId, fingerprint) { function setFingerprint(ItemType, ItemId, fingerprint) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/api/artifacts.php?" + window.authz, url: "/api/artifacts.php?" + window.authz,
contentType: "text/plain", contentType: "text/plain",
@ -1818,7 +1819,7 @@ function doAcquireMod() {
} }
const count = parseInt($("#mod-count").val()); const count = parseInt($("#mod-count").val());
if (count != 0) { if (count != 0) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/addItems?" + window.authz, url: "/custom/addItems?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -1895,7 +1896,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
); );
} else { } else {
if ((await res.text()) == "Log-in expired") { if ((await res.text()) == "Log-in expired") {
revalidateAuthz(() => { revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/cheats") { if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats"); single.loadRoute("/webui/cheats");
} }
@ -1912,7 +1913,7 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
}); });
function doUnlockAllFocusSchools() { function doUnlockAllFocusSchools() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(async data => { $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(async data => {
const missingFocusUpgrades = { const missingFocusUpgrades = {
"/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true, "/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true,
@ -1963,13 +1964,13 @@ function unlockFocusSchool(upgradeType) {
} }
function doHelminthUnlockAll() { function doHelminthUnlockAll() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post("/api/infestedFoundry.php?" + window.authz + "&mode=custom_unlockall"); $.post("/api/infestedFoundry.php?" + window.authz + "&mode=custom_unlockall");
}); });
} }
function doIntrinsicsUnlockAll() { function doIntrinsicsUnlockAll() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.get("/custom/unlockAllIntrinsics?" + window.authz); $.get("/custom/unlockAllIntrinsics?" + window.authz);
}); });
} }
@ -1981,7 +1982,7 @@ function doAddAllMods() {
} }
modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser"); modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser");
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => { req.done(data => {
for (const modOwned of data.RawUpgrades) { for (const modOwned of data.RawUpgrades) {
@ -2013,7 +2014,7 @@ function doAddAllMods() {
} }
function doRemoveUnrankedMods() { function doRemoveUnrankedMods() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(inventory => { req.done(inventory => {
window.itemListPromise.then(itemMap => { window.itemListPromise.then(itemMap => {
@ -2038,7 +2039,7 @@ function doRemoveUnrankedMods() {
} }
function doAddMissingMaxRankMods() { function doAddMissingMaxRankMods() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => { fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => {
updateInventory(); updateInventory();
}); });
@ -2060,7 +2061,7 @@ function doPushArchonCrystalUpgrade() {
$("[list='datalist-archonCrystalUpgrades']").addClass("is-invalid").focus(); $("[list='datalist-archonCrystalUpgrades']").addClass("is-invalid").focus();
return; return;
} }
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.get( $.get(
"/custom/pushArchonCrystalUpgrade?" + "/custom/pushArchonCrystalUpgrade?" +
window.authz + window.authz +
@ -2078,7 +2079,7 @@ function doPushArchonCrystalUpgrade() {
} }
function doPopArchonCrystalUpgrade(type) { function doPopArchonCrystalUpgrade(type) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.get( $.get(
"/custom/popArchonCrystalUpgrade?" + "/custom/popArchonCrystalUpgrade?" +
window.authz + window.authz +
@ -2093,7 +2094,7 @@ function doPopArchonCrystalUpgrade(type) {
} }
function doImport() { function doImport() {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/import?" + window.authz, url: "/custom/import?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -2110,7 +2111,7 @@ function doImport() {
function doChangeSupportedSyndicate() { function doChangeSupportedSyndicate() {
const uniqueName = document.getElementById("changeSyndicate").value; const uniqueName = document.getElementById("changeSyndicate").value;
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.get("/api/setSupportedSyndicate.php?" + window.authz + "&syndicate=" + uniqueName).done(function () { $.get("/api/setSupportedSyndicate.php?" + window.authz + "&syndicate=" + uniqueName).done(function () {
updateInventory(); updateInventory();
}); });
@ -2118,7 +2119,7 @@ function doChangeSupportedSyndicate() {
} }
function doAddCurrency(currency) { function doAddCurrency(currency) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/addCurrency?" + window.authz, url: "/custom/addCurrency?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -2133,7 +2134,7 @@ function doAddCurrency(currency) {
} }
function doQuestUpdate(operation, itemType) { function doQuestUpdate(operation, itemType) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType, url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
contentType: "application/json" contentType: "application/json"
@ -2144,7 +2145,7 @@ function doQuestUpdate(operation, itemType) {
} }
function doBulkQuestUpdate(operation) { function doBulkQuestUpdate(operation) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/manageQuests?" + window.authz + "&operation=" + operation, url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
contentType: "application/json" contentType: "application/json"
@ -2242,7 +2243,7 @@ function handleModularSelection(category) {
} }
function setBooster(ItemType, ExpiryDate, callback) { function setBooster(ItemType, ExpiryDate, callback) {
revalidateAuthz(() => { revalidateAuthz().then(() => {
$.post({ $.post({
url: "/custom/setBooster?" + window.authz, url: "/custom/setBooster?" + window.authz,
contentType: "application/json", contentType: "application/json",
@ -2332,3 +2333,9 @@ async function doMaxPlexus() {
toast(loc("code_noEquipmentToRankUp")); toast(loc("code_noEquipmentToRankUp"));
} }
} }
async function doUnlockAllMissions() {
await revalidateAuthz();
await fetch("/custom/completeAllMissions?" + window.authz);
updateInventory();
}

View File

@ -158,6 +158,7 @@ dict = {
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`, cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
cheats_noDeathMarks: `Keine Todesmarkierungen`, cheats_noDeathMarks: `Keine Todesmarkierungen`,
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`, cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,

View File

@ -157,6 +157,7 @@ dict = {
cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `No Vendor Purchase Limits`,
cheats_noDeathMarks: `No Death Marks`, cheats_noDeathMarks: `No Death Marks`,
cheats_noKimCooldowns: `No KIM Cooldowns`, cheats_noKimCooldowns: `No KIM Cooldowns`,
cheats_fullyStockedVendors: `Fully Stocked Vendors`,
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`, cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`, cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`, cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,

View File

@ -158,6 +158,7 @@ dict = {
cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`, cheats_noVendorPurchaseLimits: `Sin límite de compras de vendedores`,
cheats_noDeathMarks: `Sin marcas de muerte`, cheats_noDeathMarks: `Sin marcas de muerte`,
cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`, cheats_noKimCooldowns: `Sin tiempo de espera para conversaciones KIM`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`, cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`, cheats_unlockAllProfitTakerStages: `Deslobquea todas las etapas del Roba-ganancias`,
cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`, cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,

View File

@ -158,6 +158,7 @@ dict = {
cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`, cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
cheats_noDeathMarks: `Aucune marque d'assassin`, cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`, cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`, cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,

View File

@ -158,6 +158,7 @@ dict = {
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`, cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
cheats_noDeathMarks: `Без меток сметри`, cheats_noDeathMarks: `Без меток сметри`,
cheats_noKimCooldowns: `Чаты KIM без кулдауна`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`, cheats_syndicateMissionsRepeatable: `[UNTRANSLATED] Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`, cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`, cheats_instantFinishRivenChallenge: `[UNTRANSLATED] Instant Finish Riven Challenge`,

View File

@ -158,6 +158,7 @@ dict = {
cheats_noVendorPurchaseLimits: `商城或商人无购买限制`, cheats_noVendorPurchaseLimits: `商城或商人无购买限制`,
cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`, cheats_noDeathMarks: `无死亡标记(不会被 Stalker/Grustrag 三霸/Zanuka 猎人等标记)`,
cheats_noKimCooldowns: `无 KIM 冷却时间`, cheats_noKimCooldowns: `无 KIM 冷却时间`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_syndicateMissionsRepeatable: `集团任务可重复`, cheats_syndicateMissionsRepeatable: `集团任务可重复`,
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`, cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`, cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,