Compare commits

...

38 Commits

Author SHA1 Message Date
AlexisinGit
11c085fcd7 cheat: crackRelicForPlatinum 2025-08-31 18:17:15 +08:00
AlexisinGit
bbbc554939 cheat: extraRelicRewards 2025-08-31 17:59:25 +08:00
AlexisinGit
7ea9460b11 cheat: playerSkillGainsMultiplier (Intrinsics) 2025-08-31 17:47:15 +08:00
AlexisinGit
83b44e8e13 cheat: extraMissionRewards 2025-08-31 17:46:36 +08:00
AlexisinGit
542694b576 cheat: nemesisExtraWeapon 2025-08-31 17:35:25 +08:00
AlexisinGit
94df06490f cheat: nemesisWeaponFusionMultiplier 2025-08-31 17:35:25 +08:00
AlexisinGit
29afa68065 cheat: nemesisHintProgressMultiplier 2025-08-31 16:25:47 +08:00
AlexisinGit
008b45df30 cheat: antivirusGainMultiplier 2025-08-31 16:20:09 +08:00
AlexisinGit
65fbd9c5fc cheat: nemesisHenchmenKillsMultiplier 2025-08-31 16:07:01 +08:00
AlexisinGit
4f0c9e9695 cheat: nemesisAlwaysCorrect
new IAccountCheats type

Update nemesisController.ts

webUI updates
2025-08-31 16:02:04 +08:00
AlexisinGit
259bfd5800 cheat: gainNoNegativeSyndicateStanding
New IAccountCheats type

Update inventoryService.ts

webUI updates
2025-08-31 16:02:04 +08:00
AlexisinGit
4d03246e16 setAccountCheatController type check 2025-08-31 16:02:04 +08:00
9662da00de chore(webui): update uk & ru (#2728)
Reviewed-on: OpenWF/SpaceNinjaServer#2728
Co-authored-by: LoseFace <loseface@noreply.localhost>
Co-committed-by: LoseFace <loseface@noreply.localhost>
2025-08-30 19:35:06 -07:00
662d824369 chore: move unlockAllSimarisResearchEntries to a per-account button (#2726)
Closes #2725. Re #2361.

Reviewed-on: OpenWF/SpaceNinjaServer#2726
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-30 19:34:55 -07:00
a0bac12e95 fix: put vault medallion into correct place (#2723)
Re #2719

Reviewed-on: OpenWF/SpaceNinjaServer#2723
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-30 19:34:30 -07:00
e98cb2ec24 feat(webui): add item by ItemType (#2704)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2704
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-30 19:34:22 -07:00
b5c6c3e485 fix: don't push thermal fractures event if it's not activated yet (#2722)
Closes #2721

Reviewed-on: OpenWF/SpaceNinjaServer#2722
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-29 17:37:34 -07:00
fa65ba3f25 chore: correct breaks in Thermia Fractures cycle (#2724)
Reviewed-on: OpenWF/SpaceNinjaServer#2724
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-29 17:37:23 -07:00
0c54c064eb fix: Pathos Clamps Upon Isleweave Clearing (#2718)
https://wiki.warframe.com/w/Isleweaver#Normal

![image.png](/attachments/ad46e7d2-e28c-47cf-8b13-237a29bc7cc6)

Co-authored-by: AlexisinGit <136088944+AlexisinGit@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2718
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AlexisinGit <alexisingit@noreply.localhost>
Co-committed-by: AlexisinGit <alexisingit@noreply.localhost>
2025-08-29 17:36:42 -07:00
b4e789bf0d chore: move unlock all profit taker stages to a per-account button (#2717)
Re #2361, generated with OpenAI Codex.

Reviewed-on: OpenWF/SpaceNinjaServer#2717
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-29 17:36:31 -07:00
9add016d7b feat: finishInvasionsInOneMission (#2715)
#2646

Co-authored-by: AlexisinGit <136088944+AlexisinGit@users.noreply.github.com>
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2715
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AlexisinGit <alexisingit@noreply.localhost>
Co-committed-by: AlexisinGit <alexisingit@noreply.localhost>
2025-08-29 17:36:11 -07:00
a2171c80a5 fix: CrewShipFusion bug with SubroutineIndex (#2714)
Crewship fusion didn't agree with player's choice.

Expected results: ![image.png](/attachments/a61c96c7-1624-4dd4-9e4c-c6199d5af95a)

What I actually get: ![image.png](/attachments/cf58aed4-18d9-4d97-8894-6c53762f366d)

```
export interface ICrewShipComponentFingerprint extends IInnateDamageFingerprint {
    SubroutineIndex?: number;
}
```

The interface already demand SubroutineIndex to exist, so it would be unnecessary to check.

Meanwhile, inferiorFingerprint.SubroutineIndex could be 0, 1, 2... and might be handled incorrectly as true/ false.

Co-authored-by: AlexisinGit <136088944+AlexisinGit@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2714
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AlexisinGit <alexisingit@noreply.localhost>
Co-committed-by: AlexisinGit <alexisingit@noreply.localhost>
2025-08-28 05:50:28 -07:00
5a2fa2c2c3 chore: move a few more cheat toggles to be per-account (#2713)
Re #2361

Reviewed-on: OpenWF/SpaceNinjaServer#2713
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-28 05:50:21 -07:00
4b2b184b8f feat: additional operator appearance slots (#2712)
Closes #2710

Reviewed-on: OpenWF/SpaceNinjaServer#2712
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-28 05:50:13 -07:00
dc401de1e9 chore: use raw running in docker image (#2711)
We can rely on having up-to-date Node.js here, and reducing the size by like ~55 MiB seems decent.

Reviewed-on: OpenWF/SpaceNinjaServer#2711
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-28 05:50:06 -07:00
1439fdc083 chore(vscode): set typescript.preferences.preferTypeOnlyAutoImports 2025-08-27 23:44:12 +02:00
6771a129f5 fixup: remove fix-imports 2025-08-27 22:41:22 +02:00
f13de810e5 fixup: use eslint:fix instead of prettier in fix script 2025-08-27 22:33:55 +02:00
c52f7dcedc fixup: remove paths from tsconfig 2025-08-27 22:31:22 +02:00
0bf142ed50 fix: view profile on U39.1+ (#2709)
Reviewed-on: OpenWF/SpaceNinjaServer#2709
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 13:06:36 -07:00
0d791ad145 fix: incorrect ordering of relicQualitySuffixes (#2708)
Reviewed-on: OpenWF/SpaceNinjaServer#2708
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 13:06:28 -07:00
5396eefe75 chore: move dependencies only required for build to 'optional' (#2707)
This is a bit of a misnomer because npm installs them just like dev deps unless an appropriate --omit flag is added. Anyway, these deps are ~55 MB, so being able to omit that chunk for raw running might be good.

Reviewed-on: OpenWF/SpaceNinjaServer#2707
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 13:06:18 -07:00
a9a197b005 chore: add some missing entries to .dockerignore (#2706)
Reviewed-on: OpenWF/SpaceNinjaServer#2706
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 13:06:01 -07:00
1ade801e7c fix: 5 disinfection progress per mission until 95 for Technocyte Coda (#2705)
Co-authored-by: AlexisinGit <136088944+AlexisinGit@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2705
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AlexisinGit <alexisingit@noreply.localhost>
Co-committed-by: AlexisinGit <alexisingit@noreply.localhost>
2025-08-27 13:05:49 -07:00
c9cc1fa089 chore: define node version constraints (#2703)
`>=20.18.1` is required for `npm i && npm run build && npm run start` to succeed.
`npm run raw` would require `>=22.7.0`.

Reviewed-on: OpenWF/SpaceNinjaServer#2703
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 01:30:36 -07:00
287acab892 chore: abort update and start scripts when .git folder is missing (#2702)
In that case, updating is obviously not possible.

Reviewed-on: OpenWF/SpaceNinjaServer#2702
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 01:30:19 -07:00
30398021b3 feat: claim all recipes (#2700)
Closes #2699

tried to make it a cleaner diff, but this is the best I could do

Reviewed-on: OpenWF/SpaceNinjaServer#2700
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-27 01:29:46 -07:00
15578b04d2 chore: move baro cheats to worldstate section (#2695)
Reviewed-on: OpenWF/SpaceNinjaServer#2695
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-27 01:29:38 -07:00
46 changed files with 1156 additions and 610 deletions

View File

@ -3,3 +3,6 @@
Dockerfile*
.*
docker-data/
node_modules/
static/data/
logs/

View File

@ -12,14 +12,13 @@ jobs:
- name: Setup Node.js environment
uses: actions/setup-node@v4.0.2
with:
node-version: ">=20.6.0"
node-version: ">=20.18.1"
- run: npm ci
- run: cp config-vanilla.json config.json
- run: npm run verify
- run: npm run lint:ci
- run: npm run prettier
- run: npm run update-translations
- run: npm run fix-imports
- name: Fail if there are uncommitted changes
run: |
if [[ -n "$(git status --porcelain)" ]]; then

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.preferences.preferTypeOnlyAutoImports": true
}

View File

@ -5,8 +5,7 @@ RUN apk add --no-cache bash jq
COPY . /app
WORKDIR /app
RUN npm i --omit=dev
RUN npm run build
RUN npm i --omit=dev --omit=optional
RUN date '+%d %B %Y' > BUILD_DATE
ENTRYPOINT ["/app/docker-entrypoint.sh"]

View File

@ -2,24 +2,27 @@
echo Updating SpaceNinjaServer...
git fetch --prune
git stash
git checkout -f origin/main
if exist static\data\0\ (
echo Updating stripped assets...
cd static\data\0\
git pull
cd ..\..\..\
)
echo Updating dependencies...
call npm i --omit=dev
call npm run build
if %errorlevel% == 0 (
call npm run start
echo SpaceNinjaServer seems to have crashed.
git stash
git checkout -f origin/main
if exist static\data\0\ (
echo Updating stripped assets...
cd static\data\0\
git pull
cd ..\..\..\
)
echo Updating dependencies...
call npm i --omit=dev
call npm run build
if %errorlevel% == 0 (
call npm run start
echo SpaceNinjaServer seems to have crashed.
)
)
:a
pause > nul
goto a

View File

@ -2,22 +2,23 @@
echo "Updating SpaceNinjaServer..."
git fetch --prune
git stash
git checkout -f origin/main
if [ -d "static/data/0/" ]; then
echo "Updating stripped assets..."
cd static/data/0/
git pull
cd ../../../
fi
echo "Updating dependencies..."
npm i --omit=dev
npm run build
if [ $? -eq 0 ]; then
npm run start
echo "SpaceNinjaServer seems to have crashed."
fi
git stash
git checkout -f origin/main
if [ -d "static/data/0/" ]; then
echo "Updating stripped assets..."
cd static/data/0/
git pull
cd ../../../
fi
echo "Updating dependencies..."
npm i --omit=dev
npm run build
if [ $? -eq 0 ]; then
npm run start
echo "SpaceNinjaServer seems to have crashed."
fi
fi

View File

@ -11,7 +11,6 @@
"administratorNames": [],
"autoCreateAccount": true,
"skipTutorial": false,
"skipAllDialogue": false,
"unlockAllScans": false,
"unlockAllShipFeatures": false,
"unlockAllShipDecorations": false,
@ -19,9 +18,6 @@
"unlockAllSkins": false,
"unlockAllCapturaScenes": false,
"fullyStockedVendors": false,
"baroAlwaysAvailable": false,
"baroFullyStocked": false,
"unlockAllProfitTakerStages": false,
"skipClanKeyCrafting": false,
"noDojoRoomBuildStage": false,
"noDecoBuildStage": false,
@ -29,9 +25,7 @@
"noDojoResearchCosts": false,
"noDojoResearchTime": false,
"fastClanAscension": false,
"missionsCanGiveAllRelics": false,
"unlockAllSimarisResearchEntries": false,
"disableDailyTribute": false,
"spoofMasteryRank": -1,
"relicRewardItemCountMultiplier": 1,
"nightwaveStandingMultiplier": 1,
@ -45,6 +39,10 @@
"affinityBoost": false,
"resourceBoost": false,
"tennoLiveRelay": false,
"baroTennoConRelay": false,
"baroAlwaysAvailable": false,
"baroFullyStocked": false,
"varziaFullyStocked": false,
"wolfHunt": false,
"orphixVenom": false,
"longShadow": false,
@ -71,10 +69,9 @@
"duviriOverride": "",
"nightwaveOverride": "",
"allTheFissures": "",
"circuitGameModes": null,
"darvoStockMultiplier": 1,
"varziaOverride": "",
"varziaFullyStocked": false
"circuitGameModes": null,
"darvoStockMultiplier": 1
},
"dev": {
"keepVendorsExpired": false

View File

@ -5,4 +5,4 @@ if [ ! -f conf/config.json ]; then
jq --arg value "mongodb://openwfagent:spaceninjaserver@mongodb:27017/" '.mongodbUrl = $value' /app/config-vanilla.json > /app/conf/config.json
fi
exec npm run start -- --configPath conf/config.json
exec npm run raw -- --configPath conf/config.json

41
package-lock.json generated
View File

@ -9,11 +9,6 @@
"version": "0.1.0",
"license": "GNU",
"dependencies": {
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -21,7 +16,6 @@
"mongoose": "^8.11.0",
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"typescript": "^5.7",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.83",
"warframe-riven-info": "^0.1.2",
@ -38,6 +32,14 @@
"eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3",
"tree-kill": "^1.2.2"
},
"optionalDependencies": {
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"typescript": "^5.7"
}
},
"node_modules/@colors/colors": {
@ -349,6 +351,7 @@
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/connect": "*",
"@types/node": "*"
@ -359,6 +362,7 @@
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
@ -368,6 +372,7 @@
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz",
"integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0",
@ -379,6 +384,7 @@
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz",
"integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@ -390,7 +396,8 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/@types/json5": {
"version": "0.0.29",
@ -403,13 +410,15 @@
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/@types/morgan": {
"version": "1.9.10",
"resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.10.tgz",
"integrity": "sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
@ -419,6 +428,7 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
"license": "MIT",
"optional": true,
"dependencies": {
"undici-types": "~7.10.0"
}
@ -427,19 +437,22 @@
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/@types/range-parser": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/@types/send": {
"version": "0.17.5",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz",
"integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/mime": "^1",
"@types/node": "*"
@ -450,6 +463,7 @@
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz",
"integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/http-errors": "*",
"@types/node": "*",
@ -473,6 +487,7 @@
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.10.tgz",
"integrity": "sha512-svjGZvPB7EzuYS94cI7a+qhwgGU1y89wUgjT6E2wVUfmAGIvRfT7obBvRtnhXCSsoMdlG4gBFGE7MfkIXZLoww==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
@ -491,6 +506,7 @@
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
@ -735,6 +751,7 @@
"resolved": "https://registry.npmjs.org/@typescript/native-preview/-/native-preview-7.0.0-dev.20250826.1.tgz",
"integrity": "sha512-+NuzOfk/lu6pLYSCio+R7uzJ9pfOasc1fshxVmLp6wgcB8yuUYYvBaT7CoHapUnNBYZXkJ9u0UOECnq3dbzgSQ==",
"license": "Apache-2.0",
"optional": true,
"bin": {
"tsgo": "bin/tsgo.js"
},
@ -5397,6 +5414,7 @@
"version": "5.9.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz",
"integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@ -5438,7 +5456,8 @@
"version": "7.10.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
"license": "MIT"
"license": "MIT",
"optional": true
},
"node_modules/unpipe": {
"version": "1.0.0",

View File

@ -22,17 +22,11 @@
"lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.cjs",
"fix-imports": "cd scripts && node fix-imports.cjs",
"fix": "npm run update-translations && npm run fix-imports && npm run prettier"
"fix": "npm run update-translations && npm run lint:fix"
},
"license": "GNU",
"type": "module",
"dependencies": {
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -40,7 +34,6 @@
"mongoose": "^8.11.0",
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"typescript": "^5.7",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.83",
"warframe-riven-info": "^0.1.2",
@ -48,6 +41,14 @@
"winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2"
},
"optionalDependencies": {
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"typescript": "^5.7"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
@ -57,5 +58,8 @@
"eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3",
"tree-kill": "^1.2.2"
},
"engines": {
"node": ">=20.18.1"
}
}

View File

@ -1,75 +0,0 @@
/* eslint-disable */
const fs = require("fs");
const path = require("path");
const root = path.join(process.cwd(), "..");
function listFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
let results = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results = results.concat(listFiles(fullPath));
} else {
results.push(fullPath);
}
}
return results;
}
const files = listFiles(path.join(root, "src"));
for (const file of files) {
let content;
try {
content = fs.readFileSync(file, "utf8");
} catch (e) {
continue;
}
const dir = path.dirname(file);
const fixedContent = content.replaceAll(/from "([^"]+)";/g, (sub, importPath) => {
if (importPath.startsWith("@/") || importPath.startsWith(".")) {
const base = importPath.startsWith("@/")
? path.join(root, importPath.slice(2))
: path.resolve(dir, importPath);
let target = base;
if (fs.existsSync(target)) {
const stat = fs.statSync(target);
if (stat.isDirectory()) {
if (fs.existsSync(path.join(target, "index.ts"))) {
target = path.join(target, "index.ts");
} else {
return sub;
}
} else {
const ext = path.extname(target);
if (!ext) {
target += ".ts";
}
}
} else if (fs.existsSync(target + ".ts")) {
target += ".ts";
} else if (fs.existsSync(path.join(target, "index.ts"))) {
target = path.join(target, "index.ts");
} else {
return sub;
}
let relative = path.relative(dir, target).replace(/\\/g, "/");
if (!path.extname(relative)) {
relative += ".ts";
}
if (!relative.startsWith(".")) {
relative = "./" + relative;
}
console.log(`${importPath} -> ${relative}`);
return sub.split(importPath).join(relative);
}
return sub;
});
if (content != fixedContent) {
fs.writeFileSync(file, fixedContent, "utf8");
}
}

View File

@ -31,7 +31,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
const strings = extractStrings(line);
if (Object.keys(strings).length > 0) {
Object.entries(strings).forEach(([key, value]) => {
if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED] ")) {
if (targetStrings.hasOwnProperty(key) && !targetStrings[key].startsWith("[UNTRANSLATED]")) {
fs.writeSync(fileHandle, ` ${key}: \`${targetStrings[key]}\`,\n`);
} else {
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);

View File

@ -13,8 +13,6 @@ import { payRouter } from "./routes/pay.ts";
import { statsRouter } from "./routes/stats.ts";
import { webuiRouter } from "./routes/webui.ts";
import { worldStateController } from "./controllers/dynamic/worldStateController.ts";
const app = express();
app.use((req, _res, next) => {
@ -47,9 +45,6 @@ app.use("/pay", payRouter);
app.use("/stats", statsRouter);
app.use("/", webuiRouter);
// U39.1+ gets worldState from that location
app.get("/worldState.php", worldStateController);
app.use(unknownEndpointHandler);
app.use(errorHandler);

View File

@ -4,8 +4,9 @@
import type { RequestHandler } from "express";
import { logger } from "../../utils/logger.ts";
import { getRecipe } from "../../services/itemDataService.ts";
import type { IOid, IOidWithLegacySupport } from "../../types/commonTypes.ts";
import type { IOidWithLegacySupport } from "../../types/commonTypes.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import type { TAccountDocument } from "../../services/loginService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import {
getInventory,
@ -21,240 +22,254 @@ import {
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import type { IPendingRecipeDatabase } from "../../types/inventoryTypes/inventoryTypes.ts";
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { toOid2 } from "../../helpers/inventoryHelpers.ts";
import { fromOid, toOid2 } from "../../helpers/inventoryHelpers.ts";
import type { TInventoryDatabaseDocument } from "../../models/inventoryModels/inventoryModel.ts";
import type { IRecipe } from "warframe-public-export-plus";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import { EquipmentFeatures, Status } from "../../types/equipmentTypes.ts";
interface IClaimCompletedRecipeRequest {
RecipeIds: IOid[];
RecipeIds: IOidWithLegacySupport[];
}
interface IClaimCompletedRecipeResponse {
InventoryChanges: IInventoryChanges;
BrandedSuits?: IOidWithLegacySupport[];
}
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
const account = await getAccountForRequest(req);
const inventory = await getInventory(account._id.toString());
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
if (!pendingRecipe) {
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
}
//check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) {
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// }
inventory.PendingRecipes.pull(pendingRecipe._id);
const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) {
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
}
if (req.query.cancel) {
const inventoryChanges: IInventoryChanges = {};
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
} else {
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
let BrandedSuits: undefined | IOidWithLegacySupport[];
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(
x => x.ItemType == recipe.resultType
);
if (pendingLoadoutIndex != -1) {
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
if (loadoutIndex != -1) {
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
}
logger.debug(
"moving spectre loadout from pending to active",
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
);
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
}
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
inventory.BrandedSuits!.splice(
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
1
);
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
const resp: IClaimCompletedRecipeResponse = {
InventoryChanges: {}
};
for (const recipeId of claimCompletedRecipeRequest.RecipeIds) {
const pendingRecipe = inventory.PendingRecipes.id(fromOid(recipeId));
if (!pendingRecipe) {
throw new Error(`no pending recipe found with id ${fromOid(recipeId)}`);
}
let InventoryChanges: IInventoryChanges = {};
if (recipe.consumeOnUse) {
addRecipes(inventory, [
//check recipe is indeed ready to be completed
// if (pendingRecipe.CompletionDate > new Date()) {
// throw new Error(`recipe ${pendingRecipe._id} is not ready to be completed`);
// }
inventory.PendingRecipes.pull(pendingRecipe._id);
const recipe = getRecipe(pendingRecipe.ItemType);
if (!recipe) {
throw new Error(`no completed item found for recipe ${pendingRecipe._id.toString()}`);
}
if (req.query.cancel) {
const inventoryChanges: IInventoryChanges = {};
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
await inventory.save();
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
return;
}
await claimCompletedRecipe(account, inventory, recipe, pendingRecipe, resp, req.query.rush);
}
await inventory.save();
res.json(resp);
};
const claimCompletedRecipe = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
recipe: IRecipe,
pendingRecipe: IPendingRecipeDatabase,
resp: IClaimCompletedRecipeResponse,
rush: any
): Promise<void> => {
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
inventory.PendingSpectreLoadouts ??= [];
inventory.SpectreLoadouts ??= [];
const pendingLoadoutIndex = inventory.PendingSpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
if (pendingLoadoutIndex != -1) {
const loadoutIndex = inventory.SpectreLoadouts.findIndex(x => x.ItemType == recipe.resultType);
if (loadoutIndex != -1) {
inventory.SpectreLoadouts.splice(loadoutIndex, 1);
}
logger.debug(
"moving spectre loadout from pending to active",
inventory.toJSON().PendingSpectreLoadouts![pendingLoadoutIndex]
);
inventory.SpectreLoadouts.push(inventory.PendingSpectreLoadouts[pendingLoadoutIndex]);
inventory.PendingSpectreLoadouts.splice(pendingLoadoutIndex, 1);
}
} else if (recipe.secretIngredientAction == "SIA_UNBRAND") {
inventory.BrandedSuits!.splice(
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
1
);
resp.BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
}
if (recipe.consumeOnUse) {
addRecipes(inventory, [
{
ItemType: pendingRecipe.ItemType,
ItemCount: -1
}
]);
}
if (rush) {
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
const start = end - recipe.buildTime;
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
const progress = secondsElapsed / recipe.buildTime;
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
const cost =
progress > 0.5 ? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5))) : recipe.skipBuildTimePrice;
combineInventoryChanges(resp.InventoryChanges, updateCurrency(inventory, cost, true));
}
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
pet.Details!.HatchDate = new Date();
}
let canSetActive = true;
for (const pet of inventory.KubrowPets) {
if (pet.Details!.Status == Status.StatusAvailable) {
canSetActive = false;
break;
}
}
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, resp.InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
// Quite the special case here...
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
const umbraModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModC = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
resp.InventoryChanges.Upgrades ??= [];
resp.InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
await addPowerSuit(
inventory,
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
{
ItemType: pendingRecipe.ItemType,
ItemCount: -1
}
]);
}
if (req.query.rush) {
const end = Math.trunc(pendingRecipe.CompletionDate.getTime() / 1000);
const start = end - recipe.buildTime;
const secondsElapsed = Math.trunc(Date.now() / 1000) - start;
const progress = secondsElapsed / recipe.buildTime;
logger.debug(`rushing recipe at ${Math.trunc(progress * 100)}% completion`);
const cost =
progress > 0.5
? Math.round(recipe.skipBuildTimePrice * (1 - (progress - 0.5)))
: recipe.skipBuildTimePrice;
InventoryChanges = {
...InventoryChanges,
...updateCurrency(inventory, cost, true)
};
}
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
XP: 900_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
resp.InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
XP: 900_000
});
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
if (pet.Details!.HatchDate!.getTime() > Date.now()) {
pet.Details!.HatchDate = new Date();
}
let canSetActive = true;
for (const pet of inventory.KubrowPets) {
if (pet.Details!.Status == Status.StatusAvailable) {
canSetActive = false;
break;
}
}
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
if (recipe.resultType == "/Lotus/Powersuits/Excalibur/ExcaliburUmbra") {
// Quite the special case here...
// We don't just get Umbra, but also Skiajati and Umbra Mods. Both items are max rank, potatoed, and with the mods are pre-installed.
// Source: https://wiki.warframe.com/w/The_Sacrifice, https://wiki.warframe.com/w/Excalibur/Umbra, https://wiki.warframe.com/w/Skiajati
const umbraModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const umbraModC = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Umbra/WarframeUmbraModC",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModA = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModA",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
const sacrificeModB = (
await addItem(
inventory,
"/Lotus/Upgrades/Mods/Sets/Sacrifice/MeleeSacrificeModB",
1,
false,
undefined,
`{"lvl":5}`
)
).Upgrades![0];
InventoryChanges.Upgrades ??= [];
InventoryChanges.Upgrades.push(umbraModA, umbraModB, umbraModC, sacrificeModA, sacrificeModB);
await addPowerSuit(
addEquipment(
inventory,
"Melee",
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
resp.InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
XP: 450_000
});
} else {
combineInventoryChanges(
resp.InventoryChanges,
await addItem(
inventory,
"/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
{
Configs: [
{
Upgrades: [
"",
"",
"",
"",
"",
umbraModA.ItemId.$oid,
umbraModB.ItemId.$oid,
umbraModC.ItemId.$oid
]
}
],
XP: 900_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Powersuits/Excalibur/ExcaliburUmbra",
XP: 900_000
});
addEquipment(
inventory,
"Melee",
"/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
{
Configs: [
{ Upgrades: ["", "", "", "", "", "", sacrificeModA.ItemId.$oid, sacrificeModB.ItemId.$oid] }
],
XP: 450_000,
Features: EquipmentFeatures.DOUBLE_CAPACITY
},
InventoryChanges
);
inventory.XPInfo.push({
ItemType: "/Lotus/Weapons/Tenno/Melee/Swords/UmbraKatana/UmbraKatana",
XP: 450_000
});
} else {
InventoryChanges = {
...InventoryChanges,
...(await addItem(
inventory,
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
))
};
}
recipe.resultType,
recipe.num,
false,
undefined,
pendingRecipe.TargetFingerprint
)
);
}
if (
inventory.claimingBlueprintRefundsIngredients &&
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
) {
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
}
await inventory.save();
res.json({ InventoryChanges, BrandedSuits });
}
if (
inventory.claimingBlueprintRefundsIngredients &&
recipe.secretIngredientAction != "SIA_CREATE_KUBROW" // Can't refund the egg
) {
await refundRecipeIngredients(inventory, resp.InventoryChanges, recipe, pendingRecipe);
}
};

View File

@ -81,7 +81,7 @@ export const crewShipFusionController: RequestHandler = async (req, res) => {
const newFval = (newPerc - rangeA[0]) / (rangeA[1] - rangeA[0]);
buffA.Value = Math.trunc(newFval * 0x3fffffff);
}
if (inferiorFingerprint.SubroutineIndex) {
if (inferiorFingerprint.SubroutineIndex !== undefined) {
const useSuperiorSubroutine = tierA < tierB ? !payload.UseSubroutineA : payload.UseSubroutineA;
if (!useSuperiorSubroutine) {
fingerprint.SubroutineIndex = inferiorFingerprint.SubroutineIndex;

View File

@ -306,7 +306,7 @@ export const getInventoryResponse = async (
inventoryResponse.PrimeTokens = 999999999;
}
if (config.skipAllDialogue) {
if (inventory.skipAllDialogue) {
inventoryResponse.TauntHistory = [
{
node: "TreasureTutorial",
@ -486,42 +486,9 @@ export const getInventoryResponse = async (
}
}
if (config.unlockAllProfitTakerStages) {
inventoryResponse.CompletedJobChains ??= [];
const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
if (EudicoHeists) {
EudicoHeists.Jobs = allEudicoHeistJobs;
} else {
inventoryResponse.CompletedJobChains.push({
LocationTag: "EudicoHeists",
Jobs: allEudicoHeistJobs
});
}
}
if (config.unlockAllSimarisResearchEntries) {
inventoryResponse.LibraryPersonalTarget = undefined;
inventoryResponse.LibraryPersonalProgress = [
"/Lotus/Types/Game/Library/Targets/Research1Target",
"/Lotus/Types/Game/Library/Targets/Research2Target",
"/Lotus/Types/Game/Library/Targets/Research3Target",
"/Lotus/Types/Game/Library/Targets/Research4Target",
"/Lotus/Types/Game/Library/Targets/Research5Target",
"/Lotus/Types/Game/Library/Targets/Research6Target",
"/Lotus/Types/Game/Library/Targets/Research7Target"
].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
}
return inventoryResponse;
};
const allEudicoHeistJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
];
const getExpRequiredForMr = (rank: number): number => {
if (rank <= 30) {
return 2500 * rank * rank;

View File

@ -140,7 +140,11 @@ const createLoginResponse = (
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
}
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
resp.platformCDNs = [`${myUrlBase}/`];
if (version_compare(buildLabel, "2025.08.26.09.49") >= 0) {
resp.platformCDNs = [`${myUrlBase}/dynamic/`];
} else {
resp.platformCDNs = [`${myUrlBase}/`];
}
}
return resp;
};

View File

@ -8,7 +8,6 @@ import {
setAccountGotLoginRewardToday
} from "../../services/loginRewardService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { config } from "../../services/configService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const loginRewardsController: RequestHandler = async (req, res) => {
@ -17,41 +16,42 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
res.json({
DailyTributeInfo: {
IsMilestoneDay: isMilestoneDay,
IsChooseRewardSet: isLoginRewardAChoice(account),
LoginDays: account.LoginDays,
NextMilestoneReward: "",
NextMilestoneDay: nextMilestoneDay
}
} satisfies ILoginRewardsReponse);
return;
}
if (today != account.LastLoginRewardDate) {
const inventory = await getInventory(account._id.toString());
if (!inventory.disableDailyTribute) {
const randomRewards = getRandomLoginRewards(account, inventory);
const response: ILoginRewardsReponse = {
DailyTributeInfo: {
Rewards: randomRewards,
IsMilestoneDay: isMilestoneDay,
IsChooseRewardSet: randomRewards.length != 1,
LoginDays: account.LoginDays,
NextMilestoneReward: "",
NextMilestoneDay: nextMilestoneDay,
HasChosenReward: false
},
LastLoginRewardDate: today
};
if (!isMilestoneDay && randomRewards.length == 1) {
response.DailyTributeInfo.HasChosenReward = true;
response.DailyTributeInfo.ChosenReward = randomRewards[0];
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
setAccountGotLoginRewardToday(account);
await Promise.all([inventory.save(), account.save()]);
const inventory = await getInventory(account._id.toString());
const randomRewards = getRandomLoginRewards(account, inventory);
const response: ILoginRewardsReponse = {
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
}
res.json(response);
return;
}
}
res.json({
DailyTributeInfo: {
Rewards: randomRewards,
IsMilestoneDay: isMilestoneDay,
IsChooseRewardSet: randomRewards.length != 1,
IsChooseRewardSet: isLoginRewardAChoice(account),
LoginDays: account.LoginDays,
NextMilestoneReward: "",
NextMilestoneDay: nextMilestoneDay,
HasChosenReward: false
},
LastLoginRewardDate: today
};
if (!isMilestoneDay && randomRewards.length == 1) {
response.DailyTributeInfo.HasChosenReward = true;
response.DailyTributeInfo.ChosenReward = randomRewards[0];
response.DailyTributeInfo.NewInventory = await claimLoginReward(inventory, randomRewards[0]);
setAccountGotLoginRewardToday(account);
await Promise.all([inventory.save(), account.save()]);
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
}
res.json(response);
NextMilestoneDay: nextMilestoneDay
}
} satisfies ILoginRewardsReponse);
};

View File

@ -95,6 +95,16 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
ConquestCompletedMissionsCount
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
const extraMissionRewards = inventory.extraMissionRewards ?? 0;
if (extraMissionRewards >= 1) {
for (let i = 0; i < extraMissionRewards; i++) {
const rngMissionReport = missionReport;
rngMissionReport.RewardInfo!.rewardSeed = generateRewardSeed();
logger.debug("extra mission rewards with new seed, this might mismatch the mission report.");
await addMissionRewards(account, inventory, rngMissionReport, firstCompletion);
}
}
if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();
}

View File

@ -47,6 +47,9 @@ export const nemesisController: RequestHandler = async (req, res) => {
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
const sourceFingerprint = JSON.parse(sourceWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
const fusionMultiplier = (await getInventory(account._id.toString(), "nemesisWeaponFusionMultiplier"))
.nemesisWeaponFusionMultiplier;
// Update destination damage type if desired
if (body.UseSourceDmgType) {
destFingerprint.buffs[0].Tag = sourceFingerprint.buffs[0].Tag;
@ -55,7 +58,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
// Upgrade destination damage value
const destDamage = 0.25 + (destFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
const sourceDamage = 0.25 + (sourceFingerprint.buffs[0].Value / 0x3fffffff) * (0.6 - 0.25);
let newDamage = Math.max(destDamage, sourceDamage) * 1.1;
let newDamage = Math.max(destDamage, sourceDamage) * 1.1 * (fusionMultiplier ?? 1);
if (newDamage >= 0.5794998) {
newDamage = 0.6;
}
@ -101,9 +104,16 @@ export const nemesisController: RequestHandler = async (req, res) => {
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
);
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
const alwaysCorrectCheat = (await getInventory(account._id.toString(), "nemesisAlwaysCorrect"))
.nemesisAlwaysCorrect;
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
if (alwaysCorrectCheat) {
guess[0] = guess[1] = guess[2] = passcode;
}
const result1 = passcode == guess[0] ? GUESS_CORRECT : GUESS_INCORRECT;
const result2 = passcode == guess[1] ? GUESS_CORRECT : GUESS_INCORRECT;
const result3 = passcode == guess[2] ? GUESS_CORRECT : GUESS_INCORRECT;
@ -149,7 +159,10 @@ export const nemesisController: RequestHandler = async (req, res) => {
break;
}
}
inventory.Nemesis!.HenchmenKilled += antivirusGain;
const antivirusGainMultiplier = (
await getInventory(account._id.toString(), "nemesisAntivirusGainMultiplier")
).nemesisAntivirusGainMultiplier;
inventory.Nemesis!.HenchmenKilled += antivirusGain * (antivirusGainMultiplier ?? 1);
if (inventory.Nemesis!.HenchmenKilled >= 100) {
inventory.Nemesis!.HenchmenKilled = 100;
@ -195,7 +208,9 @@ export const nemesisController: RequestHandler = async (req, res) => {
// Evaluate guess
const correct =
body.guess == GUESS_WILDCARD || getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess;
body.guess == GUESS_WILDCARD ||
getNemesisPasscode(inventory.Nemesis!)[body.position] == body.guess ||
alwaysCorrectCheat;
// Update entry
const guess = decodeNemesisGuess(

View File

@ -0,0 +1,16 @@
import { getInventory, updateCurrency } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
export const upgradeOperatorController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(
accountId,
"OperatorCustomizationSlotPurchases PremiumCredits PremiumCreditsFree"
);
inventory.OperatorCustomizationSlotPurchases ??= 0;
inventory.OperatorCustomizationSlotPurchases += 1;
const inventoryChanges = updateCurrency(inventory, 10, true);
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });
};

View File

@ -64,9 +64,9 @@ interface ItemLists {
const relicQualitySuffixes: Record<TRelicQuality, string> = {
VPQ_BRONZE: "",
VPQ_SILVER: " [Flawless]",
VPQ_GOLD: " [Radiant]",
VPQ_PLATINUM: " [Exceptional]"
VPQ_SILVER: " [Exceptional]",
VPQ_GOLD: " [Flawless]",
VPQ_PLATINUM: " [Radiant]"
};
/*const toTitleCase = (str: string): string => {

View File

@ -7,12 +7,12 @@ export const setAccountCheatController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = req.body as ISetAccountCheatRequest;
const inventory = await getInventory(accountId, payload.key);
inventory[payload.key] = payload.value;
inventory[payload.key] = payload.value as never;
await inventory.save();
res.end();
};
interface ISetAccountCheatRequest {
key: keyof IAccountCheats;
value: boolean;
value: boolean | number;
}

View File

@ -0,0 +1,24 @@
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
const allEudicoHeistJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
];
export const unlockAllProfitTakerStagesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "CompletedJobChains");
inventory.CompletedJobChains ??= [];
const chain = inventory.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
if (chain) {
chain.Jobs = allEudicoHeistJobs;
} else {
inventory.CompletedJobChains.push({ LocationTag: "EudicoHeists", Jobs: allEudicoHeistJobs });
}
await inventory.save();
res.end();
};

View File

@ -0,0 +1,20 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
export const unlockAllSimarisResearchEntriesController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "LibraryPersonalTarget LibraryPersonalProgress");
inventory.LibraryPersonalTarget = undefined;
inventory.LibraryPersonalProgress = [
"/Lotus/Types/Game/Library/Targets/Research1Target",
"/Lotus/Types/Game/Library/Targets/Research2Target",
"/Lotus/Types/Game/Library/Targets/Research3Target",
"/Lotus/Types/Game/Library/Targets/Research4Target",
"/Lotus/Types/Game/Library/Targets/Research5Target",
"/Lotus/Types/Game/Library/Targets/Research6Target",
"/Lotus/Types/Game/Library/Targets/Research7Target"
].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
await inventory.save();
res.end();
};

View File

@ -9,6 +9,7 @@ import { addMiscItems, combineInventoryChanges } from "../services/inventoryServ
import { handleStoreItemAcquisition } from "../services/purchaseService.ts";
import type { IInventoryChanges } from "../types/purchaseTypes.ts";
import { config } from "../services/configService.ts";
import { log } from "winston";
export const crackRelic = async (
inventory: TInventoryDatabaseDocument,
@ -17,11 +18,11 @@ export const crackRelic = async (
): Promise<IRngResult> => {
const relic = ExportRelics[participant.VoidProjection];
let weights = refinementToWeights[relic.quality];
if (relic.quality == "VPQ_SILVER" && config.exceptionalRelicsAlwaysGiveBronzeReward) {
if (relic.quality == "VPQ_SILVER" && inventory.exceptionalRelicsAlwaysGiveBronzeReward) {
weights = { COMMON: 1, UNCOMMON: 0, RARE: 0, LEGENDARY: 0 };
} else if (relic.quality == "VPQ_GOLD" && config.flawlessRelicsAlwaysGiveSilverReward) {
} else if (relic.quality == "VPQ_GOLD" && inventory.flawlessRelicsAlwaysGiveSilverReward) {
weights = { COMMON: 0, UNCOMMON: 1, RARE: 0, LEGENDARY: 0 };
} else if (relic.quality == "VPQ_PLATINUM" && config.radiantRelicsAlwaysGiveGoldReward) {
} else if (relic.quality == "VPQ_PLATINUM" && inventory.radiantRelicsAlwaysGiveGoldReward) {
weights = { COMMON: 0, UNCOMMON: 0, RARE: 1, LEGENDARY: 0 };
}
logger.debug(`opening a relic of quality ${relic.quality}; rarity weights are`, weights);
@ -54,6 +55,26 @@ export const crackRelic = async (
(await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
);
if (inventory.crackRelicForPlatinum) {
let platinumReward = 0;
switch (reward.rarity) {
case "COMMON":
platinumReward = inventory.relicPlatinumCommon ?? 2;
break;
case "UNCOMMON":
platinumReward = inventory.relicPlatinumUncommon ?? 5;
break;
case "RARE":
platinumReward = inventory.relicPlatinumRare ?? 12;
break;
case "LEGENDARY":
logger.warn(`got a legendary reward for a relic!`);
break;
}
logger.debug(`adding ${platinumReward} platinum to inventory for a ${reward.rarity} reward`);
inventory.PremiumCredits += platinumReward;
}
return reward;
};

View File

@ -1428,12 +1428,14 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
accountOwnerId: Schema.Types.ObjectId,
// SNS account cheats
skipAllDialogue: Boolean,
dontSubtractPurchaseCreditCost: Boolean,
dontSubtractPurchasePlatinumCost: Boolean,
dontSubtractPurchaseItemCost: Boolean,
dontSubtractPurchaseStandingCost: Boolean,
dontSubtractVoidTraces: Boolean,
dontSubtractConsumables: Boolean,
finishInvasionsInOneMission: Boolean,
infiniteCredits: Boolean,
infinitePlatinum: Boolean,
infiniteEndo: Boolean,
@ -1455,6 +1457,27 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
claimingBlueprintRefundsIngredients: Boolean,
instantResourceExtractorDrones: Boolean,
noResourceExtractorDronesDamage: Boolean,
missionsCanGiveAllRelics: Boolean,
exceptionalRelicsAlwaysGiveBronzeReward: Boolean,
flawlessRelicsAlwaysGiveSilverReward: Boolean,
radiantRelicsAlwaysGiveGoldReward: Boolean,
disableDailyTribute: Boolean,
gainNoNegativeSyndicateStanding: Boolean,
nemesisAlwaysCorrect: Boolean,
nemesisHenchmenKillsMulptiplierGrineer: Number,
nemesisHenchmenKillsMulptiplierCorpus: Number,
nemesisAntivirusGainMultiplier: Number,
nemesisHintProgressMultiplierGrineer: Number,
nemesisHintProgressMultiplierCorpus: Number,
nemesisWeaponFusionMultiplier: Number,
nemesisExtraWeapon: Number,
extraMissionRewards: Number,
playerSkillGainsMultiplierSpace: Number,
playerSkillGainsMultiplierDrifter: Number,
extraRelicRewards: Number,
relicPlatinumCommon: Number,
relicPlatinumUncommon: Number,
relicPlatinumRare: Number,
SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
@ -1564,6 +1587,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
OperatorLoadOuts: [operatorConfigSchema],
//Drifter
AdultOperatorLoadOuts: [operatorConfigSchema],
OperatorCustomizationSlotPurchases: Number,
// Kahl
KahlLoadOuts: [operatorConfigSchema],

View File

@ -162,6 +162,7 @@ import { updateQuestController } from "../controllers/api/updateQuestController.
import { updateSessionGetController, updateSessionPostController } from "../controllers/api/updateSessionController.ts";
import { updateSongChallengeController } from "../controllers/api/updateSongChallengeController.ts";
import { updateThemeController } from "../controllers/api/updateThemeController.ts";
import { upgradeOperatorController } from "../controllers/api/upgradeOperatorController.ts";
import { upgradesController } from "../controllers/api/upgradesController.ts";
import { valenceSwapController } from "../controllers/api/valenceSwapController.ts";
import { wishlistController } from "../controllers/api/wishlistController.ts";
@ -229,6 +230,7 @@ apiRouter.get("/startLibraryPersonalTarget.php", startLibraryPersonalTargetContr
apiRouter.get("/surveys.php", surveysController);
apiRouter.get("/trading.php", tradingController);
apiRouter.get("/updateSession.php", updateSessionGetController);
apiRouter.get("/upgradeOperator.php", upgradeOperatorController);
// post
apiRouter.post("/abortDojoComponent.php", abortDojoComponentController);

View File

@ -14,6 +14,8 @@ import { addMissingMaxRankModsController } from "../controllers/custom/addMissin
import { webuiFileChangeDetectedController } from "../controllers/custom/webuiFileChangeDetectedController.ts";
import { completeAllMissionsController } from "../controllers/custom/completeAllMissionsController.ts";
import { addMissingHelminthBlueprintsController } from "../controllers/custom/addMissingHelminthBlueprintsController.ts";
import { unlockAllProfitTakerStagesController } from "../controllers/custom/unlockAllProfitTakerStagesController.ts";
import { unlockAllSimarisResearchEntriesController } from "../controllers/custom/unlockAllSimarisResearchEntriesController.ts";
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
import { createAccountController } from "../controllers/custom/createAccountController.ts";
@ -48,6 +50,8 @@ customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.get("/unlockAllProfitTakerStages", unlockAllProfitTakerStagesController);
customRouter.get("/unlockAllSimarisResearchEntries", unlockAllSimarisResearchEntriesController);
customRouter.post("/abilityOverride", abilityOverrideController);
customRouter.post("/createAccount", createAccountController);

View File

@ -4,7 +4,7 @@ import { repoDir } from "../helpers/pathHelper.ts";
import { args } from "../helpers/commandLineArguments.ts";
import { Inbox } from "../models/inboxModel.ts";
export interface IConfig extends IConfigRemovedOptions {
export interface IConfig {
mongodbUrl: string;
logger: {
files: boolean;
@ -18,7 +18,6 @@ export interface IConfig extends IConfigRemovedOptions {
administratorNames?: string[];
autoCreateAccount?: boolean;
skipTutorial?: boolean;
skipAllDialogue?: boolean;
unlockAllScans?: boolean;
unlockAllShipFeatures?: boolean;
unlockAllShipDecorations?: boolean;
@ -27,9 +26,6 @@ export interface IConfig extends IConfigRemovedOptions {
unlockAllCapturaScenes?: boolean;
unlockAllDecoRecipes?: boolean;
fullyStockedVendors?: boolean;
baroAlwaysAvailable?: boolean;
baroFullyStocked?: boolean;
unlockAllProfitTakerStages?: boolean;
skipClanKeyCrafting?: boolean;
noDojoRoomBuildStage?: boolean;
noDojoDecoBuildStage?: boolean;
@ -37,12 +33,6 @@ export interface IConfig extends IConfigRemovedOptions {
noDojoResearchCosts?: boolean;
noDojoResearchTime?: boolean;
fastClanAscension?: boolean;
missionsCanGiveAllRelics?: boolean;
exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
flawlessRelicsAlwaysGiveSilverReward?: boolean;
radiantRelicsAlwaysGiveGoldReward?: boolean;
unlockAllSimarisResearchEntries?: boolean;
disableDailyTribute?: boolean;
spoofMasteryRank?: number;
relicRewardItemCountMultiplier?: number;
nightwaveStandingMultiplier?: number;
@ -57,6 +47,9 @@ export interface IConfig extends IConfigRemovedOptions {
resourceBoost?: boolean;
tennoLiveRelay?: boolean;
baroTennoConRelay?: boolean;
baroAlwaysAvailable?: boolean;
baroFullyStocked?: boolean;
varziaFullyStocked?: boolean;
wolfHunt?: boolean;
orphixVenom?: boolean;
longShadow?: boolean;
@ -83,10 +76,9 @@ export interface IConfig extends IConfigRemovedOptions {
duviriOverride?: string;
nightwaveOverride?: string;
allTheFissures?: string;
varziaOverride?: string;
circuitGameModes?: string[];
darvoStockMultiplier?: number;
varziaOverride?: string;
varziaFullyStocked?: boolean;
};
dev?: {
keepVendorsExpired?: boolean;
@ -94,6 +86,7 @@ export interface IConfig extends IConfigRemovedOptions {
}
export const configRemovedOptionsKeys = [
"skipAllDialogue",
"infiniteCredits",
"infinitePlatinum",
"infiniteEndo",
@ -110,6 +103,8 @@ export const configRemovedOptionsKeys = [
"unlockDoubleCapacityPotatoesEverywhere",
"unlockExilusEverywhere",
"unlockArcanesEverywhere",
"unlockAllProfitTakerStages",
"unlockAllSimarisResearchEntries",
"noDailyStandingLimits",
"noDailyFocusLimit",
"noArgonCrystalDecay",
@ -120,12 +115,15 @@ export const configRemovedOptionsKeys = [
"syndicateMissionsRepeatable",
"instantFinishRivenChallenge",
"instantResourceExtractorDrones",
"noResourceExtractorDronesDamage"
] as const;
type IConfigRemovedOptions = {
[K in (typeof configRemovedOptionsKeys)[number]]?: boolean;
};
"noResourceExtractorDronesDamage",
"baroAlwaysAvailable",
"baroFullyStocked",
"missionsCanGiveAllRelics",
"exceptionalRelicsAlwaysGiveBronzeReward",
"flawlessRelicsAlwaysGiveSilverReward",
"radiantRelicsAlwaysGiveGoldReward",
"disableDailyTribute"
];
export const configPath = path.join(repoDir, args.configPath ?? "config.json");

View File

@ -1,6 +1,13 @@
import chokidar from "chokidar";
import { logger } from "../utils/logger.ts";
import { config, configPath, configRemovedOptionsKeys, loadConfig, syncConfigWithDatabase } from "./configService.ts";
import {
config,
configPath,
configRemovedOptionsKeys,
loadConfig,
syncConfigWithDatabase,
type IConfig
} from "./configService.ts";
import { saveConfig, shouldReloadConfig } from "./configWriterService.ts";
import { getWebPorts, startWebServer, stopWebServer } from "./webService.ts";
import { sendWsBroadcast } from "./wsService.ts";
@ -35,9 +42,11 @@ chokidar.watch(configPath).on("change", () => {
export const validateConfig = (): void => {
let modified = false;
for (const key of configRemovedOptionsKeys) {
if (config[key] !== undefined) {
logger.debug(`Spotted removed option ${key} with value ${config[key]} in config.json.`);
delete config[key];
if (config[key as keyof IConfig] !== undefined) {
logger.debug(
`Spotted removed option ${key} with value ${String(config[key as keyof IConfig])} in config.json.`
);
delete config[key as keyof IConfig];
modified = true;
}
}

View File

@ -36,7 +36,7 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
// Baro
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
newEventMessages.push({
sndr: "/Lotus/Language/G1Quests/VoidTraderName",

View File

@ -689,6 +689,7 @@ export const addItem = async (
// Path-based duck typing
switch (typeName.substr(1).split("/")[1]) {
case "Powersuits":
if (typeName.endsWith("AugmentCard")) break;
switch (typeName.substr(1).split("/")[2]) {
default: {
return {
@ -773,6 +774,10 @@ export const addItem = async (
}
}
break;
case "Skins": {
return addSkin(inventory, typeName);
}
}
break;
}
@ -869,6 +874,26 @@ export const addItem = async (
break;
}
break;
case "Weapons": {
if (typeName.substr(1).split("/")[4] == "MeleeTrees") break;
const productCategory = typeName.substr(1).split("/")[3];
switch (productCategory) {
case "Pistols":
case "LongGuns":
case "Melee": {
const inventoryChanges = addEquipment(inventory, productCategory, typeName);
return {
...inventoryChanges,
...occupySlot(
inventory,
productCategoryToInventoryBin(productCategory) ?? InventorySlot.WEAPONS,
premiumPurchase
)
};
}
}
break;
}
}
throw new Error(`unable to add item: ${typeName}`);
};
@ -1321,6 +1346,10 @@ export const addStanding = (
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
const syndicateMeta = ExportSyndicates[syndicateTag];
if (inventory.gainNoNegativeSyndicateStanding) {
gainedStanding = Math.max(gainedStanding, 0);
}
if (!syndicate) {
syndicate =
inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0, Title: 0 }) - 1];

View File

@ -201,10 +201,29 @@ export const addMissionInventoryUpdates = async (
inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
}
if (inventoryUpdates.RewardInfo.NemesisHenchmenKills && inventory.Nemesis) {
inventory.Nemesis.HenchmenKilled += inventoryUpdates.RewardInfo.NemesisHenchmenKills;
let HenchmenKilledMultiplier = 1;
switch (inventory.Nemesis.Faction) {
case "FC_GRINEER":
HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMulptiplierGrineer ?? 1;
break;
case "FC_CORPUS":
HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMulptiplierCorpus ?? 1;
break;
}
inventory.Nemesis.HenchmenKilled +=
inventoryUpdates.RewardInfo.NemesisHenchmenKills * HenchmenKilledMultiplier;
}
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
let HintProgressMultiplier = 1;
switch (inventory.Nemesis.Faction) {
case "FC_GRINEER":
HintProgressMultiplier = inventory.nemesisHintProgressMultiplierGrineer ?? 1;
break;
case "FC_CORPUS":
HintProgressMultiplier = inventory.nemesisHintProgressMultiplierCorpus ?? 1;
break;
}
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress * HintProgressMultiplier;
if (inventory.Nemesis.Faction != "FC_INFESTATION" && inventory.Nemesis.Hints.length != 3) {
const progressNeeded = [35, 60, 100][inventory.Nemesis.Hints.length];
if (inventory.Nemesis.HintProgress >= progressNeeded) {
@ -347,8 +366,10 @@ export const addMissionInventoryUpdates = async (
break;
}
case "PlayerSkillGains": {
inventory.PlayerSkills.LPP_SPACE += value.LPP_SPACE ?? 0;
inventory.PlayerSkills.LPP_DRIFTER += value.LPP_DRIFTER ?? 0;
inventory.PlayerSkills.LPP_SPACE +=
(value.LPP_SPACE ?? 0) * (inventory.playerSkillGainsMultiplierSpace ?? 1);
inventory.PlayerSkills.LPP_DRIFTER +=
(value.LPP_DRIFTER ?? 0) * (inventory.playerSkillGainsMultiplierDrifter ?? 1);
break;
}
case "CustomMarkers": {
@ -775,6 +796,11 @@ export const addMissionInventoryUpdates = async (
}
case "InvasionProgress": {
for (const clientProgress of value) {
if (inventory.finishInvasionsInOneMission) {
clientProgress.Delta *= 3;
clientProgress.AttackerScore *= 3;
clientProgress.DefenderScore *= 3;
}
const dbProgress = inventory.QualifyingInvasions.find(x =>
x.invasionId.equals(clientProgress._id.$oid)
);
@ -838,6 +864,8 @@ export const addMissionInventoryUpdates = async (
const att: string[] = [];
let countedAtt: ITypeCount[] | undefined;
const extraWeaponCheat = inventory.nemesisExtraWeapon ?? 0; // 0 means no extra weapon and token
if (value.killed) {
if (
value.weaponLoc &&
@ -847,6 +875,20 @@ export const addMissionInventoryUpdates = async (
giveNemesisWeaponRecipe(inventory, weaponType, value.nemesisName, value.weaponLoc, profile);
att.push(weaponType);
}
if (extraWeaponCheat >= 1) {
for (let i = 0; i < extraWeaponCheat; i++) {
const randomIndex = Math.floor(Math.random() * manifest.weapons.length);
const randomWeapon = manifest.weapons[randomIndex];
giveNemesisWeaponRecipe(
inventory,
randomWeapon,
value.nemesisName,
value.weaponLoc,
profile
);
att.push(randomWeapon);
}
}
//if (value.petLoc) {
if (profile.petHead) {
giveNemesisPetRecipe(inventory, value.nemesisName, profile);
@ -889,7 +931,7 @@ export const addMissionInventoryUpdates = async (
countedAtt = [
{
ItemType: "/Lotus/Types/Items/MiscItems/CodaWeaponBucks",
ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp)
ItemCount: getKillTokenRewardCount(inventory.Nemesis.fp) * (extraWeaponCheat + 1)
}
];
addMiscItems(inventory, countedAtt);
@ -1323,6 +1365,21 @@ export const addMissionRewards = async (
) {
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
if ((inventory.extraRelicRewards ?? 0) >= 1) {
for (let i = 0; i != inventory.extraRelicRewards; ++i) {
//give a relic that will be removed later in crackRelic()
const miscItemChanges = [
{
ItemType: voidTearWave.Participants[0].VoidProjection,
ItemCount: 1
}
];
addMiscItems(inventory, miscItemChanges);
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
}
}
}
if (strippedItems) {
@ -1417,9 +1474,14 @@ export const addMissionRewards = async (
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
inventory.Nemesis.MissionCount += 1;
let antivirusGain = 5;
antivirusGain *= inventory.nemesisAntivirusGainMultiplier ?? 1;
inventory.Nemesis.HenchmenKilled = Math.min(inventory.Nemesis.HenchmenKilled + antivirusGain, 95); // 5 progress per mission until 95
inventoryChanges.Nemesis.MissionCount ??= 0;
inventoryChanges.Nemesis.MissionCount += 1;
inventoryChanges.Nemesis.HenchmenKilled ??= 0;
inventoryChanges.Nemesis.HenchmenKilled = inventory.Nemesis.HenchmenKilled;
}
inventoryChanges.Nemesis.InfNodes = inventory.Nemesis.InfNodes;
@ -1473,7 +1535,7 @@ export const addMissionRewards = async (
if (vault) {
currentJob = vault;
if (jobType.endsWith("VaultBounty")) {
currentJob.xpAmounts = [currentJob.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)];
currentJob.xpAmounts[rewardInfo.JobTier!] = currentJob.xpAmounts.reduce((s, a) => s + a, 0);
}
}
}
@ -1490,15 +1552,22 @@ export const addMissionRewards = async (
medallionAmount = Math.floor(endlessJob.xpAmounts[index] * (1 + 0.15000001 * excess));
}
}
await addItem(inventory, "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", medallionAmount);
MissionRewards.push({
StoreItem: "/Lotus/StoreItems/Types/Items/Deimos/EntratiFragmentUncommonB",
ItemCount: medallionAmount
});
SyndicateXPItemReward = medallionAmount;
logger.debug(
`Giving ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
);
if (typeof medallionAmount === "number" && !isNaN(medallionAmount)) {
await addItem(inventory, "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB", medallionAmount);
MissionRewards.push({
StoreItem: "/Lotus/StoreItems/Types/Items/Deimos/EntratiFragmentUncommonB",
ItemCount: medallionAmount
});
SyndicateXPItemReward = medallionAmount;
logger.debug(
`Giving ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
);
} else {
logger.warning(
`${jobType} tried to give ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
);
logger.warning(`currentJob`, { currentJob: currentJob });
}
} else {
const specialCase = [
{ endings: ["Heists/HeistProfitTakerBountyOne"], stage: 2, amount: 1000 },
@ -1829,6 +1898,10 @@ function getRandomMissionDrops(
ItemCount: 10
});
}
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
ItemCount: 10
});
rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"];
} else if (RewardInfo.T == 19) {
if (config.worldState?.eightClaw) {
@ -1837,6 +1910,10 @@ function getRandomMissionDrops(
ItemCount: 15
});
}
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
ItemCount: 15
});
rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
];
@ -2194,7 +2271,7 @@ function getRandomMissionDrops(
}
}
if (config.missionsCanGiveAllRelics) {
if (inventory.missionsCanGiveAllRelics) {
for (const drop of drops) {
const itemType = fromStoreItem(drop.StoreItem);
if (itemType in ExportRelics) {

View File

@ -2630,84 +2630,92 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
);
}
const thermiaFracturesCycleDay = day % 32;
const isThermiaFracturesActive = thermiaFracturesCycleDay < 14;
// Thermia Fractures activates for 14 days, with alternating 4 and 3-day breaks
const thermiaFracturesCycleDay = day % 35;
const isThermiaFracturesActive =
thermiaFracturesCycleDay < 14 || (thermiaFracturesCycleDay >= 18 && thermiaFracturesCycleDay < 32);
const activeThermiaFracturesCycleDay =
thermiaFracturesCycleDay - (thermiaFracturesCycleDay < 14 ? 0 : thermiaFracturesCycleDay < 18 ? 14 : 32);
if (config.worldState?.thermiaFracturesOverride ?? isThermiaFracturesActive) {
const activeStartDay = day - thermiaFracturesCycleDay;
const activeStartDay = day - activeThermiaFracturesCycleDay;
const count = config.worldState?.thermiaFracturesProgressOverride ?? 0;
const activation = config.worldState?.thermiaFracturesOverride ? 1740416400000 : getSortieTime(activeStartDay);
const expiry = config.worldState?.thermiaFracturesOverride ? 2000000000000 : getSortieTime(activeStartDay + 14);
worldState.Goals.push({
_id: { $oid: "5c7cb0d00000000000000000" },
Activation: { $date: { $numberLong: activation.toString() } },
Expiry: { $date: { $numberLong: expiry.toString() } },
Node: "SolNode129",
ScoreVar: "FissuresClosed",
ScoreLocTag: "/Lotus/Language/G1Quests/HeatFissuresEventScore",
Count: count,
HealthPct: count / 100,
Regions: [1],
Desc: "/Lotus/Language/G1Quests/HeatFissuresEventName",
ToolTip: "/Lotus/Language/G1Quests/HeatFissuresEventDesc",
OptionalInMission: true,
Tag: "HeatFissure",
UpgradeIds: [{ $oid: "5c81cefa4c4566791728eaa7" }, { $oid: "5c81cefa4c4566791728eaa6" }],
Personal: true,
Community: true,
Goal: 100,
Reward: {
items: ["/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpBFG/Vandal/VandalCrpBFG"]
},
InterimGoals: [5, 25, 50, 75],
InterimRewards: [
{ items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/OrbBadgeItem"] },
{
items: [
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Shotgun/ShotgunMedicMod",
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Rifle/SerratedRushMod"
]
},
{
items: [
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Pistol/MultishotDodgeMod",
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Melee/CritDamageChargeSpeedMod"
]
},
{ items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrbSigil"] }
]
});
worldState.NodeOverrides.push({
_id: { $oid: "5c7cb0d00000000000000000" },
Activation: { $date: { $numberLong: activation.toString() } },
Expiry: { $date: { $numberLong: expiry.toString() } },
Node: "SolNode129",
Faction: "FC_CORPUS",
CustomNpcEncounters: ["/Lotus/Types/Gameplay/Venus/Encounters/Heists/ExploiterHeistFissure"]
});
if (count >= 35) {
worldState.GlobalUpgrades.push({
_id: { $oid: "5c81cefa4c4566791728eaa6" },
// If we push it, the game may show the event even tho it's not activated yet (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2721)
if (timeMs >= activation) {
worldState.Goals.push({
_id: { $oid: "5c7cb0d00000000000000000" },
Activation: { $date: { $numberLong: activation.toString() } },
ExpiryDate: { $date: { $numberLong: expiry.toString() } },
UpgradeType: "GAMEPLAY_MONEY_REWARD_AMOUNT",
OperationType: "MULTIPLY",
Value: 2,
Nodes: ["SolNode129"]
Expiry: { $date: { $numberLong: expiry.toString() } },
Node: "SolNode129",
ScoreVar: "FissuresClosed",
ScoreLocTag: "/Lotus/Language/G1Quests/HeatFissuresEventScore",
Count: count,
HealthPct: count / 100,
Regions: [1],
Desc: "/Lotus/Language/G1Quests/HeatFissuresEventName",
ToolTip: "/Lotus/Language/G1Quests/HeatFissuresEventDesc",
OptionalInMission: true,
Tag: "HeatFissure",
UpgradeIds: [{ $oid: "5c81cefa4c4566791728eaa7" }, { $oid: "5c81cefa4c4566791728eaa6" }],
Personal: true,
Community: true,
Goal: 100,
Reward: {
items: ["/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpBFG/Vandal/VandalCrpBFG"]
},
InterimGoals: [5, 25, 50, 75],
InterimRewards: [
{ items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/OrbBadgeItem"] },
{
items: [
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Shotgun/ShotgunMedicMod",
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Rifle/SerratedRushMod"
]
},
{
items: [
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Pistol/MultishotDodgeMod",
"/Lotus/StoreItems/Upgrades/Mods/DualSource/Melee/CritDamageChargeSpeedMod"
]
},
{ items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrbSigil"] }
]
});
}
// Not sure about that
if (count == 100) {
worldState.GlobalUpgrades.push({
_id: { $oid: "5c81cefa4c4566791728eaa7" },
worldState.NodeOverrides.push({
_id: { $oid: "5c7cb0d00000000000000000" },
Activation: { $date: { $numberLong: activation.toString() } },
ExpiryDate: { $date: { $numberLong: expiry.toString() } },
UpgradeType: "GAMEPLAY_PICKUP_AMOUNT",
OperationType: "MULTIPLY",
Value: 2,
Nodes: ["SolNode129"]
Expiry: { $date: { $numberLong: expiry.toString() } },
Node: "SolNode129",
Faction: "FC_CORPUS",
CustomNpcEncounters: ["/Lotus/Types/Gameplay/Venus/Encounters/Heists/ExploiterHeistFissure"]
});
if (count >= 35) {
worldState.GlobalUpgrades.push({
_id: { $oid: "5c81cefa4c4566791728eaa6" },
Activation: { $date: { $numberLong: activation.toString() } },
ExpiryDate: { $date: { $numberLong: expiry.toString() } },
UpgradeType: "GAMEPLAY_MONEY_REWARD_AMOUNT",
OperationType: "MULTIPLY",
Value: 2,
Nodes: ["SolNode129"]
});
}
// Not sure about that
if (count == 100) {
worldState.GlobalUpgrades.push({
_id: { $oid: "5c81cefa4c4566791728eaa7" },
Activation: { $date: { $numberLong: activation.toString() } },
ExpiryDate: { $date: { $numberLong: expiry.toString() } },
UpgradeType: "GAMEPLAY_PICKUP_AMOUNT",
OperationType: "MULTIPLY",
Value: 2,
Nodes: ["SolNode129"]
});
}
}
}
@ -2933,7 +2941,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
{
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
const baroActualStart = baroStart + unixTimesInMs.day * (config.worldState?.baroAlwaysAvailable ? 0 : 12);
const baroEnd = baroStart + unixTimesInMs.day * 14;
const baroNode = ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4];
const vt: IVoidTrader = {
@ -2946,7 +2954,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
};
worldState.VoidTraders.push(vt);
if (isBeforeNextExpectedWorldStateRefresh(timeMs, baroActualStart)) {
if (config.baroFullyStocked) {
if (config.worldState?.baroFullyStocked) {
fullyStockBaro(vt);
} else {
const rng = new SRng(new SRng(baroIndex).randomInt(0, 100_000));

View File

@ -21,12 +21,14 @@ export type InventoryDatabaseEquipment = {
// Fields specific to SNS
export interface IAccountCheats {
skipAllDialogue?: boolean;
dontSubtractPurchaseCreditCost?: boolean;
dontSubtractPurchasePlatinumCost?: boolean;
dontSubtractPurchaseItemCost?: boolean;
dontSubtractPurchaseStandingCost?: boolean;
dontSubtractVoidTraces?: boolean;
dontSubtractConsumables?: boolean;
finishInvasionsInOneMission?: boolean;
infiniteCredits?: boolean;
infinitePlatinum?: boolean;
infiniteEndo?: boolean;
@ -48,6 +50,28 @@ export interface IAccountCheats {
claimingBlueprintRefundsIngredients?: boolean;
instantResourceExtractorDrones?: boolean;
noResourceExtractorDronesDamage?: boolean;
missionsCanGiveAllRelics?: boolean;
exceptionalRelicsAlwaysGiveBronzeReward?: boolean;
flawlessRelicsAlwaysGiveSilverReward?: boolean;
radiantRelicsAlwaysGiveGoldReward?: boolean;
disableDailyTribute?: boolean;
gainNoNegativeSyndicateStanding?: boolean;
nemesisAlwaysCorrect?: boolean;
nemesisHenchmenKillsMulptiplierGrineer?: number;
nemesisHenchmenKillsMulptiplierCorpus?: number;
nemesisAntivirusGainMultiplier?: number;
nemesisHintProgressMultiplierGrineer?: number;
nemesisHintProgressMultiplierCorpus?: number;
nemesisWeaponFusionMultiplier?: number;
nemesisExtraWeapon?: number;
extraMissionRewards?: number;
playerSkillGainsMultiplierSpace?: number;
playerSkillGainsMultiplierDrifter?: number;
extraRelicRewards?: number;
crackRelicForPlatinum?: boolean;
relicPlatinumCommon?: number;
relicPlatinumUncommon?: number;
relicPlatinumRare?: number;
}
export interface IInventoryDatabase
@ -374,6 +398,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CrewMembers: ICrewMemberClient[];
LotusCustomization?: ILotusCustomization;
UseAdultOperatorLoadout?: boolean;
OperatorCustomizationSlotPurchases?: number;
NemesisAbandonedRewards: string[];
LastInventorySync?: IOid;
NextRefill?: IMongoDate;

View File

@ -92,12 +92,36 @@
<div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
<p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
<div class="card mb-3">
<h5 class="card-header" data-loc="inventory_addItems"></h5>
<form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;">
<input class="form-control" id="miscitem-count" type="number" value="1" />
<input class="form-control w-50" id="miscitem-type" list="datalist-miscitems" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="card-header">
<ul class="nav nav-tabs card-header-tabs">
<li class="nav-item">
<button class="nav-link" id="miscItems-tab" data-bs-toggle="tab" data-bs-target="#miscItems-tab-content" data-loc="inventory_addItems"></button>
</li>
<li class="nav-item">
<button class="nav-link" id="typeName-tab" data-bs-toggle="tab" data-bs-target="#typeName-tab-content" data-loc="inventory_addItemByItemType"></button>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane" id="miscItems-tab-content">
<form class="card-body input-group" onsubmit="doAcquireMiscItems();return false;">
<input class="form-control" id="miscitem-count" type="number" value="1" />
<input class="form-control w-50" id="miscitem-type" list="datalist-miscitems" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
</div>
<div class="tab-pane" id="typeName-tab-content">
<form class="card-body" onsubmit="addItemByItemType();return false;">
<p data-loc="inventory_addItemByItemType_warning"></p>
<div class="input-group">
<input class="form-control" id="typeName-type" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-3">
@ -647,6 +671,10 @@
<div class="card">
<h5 class="card-header" data-loc="cheats_account"></h5>
<div class="card-body" id="account-cheats">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipAllDialogue" />
<label class="form-check-label" for="skipAllDialogue" data-loc="cheats_skipAllDialogue"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dontSubtractPurchaseCreditCost" />
<label class="form-check-label" for="dontSubtractPurchaseCreditCost" data-loc="cheats_dontSubtractPurchaseCreditCost"></label>
@ -755,14 +783,150 @@
<input class="form-check-input" type="checkbox" id="claimingBlueprintRefundsIngredients" />
<label class="form-check-label" for="claimingBlueprintRefundsIngredients" data-loc="cheats_claimingBlueprintRefundsIngredients"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="missionsCanGiveAllRelics" />
<label class="form-check-label" for="missionsCanGiveAllRelics" data-loc="cheats_missionsCanGiveAllRelics"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="exceptionalRelicsAlwaysGiveBronzeReward" />
<label class="form-check-label" for="exceptionalRelicsAlwaysGiveBronzeReward" data-loc="cheats_exceptionalRelicsAlwaysGiveBronzeReward"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="flawlessRelicsAlwaysGiveSilverReward" />
<label class="form-check-label" for="flawlessRelicsAlwaysGiveSilverReward" data-loc="cheats_flawlessRelicsAlwaysGiveSilverReward"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="radiantRelicsAlwaysGiveGoldReward" />
<label class="form-check-label" for="radiantRelicsAlwaysGiveGoldReward" data-loc="cheats_radiantRelicsAlwaysGiveGoldReward"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="disableDailyTribute" />
<label class="form-check-label" for="disableDailyTribute" data-loc="cheats_disableDailyTribute"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="finishInvasionsInOneMission" />
<label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="gainNoNegativeSyndicateStanding" />
<label class="form-check-label" for="gainNoNegativeSyndicateStanding" data-loc="cheats_gainNoNegativeSyndicateStanding"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="nemesisAlwaysCorrect" />
<label class="form-check-label" for="nemesisAlwaysCorrect" data-loc="cheats_nemesisAlwaysCorrect"></label>
</div>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHenchmenKillsMultiplierGrineer" data-loc="cheats_nemesisHenchmenKillsMultiplierGrineer"></label>
<div class="input-group">
<input class="form-control" id="nemesisHenchmenKillsMultiplierGrineer" type="number" min="-1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHenchmenKillsMultiplierCorpus" data-loc="cheats_nemesisHenchmenKillsMultiplierCorpus"></label>
<div class="input-group">
<input class="form-control" id="nemesisHenchmenKillsMultiplierCorpus" type="number" min="-1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisAntivirusGainMultiplier" data-loc="cheats_nemesisAntivirusGainMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nemesisAntivirusGainMultiplier" type="number" min="-1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHintProgressMultiplierGrineer" data-loc="cheats_nemesisHintProgressMultiplierGrineer"></label>
<div class="input-group">
<input class="form-control" id="nemesisHintProgressMultiplierGrineer" type="number" min="-1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisHintProgressMultiplierCorpus" data-loc="cheats_nemesisHintProgressMultiplierCorpus"></label>
<div class="input-group">
<input class="form-control" id="nemesisHintProgressMultiplierCorpus" type="number" min="-1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisWeaponFusionMultiplier" data-loc="cheats_nemesisWeaponFusionMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nemesisWeaponFusionMultiplier" type="number" min="1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="nemesisExtraWeapon" data-loc="cheats_nemesisExtraWeapon"></label>
<div class="input-group">
<input class="form-control" id="nemesisExtraWeapon" type="number" min="0" max="65535" data-default="0" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="extraMissionRewards" data-loc="cheats_extraMissionRewards"></label>
<div class="input-group">
<input class="form-control" id="extraMissionRewards" type="number" min="0" max="65535" data-default="0" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="playerSkillGainsMultiplierSpace" data-loc="cheats_playerSkillGainsMultiplierSpace"></label>
<div class="input-group">
<input class="form-control" id="playerSkillGainsMultiplierSpace" type="number" min="1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="playerSkillGainsMultiplierDrifter" data-loc="cheats_playerSkillGainsMultiplierDrifter"></label>
<div class="input-group">
<input class="form-control" id="playerSkillGainsMultiplierDrifter" type="number" min="1" max="65535" data-default="1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="extraRelicRewards" data-loc="cheats_extraRelicRewards"></label>
<div class="input-group">
<input class="form-control" id="extraRelicRewards" type="number" min="0" max="65535" data-default="0" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="crackRelicForPlatinum" />
<label class="form-check-label" for="crackRelicForPlatinum" data-loc="cheats_crackRelicForPlatinum"></label>
</div>
<form class="form-group mt-2">
<label class="form-label" for="relicPlatinumCommon" data-loc="cheats_relicPlatinumCommon"></label>
<div class="input-group">
<input class="form-control" id="relicPlatinumCommon" type="number" min="0" max="65535" data-default="2" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="relicPlatinumUncommon" data-loc="cheats_relicPlatinumUncommon"></label>
<div class="input-group">
<input class="form-control" id="relicPlatinumUncommon" type="number" min="0" max="65535" data-default="5" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="relicPlatinumRare" data-loc="cheats_relicPlatinumRare"></label>
<div class="input-group">
<input class="form-control" id="relicPlatinumRare" type="number" min="0" max="65535" data-default="12" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<div class="mt-2 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="debounce(unlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
<button class="btn btn-primary" onclick="debounce(markAllAsRead);" data-loc="cheats_markAllAsRead"></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="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="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllProfitTakerStages);" data-loc="cheats_unlockAllProfitTakerStages"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllSimarisResearchEntries);" data-loc="cheats_unlockAllSimarisResearchEntries"></button>
</div>
<form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>
@ -786,10 +950,6 @@
<input class="form-check-input" type="checkbox" id="skipTutorial" />
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipAllDialogue" />
<label class="form-check-label" for="skipAllDialogue" data-loc="cheats_skipAllDialogue"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllScans" />
<label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
@ -822,18 +982,6 @@
<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">
<input class="form-check-input" type="checkbox" id="baroAlwaysAvailable" />
<label class="form-check-label" for="baroAlwaysAvailable" data-loc="cheats_baroAlwaysAvailable"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="baroFullyStocked" />
<label class="form-check-label" for="baroFullyStocked" data-loc="cheats_baroFullyStocked"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllProfitTakerStages" />
<label class="form-check-label" for="unlockAllProfitTakerStages" data-loc="cheats_unlockAllProfitTakerStages"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
<label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
@ -862,30 +1010,6 @@
<input class="form-check-input" type="checkbox" id="fastClanAscension" />
<label class="form-check-label" for="fastClanAscension" data-loc="cheats_fastClanAscension"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="missionsCanGiveAllRelics" />
<label class="form-check-label" for="missionsCanGiveAllRelics" data-loc="cheats_missionsCanGiveAllRelics"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="exceptionalRelicsAlwaysGiveBronzeReward" />
<label class="form-check-label" for="exceptionalRelicsAlwaysGiveBronzeReward" data-loc="cheats_exceptionalRelicsAlwaysGiveBronzeReward"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="flawlessRelicsAlwaysGiveSilverReward" />
<label class="form-check-label" for="flawlessRelicsAlwaysGiveSilverReward" data-loc="cheats_flawlessRelicsAlwaysGiveSilverReward"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="radiantRelicsAlwaysGiveGoldReward" />
<label class="form-check-label" for="radiantRelicsAlwaysGiveGoldReward" data-loc="cheats_radiantRelicsAlwaysGiveGoldReward"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllSimarisResearchEntries" />
<label class="form-check-label" for="unlockAllSimarisResearchEntries" data-loc="cheats_unlockAllSimarisResearchEntries"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="disableDailyTribute" />
<label class="form-check-label" for="disableDailyTribute" data-loc="cheats_disableDailyTribute"></label>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
@ -933,6 +1057,14 @@
<input class="form-check-input" type="checkbox" id="worldState.baroTennoConRelay" />
<label class="form-check-label" for="worldState.baroTennoConRelay" data-loc="worldState_baroTennoConRelay"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.baroAlwaysAvailable" />
<label class="form-check-label" for="worldState.baroAlwaysAvailable" data-loc="cheats_baroAlwaysAvailable"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.baroFullyStocked" />
<label class="form-check-label" for="worldState.baroFullyStocked" data-loc="cheats_baroFullyStocked"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.varziaFullyStocked" />
<label class="form-check-label" for="worldState.varziaFullyStocked" data-loc="worldState_varziaFullyStocked"></label>

View File

@ -649,6 +649,14 @@ function updateInventory() {
];
// Populate inventory route
document.getElementById("typeName-tab").classList.remove("active");
document.getElementById("typeName-tab-content").classList.remove("active", "show");
document.getElementById("typeName-type").value = "";
document.getElementById("miscItems-tab").classList.add("active");
document.getElementById("miscItems-tab-content").classList.add("active", "show");
["RegularCredits", "PremiumCredits", "FusionPoints", "PrimeTokens"].forEach(currency => {
document.getElementById(currency + "-owned").textContent = loc("currency_owned")
.split("|COUNT|")
@ -1495,7 +1503,11 @@ function updateInventory() {
});
for (const elm of accountCheats) {
elm.checked = !!data[elm.id];
if (elm.type === "checkbox") {
elm.checked = !!data[elm.id];
} else if (elm.type === "number") {
elm.value = data[elm.id] !== undefined ? data[elm.id] : elm.getAttribute("data-default") || "";
}
}
});
});
@ -1685,7 +1697,7 @@ function doAcquireEvolution() {
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
}
$(document).on("input", "input[list]", function () {
$(document).on("input", "input", function () {
$(this).removeClass("is-invalid");
});
@ -2008,6 +2020,35 @@ function doAcquireMiscItems() {
}
}
function addItemByItemType() {
const ItemType = document.getElementById("typeName-type").value;
// Must start with "/Lotus/", contain only AZ letters, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z]+(?:\/[A-Za-z]+)*)$/.test(ItemType)) {
$("#typeName-type").addClass("is-invalid").focus();
return;
}
revalidateAuthz().then(() => {
$.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify([
{
ItemType,
ItemCount: 1
}
])
})
.done(function (_, _, jqXHR) {
if (jqXHR.status === 200) {
updateInventory();
}
})
.fail(function () {
$("#typeName-type").addClass("is-invalid").focus();
});
});
}
function doAcquireRiven() {
let fingerprint;
try {
@ -2330,15 +2371,16 @@ function doIntrinsicsUnlockAll() {
});
}
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
document.querySelectorAll("#account-cheats input[type=checkbox], #account-cheats input[type=number]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
const value = elm.type === "checkbox" ? elm.checked : elm.value;
$.post({
url: "/custom/setAccountCheat?" + window.authz /*+ "&wsid=" + wsid*/,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: elm.checked
value: value
})
});
});
@ -2713,12 +2755,24 @@ async function doMaxPlexus() {
}
}
async function doUnlockAllMissions() {
async function unlockAllMissions() {
await revalidateAuthz();
await fetch("/custom/completeAllMissions?" + window.authz);
toast(loc("cheats_unlockAllMissions_ok"));
}
async function unlockAllProfitTakerStages() {
await revalidateAuthz();
await fetch("/custom/unlockAllProfitTakerStages?" + window.authz);
toast(loc("cheats_unlockSucc"));
}
async function unlockAllSimarisResearchEntries() {
await revalidateAuthz();
await fetch("/custom/unlockAllSimarisResearchEntries?" + window.authz);
toast(loc("cheats_unlockSucc"));
}
const importSamples = {
maxFocus: {
FocusUpgrades: [

View File

@ -79,6 +79,8 @@ dict = {
navbar_cheats: `Cheats`,
navbar_import: `Importieren`,
inventory_addItems: `Gegenstände hinzufügen`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
inventory_suits: `Warframes`,
inventory_longGuns: `Primärwaffen`,
inventory_pistols: `Sekundärwaffen`,
@ -210,6 +212,7 @@ dict = {
cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -238,6 +241,24 @@ dict = {
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_markAllAsRead: `Posteingang als gelesen markieren`,
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
worldState: `Weltstatus`,
worldState_creditBoost: `Event Booster: Credit`,

View File

@ -78,6 +78,8 @@ dict = {
navbar_cheats: `Cheats`,
navbar_import: `Import`,
inventory_addItems: `Add Items`,
inventory_addItemByItemType: `Raw`,
inventory_addItemByItemType_warning: `Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
inventory_suits: `Warframes`,
inventory_longGuns: `Primary Weapons`,
inventory_pistols: `Secondary Weapons`,
@ -209,6 +211,7 @@ dict = {
cheats_baroFullyStocked: `Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
cheats_unlockSucc: `Successfully unlocked.`,
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
@ -237,6 +240,24 @@ dict = {
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_markAllAsRead: `Mark Inbox As Read`,
cheats_finishInvasionsInOneMission: `Finish Invasions in One Mission`,
cheats_gainNoNegativeSyndicateStanding: `Gain No Negative Syndicate Standing`,
cheats_nemesisAlwaysCorrect: `Any Guess is Correct`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `Hint Progress Multiplier (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `Nemesis Weapon Fusion Multiplier`,
cheats_nemesisExtraWeapon: `Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
cheats_extraMissionRewards: `Extra Mission Rewards`,
cheats_playerSkillGainsMultiplierSpace: `Intrinsics Gains Multiplier (Space)`,
cheats_playerSkillGainsMultiplierDrifter: `Intrinsics Gains Multiplier (Drifter)`,
cheats_extraRelicRewards: `Extra Relic Rewards`,
cheats_crackRelicForPlatinum: `Crack Relic for Platinum`,
cheats_relicPlatinumCommon: `Platinum on Common Rewards`,
cheats_relicPlatinumUncommon: `Platinum on Uncommon Rewards`,
cheats_relicPlatinumRare: `Platinum on Rare Rewards`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,

View File

@ -79,6 +79,8 @@ dict = {
navbar_cheats: `Trucos`,
navbar_import: `Importar`,
inventory_addItems: `Agregar objetos`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
inventory_suits: `Warframes`,
inventory_longGuns: `Armas primarias`,
inventory_pistols: `Armas secundarias`,
@ -210,6 +212,7 @@ dict = {
cheats_baroFullyStocked: `Baro con stock completo`,
cheats_syndicateMissionsRepeatable: `Misiones de sindicato rejugables`,
cheats_unlockAllProfitTakerStages: `Desbloquea todas las etapas del Roba-ganancias`,
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
cheats_instantFinishRivenChallenge: `Terminar desafío de agrietado inmediatamente`,
cheats_instantResourceExtractorDrones: `Drones de extracción de recursos instantáneos`,
cheats_noResourceExtractorDronesDamage: `Sin daño a los drones extractores de recursos`,
@ -238,6 +241,24 @@ dict = {
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`,
cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
worldState: `Estado del mundo`,
worldState_creditBoost: `Potenciador de Créditos`,

View File

@ -79,6 +79,8 @@ dict = {
navbar_cheats: `Cheats`,
navbar_import: `Importer`,
inventory_addItems: `Ajouter des items`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
inventory_suits: `Warframes`,
inventory_longGuns: `Armes principales`,
inventory_pistols: `Armes secondaires`,
@ -210,6 +212,7 @@ dict = {
cheats_baroFullyStocked: `Stock de Baro au max`,
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
@ -238,6 +241,24 @@ dict = {
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
worldState: `Carte Solaire`,
worldState_creditBoost: `Booster de Crédit`,

View File

@ -79,6 +79,8 @@ dict = {
navbar_cheats: `Читы`,
navbar_import: `Импорт`,
inventory_addItems: `Добавить предметы`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
inventory_addItemByItemType_warning: `Используйте эту функцию на свой страх и риск. Она может повредить ваш инвентарь, и в случае проблем вам придётся удалять предметы вручную.`,
inventory_suits: `Варфреймы`,
inventory_longGuns: `Основное оружие`,
inventory_pistols: `Вторичное оружие`,
@ -210,6 +212,7 @@ dict = {
cheats_baroFullyStocked: `Баро полностью укомплектован`,
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
cheats_unlockSucc: `Успешно разблокировано.`,
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
@ -238,6 +241,24 @@ dict = {
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`,
cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
worldState: `Состояние мира`,
worldState_creditBoost: `Глобальный бустер Кредитов`,

View File

@ -79,6 +79,8 @@ dict = {
navbar_cheats: `Чити`,
navbar_import: `Імпорт`,
inventory_addItems: `Додати предмети`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
inventory_suits: `Ворфрейми`,
inventory_longGuns: `Основна зброя`,
inventory_pistols: `Допоміжна зброя`,
@ -210,6 +212,7 @@ dict = {
cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_unlockSucc: `Успішно розблоковано.`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
@ -238,6 +241,24 @@ dict = {
cheats_changeSupportedSyndicate: `Підтримуваний синдикат`,
cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`,
cheats_gainNoNegativeSyndicateStanding: `[UNTRANSLATED] Gain No Negative Syndicate Standing`,
cheats_nemesisAlwaysCorrect: `[UNTRANSLATED] Any Guess is Correct`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `[UNTRANSLATED] Nemesis Weapon Fusion Multiplier`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
cheats_extraMissionRewards: `[UNTRANSLATED] Extra Mission Rewards`,
cheats_playerSkillGainsMultiplierSpace: `[UNTRANSLATED] Intrinsics Gains Multiplier (Space)`,
cheats_playerSkillGainsMultiplierDrifter: `[UNTRANSLATED] Intrinsics Gains Multiplier (Drifter)`,
cheats_extraRelicRewards: `[UNTRANSLATED] Extra Relic Rewards`,
cheats_crackRelicForPlatinum: `[UNTRANSLATED] Crack Relic for Platinum`,
cheats_relicPlatinumCommon: `[UNTRANSLATED] Platinum on Common Rewards`,
cheats_relicPlatinumUncommon: `[UNTRANSLATED] Platinum on Uncommon Rewards`,
cheats_relicPlatinumRare: `[UNTRANSLATED] Platinum on Rare Rewards`,
worldState: `Стан світу`,
worldState_creditBoost: `Глобальне посилення Кредитів`,

View File

@ -79,6 +79,8 @@ dict = {
navbar_cheats: `作弊选项`,
navbar_import: `导入`,
inventory_addItems: `添加物品`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`,
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`,
inventory_suits: `战甲`,
inventory_longGuns: `主要武器`,
inventory_pistols: `次要武器`,
@ -210,6 +212,7 @@ dict = {
cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
cheats_unlockSucc: `[UNTRANSLATED] Successfully unlocked.`,
cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
@ -238,6 +241,24 @@ dict = {
cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`,
cheats_markAllAsRead: `收件箱全部标记为已读`,
cheats_finishInvasionsInOneMission: `一场任务完成整场入侵`,
cheats_gainNoNegativeSyndicateStanding: `集团声望不倒扣不掉段`,
cheats_nemesisAlwaysCorrect: `玄骸密码总是正确`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `玄骸怒气倍率 (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `玄骸怒气倍率 (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `杀毒进度倍率 (科腐者)`,
cheats_nemesisHintProgressMultiplierGrineer: `解密进度倍率 (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `解密进度倍率 (Corpus)`,
cheats_nemesisWeaponFusionMultiplier: `玄骸武器效价融合倍率`,
cheats_nemesisExtraWeapon: `额外玄骸武器/代币 (0为禁用)`,
cheats_extraMissionRewards: `额外任务奖励`,
cheats_playerSkillGainsMultiplierSpace: `內源之力获取倍率 (九重天)`,
cheats_playerSkillGainsMultiplierDrifter: `內源之力获取倍率 (漂泊者)`,
cheats_extraRelicRewards: `额外遗物奖励`,
cheats_crackRelicForPlatinum: `打开遗物时获得白金`,
cheats_relicPlatinumCommon: `普通奖励的白金`,
cheats_relicPlatinumUncommon: `罕见奖励的白金`,
cheats_relicPlatinumRare: `稀有奖励的白金`,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,

View File

@ -105,9 +105,6 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */,
"paths": {
"@/*": ["./*"]
}
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}