Compare commits

...

53 Commits

Author SHA1 Message Date
02f0935710 feat(webui): skins (#2816)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2816
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-10-09 23:02:09 -07:00
af4c3a93ce feat: forceRemoveItem.php (#2884)
Closes #2883

Reviewed-on: OpenWF/SpaceNinjaServer#2884
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-09 23:02:00 -07:00
4141970530 fix: use correct items for Hallowed Nightmares (#2885)
Reviewed-on: OpenWF/SpaceNinjaServer#2885
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-10-09 23:01:49 -07:00
ca589cb7cf feat: QTCC floofs alerts (#2886)
Re #2842

Reviewed-on: OpenWF/SpaceNinjaServer#2886
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-10-09 23:01:36 -07:00
d6ed22d1ff chore(webui): Void Corruption translations (#2887)
Reviewed-on: OpenWF/SpaceNinjaServer#2887
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-10-09 23:01:20 -07:00
610a432e46 fixup for 2ca895a5f88be3ab43943142e5d559823cac7387 2025-10-09 11:01:49 +02:00
2ca895a5f8 feat: Void Corruption 2025 (#2865)
Reviewed-on: OpenWF/SpaceNinjaServer#2865
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Slayer55555 <slayer55555@noreply.localhost>
Co-committed-by: Slayer55555 <slayer55555@noreply.localhost>
2025-10-09 00:28:34 -07:00
fd2286c253 fix: send back entire dojo when a room build has been cancelled (#2879)
Fixes #2877

Reviewed-on: OpenWF/SpaceNinjaServer#2879
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-09 00:28:16 -07:00
5a582daa1a chore(webui): debounce guild tech actions (#2882)
Closes #2880

Reviewed-on: OpenWF/SpaceNinjaServer#2882
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-09 00:28:09 -07:00
6a571e5e78 chore: explicitly declare body-parser dependency for pnpm (#2873)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2873
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Mind <1634300602@qq.com>
Co-committed-by: Mind <1634300602@qq.com>
2025-10-07 23:21:17 -07:00
0349c4a32c feat: increase BountyScore for additional stratos emblems earned (#2872)
Closes #2871

Reviewed-on: OpenWF/SpaceNinjaServer#2872
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-07 23:20:48 -07:00
e1563bf298 fix(webui): allow digits in itemtype for add items(raw) (#2870)
For items like `/Lotus/Types/Keys/TacAlertKeyAnniversary2023k`

Reviewed-on: OpenWF/SpaceNinjaServer#2870
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-10-07 23:18:03 -07:00
af6f422fec feat: nemesis mode t / LastNemesisAllySpawnTime (#2869)
Also some import stuff. Closes #2867

Reviewed-on: OpenWF/SpaceNinjaServer#2869
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-07 23:17:50 -07:00
f5c1b83598 fix: only commit 'Missions' on successful completion (#2866)
Fixes SP missions being marked as completed when failing/quitting.

Reviewed-on: OpenWF/SpaceNinjaServer#2866
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:57:18 -07:00
30f380f37e chore(webui): refresh when creating/deleting a clan in-game (#2864)
So the clan tab shows/hides instantly as expected.

Reviewed-on: OpenWF/SpaceNinjaServer#2864
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:57:08 -07:00
0f7a85db59 chore(webui): sync account cheats between different webui tabs (#2863)
Reviewed-on: OpenWF/SpaceNinjaServer#2863
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:51 -07:00
43bc12713a chore(webui): force account cheat element state after request is done (#2862)
There's a very slim chance we get an inventory response between sending the setAccountCheat request and receiving the response, in which case the element state would be ingruent.

Reviewed-on: OpenWF/SpaceNinjaServer#2862
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:35 -07:00
6022bf97b5 feat: nemesis mode d (#2860)
Reviewed-on: OpenWF/SpaceNinjaServer#2860
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:20 -07:00
159e151dc0 chore: check for xpBasedLevelCapDisabled in missionInventoryUpdate (#2859)
The bootstrapper provides this field since 0.8.2, so I think this field being absent is now more likely to mean that the patch is not in effect.

Reviewed-on: OpenWF/SpaceNinjaServer#2859
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:56:04 -07:00
56954260c8 chore(webui): debounce quest updates (#2858)
Closes #2855

Reviewed-on: OpenWF/SpaceNinjaServer#2858
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:55:46 -07:00
c535044af8 fix: use 1-based indexing for clan ranks for versions before U24 (#2857)
Reviewed-on: OpenWF/SpaceNinjaServer#2857
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:55:33 -07:00
f5146be129 fix: handle dojo room build request from old versions (#2854)
Reviewed-on: OpenWF/SpaceNinjaServer#2854
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:54:57 -07:00
d38ec06ed6 fix: disallow creating a clan from an account that's already in one (#2853)
Just a slight precaution to avoid snowballing problems.

Reviewed-on: OpenWF/SpaceNinjaServer#2853
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:54:35 -07:00
060f65900f fix: transform inventoryResponse.GuildId for older versions (#2852)
Reviewed-on: OpenWF/SpaceNinjaServer#2852
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-06 22:54:22 -07:00
66d3057d40 chore: fix typo 2025-10-06 08:33:44 +02:00
b14a5925df fix: setGuildMotd response for U29.3.1 (#2851)
Unsure which version introduced long descriptions exactly, but it surely wasn't this one. :^)

Reviewed-on: OpenWF/SpaceNinjaServer#2851
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:25:08 -07:00
9da47c406a fix: put CompletionTime of initial clan hall in the past (#2850)
For old versions, TimeRemaining of 0 would cause this to show in yellow, we need it to be negative.

Reviewed-on: OpenWF/SpaceNinjaServer#2850
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:59 -07:00
09065bdb4e chore: let webui know when client called updateQuest (#2849)
Reviewed-on: OpenWF/SpaceNinjaServer#2849
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:49 -07:00
8f04fc5fdf fix: default quest progress c to -1 (#2848)
Fixes #2846

Reviewed-on: OpenWF/SpaceNinjaServer#2848
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:38 -07:00
230ee5f638 fix: anniversary mission inbox messages showing unresolved |LOTUS_NAME| (#2845)
Reviewed-on: OpenWF/SpaceNinjaServer#2845
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 23:24:28 -07:00
21db6ce265 chore(webui): update uk & ru (#2844)
Reviewed-on: OpenWF/SpaceNinjaServer#2844
Co-authored-by: LoseFace <loseface@noreply.localhost>
Co-committed-by: LoseFace <loseface@noreply.localhost>
2025-10-05 23:24:14 -07:00
1ecf53c96b chore: don't add 'alwaysAvailable' skins with unlockAllSkins (#2843)
Reviewed-on: OpenWF/SpaceNinjaServer#2843
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-05 05:56:58 -07:00
e67ef63b77 fix: avoid using assassination node for an earlier sortie mission (#2838)
Closes #2837

Reviewed-on: OpenWF/SpaceNinjaServer#2838
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-04 04:18:21 -07:00
5772ebe746 feat(import): boosters (#2836)
Reviewed-on: OpenWF/SpaceNinjaServer#2836
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-03 06:46:07 -07:00
0136e4d152 chore(webui): clarify /sync command goes into chat (#2835)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2835
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: LoseFace <loseface@noreply.localhost>
Co-committed-by: LoseFace <loseface@noreply.localhost>
2025-10-03 06:45:57 -07:00
8b3ee4b4f5 chore: allow sortie image randomisation for most tilesets (#2834)
This should reduce the impact while we investigate #2833

Reviewed-on: OpenWF/SpaceNinjaServer#2834
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-02 05:27:56 -07:00
6e8800f048 chore(webui): fix typos (#2832)
also updated author credits

Reviewed-on: OpenWF/SpaceNinjaServer#2832
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-10-01 01:23:08 -07:00
d65a667acd fix: ensure sorties show 'correct' image for corpus ice planet tileset (#2831)
Reviewed-on: OpenWF/SpaceNinjaServer#2831
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-30 00:00:13 -07:00
c6a3e86d2b fix(webui): invoke giveKeyChainStageTriggered for new stage (#2830)
Previously, this caused the old stage to just be reinitiated so we never went backwards.

Closes #2829

Reviewed-on: OpenWF/SpaceNinjaServer#2830
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 23:59:35 -07:00
a8e41c95e7 chore: move createNewEventMessages from inboxService to inboxController (#2828)
This function wasn't used anywhere else and caused a recursive include in inboxService.

Reviewed-on: OpenWF/SpaceNinjaServer#2828
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 23:59:26 -07:00
9426359370 feat: Nights of Naberus (#2817)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2817
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Gian <gianplu55@gmail.com>
Co-committed-by: Gian <gianplu55@gmail.com>
2025-09-29 23:59:17 -07:00
e5247700df fix: use safe navigation to check for replay in giveKeyChainMessage (#2826)
Reviewed-on: OpenWF/SpaceNinjaServer#2826
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 02:00:32 -07:00
1c3f1e2276 feat: DeleteAllReadNonCin (#2824)
Closes #2822

Reviewed-on: OpenWF/SpaceNinjaServer#2824
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 02:00:17 -07:00
7710e7c13f feat: inbox message for relics cracked during an unfinished mission (#2823)
Closes #2821

Reviewed-on: OpenWF/SpaceNinjaServer#2823
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-29 02:00:05 -07:00
a64c5ea3c1 chore(webui): remove administratorNames entry when deleting account (#2820)
Closes #2819

Reviewed-on: OpenWF/SpaceNinjaServer#2820
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-28 01:10:45 -07:00
17e1eb86dd fix(webui): don't send off 2 addXp requests at once (#2815)
One would likely fail due to Mongoose's array versioning

Closes #2811

Reviewed-on: OpenWF/SpaceNinjaServer#2815
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-27 03:23:53 -07:00
de9dfb3d71 fix: show endless relic rewards in EOM screen (#2813)
Closes #2812

Reviewed-on: OpenWF/SpaceNinjaServer#2813
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-09-26 04:42:08 -07:00
fc38f818dd feat: nemesis henchmen kills multiplier cheat (#2806)
Co-authored-by: AlexisinGit <136088944+AlexisinGit@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2806
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: AlexisinGit <alexisingit@noreply.localhost>
Co-committed-by: AlexisinGit <alexisingit@noreply.localhost>
2025-09-26 04:41:54 -07:00
e76f08db89 chore(webui): update to Spanish translation (#2814)
Reviewed-on: OpenWF/SpaceNinjaServer#2814
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-09-25 10:51:44 -07:00
7bcb5f21ce chore(webui): unify Invigoration code (#2809)
Reviewed-on: OpenWF/SpaceNinjaServer#2809
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-09-25 10:21:04 -07:00
3641d63f6f chore(webui): update to Spanish translation (#2810)
Reviewed-on: OpenWF/SpaceNinjaServer#2810
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-09-24 23:49:10 -07:00
71c4835a69 chore(webui): unify Boosters code (#2808)
Reviewed-on: OpenWF/SpaceNinjaServer#2808
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-09-24 08:41:35 -07:00
86a63ace41 chore(webui): adjust checks for guild view requests (#2807)
Reviewed-on: OpenWF/SpaceNinjaServer#2807
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-09-24 08:41:04 -07:00
55 changed files with 1503 additions and 618 deletions

View File

@ -35,5 +35,5 @@ SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [confi
- `RadioLegion2Syndicate` for The Emissary
- `RadioLegionIntermissionSyndicate` for Intermission I
- `RadioLegionSyndicate` for The Wolf of Saturn Six
- `allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `worldState.allTheFissures` can be set to `normal` or `hard` to enable all fissures either in normal or steel path, respectively.
- `worldState.circuitGameModes` can be set to an array of game modes which will override the otherwise-random pattern in The Circuit. Valid element values are `Survival`, `VoidFlood`, `Excavation`, `Defense`, `Exterminate`, `Assassination`, and `Alchemy`.

View File

@ -38,8 +38,14 @@
"anniversary": null,
"hallowedNightmares": false,
"hallowedNightmaresRewardsOverride": 0,
"naberusNightsOverride": null,
"proxyRebellion": false,
"proxyRebellionRewardsOverride": 0,
"voidCorruption2025Week1": false,
"voidCorruption2025Week2": false,
"voidCorruption2025Week3": false,
"voidCorruption2025Week4": false,
"qtccAlerts": false,
"galleonOfGhouls": 0,
"ghoulEmergenceOverride": null,
"plagueStarOverride": null,

10
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0",
"license": "GNU",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -17,7 +18,7 @@
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.89",
"warframe-public-export-plus": "^0.5.90",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
@ -37,6 +38,7 @@
"node": ">=20.18.1"
},
"optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
@ -5532,9 +5534,9 @@
}
},
"node_modules/warframe-public-export-plus": {
"version": "0.5.89",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.89.tgz",
"integrity": "sha512-a6dM1MirzofSsuv3LlRQHFLSSIGKPVSN93dcXSDmA3njsWqOGjJJdWyXqcyxxYw8rEB8CNowSHst/MUmKvKlRg=="
"version": "0.5.90",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.90.tgz",
"integrity": "sha512-PzYFJ+qOZawPOVx9+hGlOosGdqSJMSRL6c1z3CCXMiY5SEHUs7p9JIMjkqnvg1g7vflVWnyG6E5/2gYkQs0d7w=="
},
"node_modules/warframe-riven-info": {
"version": "0.1.2",

View File

@ -27,6 +27,7 @@
"license": "GNU",
"type": "module",
"dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
@ -35,13 +36,14 @@
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.89",
"warframe-public-export-plus": "^0.5.90",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2"
},
"optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",

View File

@ -31,12 +31,13 @@ export const abortDojoComponentController: RequestHandler = async (req, res) =>
if (request.DecoId) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
} else {
await removeDojoRoom(guild, request.ComponentId);
await guild.save();
res.json(await getDojoClient(guild, 0));
}
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
};
interface IAbortDojoComponentRequest {

View File

@ -5,11 +5,23 @@ import { Guild, GuildMember } from "../../models/guildModel.ts";
import { createUniqueClanName, getGuildClient, giveClanKey } from "../../services/guildService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const createGuildController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
if (inventory.GuildId) {
const guild = await Guild.findById(inventory.GuildId);
if (guild) {
res.json({
...(await getGuildClient(guild, account))
});
return;
}
}
// Remove pending applications for this account
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
@ -27,7 +39,6 @@ export const createGuildController: RequestHandler = async (req, res) => {
rank: 0
});
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
inventory.GuildId = guild._id;
const inventoryChanges: IInventoryChanges = {};
giveClanKey(inventory, inventoryChanges);
@ -37,6 +48,7 @@ export const createGuildController: RequestHandler = async (req, res) => {
...(await getGuildClient(guild, account)),
InventoryChanges: inventoryChanges
});
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
};
interface ICreateGuildRequest {

View File

@ -0,0 +1,27 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
export const forceRemoveItemController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems");
const body = getJSONfromString<IForceRemoveItemRequest>(String(req.body));
const inventoryChanges: IInventoryChanges = {};
for (const item of body.items) {
const index = inventory.MiscItems.findIndex(x => x.ItemType == item);
if (index != -1) {
inventoryChanges.MiscItems ??= [];
inventoryChanges.MiscItems.push({ ItemType: item, ItemCount: inventory.MiscItems[index].ItemCount * -1 });
inventory.MiscItems.splice(index, 1);
}
}
await inventory.save();
res.json({ InventoryChanges: inventoryChanges });
};
interface IForceRemoveItemRequest {
items: string[];
}

View File

@ -19,7 +19,7 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
_id: new Types.ObjectId(),
pf: "/Lotus/Levels/ClanDojo/DojoHall.level",
ppf: "",
CompletionTime: new Date(Date.now()),
CompletionTime: new Date(Date.now() - 1000),
DecoCapacity: 600
});
await guild.save();

View File

@ -11,7 +11,11 @@ export const getVoidProjectionRewardsController: RequestHandler = async (req, re
if (data.ParticipantInfo.QualifiesForReward && !data.ParticipantInfo.HaveRewardResponse) {
const inventory = await getInventory(accountId);
await crackRelic(inventory, data.ParticipantInfo);
const reward = await crackRelic(inventory, data.ParticipantInfo);
if (!inventory.MissionRelicRewards || inventory.MissionRelicRewards.length >= data.CurrentWave) {
inventory.MissionRelicRewards = [];
}
inventory.MissionRelicRewards.push({ ItemType: reward.type, ItemCount: reward.itemCount });
await inventory.save();
}

View File

@ -1,12 +1,13 @@
import type { RequestHandler } from "express";
import type { Request, RequestHandler } from "express";
import { Inbox } from "../../models/inboxModel.ts";
import {
createMessage,
createNewEventMessages,
deleteAllMessagesRead,
deleteAllMessagesReadNonCin,
deleteMessageRead,
getAllMessagesSorted,
getMessage
getMessage,
type IMessageCreationTemplate
} from "../../services/inboxService.ts";
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "../../services/loginService.ts";
import {
@ -21,6 +22,9 @@ import { ExportFlavour } from "warframe-public-export-plus";
import { handleStoreItemAcquisition } from "../../services/purchaseService.ts";
import { fromStoreItem, isStoreItem } from "../../services/itemDataService.ts";
import type { IOid } from "../../types/commonTypes.ts";
import { unixTimesInMs } from "../../constants/timeConstants.ts";
import { config } from "../../services/configService.ts";
import { Types } from "mongoose";
export const inboxController: RequestHandler = async (req, res) => {
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
@ -31,11 +35,11 @@ export const inboxController: RequestHandler = async (req, res) => {
if (deleteId) {
if (deleteId === "DeleteAllRead") {
await deleteAllMessagesRead(accountId);
res.status(200).end();
return;
} else if (deleteId === "DeleteAllReadNonCin") {
await deleteAllMessagesReadNonCin(accountId);
} else {
await deleteMessageRead(parseOid(deleteId as string));
}
await deleteMessageRead(parseOid(deleteId as string));
res.status(200).end();
} else if (messageId) {
const message = await getMessage(parseOid(messageId as string));
@ -134,6 +138,119 @@ export const inboxController: RequestHandler = async (req, res) => {
}
};
const createNewEventMessages = async (req: Request): Promise<void> => {
const account = await getAccountForRequest(req);
const newEventMessages: IMessageCreationTemplate[] = [];
// Baro
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
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",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
startDate: new Date(baroActualStart),
endDate: new Date(baroStart + unixTimesInMs.day * 14),
CrossPlatform: true,
arg: [
{
Key: "NODE_NAME",
Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
}
],
date: new Date(baroActualStart)
});
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
const promises = [];
if (config.worldState?.creditBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleCreditsName",
msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.affinityBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleAffinityName",
msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.resourceBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleResourceName",
msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.galleonOfGhouls) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
})()
);
}
await Promise.all(promises);
if (newEventMessages.length === 0) {
return;
}
await createMessage(account._id, newEventMessages);
const latestEventMessage = newEventMessages.reduce((prev, current) =>
prev.startDate! > current.startDate! ? prev : current
);
account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
await account.save();
};
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
const parseOid = (oid: string): string => {
if (oid[0] == "{") {

View File

@ -177,7 +177,7 @@ export const inventoryController: RequestHandler = async (request, response) =>
}
}
cleanupInventory(inventory);
await cleanupInventory(inventory);
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
//await inventory.save();
@ -335,15 +335,16 @@ export const getInventoryResponse = async (
}
if (config.unlockAllSkins) {
const missingWeaponSkins = new Set(Object.keys(ExportCustoms));
inventoryResponse.WeaponSkins.forEach(x => missingWeaponSkins.delete(x.ItemType));
for (const uniqueName of missingWeaponSkins) {
inventoryResponse.WeaponSkins.push({
ItemId: {
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
},
ItemType: uniqueName
});
const ownedWeaponSkins = new Set<string>(inventoryResponse.WeaponSkins.map(x => x.ItemType));
for (const [uniqueName, meta] of Object.entries(ExportCustoms)) {
if (!meta.alwaysAvailable && !ownedWeaponSkins.has(uniqueName)) {
inventoryResponse.WeaponSkins.push({
ItemId: {
$oid: "ca70ca70ca70ca70" + catBreadHash(uniqueName).toString(16).padStart(8, "0")
},
ItemType: uniqueName
});
}
}
}
@ -460,6 +461,9 @@ export const getInventoryResponse = async (
toLegacyOid(id);
}
}
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
}
}
}

View File

@ -9,6 +9,9 @@ import type { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "../../
import { logger } from "../../utils/logger.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts";
import { handleNonceInvalidation } from "../../services/wsService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { createMessage } from "../../services/inboxService.ts";
import { fromStoreItem } from "../../services/itemDataService.ts";
export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
@ -76,6 +79,24 @@ export const loginController: RequestHandler = async (request, response) => {
handleNonceInvalidation(account._id.toString());
// If the client crashed during an endless fissure mission, discharge rewards to an inbox message. (https://www.reddit.com/r/Warframe/comments/5uwwjm/til_if_you_crash_during_a_fissure_you_keep_any/)
const inventory = await getInventory(account._id.toString(), "MissionRelicRewards");
if (inventory.MissionRelicRewards) {
await createMessage(account._id, [
{
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Menu/VoidProjectionItemsMessage",
sub: "/Lotus/Language/Menu/VoidProjectionItemsSubject",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
countedAtt: inventory.MissionRelicRewards.map(x => ({ ...x, ItemType: fromStoreItem(x.ItemType) })),
attVisualOnly: true,
highPriority: true // TOVERIFY
}
]);
inventory.MissionRelicRewards = undefined;
await inventory.save();
}
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
};

View File

@ -129,14 +129,22 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
res.json(deltas);
} else if (missionReport.RewardInfo) {
logger.debug(`classic mission completion, sending everything`);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
const inventoryResponse = await getInventoryResponse(
inventory,
"xpBasedLevelCapDisabled" in req.query,
account.BuildLabel
);
res.json({
InventoryJson: JSON.stringify(inventoryResponse),
...deltas
} satisfies IMissionInventoryUpdateResponse);
} else {
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`);
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
const inventoryResponse = await getInventoryResponse(
inventory,
"xpBasedLevelCapDisabled" in req.query,
account.BuildLabel
);
res.json({
InventoryJson: JSON.stringify(inventoryResponse)
} satisfies IMissionInventoryUpdateResponseBackToDryDock);

View File

@ -1,4 +1,4 @@
import { fromDbOid, version_compare } from "../../helpers/inventoryHelpers.ts";
import { fromDbOid, toMongoDate, version_compare } from "../../helpers/inventoryHelpers.ts";
import type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts";
import {
antivirusMods,
@ -149,7 +149,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;
@ -307,6 +310,26 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({
target: inventory.toJSON().Nemesis
});
} else if ((req.query.mode as string) == "t") {
const inventory = await getInventory(account._id.toString(), "LastNemesisAllySpawnTime");
//const body = getJSONfromString<IUpdateAllySpawnTimeRequest>(String(req.body));
const now = new Date(Math.trunc(Date.now() / 1000) * 1000);
inventory.LastNemesisAllySpawnTime = now;
await inventory.save();
res.json({
NewTime: toMongoDate(now)
} satisfies IUpdateAllySpawnTimeResponse);
} else if ((req.query.mode as string) == "d") {
const inventory = await getInventory(account._id.toString(), "NemesisHistory");
const body = getJSONfromString<IRelinquishAdversariesRequest>(String(req.body));
for (const fp of body.nemesisFingerprints) {
const index = inventory.NemesisHistory!.findIndex(x => x.fp == fp);
if (index != -1) {
inventory.NemesisHistory!.splice(index, 1);
}
}
await inventory.save();
res.json(body);
} else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(account._id.toString(), "Nemesis");
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
@ -444,3 +467,15 @@ const consumeModCharge = (
response.UpgradeNew.push(true);
}
};
interface IRelinquishAdversariesRequest {
nemesisFingerprints: (bigint | number)[];
}
// interface IUpdateAllySpawnTimeRequest {
// LastSpawnTime: IMongoDate;
// }
interface IUpdateAllySpawnTimeResponse {
NewTime: IMongoDate;
}

View File

@ -10,6 +10,7 @@ import {
import { createMessage } from "../../services/inboxService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { GuildPermission } from "../../types/guildTypes.ts";
import type { RequestHandler } from "express";
@ -85,6 +86,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
ItemToRemove: "/Lotus/Types/Keys/DojoKey",
RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint"
});
sendWsBroadcastTo(payload.userId, { update_inventory: true });
};
interface IRemoveFromGuildRequest {

View File

@ -77,6 +77,9 @@ export const sellController: RequestHandler = async (req, res) => {
requiredFields.add("CrewShipSalvagedWeaponSkins");
}
}
if (payload.Items.WeaponSkins) {
requiredFields.add("WeaponSkins");
}
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
// Give currency
@ -302,6 +305,11 @@ export const sellController: RequestHandler = async (req, res) => {
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
});
}
if (payload.Items.WeaponSkins) {
payload.Items.WeaponSkins.forEach(sellItem => {
inventory.WeaponSkins.pull({ _id: sellItem.String });
});
}
await inventory.save();
res.json({
@ -335,6 +343,7 @@ interface ISellRequest {
CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[];
FusionTreasures?: ISellItem[];
WeaponSkins?: ISellItem[]; // SNS specific field
};
SellPrice: number;
SellCurrency:

View File

@ -57,7 +57,7 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
await guild.save();
}
if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.03.24.20.24") > 0) {
if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.11.04.18.58") > 0) {
res.json({ IsLongMOTD, MOTD });
} else {
res.send(MOTD).end();

View File

@ -13,6 +13,7 @@ import { Types } from "mongoose";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { getAccountForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { fromOid } from "../../helpers/inventoryHelpers.ts";
interface IStartDojoRecipeRequest {
PlacedComponent: IDojoComponentClient;
@ -50,7 +51,7 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
_id: componentId,
pf: request.PlacedComponent.pf,
ppf: request.PlacedComponent.ppf,
pi: new Types.ObjectId(request.PlacedComponent.pi!.$oid),
pi: new Types.ObjectId(fromOid(request.PlacedComponent.pi!)),
op: request.PlacedComponent.op,
pp: request.PlacedComponent.pp,
DecoCapacity: room?.decoCapacity

View File

@ -5,6 +5,7 @@ import type { IUpdateQuestRequest } from "../../services/questService.ts";
import { updateQuestKey } from "../../services/questService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
export const updateQuestController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId);
@ -29,4 +30,5 @@ export const updateQuestController: RequestHandler = async (req, res) => {
await inventory.save();
res.send(updateQuestResponse);
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -1,16 +1,16 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { hasAccessToDojo, getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
import { GuildPermission } from "../../types/guildTypes.ts";
import type { ITypeCount } from "../../types/commonTypes.ts";
export const addVaultDecoRecipeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as ITypeCount[];
const inventory = await getInventory(accountId, "LevelKeys GuildId");
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.status(400).send("-1").end();
return;
}

View File

@ -1,5 +1,5 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getAccountForRequest } from "../../services/loginService.ts";
import { Account, Ignore } from "../../models/loginModel.ts";
import { Inbox } from "../../models/inboxModel.ts";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
@ -12,33 +12,44 @@ import { Leaderboard } from "../../models/leaderboardModel.ts";
import { deleteGuild } from "../../services/guildService.ts";
import { Friendship } from "../../models/friendModel.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { config } from "../../services/configService.ts";
import { saveConfig } from "../../services/configWriterService.ts";
export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const account = await getAccountForRequest(req);
// If this account is an admin, remove it from administratorNames
if (config.administratorNames) {
const adminIndex = config.administratorNames.indexOf(account.DisplayName);
if (adminIndex != -1) {
config.administratorNames.splice(adminIndex, 1);
await saveConfig();
}
}
// If account is the founding warlord of a guild, delete that guild as well.
const guildMember = await GuildMember.findOne({ accountId, rank: 0, status: 0 });
const guildMember = await GuildMember.findOne({ accountId: account._id, rank: 0, status: 0 });
if (guildMember) {
await deleteGuild(guildMember.guildId);
}
await Promise.all([
Account.deleteOne({ _id: accountId }),
Friendship.deleteMany({ owner: accountId }),
Friendship.deleteMany({ friend: accountId }),
GuildMember.deleteMany({ accountId: accountId }),
Ignore.deleteMany({ ignorer: accountId }),
Ignore.deleteMany({ ignoree: accountId }),
Inbox.deleteMany({ ownerId: accountId }),
Inventory.deleteOne({ accountOwnerId: accountId }),
Leaderboard.deleteMany({ ownerId: accountId }),
Loadout.deleteOne({ loadoutOwnerId: accountId }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: accountId }),
Ship.deleteMany({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId })
Account.deleteOne({ _id: account._id }),
Friendship.deleteMany({ owner: account._id }),
Friendship.deleteMany({ friend: account._id }),
GuildMember.deleteMany({ accountId: account._id }),
Ignore.deleteMany({ ignorer: account._id }),
Ignore.deleteMany({ ignoree: account._id }),
Inbox.deleteMany({ ownerId: account._id }),
Inventory.deleteOne({ accountOwnerId: account._id }),
Leaderboard.deleteMany({ ownerId: account._id }),
Loadout.deleteOne({ loadoutOwnerId: account._id }),
PersonalRooms.deleteOne({ personalRoomsOwnerId: account._id }),
Ship.deleteMany({ ShipOwnerId: account._id }),
Stats.deleteOne({ accountOwnerId: account._id })
]);
sendWsBroadcastTo(accountId, { logged_out: true });
sendWsBroadcastTo(account._id.toString(), { logged_out: true });
res.end();
};

View File

@ -1,36 +0,0 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
const DEFAULT_UPGRADE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
export const editSuitInvigorationUpgradeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const { oid, data } = req.body as {
oid: string;
data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
};
};
const inventory = await getInventory(accountId);
const suit = inventory.Suits.id(oid)!;
if (data) {
suit.DefensiveUpgrade = data.DefensiveUpgrade;
suit.OffensiveUpgrade = data.OffensiveUpgrade;
if (data.UpgradesExpiry) {
suit.UpgradesExpiry = new Date(data.UpgradesExpiry);
} else {
suit.UpgradesExpiry = new Date(Date.now() + DEFAULT_UPGRADE_EXPIRY_MS);
}
} else {
suit.DefensiveUpgrade = undefined;
suit.OffensiveUpgrade = undefined;
suit.UpgradesExpiry = undefined;
}
await inventory.save();
res.end();
broadcastInventoryUpdate(req);
};

View File

@ -66,6 +66,7 @@ interface ItemLists {
VaultDecoRecipes: ListedItem[];
FlavourItems: ListedItem[];
ShipDecorations: ListedItem[];
WeaponSkins: ListedItem[];
//circuitGameModes: ListedItem[];
}
@ -107,7 +108,8 @@ const getItemListsController: RequestHandler = (req, response) => {
TechProjects: [],
VaultDecoRecipes: [],
FlavourItems: [],
ShipDecorations: []
ShipDecorations: [],
WeaponSkins: []
/*circuitGameModes: [
{
uniqueName: "Survival",
@ -298,10 +300,18 @@ const getItemListsController: RequestHandler = (req, response) => {
});
}
for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
if (
item.productCategory == "WeaponSkins" &&
!uniqueName.startsWith("/Lotus/Types/Game/Lotus") && // Base Items
!uniqueName.endsWith("ProjectileSkin") && // UnrealTournament ProjectileSkins
!uniqueName.endsWith("Coat") // Frost Prime stuff
) {
res.WeaponSkins.push({
uniqueName: uniqueName,
name: getString(item.name, lang),
alwaysAvailable: item.alwaysAvailable
});
}
}
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {

View File

@ -115,7 +115,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
if (stage > 0) {
await giveKeyChainStageTriggered(inventory, {
KeyChain: questKey.ItemType,
ChainStage: stage
ChainStage: stage - 1
});
}
}

View File

@ -1,22 +1,31 @@
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { sendWsBroadcastEx, sendWsBroadcastTo } from "../../services/wsService.ts";
import type { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express";
import { logger } from "../../utils/logger.ts";
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;
if (payload.value == undefined) {
logger.warn(`Aborting setting ${payload.key} as undefined!`);
return;
}
inventory[payload.key] = payload.value as never;
await inventory.save();
res.end();
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
} else {
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
}
};
interface ISetAccountCheatRequest {
key: keyof IAccountCheats;
value: boolean;
value: IAccountCheats[keyof IAccountCheats];
}

View File

@ -1,44 +1,19 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { ExportBoosters } from "warframe-public-export-plus";
import type { IBooster } from "../../types/inventoryTypes/inventoryTypes.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
const I32_MAX = 0x7fffffff;
export const setBoosterController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as { ItemType: string; ExpiryDate: number }[];
const requests = req.body as IBooster[];
const inventory = await getInventory(accountId, "Boosters");
const boosters = inventory.Boosters;
if (
requests.some(request => {
if (typeof request.ItemType !== "string") return true;
if (Object.entries(ExportBoosters).find(([_, item]) => item.typeName === request.ItemType) === undefined)
return true;
if (typeof request.ExpiryDate !== "number") return true;
if (request.ExpiryDate < 0 || request.ExpiryDate > I32_MAX) return true;
return false;
})
) {
res.status(400).send("Invalid ItemType provided.");
return;
}
const now = Math.trunc(Date.now() / 1000);
for (const { ItemType, ExpiryDate } of requests) {
if (ExpiryDate <= now) {
// remove expired boosters
const index = boosters.findIndex(item => item.ItemType === ItemType);
if (index !== -1) {
boosters.splice(index, 1);
}
for (const request of requests) {
const index = inventory.Boosters.findIndex(item => item.ItemType === request.ItemType);
if (index !== -1) {
inventory.Boosters[index].ExpiryDate = request.ExpiryDate;
} else {
const boosterItem = boosters.find(item => item.ItemType === ItemType);
if (boosterItem) {
boosterItem.ExpiryDate = ExpiryDate;
} else {
boosters.push({ ItemType, ExpiryDate });
}
inventory.Boosters.push(request);
}
}
await inventory.save();

View File

@ -1,5 +1,5 @@
import { GuildMember } from "../../models/guildModel.ts";
import { getGuildForRequestEx, hasAccessToDojo } from "../../services/guildService.ts";
import { getGuildForRequestEx } from "../../services/guildService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { IGuildCheats } from "../../types/guildTypes.ts";
@ -8,12 +8,12 @@ import type { RequestHandler } from "express";
export const setGuildCheatController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const payload = req.body as ISetGuildCheatRequest;
const inventory = await getInventory(accountId, `${payload.key} GuildId LevelKeys`);
const inventory = await getInventory(accountId, `GuildId`);
const guild = await getGuildForRequestEx(req, inventory);
const member = await GuildMember.findOne({ accountId: accountId, guildId: guild._id });
if (member) {
if (!hasAccessToDojo(inventory) || member.rank > 1) {
if (member.rank > 1) {
res.end();
return;
}

View File

@ -0,0 +1,27 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const setInvigorationController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as ISetInvigorationRequest;
const inventory = await getInventory(accountId, "Suits");
const suit = inventory.Suits.id(request.oid);
if (suit) {
const hasUpgrades = request.DefensiveUpgrade && request.OffensiveUpgrade && request.UpgradesExpiry;
suit.DefensiveUpgrade = hasUpgrades ? request.DefensiveUpgrade : undefined;
suit.OffensiveUpgrade = hasUpgrades ? request.OffensiveUpgrade : undefined;
suit.UpgradesExpiry = hasUpgrades ? new Date(request.UpgradesExpiry) : undefined;
await inventory.save();
broadcastInventoryUpdate(req);
}
res.end();
};
interface ISetInvigorationRequest {
oid: string;
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry: number;
}

View File

@ -2,7 +2,6 @@ import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import {
hasAccessToDojo,
getGuildForRequestEx,
setGuildTechLogState,
processFundedGuildTechProject,
@ -19,9 +18,9 @@ import { GuildMember } from "../../models/guildModel.ts";
export const addTechProjectController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as ITechProjectRequest[];
const inventory = await getInventory(accountId, "LevelKeys GuildId");
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}
@ -54,9 +53,9 @@ export const addTechProjectController: RequestHandler = async (req, res) => {
export const removeTechProjectController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as ITechProjectRequest[];
const inventory = await getInventory(accountId, "LevelKeys GuildId");
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}
@ -74,13 +73,13 @@ export const removeTechProjectController: RequestHandler = async (req, res) => {
export const fundTechProjectController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as ITechProjectRequest[];
const inventory = await getInventory(accountId, "LevelKeys GuildId");
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
const guildMember = (await GuildMember.findOne(
{ accountId, guildId: guild._id },
"RegularCreditsContributed MiscItemsContributed"
))!;
if (!hasAccessToDojo(inventory)) {
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}
@ -105,9 +104,9 @@ export const fundTechProjectController: RequestHandler = async (req, res) => {
export const completeTechProjectsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const requests = req.body as ITechProjectRequest[];
const inventory = await getInventory(accountId, "LevelKeys GuildId");
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!hasAccessToDojo(inventory)) {
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
res.status(400).send("-1").end();
return;
}

View File

@ -20,13 +20,14 @@ import type {
} from "../../types/inventoryTypes/inventoryTypes.ts";
import { LoadoutIndex } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express";
import { catBreadHash, getJSONfromString } from "../../helpers/stringHelpers.ts";
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import type { IStatsClient } from "../../types/statTypes.ts";
import { toStoreItem } from "../../services/itemDataService.ts";
import type { FlattenMaps } from "mongoose";
import type { IEquipmentClient } from "../../types/equipmentTypes.ts";
import type { ILoadoutConfigClient } from "../../types/saveLoadoutTypes.ts";
import { skinLookupTable } from "../../helpers/skinLookupTable.ts";
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
const account = await Account.findById(playerId, "DisplayName");
@ -261,8 +262,6 @@ interface IXPComponentClient {
locTags?: Record<string, string>;
}
let skinLookupTable: Record<number, string> | undefined;
const resolveAndCollectSkins = (
inventory: TInventoryDatabaseDocument,
skins: Set<string>,
@ -274,12 +273,6 @@ const resolveAndCollectSkins = (
// Resolve oids to type names
if (config.Skins[i].length == 24) {
if (config.Skins[i].substring(0, 16) == "ca70ca70ca70ca70") {
if (!skinLookupTable) {
skinLookupTable = {};
for (const key of Object.keys(ExportCustoms)) {
skinLookupTable[catBreadHash(key)] = key;
}
}
config.Skins[i] = skinLookupTable[parseInt(config.Skins[i].substring(16), 16)];
} else {
const skinItem = inventory.WeaponSkins.id(config.Skins[i]);

View File

@ -54,6 +54,9 @@ export const crackRelic = async (
(await handleStoreItemAcquisition(reward.type, inventory, reward.itemCount)).InventoryChanges
);
// Client has picked its own reward (for lack of choice)
participant.ChosenRewardOwner = participant.AccountId;
return reward;
};

View File

@ -0,0 +1,8 @@
import { ExportCustoms } from "warframe-public-export-plus";
import { catBreadHash } from "./stringHelpers.ts";
export const skinLookupTable: Record<number, string> = {};
for (const key of Object.keys(ExportCustoms)) {
skinLookupTable[catBreadHash(key)] = key;
}

View File

@ -1462,11 +1462,20 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
flawlessRelicsAlwaysGiveSilverReward: Boolean,
radiantRelicsAlwaysGiveGoldReward: Boolean,
disableDailyTribute: Boolean,
nemesisHenchmenKillsMultiplierGrineer: Number,
nemesisHenchmenKillsMultiplierCorpus: Number,
nemesisAntivirusGainMultiplier: Number,
nemesisHintProgressMultiplierGrineer: Number,
nemesisHintProgressMultiplierCorpus: Number,
nemesisExtraWeapon: Number,
SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 },
RewardSeed: BigInt,
// Temporary data so we can show all relic rewards from an endless mission at EOM
MissionRelicRewards: { type: [typeCountSchema], default: undefined },
//Credit
RegularCredits: { type: Number, default: 0 },
//Platinum
@ -1734,7 +1743,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema,
NemesisHistory: { type: [nemesisSchema], default: undefined },
//LastNemesisAllySpawnTime: Schema.Types.Mixed,
LastNemesisAllySpawnTime: { type: Date, default: undefined },
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: settingsSchema,
@ -1835,6 +1844,7 @@ inventorySchema.set("toJSON", {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;
delete returnedObject.MissionRelicRewards;
const inventoryDatabase = returnedObject as Partial<IInventoryDatabase>;
const inventoryResponse = returnedObject as IInventoryClient;
@ -1854,6 +1864,9 @@ inventorySchema.set("toJSON", {
if (inventoryDatabase.BlessingCooldown) {
inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown);
}
if (inventoryDatabase.LastNemesisAllySpawnTime) {
inventoryResponse.LastNemesisAllySpawnTime = toMongoDate(inventoryDatabase.LastNemesisAllySpawnTime);
}
if (inventoryDatabase.NextRefill) {
inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill);
}

View File

@ -53,6 +53,7 @@ import { evolveWeaponController } from "../controllers/api/evolveWeaponControlle
import { findSessionsController } from "../controllers/api/findSessionsController.ts";
import { fishmongerController } from "../controllers/api/fishmongerController.ts";
import { focusController } from "../controllers/api/focusController.ts";
import { forceRemoveItemController } from "../controllers/api/forceRemoveItemController.ts";
import { fusionTreasuresController } from "../controllers/api/fusionTreasuresController.ts";
import { gardeningController } from "../controllers/api/gardeningController.ts";
import { genericUpdateController } from "../controllers/api/genericUpdateController.ts";
@ -66,8 +67,8 @@ import { getGuildEventScoreController } from "../controllers/api/getGuildEventSc
import { getGuildLogController } from "../controllers/api/getGuildLogController.ts";
import { getIgnoredUsersController } from "../controllers/api/getIgnoredUsersController.ts";
import { getNewRewardSeedController } from "../controllers/api/getNewRewardSeedController.ts";
import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts";
import { getPastWeeklyChallengesController } from "../controllers/api/getPastWeeklyChallengesController.ts";
import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts";
import { getShipController } from "../controllers/api/getShipController.ts";
import { getVendorInfoController } from "../controllers/api/getVendorInfoController.ts";
import { getVoidProjectionRewardsController } from "../controllers/api/getVoidProjectionRewardsController.ts";
@ -273,6 +274,7 @@ apiRouter.post("/evolveWeapon.php", evolveWeaponController);
apiRouter.post("/findSessions.php", findSessionsController);
apiRouter.post("/fishmonger.php", fishmongerController);
apiRouter.post("/focus.php", focusController);
apiRouter.post("/forceRemoveItem.php", forceRemoveItemController);
apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
apiRouter.post("/gardening.php", gardeningController);
apiRouter.post("/genericUpdate.php", genericUpdateController);

View File

@ -43,7 +43,7 @@ import { setBoosterController } from "../controllers/custom/setBoosterController
import { updateFingerprintController } from "../controllers/custom/updateFingerprintController.ts";
import { unlockLevelCapController } from "../controllers/custom/unlockLevelCapController.ts";
import { changeModularPartsController } from "../controllers/custom/changeModularPartsController.ts";
import { editSuitInvigorationUpgradeController } from "../controllers/custom/editSuitInvigorationUpgradeController.ts";
import { setInvigorationController } from "../controllers/custom/setInvigorationController.ts";
import { setAccountCheatController } from "../controllers/custom/setAccountCheatController.ts";
import { setGuildCheatController } from "../controllers/custom/setGuildCheatController.ts";
@ -92,7 +92,7 @@ customRouter.post("/setBooster", setBoosterController);
customRouter.post("/updateFingerprint", updateFingerprintController);
customRouter.post("/unlockLevelCap", unlockLevelCapController);
customRouter.post("/changeModularParts", changeModularPartsController);
customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController);
customRouter.post("/setInvigoration", setInvigorationController);
customRouter.post("/setAccountCheat", setAccountCheatController);
customRouter.post("/setGuildCheat", setGuildCheatController);

View File

@ -48,8 +48,14 @@ export interface IConfig {
anniversary?: number;
hallowedNightmares?: boolean;
hallowedNightmaresRewardsOverride?: number;
naberusNightsOverride?: boolean;
proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number;
voidCorruption2025Week1?: boolean;
voidCorruption2025Week2?: boolean;
voidCorruption2025Week3?: boolean;
voidCorruption2025Week4?: boolean;
qtccAlerts?: boolean;
galleonOfGhouls?: number;
ghoulEmergenceOverride?: boolean;
plagueStarOverride?: boolean;

View File

@ -22,7 +22,7 @@ import type {
ITechProjectDatabase
} from "../types/guildTypes.ts";
import { GuildPermission } from "../types/guildTypes.ts";
import { toMongoDate, toOid, toOid2 } from "../helpers/inventoryHelpers.ts";
import { toMongoDate, toOid, toOid2, version_compare } from "../helpers/inventoryHelpers.ts";
import type { Types } from "mongoose";
import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
@ -68,9 +68,15 @@ export const getGuildClient = async (
let missingEntry = true;
const dataFillInPromises: Promise<void>[] = [];
for (const guildMember of guildMembers) {
// Use 1-based indexing for clan ranks for versions before U24. In my testing, 2018.06.14.23.21 and below used 1-based indexing and 2019.04.04.21.31 and above used 0-based indexing. I didn't narrow it down further, but I think U24 is a good spot for them to have changed it.
let rankBase = 0;
if (account.BuildLabel && version_compare(account.BuildLabel, "2018.11.08.14.45") < 0) {
rankBase += 1;
}
const member: IGuildMemberClient = {
_id: toOid2(guildMember.accountId, account.BuildLabel),
Rank: guildMember.rank,
Rank: guildMember.rank + rankBase,
Status: guildMember.status,
Note: guildMember.RequestMsg,
RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined

View File

@ -6,6 +6,7 @@ import type {
} from "../types/inventoryTypes/commonInventoryTypes.ts";
import type { IMongoDate } from "../types/commonTypes.ts";
import type {
IBooster,
IDialogueClient,
IDialogueDatabase,
IDialogueHistoryClient,
@ -72,6 +73,7 @@ import type {
ITailorShop,
ITailorShopDatabase
} from "../types/personalRoomsTypes.ts";
import { fromMongoDate } from "../helpers/inventoryHelpers.ts";
const convertDate = (value: IMongoDate): Date => {
return new Date(parseInt(value.$date.$numberLong));
@ -325,7 +327,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"GiftsRemaining",
"ChallengesFixVersion",
"Founder",
"Guide"
"Guide",
"BountyScore",
"EntratiVaultCountLastPeriod",
"EntratiLabConquestUnlocked",
"EntratiLabConquestHardModeStatus",
"EntratiLabConquestCacheScoreMission",
"EchoesHexConquestUnlocked",
"EchoesHexConquestHardModeStatus",
"EchoesHexConquestCacheScoreMission"
] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
@ -353,12 +363,28 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"NodeIntrosCompleted",
"DeathMarks",
"Wishlist",
"NemesisAbandonedRewards"
"NemesisAbandonedRewards",
"EntratiLabConquestActiveFrameVariants",
"EchoesHexConquestActiveFrameVariants",
"EchoesHexConquestActiveStickers"
] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
}
}
// IMongoDate
for (const key of [
"Created",
"TrainingDate",
"BlessingCooldown",
"LastNemesisAllySpawnTime",
"NextRefill",
"EntratiVaultCountResetDate"
] as const) {
if (client[key] !== undefined) {
db[key] = fromMongoDate(client[key]);
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {
@ -463,6 +489,9 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
if (client.Accolades !== undefined) {
db.Accolades = client.Accolades;
}
if (client.Boosters !== undefined) {
replaceArray<IBooster>(db.Boosters, client.Boosters);
}
};
export const importLoadOutConfig = (client: ILoadoutConfigClient): ILoadoutConfigDatabase => {

View File

@ -1,11 +1,6 @@
import type { IMessageDatabase } from "../models/inboxModel.ts";
import { Inbox } from "../models/inboxModel.ts";
import { getAccountForRequest } from "./loginService.ts";
import type { HydratedDocument } from "mongoose";
import { Types } from "mongoose";
import type { Request } from "express";
import { unixTimesInMs } from "../constants/timeConstants.ts";
import { config } from "./configService.ts";
import type { HydratedDocument, Types } from "mongoose";
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
@ -29,117 +24,8 @@ export const deleteAllMessagesRead = async (accountId: string): Promise<void> =>
await Inbox.deleteMany({ ownerId: accountId, r: true });
};
export const createNewEventMessages = async (req: Request): Promise<void> => {
const account = await getAccountForRequest(req);
const newEventMessages: IMessageCreationTemplate[] = [];
// Baro
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
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",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
startDate: new Date(baroActualStart),
endDate: new Date(baroStart + unixTimesInMs.day * 14),
CrossPlatform: true,
arg: [
{
Key: "NODE_NAME",
Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
}
],
date: new Date(baroActualStart)
});
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
const promises = [];
if (config.worldState?.creditBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleCreditsName",
msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.affinityBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleAffinityName",
msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.resourceBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleResourceName",
msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.galleonOfGhouls) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
})()
);
}
await Promise.all(promises);
if (newEventMessages.length === 0) {
return;
}
await createMessage(account._id, newEventMessages);
const latestEventMessage = newEventMessages.reduce((prev, current) =>
prev.startDate! > current.startDate! ? prev : current
);
account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
await account.save();
export const deleteAllMessagesReadNonCin = async (accountId: string): Promise<void> => {
await Inbox.deleteMany({ ownerId: accountId, r: true, cinematic: null });
};
export const createMessage = async (

View File

@ -92,6 +92,7 @@ import type {
} from "../types/equipmentTypes.ts";
import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts";
import type { ITypeCount } from "../types/commonTypes.ts";
import { skinLookupTable } from "../helpers/skinLookupTable.ts";
export const createInventory = async (
accountOwnerId: Types.ObjectId,
@ -1489,7 +1490,13 @@ export const addSkin = (
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
if (typeName == "/Lotus/Upgrades/Skins/Clan/BountyHunterBadgeItem") {
logger.debug(`account already owns stratos emblem, increasing bounty score instead`);
inventory.BountyScore ??= 0;
inventory.BountyScore += 1;
} else {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
}
} else {
const index =
inventory.WeaponSkins.push({
@ -2254,7 +2261,7 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void
});
};
export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => {
export const cleanupInventory = async (inventory: TInventoryDatabaseDocument): Promise<void> => {
inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid);
let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
@ -2308,6 +2315,99 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
logger.debug(`removed ModularParts from ${numFixed} non-modular items`);
}
}
{
const weaponMap = new Map<string, string>();
for (const skin of inventory.WeaponSkins) {
weaponMap.set(skin.ItemType, skin._id.toString());
}
const itemsToAdd = new Set<string>();
for (const key of equipmentKeys) {
if (key in inventory) {
for (const equipment of inventory[key]) {
for (const config of equipment.Configs) {
if (config.Skins) collectSkins(config.Skins, weaponMap, itemsToAdd);
}
}
}
}
for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
if (key in inventory) {
for (const loadOut of inventory[key]) {
if (loadOut.Skins) collectSkins(loadOut.Skins, weaponMap, itemsToAdd);
}
}
}
if (inventory.LotusCustomization?.Skins)
collectSkins(inventory.LotusCustomization.Skins, weaponMap, itemsToAdd);
if (itemsToAdd.size > 0) {
logger.debug(`Adding ${itemsToAdd.size} items due to migration from unlockAllSkins cheat`);
const inventoryChanges = await addItems(inventory, Array.from(itemsToAdd));
if (inventoryChanges.WeaponSkins) {
for (const skin of inventoryChanges.WeaponSkins as IWeaponSkinClient[]) {
weaponMap.set(skin.ItemType, skin.ItemId.toString());
}
}
for (const key of equipmentKeys) {
if (key in inventory) {
for (const equipment of inventory[key]) {
for (const config of equipment.Configs) {
if (config.Skins) replaceSkinIds(config.Skins, weaponMap);
}
}
}
}
for (const key of ["AdultOperatorLoadOuts", "OperatorLoadOuts", "KahlLoadOuts"] as const) {
if (key in inventory) {
for (const loadOut of inventory[key]) {
if (loadOut.Skins) replaceSkinIds(loadOut.Skins, weaponMap);
}
}
}
if (inventory.LotusCustomization?.Skins) replaceSkinIds(inventory.LotusCustomization.Skins, weaponMap);
}
}
};
const collectSkins = (skins: string[], weaponMap: Map<string, string>, itemsToAdd: Set<string>): void => {
for (const skinId of skins) {
if (skinId.startsWith("ca70ca70ca70ca70")) {
const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
if (!weaponMap.has(typeName)) {
const { requirement } = ExportCustoms[typeName];
if (typeof requirement == "string") {
itemsToAdd.add(requirement);
} else {
itemsToAdd.add(typeName);
}
}
}
}
};
const replaceSkinIds = (skins: string[], weaponMap: Map<string, string>): void => {
for (let i = 0; i < skins.length; i++) {
const skinId = skins[i];
if (skinId.startsWith("ca70ca70ca70ca70")) {
const typeName = skinLookupTable[parseInt(skinId.slice(16), 16)];
const inventoryId = weaponMap.get(typeName);
if (inventoryId) {
skins[i] = inventoryId;
} else if (typeName in ExportCustoms) {
const { requirement } = ExportCustoms[typeName];
skins[i] = typeof requirement == "string" ? typeName : "";
}
}
}
};
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {

View File

@ -210,10 +210,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.nemesisHenchmenKillsMultiplierGrineer ?? 1;
break;
case "FC_CORPUS":
HenchmenKilledMultiplier = inventory.nemesisHenchmenKillsMultiplierCorpus ?? 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) {
@ -290,9 +309,6 @@ export const addMissionInventoryUpdates = async (
}
break;
}
case "Missions":
addMissionComplete(inventory, value);
break;
case "LastRegionPlayed":
if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) {
inventory.LastRegionPlayed = value;
@ -819,6 +835,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 &&
@ -827,6 +845,20 @@ export const addMissionInventoryUpdates = async (
const weaponType = manifest.weapons[inventory.Nemesis.WeaponIdx];
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,
undefined,
profile
);
att.push(randomWeapon);
}
}
}
//if (value.petLoc) {
if (profile.petHead) {
@ -870,7 +902,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);
@ -1119,6 +1151,27 @@ export const addMissionRewards = async (
let ConquestCompletedMissionsCount;
let missionCompletionCredits = 0;
if (rewardInfo.alertId) {
const alert = getWorldState().Alerts.find(x => x._id.$oid == rewardInfo.alertId);
if (!alert) {
logger.warn(`mission completed unknown alert`, { alertId: rewardInfo.alertId });
} else {
if (inventory.CompletedAlerts.includes(alert._id.$oid)) {
logger.debug(`alert ${alert._id.$oid} already completed, skipping alert reward`);
} else {
inventory.CompletedAlerts.push(alert._id.$oid);
if (alert.MissionInfo.missionReward) {
missionCompletionCredits += addFixedLevelRewards(
alert.MissionInfo.missionReward,
MissionRewards,
rewardInfo
);
}
}
}
}
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (rewardInfo.goalId) {
@ -1173,6 +1226,9 @@ export const addMissionRewards = async (
if (missions && missions.Tag in ExportRegions) {
const node = ExportRegions[missions.Tag];
// cannot add this with normal updates because { Tier: 1 } would mark the SP node as completed even on a failure
addMissionComplete(inventory, missions);
//node based credit rewards for mission completion
if (isEligibleForCreditReward(rewardInfo, missions, node)) {
const levelCreditReward = getLevelCreditRewards(node);
@ -1299,13 +1355,21 @@ export const addMissionRewards = async (
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
});
if (
voidTearWave &&
voidTearWave.Participants[0].QualifiesForReward &&
!voidTearWave.Participants[0].HaveRewardResponse
) {
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
if (voidTearWave && voidTearWave.Participants[0].QualifiesForReward) {
if (!voidTearWave.Participants[0].HaveRewardResponse) {
// non-endless fissure; giving reward now
const reward = await crackRelic(inventory, voidTearWave.Participants[0], inventoryChanges);
MissionRewards.push({ StoreItem: reward.type, ItemCount: reward.itemCount });
} else if (inventory.MissionRelicRewards) {
// endless fissure; already gave reward(s) but should still show in EOM screen
for (const reward of inventory.MissionRelicRewards) {
MissionRewards.push({
StoreItem: reward.ItemType,
ItemCount: reward.ItemCount
});
}
inventory.MissionRelicRewards = undefined;
}
}
if (strippedItems) {
@ -1400,7 +1464,9 @@ export const addMissionRewards = async (
if (inventory.Nemesis.Faction == "FC_INFESTATION") {
inventory.Nemesis.MissionCount += 1;
inventory.Nemesis.HenchmenKilled = Math.min(inventory.Nemesis.HenchmenKilled + 5, 95); // 5 progress per mission until 95
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;
@ -2438,95 +2504,95 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyA": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyB": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyD": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/LanternEndlessEventKeyC": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericEventRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloween": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBonusTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenBonus": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyHalloweenTimeAttack": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsBody",
sub: "/Lotus/Language/G1Quests/TacAlertHalloweenRewardsTitle",
icon: "/Lotus/Interface/Icons/Npcs/LotusVamp_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionOne": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionTwo": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionThree": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/RazorbackArmadaRewardBody",
sub: "/Lotus/Language/G1Quests/GenericTacAlertSmallRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProxyRebellionFour": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgDesc",
sub: "/Lotus/Language/G1Quests/GenericTacAlertBadgeRewardMsgTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchEasy": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchRewardMsgA",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionOneTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["CREDIT_REWARD"]
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatch": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionTwoTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchHard": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionThreeTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
},
"/Lotus/Types/Keys/TacAlertKeyProjectNightwatchBonus": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionRewardBody",
sub: "/Lotus/Language/G1Quests/ProjectNightwatchTacAlertMissionFourTitle",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png"
@ -2562,140 +2628,140 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
icon: "/Lotus/Interface/Icons/Npcs/Entrati/Father.png"
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2019E": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020F": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeA": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017C": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020H": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2022J": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2022RewardMsgJ",
sub: "/Lotus/Language/Messages/Anniversary2022MissionTitleJ",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025D": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeA": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgC",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2020G": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2020RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2020MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017B": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgB",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2017A": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2019RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2019MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2023K": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025ChallengeModeB": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgD",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleD",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025A": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2018D": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgG",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleG",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025C": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgF",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleF",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024L": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgA",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2024ChallengeModeB": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2021I": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2024RewardMsgH",
sub: "/Lotus/Language/Messages/Anniversary2024MissionTitleH",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
arg: ["PLAYER_NAME"]
},
"/Lotus/Types/Keys/TacAlertKeyAnniversary2025B": {
sndr: "/Lotus/Language/Bosses/Lotus",
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
msg: "/Lotus/Language/Messages/Anniversary2025RewardMsgE",
sub: "/Lotus/Language/Messages/Anniversary2025MissionTitleE",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",

View File

@ -74,7 +74,7 @@ export const updateQuestStage = (
if (!questStage) {
const questStageIndex =
quest.Progress.push({
c: questStageUpdate.c ?? 0,
c: questStageUpdate.c ?? -1,
i: questStageUpdate.i ?? false,
m: questStageUpdate.m ?? false,
b: questStageUpdate.b ?? []
@ -331,7 +331,7 @@ export const giveKeyChainMessage = async (
): Promise<void> => {
const keyChainMessage = getKeyChainMessage(keyChainInfo);
if (questKey.Progress![0].c > 0) {
if ((questKey.Progress?.[0]?.c ?? 0) > 0) {
keyChainMessage.att = [];
keyChainMessage.countedAtt = [];
}

View File

@ -19,6 +19,7 @@ import type {
ICalendarDay,
ICalendarEvent,
ICalendarSeason,
IAlert,
IGoal,
IInvasion,
ILiteSortie,
@ -112,6 +113,89 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
SORTIE_BOSS_VOR: "SolNode108"
};
const configAlerts: Record<string, IAlert> = {
voidCorruption2025Week1: {
_id: { $oid: "677d452e2f324ee7b90f8ccf" },
Activation: { $date: { $numberLong: "1736524800000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode61",
missionType: "MT_SABOTAGE",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedFireRateDamagePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipCoreSabotage",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadA",
extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/GamemodeExtraEnemySpecs/CorpusSabotageTiersA",
minEnemyLevel: 10,
maxEnemyLevel: 15
}
},
voidCorruption2025Week2: {
_id: { $oid: "677d45811daeae9de40e8c0f" },
Activation: { $date: { $numberLong: "1737129600000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SettlementNode11",
missionType: "MT_DEFENSE",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritChanceFireRatePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDefense",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSquadDefenseB",
minEnemyLevel: 20,
maxEnemyLevel: 25,
maxRotations: 2
}
},
voidCorruption2025Week3: {
_id: { $oid: "677d45a494ad716c90006b9a" },
Activation: { $date: { $numberLong: "1737734400000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode118",
missionType: "MT_ARTIFACT",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedCritDamagePistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipDisruption",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA",
extraEnemySpec: "/Lotus/Types/Game/EnemySpecs/SpecialMissionSpecs/DisruptionCorpusShip",
customAdvancedSpawners: ["/Lotus/Types/Enemies/AdvancedSpawners/ErrantSpecterInvasion"],
minEnemyLevel: 30,
maxEnemyLevel: 35
}
},
voidCorruption2025Week4: {
_id: { $oid: "677d4700682d173abb0e19fe" },
Activation: { $date: { $numberLong: "1738339200000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
MissionInfo: {
location: "SolNode4",
missionType: "MT_EXTERMINATION",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 30000,
items: ["/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/CorruptedDamageRecoilPistol"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipExterminate",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipExterminateMixed",
minEnemyLevel: 40,
maxEnemyLevel: 45
}
}
};
const eidolonJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
@ -280,6 +364,14 @@ export const getSortie = (day: number): ISortie => {
}
}
const willHaveAssassination = boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2;
if (willHaveAssassination) {
const index = nodes.indexOf(sortieBossNode[boss]);
if (index != -1) {
nodes.splice(index, 1);
}
}
const selectedNodes: ISortieMission[] = [];
const missionTypes = new Set();
@ -309,7 +401,7 @@ export const getSortie = (day: number): ISortie => {
"SORTIE_MODIFIER_BOW_ONLY"
];
if (i == 2 && boss != "SORTIE_BOSS_CORRUPTED_VOR" && rng.randomInt(0, 2) == 2) {
if (i == 2 && willHaveAssassination) {
const tileset = sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets] as TSortieTileset;
pushTilesetModifiers(modifiers, tileset);
@ -361,7 +453,9 @@ export const getSortie = (day: number): ISortie => {
Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: seed,
Seed: selectedNodes.find(x => x.tileset == "CorpusIcePlanetTileset")
? 2081 // this seed produces 12 zeroes in a row if asked to pick (0, 1); this way the CorpusIcePlanetTileset image is always index 0, the 'correct' choice.
: seed,
Boss: boss,
Variants: selectedNodes
};
@ -1382,6 +1476,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
const date = new Date(timeMs);
const defenseWavesPerRotation = buildLabel && version_compare(buildLabel, "2025.03.18.16.07") < 0 ? 5 : 3;
const worldState: IWorldState = {
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
@ -1471,6 +1566,123 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.VoidTraders.push(vt);
fullyStockBaro(vt);
}
if (config.worldState) {
for (const [key, alert] of Object.entries(configAlerts)) {
if (config.worldState[key as keyof typeof config.worldState]) {
if (alert.MissionInfo.missionType == "MT_DEFENSE") {
alert.MissionInfo.maxWaveNum = defenseWavesPerRotation * (alert.MissionInfo.maxRotations ?? 1);
alert.MissionInfo.maxRotations = undefined;
}
worldState.Alerts.push(alert);
}
}
}
if (config.worldState?.qtccAlerts) {
worldState.Alerts.push(
{
_id: {
$oid: "68dc23c42e9d3acfa708ff3b"
},
Activation: {
$date: {
$numberLong: "1759327200000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
MissionInfo: {
location: "SolNode123",
missionType: "MT_SURVIVAL",
faction: "FC_CORPUS",
difficulty: 1,
missionReward: {
credits: 10000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Plushy2021QTCC"]
},
levelOverride: "/Lotus/Levels/Proc/Corpus/CorpusShipSurvivalRaid",
enemySpec: "/Lotus/Types/Game/EnemySpecs/CorpusShipEnemySpecs/CorpusShipSurvivalA",
minEnemyLevel: 20,
maxEnemyLevel: 30,
descText: "/Lotus/Language/Alerts/TennoUnitedAlert",
maxWaveNum: 5
},
Tag: "LotusGift",
ForceUnlock: true
},
{
_id: {
$oid: "68dc2466e298b4f04206687a"
},
Activation: {
$date: {
$numberLong: "1759327200000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
MissionInfo: {
location: "SolNode149",
missionType: "MT_DEFENSE",
faction: "FC_GRINEER",
difficulty: 1,
missionReward: {
credits: 10000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Plushy2022QTCC"]
},
levelOverride: "/Lotus/Levels/Proc/Grineer/GrineerShipyardsDefense",
enemySpec: "/Lotus/Types/Game/EnemySpecs/GrineerShipyardsDefenseA",
minEnemyLevel: 20,
maxEnemyLevel: 30,
descText: "/Lotus/Language/Alerts/TennoUnitedAlert",
maxWaveNum: defenseWavesPerRotation * 1
},
Tag: "LotusGift",
ForceUnlock: true
},
{
_id: {
$oid: "68dc26865e7cb56b820b4252"
},
Activation: {
$date: {
$numberLong: "1759327200000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
MissionInfo: {
location: "SolNode39",
missionType: "MT_EXCAVATE",
faction: "FC_GRINEER",
difficulty: 1,
missionReward: {
credits: 10000,
items: ["/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyVirminkQTCC"]
},
levelOverride: "/Lotus/Levels/Proc/Grineer/GrineerForestExcavation",
enemySpec: "/Lotus/Types/Game/EnemySpecs/ForestGrineerExcavationA",
minEnemyLevel: 20,
maxEnemyLevel: 30,
descText: "/Lotus/Language/Alerts/TennoUnitedAlert",
maxWaveNum: 5
},
Tag: "LotusGift",
ForceUnlock: true
}
);
}
const isFebruary = date.getUTCMonth() == 1;
if (config.worldState?.starDaysOverride ?? isFebruary) {
worldState.Goals.push({
@ -2306,22 +2518,22 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// 2016
[
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"]
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
},
{
items: [
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor",
"/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
},
{
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor"]
}
],
// 2015
[
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"]
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"]
@ -2504,6 +2716,37 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
BonusReward: { items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] }
});
}
const isOctober = date.getUTCMonth() == 9; // October = month index 9
if (config.worldState?.naberusNightsOverride ?? isOctober) {
worldState.Goals.push({
_id: { $oid: "66fd602de1778d583419e8e7" },
Activation: {
$date: {
$numberLong: config.worldState?.naberusNightsOverride
? "1727881200000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString()
}
},
Expiry: {
$date: {
$numberLong: config.worldState?.naberusNightsOverride
? "2000000000000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString()
}
},
Count: 0,
Goal: 0,
Success: 0,
Personal: true,
Desc: "/Lotus/Language/Events/HalloweenNaberusName",
ToolTip: "/Lotus/Language/Events/HalloweenNaberusDesc",
Icon: "/Lotus/Interface/Icons/JackOLanternColour.png",
Tag: "DeimosHalloween",
Node: "DeimosHub"
});
}
if (config.worldState?.bellyOfTheBeast) {
worldState.Goals.push({
_id: { $oid: "67a5035c2a198564d62e165e" },

View File

@ -55,6 +55,12 @@ export interface IAccountCheats {
flawlessRelicsAlwaysGiveSilverReward?: boolean;
radiantRelicsAlwaysGiveGoldReward?: boolean;
disableDailyTribute?: boolean;
nemesisHenchmenKillsMultiplierGrineer?: number;
nemesisHenchmenKillsMultiplierCorpus?: number;
nemesisAntivirusGainMultiplier?: number;
nemesisHintProgressMultiplierGrineer?: number;
nemesisHintProgressMultiplierCorpus?: number;
nemesisExtraWeapon?: number;
}
export interface IInventoryDatabase
@ -86,6 +92,7 @@ export interface IInventoryDatabase
| "NextRefill"
| "Nemesis"
| "NemesisHistory"
| "LastNemesisAllySpawnTime"
| "EntratiVaultCountResetDate"
| "BrandedSuits"
| "LockedWeaponGroup"
@ -130,6 +137,7 @@ export interface IInventoryDatabase
NextRefill?: Date;
Nemesis?: INemesisDatabase;
NemesisHistory?: INemesisBaseDatabase[];
LastNemesisAllySpawnTime?: Date;
EntratiVaultCountResetDate?: Date;
BrandedSuits?: Types.ObjectId[];
LockedWeaponGroup?: ILockedWeaponGroupDatabase;
@ -141,6 +149,7 @@ export interface IInventoryDatabase
LastInventorySync?: Types.ObjectId;
EndlessXP?: IEndlessXpProgressDatabase[];
PersonalGoalProgress?: IGoalProgressDatabase[];
MissionRelicRewards?: ITypeCount[];
}
export interface IQuestKeyDatabase {
@ -353,7 +362,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ThemeStyle: string;
ThemeBackground: string;
ThemeSounds: string;
BountyScore: number;
BountyScore?: number;
//ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[];
RecentVendorPurchases?: IRecentVendorPurchaseClient[];
@ -365,7 +374,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
//InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient;
NemesisHistory?: INemesisBaseClient[];
//LastNemesisAllySpawnTime?: IMongoDate;
LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[];
PlayerSkills: IPlayerSkills;

View File

@ -178,6 +178,7 @@ export interface IRewardInfo {
goalManifest?: string;
invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
alertId?: string;
sortieId?: string;
sortieTag?: "Mission1" | "Mission2" | "Final";
sortiePrereqs?: string[];

View File

@ -1,4 +1,4 @@
import type { IMissionReward } from "warframe-public-export-plus";
import type { IMissionReward, TFaction, TMissionType } from "warframe-public-export-plus";
import type { IMongoDate, IOid } from "./commonTypes.ts";
export interface IWorldState {
@ -7,7 +7,7 @@ export interface IWorldState {
Time: number;
InGameMarket: IInGameMarket;
Goals: IGoal[];
Alerts: [];
Alerts: IAlert[];
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[];
@ -35,6 +35,33 @@ export interface IWorldState {
Tmp?: string;
}
export interface IAlert {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
MissionInfo: IAlertMissionInfo;
Tag?: string;
ForceUnlock?: true;
}
export interface IAlertMissionInfo {
location: string;
missionType: TMissionType;
faction: TFaction;
difficulty: number;
missionReward?: IMissionReward;
levelOverride?: string;
enemySpec?: string;
extraEnemySpec?: string;
customAdvancedSpawners?: string[];
minEnemyLevel?: number;
maxEnemyLevel?: number;
maxWaveNum?: number;
descText?: string;
maxRotations?: number; // SNS specific field
}
export interface IGoal {
_id: IOid;
Activation: IMongoDate;

View File

@ -476,9 +476,9 @@
</div>
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_Boosters"></h5>
<h5 class="card-header" data-loc="inventory_boosters"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
<form class="input-group mb-3" onsubmit="doAcquireBooster();return false;">
<input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
@ -526,6 +526,24 @@
</div>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_weaponSkins"></h5>
<div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('WeaponSkins');return false;">
<input class="form-control" id="acquire-type-WeaponSkins" list="datalist-WeaponSkins" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<div class="overflow-auto">
<table class="table table-hover w-100">
<tbody id="WeaponSkins-list"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
@ -538,6 +556,7 @@
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['FlavourItems']);" data-loc="inventory_bulkAddFlavourItems"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['ShipDecorations']);" data-loc="inventory_bulkAddShipDecorations"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['WeaponSkins']);" data-loc="inventory_bulkAddWeaponSkins"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
</div>
<div class="mb-2 d-flex flex-wrap gap-2">
@ -719,12 +738,12 @@
</div>
</div>
<div id="edit-suit-invigorations-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_suitInvigorationLabel"></h5>
<h5 class="card-header" data-loc="detailedView_invigorationLabel"></h5>
<div class="card-body">
<form onsubmit="submitSuitInvigorationUpgrade(event)">
<form onsubmit="submitInvigoration(event)">
<div class="mb-3">
<label for="invigoration-offensive" class="form-label" data-loc="invigorations_offensiveLabel"></label>
<select class="form-select" id="dv-invigoration-offensive">
<label for="invigoration-offensive" class="form-label" data-loc="detailedView_invigorationOffensiveLabel"></label>
<select class="form-select" id="invigoration-offensive">
<option value="" data-loc="general_none"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerStrength" data-loc="invigorations_offensive_AbilityStrength"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerRange" data-loc="invigorations_offensive_AbilityRange"></option>
@ -737,10 +756,9 @@
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeCritChance" data-loc="invigorations_offensive_MeleeCritChance"></option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-defensive" class="form-label" data-loc="invigorations_defensiveLabel"></label>
<select class="form-select" id="dv-invigoration-defensive">
<label for="invigoration-defensive" class="form-label" data-loc="detailedView_invigorationUtilityLabel"></label>
<select class="form-select" id="invigoration-defensive">
<option value="" data-loc="general_none"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationPowerEfficiency" data-loc="invigorations_utility_AbilityEfficiency"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationMovementSpeed" data-loc="invigorations_utility_SprintSpeed"></option>
@ -755,15 +773,13 @@
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergyRegen" data-loc="invigorations_utility_EnergyRegen"></option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-expiry" class="form-label" data-loc="invigorations_expiryLabel"></label>
<input type="datetime-local" class="form-control" id="dv-invigoration-expiry" />
<label for="invigoration-expiry" class="form-label" data-loc="detailedView_invigorationExpiryLabel"></label>
<input type="datetime-local" class="form-control" max="2038-01-19T03:14" id="invigoration-expiry" onblur="this.value=new Date(this.value)>new Date(this.max)?new Date(this.max).toISOString().slice(0,16):this.value"/>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" data-loc="general_setButton"></button>
<button type="button" class="btn btn-danger" onclick="clearSuitInvigorationUpgrades()" data-loc="code_remove"></button>
<button type="button" class="btn btn-danger" onclick="setInvigoration()" data-loc="code_remove"></button>
</div>
</form>
</div>
@ -1018,6 +1034,48 @@
<input class="form-check-input" type="checkbox" id="finishInvasionsInOneMission" />
<label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></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="0" 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="0" 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="0" 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="0" 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="0" 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>
<div class="mt-2 mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(doUnlockAllShipFeatures);" data-loc="cheats_unlockAllShipFeatures"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
@ -1036,7 +1094,7 @@
<label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>
<div class="input-group">
<select class="form-control" id="changeSyndicate"></select>
<button class="btn btn-primary" type="submit" data-loc="cheats_changeButton"></button>
<button class="btn btn-secondary" type="submit" data-loc="cheats_changeButton"></button>
</div>
</form>
</div>
@ -1150,6 +1208,10 @@
<label class="form-check-label" for="worldState.hallowedFlame" data-loc="worldState_hallowedFlame"></label>
<abbr data-loc-inc="worldState_hallowedNightmares|worldState_dogDays|worldState_anniversary"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.qtccAlerts" />
<label class="form-check-label" for="worldState.qtccAlerts" data-loc="worldState_qtccAlerts"></label>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.hallowedNightmares" data-loc="worldState_hallowedNightmares"></label>
@ -1168,6 +1230,14 @@
</select>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.naberusNightsOverride" data-loc="worldState_naberusNights"></label>
<select class="form-control" id="worldState.naberusNightsOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill">
<label class="form-label" for="worldState.proxyRebellion" data-loc="worldState_proxyRebellion"></label>
@ -1185,6 +1255,27 @@
</select>
</div>
</div>
<div class="mt-2">
<label class="form-label" data-loc="worldState_voidCorruption" data-loc-replace="2025"></label>
<div class="d-flex flex-wrap gap-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week1" />
<label class="form-check-label" for="worldState.voidCorruption2025Week1" data-loc="worldState_week" data-loc-replace="1"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week2" />
<label class="form-check-label" for="worldState.voidCorruption2025Week2" data-loc="worldState_week" data-loc-replace="2"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week3" />
<label class="form-check-label" for="worldState.voidCorruption2025Week3" data-loc="worldState_week" data-loc-replace="3"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.voidCorruption2025Week4" />
<label class="form-check-label" for="worldState.voidCorruption2025Week4" data-loc="worldState_week" data-loc-replace="4"></label>
</div>
</div>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label>
<abbr data-loc-inc="worldState_wolfHunt|worldState_anniversary|worldState_orphixVenom"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640"><path d="M320 576C461.4 576 576 461.4 576 320C576 178.6 461.4 64 320 64C178.6 64 64 178.6 64 320C64 461.4 178.6 576 320 576zM320 200C333.3 200 344 210.7 344 224L344 336C344 349.3 333.3 360 320 360C306.7 360 296 349.3 296 336L296 224C296 210.7 306.7 200 320 200zM293.3 416C292.7 406.1 297.6 396.7 306.1 391.5C314.6 386.4 325.3 386.4 333.8 391.5C342.3 396.7 347.2 406.1 346.6 416C347.2 425.9 342.3 435.3 333.8 440.5C325.3 445.6 314.6 445.6 306.1 440.5C297.6 435.3 292.7 425.9 293.3 416z"/></svg></abbr>
@ -1456,6 +1547,7 @@
<datalist id="datalist-VaultDecoRecipes"></datalist>
<datalist id="datalist-FlavourItems"></datalist>
<datalist id="datalist-ShipDecorations"></datalist>
<datalist id="datalist-WeaponSkins"></datalist>
<datalist id="datalist-circuitGameModes">
<option>Survival</option>
<option>VoidFlood</option>

View File

@ -599,6 +599,46 @@ function fetchItemList() {
}
itemMap[item.uniqueName] = { ...item, type };
});
} else if (type == "WeaponSkins") {
let beardNumber = 1;
let cutNumber = 13;
let adultHeadNumber = 1;
let headNumber = 1;
items.forEach(item => {
if (item.name == "") {
if (item.uniqueName.includes("/Beards/")) {
item.name = loc("code_drifterBeardName")
.split("|INDEX|")
.join(beardNumber.toString().padStart(3, "0"));
beardNumber++;
} else if (item.uniqueName.includes("/Hair/")) {
item.name = loc("code_cutName")
.split("|INDEX|")
.join(cutNumber.toString().padStart(3, "0"));
cutNumber++;
if (cutNumber == 19) cutNumber = 21;
} else if (item.uniqueName.includes("/Heads/Adult")) {
item.name = loc("code_drifterFaceName")
.split("|INDEX|")
.join(adultHeadNumber.toString().padStart(3, "0"));
adultHeadNumber++;
} else if (item.uniqueName.includes("/Heads/")) {
item.name = loc("code_operatorFaceName")
.split("|INDEX|")
.join(headNumber.toString().padStart(3, "0"));
headNumber++;
} else {
item.name = item.uniqueName;
}
}
if (!item.alwaysAvailable) {
const option = document.createElement("option");
option.setAttribute("data-key", item.uniqueName);
option.value = item.name;
document.getElementById("datalist-" + type).appendChild(option);
}
itemMap[item.uniqueName] = { ...item, type };
});
} else {
const nameToItems = {};
items.forEach(item => {
@ -837,10 +877,9 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
revalidateAuthz().then(() => {
const promises = [];
revalidateAuthz().then(async () => {
if (item.XP < maxXP) {
promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP));
await addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
}
if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
@ -851,20 +890,16 @@ function updateInventory() {
const exaltedCap =
itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
promises.push(
addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
)
await addGearExp(
"SpecialItems",
exaltedItem.ItemId.$oid,
exaltedCap - exaltedItem.XP
);
}
}
}
}
Promise.all(promises).then(() => {
updateInventory();
});
updateInventory();
});
};
a.title = loc("code_maxRank");
@ -1007,6 +1042,67 @@ function updateInventory() {
document.getElementById("EvolutionProgress-list").appendChild(tr);
});
document.getElementById("Boosters-list").innerHTML = "";
data.Boosters.forEach(item => {
if (item.ExpiryDate < Math.floor(Date.now() / 1000)) {
// Booster has expired, skip it
return;
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const form = document.createElement("form");
form.style.display = "inline-block";
form.onsubmit = function (event) {
event.preventDefault();
const maxDate = new Date(input.max);
const selectedDate = new Date(input.value);
if (selectedDate > maxDate) {
input.value = maxDate.toISOString().slice(0, 16);
}
doChangeBoosterExpiry(item.ItemType, input);
};
const input = document.createElement("input");
input.type = "datetime-local";
input.classList = "form-control form-control-sm";
input.value = formatDatetime("%Y-%m-%d %H:%M:%s", item.ExpiryDate * 1000);
input.max = "2038-01-19T03:14";
input.onblur = function () {
const maxDate = new Date(input.max);
const selectedDate = new Date(input.value);
if (selectedDate > maxDate) {
input.value = maxDate.toISOString().slice(0, 16);
}
doChangeBoosterExpiry(item.ItemType, input);
};
form.appendChild(input);
td.appendChild(form);
}
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setBooster(item.ItemType, 0);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("Boosters-list").appendChild(tr);
});
document.getElementById("FlavourItems-list").innerHTML = "";
data.FlavourItems.forEach(item => {
const datalist = document.getElementById("datalist-FlavourItems");
@ -1047,6 +1143,44 @@ function updateInventory() {
document.getElementById("FlavourItems-list").appendChild(tr);
});
document.getElementById("WeaponSkins-list").innerHTML = "";
data.WeaponSkins.forEach(item => {
if (item.ItemId.$oid.startsWith("ca70ca70ca70ca70")) return;
const datalist = document.getElementById("datalist-WeaponSkins");
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
const name = itemMap[item.ItemType]?.name?.trim();
td.textContent = name || item.ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
{
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
document.getElementById("WeaponSkins-list").removeChild(tr);
reAddToItemList(itemMap, "WeaponSkins", item.ItemType);
disposeOfGear("WeaponSkins", item.ItemId.$oid);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("WeaponSkins-list").appendChild(tr);
});
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
@ -1146,7 +1280,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("setInactive", item.ItemType);
debounce(doQuestUpdate, "setInactive", item.ItemType);
};
a.title = loc("code_setInactive");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M464 256A208 208 0 1 0 48 256a208 208 0 1 0 416 0zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256zm192-96l128 0c17.7 0 32 14.3 32 32l0 128c0 17.7-14.3 32-32 32l-128 0c-17.7 0-32-14.3-32-32l0-128c0-17.7 14.3-32 32-32z"/></svg>`;
@ -1157,7 +1291,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("resetKey", item.ItemType);
debounce(doQuestUpdate, "resetKey", item.ItemType);
};
a.title = loc("code_reset");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z"/></svg>`;
@ -1168,7 +1302,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("completeKey", item.ItemType);
debounce(doQuestUpdate, "completeKey", item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/></svg>`;
@ -1179,7 +1313,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("prevStage", item.ItemType);
debounce(doQuestUpdate, "prevStage", item.ItemType);
};
a.title = loc("code_prevStage");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160z"/></svg>`;
@ -1194,7 +1328,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
doQuestUpdate("nextStage", item.ItemType);
debounce(doQuestUpdate, "nextStage", item.ItemType);
};
a.title = loc("code_nextStage");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z"/></svg>`;
@ -1206,7 +1340,7 @@ function updateInventory() {
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "QuestKeys", item.ItemType);
doQuestUpdate("deleteKey", item.ItemType);
debounce(doQuestUpdate, "deleteKey", item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@ -1452,12 +1586,13 @@ function updateInventory() {
document.getElementById("crystals-list").appendChild(tr);
});
document.getElementById("edit-suit-invigorations-card").classList.remove("d-none");
const { OffensiveUpgrade, DefensiveUpgrade, UpgradesExpiry } =
suitInvigorationUpgradeData(item);
document.getElementById("dv-invigoration-offensive").value = OffensiveUpgrade;
document.getElementById("dv-invigoration-defensive").value = DefensiveUpgrade;
document.getElementById("dv-invigoration-expiry").value = UpgradesExpiry;
{
document.getElementById("edit-suit-invigorations-card").classList.remove("d-none");
document.getElementById("invigoration-offensive").value = item.OffensiveUpgrade || "";
document.getElementById("invigoration-defensive").value = item.DefensiveUpgrade || "";
document.getElementById("invigoration-expiry").value =
formatDatetime("%Y-%m-%d %H:%M", Number(item.UpgradesExpiry?.$date.$numberLong)) || "";
}
{
document.getElementById("loadout-card").classList.remove("d-none");
@ -1619,63 +1754,6 @@ function updateInventory() {
}
document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
document.getElementById("Boosters-list").innerHTML = "";
const now = Math.floor(Date.now() / 1000);
data.Boosters.forEach(({ ItemType, ExpiryDate }) => {
if (ExpiryDate < now) {
// Booster has expired, skip it
return;
}
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[ItemType]?.name ?? ItemType;
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000);
const inlineForm = document.createElement("form");
const input = document.createElement("input");
inlineForm.style.display = "inline-block";
inlineForm.onsubmit = function (event) {
event.preventDefault();
doChangeBoosterExpiry(ItemType, input);
};
input.type = "datetime-local";
input.classList.add("form-control");
input.classList.add("form-control-sm");
input.value = timeString;
let changed = false;
input.onchange = function () {
changed = true;
};
input.onblur = function () {
if (changed) {
doChangeBoosterExpiry(ItemType, input);
}
};
inlineForm.appendChild(input);
td.appendChild(inlineForm);
const removeButton = document.createElement("a");
removeButton.title = loc("code_remove");
removeButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
removeButton.href = "#";
removeButton.onclick = function (event) {
event.preventDefault();
setBooster(ItemType, 0);
};
td.appendChild(removeButton);
tr.appendChild(td);
}
document.getElementById("Boosters-list").appendChild(tr);
});
if (single.getCurrentPath().startsWith("/webui/guildView")) {
const guildReq = $.get("/custom/getGuild?guildId=" + window.guildId);
guildReq.done(guildData => {
@ -1771,7 +1849,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
fundGuildTechProject(item.ItemType);
debounce(fundGuildTechProject, item.ItemType);
};
a.title = loc("code_fund");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M214.6 17.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 117.3 160 488c0 17.7 14.3 32 32 32s32-14.3 32-32l0-370.7 105.4 105.4c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
@ -1788,7 +1866,7 @@ function updateInventory() {
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
completeGuildTechProject(item.ItemType);
debounce(completeGuildTechProject, item.ItemType);
};
a.title = loc("code_complete");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free v7.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M371.7 43.1C360.1 32 343 28.9 328.3 35.2S304 56 304 72l0 136.3-172.3-165.1C120.1 32 103 28.9 88.3 35.2S64 56 64 72l0 368c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9L304 303.7 304 440c0 16 9.6 30.5 24.3 36.8s31.8 3.2 43.4-7.9l192-184c7.9-7.5 12.3-18 12.3-28.9s-4.5-21.3-12.3-28.9l-192-184z"/></svg>`;
@ -1801,7 +1879,7 @@ function updateInventory() {
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "TechProjects", item.ItemType);
removeGuildTechProject(item.ItemType);
debounce(removeGuildTechProject, item.ItemType);
};
a.title = loc("code_remove");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/></svg>`;
@ -1986,34 +2064,12 @@ function updateInventory() {
}
for (const elm of accountCheats) {
elm.checked = !!data[elm.id];
}
});
});
}
function addVaultDecoRecipe() {
const uniqueName = getKey(document.getElementById("acquire-type-VaultDecoRecipes"));
if (!guildId) {
return;
}
if (!uniqueName) {
$("acquire-type-VaultDecoRecipes").addClass("is-invalid").focus();
return;
}
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json",
data: JSON.stringify([
{
ItemType: uniqueName,
ItemCount: 1
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") || "";
}
])
});
req.done(() => {
updateInventory();
}
});
});
}
@ -2866,8 +2922,8 @@ function removeCountItems(uniqueName, count) {
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)) {
// Must start with "/Lotus/", contain only letters AZ, digits 09, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z0-9]+(?:\/[A-Za-z0-9]+)*)$/.test(ItemType)) {
$("#typeName-type").addClass("is-invalid").focus();
return;
}
@ -3217,13 +3273,53 @@ function doIntrinsicsUnlockAll() {
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () {
revalidateAuthz().then(() => {
const value = elm.checked;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: elm.id,
value: elm.checked
value: value
})
}).done(() => {
elm.checked = value;
});
});
};
});
document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
const input = grp.querySelector("input");
const select = grp.querySelector("select");
const btn = grp.querySelector("button");
if (input) {
input.oninput = input.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
if (select) {
select.oninput = select.onchange = function () {
btn.classList.remove("btn-secondary");
btn.classList.add("btn-primary");
};
}
btn.onclick = function () {
btn.classList.remove("btn-primary");
btn.classList.add("btn-secondary");
const input = btn.closest(".input-group").querySelector('input[type="number"]');
if (!input) return;
revalidateAuthz().then(() => {
const value = input.value;
$.post({
url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
key: input.id,
value: parseInt(value)
})
}).done(() => {
btn.value = value;
});
});
};
@ -3467,9 +3563,9 @@ function doBulkQuestUpdate(operation) {
});
}
function toast(text) {
function toast(text, type = "primary") {
const toast = document.createElement("div");
toast.className = "toast align-items-center text-bg-primary border-0";
toast.className = `toast align-items-center text-bg-${type} border-0`;
const div = document.createElement("div");
div.className = "d-flex";
const body = document.createElement("div");
@ -3542,7 +3638,7 @@ function handleModularSelection(category) {
});
}
function setBooster(ItemType, ExpiryDate, callback) {
function setBooster(ItemType, ExpiryDate) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/setBooster?" + window.authz,
@ -3555,33 +3651,27 @@ function setBooster(ItemType, ExpiryDate, callback) {
])
}).done(function () {
updateInventory();
if (callback) callback();
});
});
}
function doAcquireBoosters() {
function doAcquireBooster() {
const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
if (!uniqueName) {
$("#acquire-type-Boosters").addClass("is-invalid").focus();
return;
}
const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days
setBooster(uniqueName, ExpiryDate, () => {
$("#acquire-type-Boosters").val("");
});
setBooster(uniqueName, Math.floor(Date.now() / 1000 + 3 * 24 * 60 * 60));
document.getElementById("acquire-type-Boosters").value = "";
}
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value);
// cast local datetime string to unix timestamp
const ExpiryDate = Math.trunc(new Date(ExpiryDateInput.value).getTime() / 1000);
const ExpiryDate = Math.floor(new Date(ExpiryDateInput.value).getTime() / 1000);
if (isNaN(ExpiryDate)) {
ExpiryDateInput.addClass("is-invalid").focus();
return false;
return;
}
setBooster(ItemType, ExpiryDate);
return true;
}
function formatDatetime(fmt, date) {
@ -4027,73 +4117,31 @@ function handleModularPartsChange(event) {
});
}
}
function suitInvigorationUpgradeData(suitData) {
let expiryDate = "";
if (suitData.UpgradesExpiry) {
if (suitData.UpgradesExpiry.$date) {
expiryDate = new Date(parseInt(suitData.UpgradesExpiry.$date.$numberLong));
} else if (typeof suitData.UpgradesExpiry === "number") {
expiryDate = new Date(suitData.UpgradesExpiry);
} else if (suitData.UpgradesExpiry instanceof Date) {
expiryDate = suitData.UpgradesExpiry;
}
if (expiryDate && !isNaN(expiryDate.getTime())) {
const year = expiryDate.getFullYear();
const month = String(expiryDate.getMonth() + 1).padStart(2, "0");
const day = String(expiryDate.getDate()).padStart(2, "0");
const hours = String(expiryDate.getHours()).padStart(2, "0");
const minutes = String(expiryDate.getMinutes()).padStart(2, "0");
expiryDate = `${year}-${month}-${day}T${hours}:${minutes}`;
} else {
expiryDate = "";
}
}
return {
oid: suitData.ItemId.$oid,
OffensiveUpgrade: suitData.OffensiveUpgrade || "",
DefensiveUpgrade: suitData.DefensiveUpgrade || "",
UpgradesExpiry: expiryDate
};
}
function submitSuitInvigorationUpgrade(event) {
function submitInvigoration(event) {
event.preventDefault();
const oid = new URLSearchParams(window.location.search).get("itemId");
const offensiveUpgrade = document.getElementById("dv-invigoration-offensive").value;
const defensiveUpgrade = document.getElementById("dv-invigoration-defensive").value;
const expiry = document.getElementById("dv-invigoration-expiry").value;
const OffensiveUpgrade = document.getElementById("invigoration-offensive").value;
const DefensiveUpgrade = document.getElementById("invigoration-defensive").value;
const expiry = document.getElementById("invigoration-expiry").value;
if (!offensiveUpgrade || !defensiveUpgrade) {
alert(loc("code_requiredInvigorationUpgrade"));
if (!OffensiveUpgrade || !DefensiveUpgrade) {
toast(loc("code_requiredInvigorationUpgrade"), "warning");
return;
}
const data = {
OffensiveUpgrade: offensiveUpgrade,
DefensiveUpgrade: defensiveUpgrade
};
if (expiry) {
data.UpgradesExpiry = new Date(expiry).getTime();
}
editSuitInvigorationUpgrade(oid, data);
setInvigoration({
OffensiveUpgrade,
DefensiveUpgrade,
UpgradesExpiry: expiry ? new Date(expiry).getTime() : Date.now() + 7 * 24 * 60 * 60 * 1000
});
}
function clearSuitInvigorationUpgrades() {
editSuitInvigorationUpgrade(new URLSearchParams(window.location.search).get("itemId"), null);
}
async function editSuitInvigorationUpgrade(oid, data) {
/* data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
}*/
function setInvigoration(data) {
const oid = new URLSearchParams(window.location.search).get("itemId");
$.post({
url: "/custom/editSuitInvigorationUpgrade?" + window.authz,
url: "/custom/setInvigoration?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, data })
data: JSON.stringify({ oid, ...data })
}).done(function () {
updateInventory();
});

View File

@ -75,8 +75,12 @@ dict = {
code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `Stalker`,
code_cutName: `Frisur: |INDEX|`,
code_drifterBeardName: `Drifter-Bart: |INDEX|`,
code_drifterFaceName: `Drifter-Gesicht: |INDEX|`,
code_operatorFaceName: `Operator-Gesicht: |INDEX|`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives Upgrade auswählen.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@ -109,9 +113,10 @@ dict = {
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
inventory_Boosters: `Booster`,
inventory_boosters: `Booster`,
inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`,
inventory_shipDecorations: `Schiffsdekorationen`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
@ -147,7 +153,7 @@ dict = {
detailedView_valenceBonusLabel: `Valenz-Bonus`,
detailedView_valenceBonusDescription: `Du kannst den Valenz-Bonus deiner Waffe festlegen oder entfernen.`,
detailedView_modularPartsLabel: `Modulare Teile ändern`,
detailedView_suitInvigorationLabel: `Warframe-Kräftigung`,
detailedView_invigorationLabel: `Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
@ -172,9 +178,9 @@ dict = {
invigorations_utility_Jumps: `+5 Sprung-Zurücksetzungen`,
invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`,
invigorations_offensiveLabel: `Offensives Upgrade`,
invigorations_defensiveLabel: `Defensives Upgrade`,
invigorations_expiryLabel: `Upgrades Ablaufdatum (optional)`,
detailedView_invigorationOffensiveLabel: `Offensives Upgrade`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`,
abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`,
@ -193,7 +199,7 @@ dict = {
cheats_skipTutorial: `Tutorial überspringen`,
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Alle Missionen freischalten`,
cheats_unlockAllMissions_ok: `Erfolgreich. Bitte beachte, dass du ein Dojo/Relais besuchen oder dich neu einloggen musst, damit die Sternenkarte aktualisiert wird.`,
cheats_infiniteCredits: `Unendlich Credits`,
@ -227,7 +233,7 @@ dict = {
cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -257,6 +263,12 @@ dict = {
cheats_changeButton: `Ändern`,
cheats_markAllAsRead: `Posteingang als gelesen markieren`,
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`,
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_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `Weltstatus`,
worldState_creditBoost: `Event Booster: Credit`,
@ -273,11 +285,13 @@ dict = {
worldState_dogDays: `Hitzefrei`,
worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`,
worldState_wolfHunt: `Wolfsjagd (2025)`,
worldState_voidCorruption: `Void-Korruption (|VAL|)`,
worldState_orphixVenom: `Orphix Gift`,
worldState_longShadow: `Lange Schatten`,
worldState_hallowedFlame: `Geweihte Flamme`,
worldState_hallowedNightmares: `Geweihte Albträume`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Das Innere der Bestie`,
@ -286,6 +300,7 @@ dict = {
worldState_eightClawProgressOverride: `[UNTRANSLATED] Eight Claw Progress`,
worldState_thermiaFractures: `Thermische Risse`,
worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `[UNTRANSLATED] from |VAL|`,
worldState_pre_year: `[UNTRANSLATED] pre |VAL|`,
worldState_week: `[UNTRANSLATED] Week |VAL|`,

View File

@ -1,5 +1,5 @@
dict = {
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
general_inventoryUpdateNoteGameWs: `Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_addButton: `Add`,
general_setButton: `Set`,
@ -74,8 +74,12 @@ dict = {
code_funded: `Funded`,
code_replays: `Replays`,
code_stalker: `Stalker`,
code_cutName: `Cut |INDEX|`,
code_drifterBeardName: `Drifter Beard |INDEX|`,
code_drifterFaceName: `Drifter Visage |INDEX|`,
code_operatorFaceName: `Operator Visage |INDEX|`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & defensive upgrade.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility upgrade.`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
@ -108,9 +112,10 @@ dict = {
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Beasts`,
inventory_evolutionProgress: `Incarnon Evolution Progress`,
inventory_Boosters: `Boosters`,
inventory_boosters: `Boosters`,
inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Ship Decorations`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
@ -120,6 +125,7 @@ dict = {
inventory_bulkAddFlavourItems: `Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
inventory_bulkAddWeaponSkins: `Add Missing Skins`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
@ -146,7 +152,7 @@ dict = {
detailedView_valenceBonusLabel: `Valence Bonus`,
detailedView_valenceBonusDescription: `You can set or remove the Valence Bonus from your weapon.`,
detailedView_modularPartsLabel: `Change Modular Parts`,
detailedView_suitInvigorationLabel: `Warframe Invigoration`,
detailedView_invigorationLabel: `Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
@ -171,9 +177,9 @@ dict = {
invigorations_utility_Jumps: `+5 Jump Resets`,
invigorations_utility_EnergyRegen: `+2 Energy Regen/s`,
invigorations_offensiveLabel: `Offensive Upgrade`,
invigorations_defensiveLabel: `Defensive Upgrade`,
invigorations_expiryLabel: `Upgrades Expiry (optional)`,
detailedView_invigorationOffensiveLabel: `Offensive Upgrade`,
detailedView_invigorationUtilityLabel: `Utility Upgrade`,
detailedView_invigorationExpiryLabel: `Invigoration Expiry (optional)`,
abilityOverride_label: `Ability Override`,
abilityOverride_onSlot: `on slot`,
@ -192,7 +198,7 @@ dict = {
cheats_skipTutorial: `Skip Tutorial`,
cheats_skipAllDialogue: `Skip All Dialogue`,
cheats_unlockAllScans: `Unlock All Scans`,
cheats_unlockSuccRelog: `Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `Unlock All Missions`,
cheats_unlockAllMissions_ok: `Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_infiniteCredits: `Infinite Credits`,
@ -226,7 +232,7 @@ dict = {
cheats_baroFullyStocked: `Baro Fully Stocked`,
cheats_syndicateMissionsRepeatable: `Syndicate Missions Repeatable`,
cheats_unlockAllProfitTakerStages: `Unlock All Profit Taker Stages`,
cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
cheats_unlockSuccInventory: `Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
cheats_instantFinishRivenChallenge: `Instant Finish Riven Challenge`,
cheats_instantResourceExtractorDrones: `Instant Resource Extractor Drones`,
cheats_noResourceExtractorDronesDamage: `No Resource Extractor Drones Damage`,
@ -256,6 +262,12 @@ dict = {
cheats_changeButton: `Change`,
cheats_markAllAsRead: `Mark Inbox As Read`,
cheats_finishInvasionsInOneMission: `Finish Invasions in One Mission`,
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_nemesisExtraWeapon: `Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,
@ -272,11 +284,13 @@ dict = {
worldState_dogDays: `Dog Days`,
worldState_dogDaysRewards: `Dog Days Rewards`,
worldState_wolfHunt: `Wolf Hunt (2025)`,
worldState_voidCorruption: `Void Corruption (|VAL|)`,
worldState_orphixVenom: `Orphix Venom`,
worldState_longShadow: `Long Shadow`,
worldState_hallowedFlame: `Hallowed Flame`,
worldState_hallowedNightmares: `Hallowed Nightmares`,
worldState_hallowedNightmaresRewards: `Hallowed Nightmares Rewards`,
worldState_naberusNights: `Nights of Naberus`,
worldState_proxyRebellion: `Proxy Rebellion`,
worldState_proxyRebellionRewards: `Proxy Rebellion Rewards`,
worldState_bellyOfTheBeast: `Belly of the Beast`,
@ -285,6 +299,7 @@ dict = {
worldState_eightClawProgressOverride: `Eight Claw Progress`,
worldState_thermiaFractures: `Thermia Fractures`,
worldState_thermiaFracturesProgressOverride: `Thermia Fractures Progress`,
worldState_qtccAlerts: `Quest to Conquer Cancer Alerts`,
worldState_from_year: `from |VAL|`,
worldState_pre_year: `pre |VAL|`,
worldState_week: `Week |VAL|`,

View File

@ -1,7 +1,7 @@
// Spanish translation by hxedcl
// Spanish translation by hxedcl, Slayer55555
dict = {
general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
general_inventoryUpdateNoteGameWs: `Nota: Puede que necesites reabrir cualquier menú en el que te encuentres para que los cambios se reflejen.`,
general_addButton: `Agregar`,
general_setButton: `Establecer`,
general_none: `Ninguno`,
@ -32,8 +32,8 @@ dict = {
code_renamePrompt: `Escribe tu nuevo nombre personalizado:`,
code_remove: `Quitar`,
code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`,
code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`,
code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`,
code_addTechProjectsConfirm: `¿Estás seguro de que quieres añadir |COUNT| proyectos de investigación a tu clan?`,
code_addDecoRecipesConfirm: `¿Estás seguro de que quieres añadir |COUNT| planos de decoración a tu clan?`,
code_succRankUp: `Ascenso exitoso.`,
code_noEquipmentToRankUp: `No hay equipo para ascender.`,
code_succAdded: `Agregado exitosamente.`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Rango`,
code_rankUp: `Subir de rango`,
code_rankDown: `Bajar de rango`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_unlockLevelCap: `Desbloquear level cap`,
code_count: `Cantidad`,
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
@ -65,18 +65,22 @@ dict = {
code_completed: `Completada`,
code_active: `Activa`,
code_pigment: `Pigmento`,
code_controller: `[UNTRANSLATED] Controller cursor`,
code_mouseLine: `[UNTRANSLATED] Line cursor`,
code_mouse: `[UNTRANSLATED] Cursor`,
code_controller: `Cursor de Mando`,
code_mouseLine: `Cursor de línea`,
code_mouse: `Cursor`,
code_itemColorPalette: `Paleta de colores |ITEM|`,
code_mature: `Listo para el combate`,
code_unmature: `Regresar el envejecimiento genético`,
code_fund: `[UNTRANSLATED] Fund`,
code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`,
code_fund: `Financiar`,
code_funded: `Financiado`,
code_replays: `Repeticiones`,
code_stalker: `Stalker`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `Barba del Viajero: |INDEX|`,
code_drifterFaceName: `Rostro del Viajero |INDEX|`,
code_operatorFaceName: `Rostro del operador |INDEX|`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`,
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`,
@ -109,18 +113,20 @@ dict = {
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestias`,
inventory_evolutionProgress: `Progreso de evolución Incarnon`,
inventory_Boosters: `Potenciadores`,
inventory_boosters: `Potenciadores`,
inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`,
inventory_shipDecorations: `Decoraciones de nave`,
inventory_weaponSkins: `Diseños`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`,
inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`,
inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
@ -147,7 +153,7 @@ dict = {
detailedView_valenceBonusLabel: `Bonus de Valéncia`,
detailedView_valenceBonusDescription: `Puedes establecer o quitar el bonus de valencia de tu arma.`,
detailedView_modularPartsLabel: `Cambiar partes modulares`,
detailedView_suitInvigorationLabel: `Vigorización de Warframe`,
detailedView_invigorationLabel: `Fortalecimiento`,
detailedView_loadoutLabel: `Equipamientos`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
@ -172,9 +178,9 @@ dict = {
invigorations_utility_Jumps: `+5 Restablecimientos de Salto`,
invigorations_utility_EnergyRegen: `+2 Regeneración de Energía/s`,
invigorations_offensiveLabel: `Mejora Ofensiva`,
invigorations_defensiveLabel: `Mejora Defensiva`,
invigorations_expiryLabel: `Caducidad de Mejoras (opcional)`,
detailedView_invigorationOffensiveLabel: `Mejora Ofensiva`,
detailedView_invigorationUtilityLabel: `Mejora de Utilidad`,
detailedView_invigorationExpiryLabel: `Caducidad del Fortalecimiento (opcional)`,
abilityOverride_label: `Intercambio de Habilidad`,
abilityOverride_onSlot: `en el espacio`,
@ -257,6 +263,12 @@ dict = {
cheats_changeButton: `Cambiar`,
cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
cheats_finishInvasionsInOneMission: `Finaliza Invasión en una mision`,
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_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `Estado del mundo`,
worldState_creditBoost: `Potenciador de Créditos`,
@ -273,11 +285,13 @@ dict = {
worldState_dogDays: `Canícula`,
worldState_dogDaysRewards: `Recompensas de Canícula`,
worldState_wolfHunt: `Cacería del Lobo (2025)`,
worldState_voidCorruption: `Corrupción del Vacío (|VAL|)`,
worldState_orphixVenom: `Veneno de Orphix`,
worldState_longShadow: `Sombra Prolongada`,
worldState_hallowedFlame: `Llama Sagrada`,
worldState_hallowedNightmares: `Pesadillas Sagradas`,
worldState_hallowedNightmaresRewards: `Recompensas de Pesadillas Sagradas`,
worldState_naberusNights: `Noches de Naberus`,
worldState_proxyRebellion: `Rebelión Proxy`,
worldState_proxyRebellionRewards: `Recompensas de Rebelión Proxy`,
worldState_bellyOfTheBeast: `Vientre de la Bestia`,
@ -286,6 +300,7 @@ dict = {
worldState_eightClawProgressOverride: `Progreso de Octava Garra`,
worldState_thermiaFractures: `Fracturas Thermia`,
worldState_thermiaFracturesProgressOverride: `Progreso de Fracturas Thermia`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `de |VAL|`,
worldState_pre_year: `antes de |VAL|`,
worldState_week: `Semana |VAL|`,
@ -401,9 +416,9 @@ dict = {
theme_dark: `Tema Oscuro`,
theme_light: `Tema Claro`,
guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
guildView_cheats: `Trucos de Clan`,
guildView_techProjects: `Investigación`,
guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
guildView_vaultDecoRecipes: `Planos de Decoración de Dojo`,
guildView_alliance: `Alianza`,
guildView_members: `Miembros`,
guildView_pending: `Pendiente`,
@ -423,11 +438,11 @@ dict = {
guildView_rank_soldier: `Soldado`,
guildView_rank_utility: `Utilitario`,
guildView_rank_warlord: `Señor de la guerra`,
guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
guildView_currency_owned: `|COUNT| en la Bóveda.`,
guildView_bulkAddTechProjects: `Añadir proyectos de Investigación Faltantes`,
guildView_bulkAddVaultDecoRecipes: `Añadir planos de Decoración de Dojo Faltantes`,
guildView_bulkFundTechProjects: `Financiar toda la Investigación`,
guildView_bulkCompleteTechProjects: `Completar toda la Investigación`,
guildView_promote: `Promover`,
guildView_demote: `Degradar`,

View File

@ -75,8 +75,12 @@ dict = {
code_funded: `Complété`,
code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `Stalker`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `Barbe du Voyageur |INDEX|`,
code_drifterFaceName: `Visage du Voyageur |INDEX|`,
code_operatorFaceName: `Visage de l'Opérateur |INDEX|`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@ -109,9 +113,10 @@ dict = {
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `Progrès de l'évolution Incarnon`,
inventory_Boosters: `Boosters`,
inventory_boosters: `Boosters`,
inventory_flavourItems: `[UNTRANSLATED] <abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Décorations du vaisseau`,
inventory_weaponSkins: `Aspects`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`,
@ -147,7 +153,7 @@ dict = {
detailedView_valenceBonusLabel: `Bonus de Valence`,
detailedView_valenceBonusDescription: `Définir le Bonus Valence de l'arme.`,
detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
detailedView_suitInvigorationLabel: `Invigoration de Warframe`,
detailedView_invigorationLabel: `Dynamisation`,
detailedView_loadoutLabel: `Équipements`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
@ -172,9 +178,9 @@ dict = {
invigorations_utility_Jumps: `+5 réinitialisations de saut`,
invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`,
invigorations_offensiveLabel: `Amélioration offensive`,
invigorations_defensiveLabel: `Amélioration défensive`,
invigorations_expiryLabel: `Expiration de l'invigoration (optionnel)`,
detailedView_invigorationOffensiveLabel: `Amélioration offensive`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`,
abilityOverride_label: `Remplacement de pouvoir`,
abilityOverride_onSlot: `Sur l'emplacement`,
@ -257,6 +263,12 @@ dict = {
cheats_changeButton: `Changer`,
cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
cheats_finishInvasionsInOneMission: `Compléter les invasions en une mission.`,
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_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`,
worldState: `Carte Solaire`,
worldState_creditBoost: `Booster de Crédit`,
@ -273,11 +285,13 @@ dict = {
worldState_dogDays: `Bataille d'Eau`,
worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`,
worldState_wolfHunt: `Chasse au Loup (2025)`,
worldState_voidCorruption: `Corruption du Néant (|VAL|)`,
worldState_orphixVenom: `Venin Orphix`,
worldState_longShadow: `La Propagation des Ombres`,
worldState_hallowedFlame: `Flamme Hantée`,
worldState_hallowedNightmares: `Cauchemars Hantés`,
worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`,
worldState_bellyOfTheBeast: `Ventre de la Bête`,
@ -286,6 +300,7 @@ dict = {
worldState_eightClawProgressOverride: `Progrès de la Huitième Griffe`,
worldState_thermiaFractures: `Crevasses Thermia`,
worldState_thermiaFracturesProgressOverride: `Progrès des Fractures Thermia`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `de |VAL|`,
worldState_pre_year: `pre-|VAL|`,
worldState_week: `Semaine |VAL|`,

View File

@ -1,6 +1,6 @@
// Russian translation by AMelonInsideLemon, LoseFace
dict = {
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду /sync в программе bootstrapper, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNote: `Примечание: Чтобы увидеть изменения в игре, вам нужно повторно синхронизировать свой инвентарь, например, используя команду загрузчика /sync в чате игры, посетив Додзё/Реле или перезагрузив игру.`,
general_inventoryUpdateNoteGameWs: `Примечание: для того, чтобы изменения вступили в силу, может потребоваться повторно открыть меню, в котором вы находитесь.`,
general_addButton: `Добавить`,
general_setButton: `Установить`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Ранг`,
code_rankUp: `Повысить ранг`,
code_rankDown: `Понизить ранг`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_unlockLevelCap: `Разблокировать ограничение уровня`,
code_count: `Количество`,
code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Профинансировано`,
code_replays: `Повторов`,
code_stalker: `Сталкер`,
code_cutName: `Причёска: |INDEX|`,
code_drifterBeardName: `Борода скитальца: |INDEX|`,
code_drifterFaceName: `Внешность скитальца: |INDEX|`,
code_operatorFaceName: `Внешность оператора: |INDEX|`,
code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
@ -109,9 +113,10 @@ dict = {
inventory_moaPets: `МОА`,
inventory_kubrowPets: `Звери`,
inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
inventory_Boosters: `Бустеры`,
inventory_boosters: `Бустеры`,
inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`,
inventory_shipDecorations: `Украшения корабля`,
inventory_weaponSkins: `Скины`,
inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`,
inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`,
inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
inventory_bulkAddWeaponSkins: `Добавить отсутствующие скины`,
inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`,
@ -147,7 +153,7 @@ dict = {
detailedView_valenceBonusLabel: `Бонус Валентности`,
detailedView_valenceBonusDescription: `Вы можете установить или убрать бонус Валентности с вашего оружия.`,
detailedView_modularPartsLabel: `Изменить модульные части`,
detailedView_suitInvigorationLabel: `Воодушевление Варфрейма`,
detailedView_invigorationLabel: `Воодушевление`,
detailedView_loadoutLabel: `Конфигурации`,
invigorations_offensive_AbilityStrength: `+200% к силе способностей.`,
@ -172,9 +178,9 @@ dict = {
invigorations_utility_Jumps: `+5 сбросов прыжка.`,
invigorations_utility_EnergyRegen: `+2 к регенерации энергии в секунду.`,
invigorations_offensiveLabel: `Атакующее улучшение`,
invigorations_defensiveLabel: `Вспомогательное улучшение`,
invigorations_expiryLabel: `Срок действия Воодушевления (необязательно)`,
detailedView_invigorationOffensiveLabel: `Атакующее улучшение`,
detailedView_invigorationUtilityLabel: `Вспомогательное улучшение`,
detailedView_invigorationExpiryLabel: `Срок действия Воодушевления (необязательно)`,
abilityOverride_label: `Переопределение способности`,
abilityOverride_onSlot: `в ячейке`,
@ -227,7 +233,7 @@ dict = {
cheats_baroFullyStocked: `Баро полностью укомплектован`,
cheats_syndicateMissionsRepeatable: `Повторять миссии синдиката`,
cheats_unlockAllProfitTakerStages: `Разблокировать все этапы Сферы извлечения прибыли`,
cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды /sync в программе bootstrapper, посетив Додзё/Реле или повторно войдя в игру.`,
cheats_unlockSuccInventory: `Успех. Обратите внимание, что вам необходимо будет повторно синхронизировать свой инвентарь, например, с помощью команды загрузчика /sync в чате игры, посетив Додзё/Реле или повторно войдя в игру.`,
cheats_instantFinishRivenChallenge: `Мгновенное завершение испытания мода Разлома`,
cheats_instantResourceExtractorDrones: `Мгновенно добывающие Дроны-сборщики`,
cheats_noResourceExtractorDronesDamage: `Без урона по Дронам-сборщикам`,
@ -257,6 +263,12 @@ dict = {
cheats_changeButton: `Изменить`,
cheats_markAllAsRead: `Пометить все входящие как прочитанные`,
cheats_finishInvasionsInOneMission: `Завершать вторжение за одну миссию`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Мультипликатор прогресса ярости (Гринир)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Мультипликатор прогресса ярости (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `Мультипликатор прогресса антивируса`,
cheats_nemesisHintProgressMultiplierGrineer: `Мультипликатор прогресса подсказки (Гринир)`,
cheats_nemesisHintProgressMultiplierCorpus: `Мультипликатор прогресса подсказки (Корпус)`,
cheats_nemesisExtraWeapon: `Дополнительное оружие/активный Кардиомиоцит за победу над Противником (0 для отключения)`,
worldState: `Состояние мира`,
worldState_creditBoost: `Глобальный бустер Кредитов`,
@ -273,11 +285,13 @@ dict = {
worldState_dogDays: `Знойные дни`,
worldState_dogDaysRewards: `Награды Знойных дней`,
worldState_wolfHunt: `Волчья Охота (2025)`,
worldState_voidCorruption: `Искажение Бездны (|VAL|)`,
worldState_orphixVenom: `Яд Орфикса`,
worldState_longShadow: `Длинная Тень`,
worldState_hallowedFlame: `Священное пламя`,
worldState_hallowedNightmares: `Священные кошмары`,
worldState_hallowedNightmaresRewards: `Награды Священных кошмаров`,
worldState_naberusNights: `Ночи Наберуса`,
worldState_proxyRebellion: `Восстание роботов`,
worldState_proxyRebellionRewards: `Награды Восстания роботов`,
worldState_bellyOfTheBeast: `Чрево зверя`,
@ -286,6 +300,7 @@ dict = {
worldState_eightClawProgressOverride: `Прогресс Восьми когтей`,
worldState_thermiaFractures: `Разломы Термии`,
worldState_thermiaFracturesProgressOverride: `Прогресс Разломов Термии`,
worldState_qtccAlerts: `Тревоги Quest to Conquer Cancer`,
worldState_from_year: `из |VAL|`,
worldState_pre_year: `до |VAL|`,
worldState_week: `Неделя |VAL|`,

View File

@ -1,6 +1,6 @@
// Ukrainian translation by LoseFace
dict = {
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду /sync в програмі bootstrapper, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_inventoryUpdateNote: `Пам'ятка: Щоб побачити зміни в грі, вам потрібно повторно синхронізувати своє спорядження, наприклад, використовуючи команду завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або перезавантаживши гру.`,
general_inventoryUpdateNoteGameWs: `Примітка: для відображення змін може знадобитися повторно відкрити меню, в якому ви перебуваєте.`,
general_addButton: `Добавити`,
general_setButton: `Встановити`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Рівень`,
code_rankUp: `Підвищити рівень`,
code_rankDown: `Понизити рівень`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_unlockLevelCap: `Розблокувати обмеження рівня`,
code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Профінансовано`,
code_replays: `Повтори`,
code_stalker: `Сталкер`,
code_cutName: `Зачіска: |INDEX|`,
code_drifterBeardName: `Борода мандрівника: |INDEX|`,
code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`,
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
@ -109,9 +113,10 @@ dict = {
inventory_moaPets: `МОА`,
inventory_kubrowPets: `Тварини`,
inventory_evolutionProgress: `Прогрес еволюції Інкарнонів`,
inventory_Boosters: `Посилення`,
inventory_boosters: `Посилення`,
inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`,
inventory_shipDecorations: `Прикраси судна`,
inventory_weaponSkins: `Вигляди`,
inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
inventory_bulkAddWeapons: `Додати відсутню зброю`,
inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`,
inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`,
inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`,
@ -147,7 +153,7 @@ dict = {
detailedView_valenceBonusLabel: `Ознака Валентності`,
detailedView_valenceBonusDescription: `Ви можете встановити або прибрати ознаку Валентності з вашої зброї.`,
detailedView_modularPartsLabel: `Змінити модульні частини`,
detailedView_suitInvigorationLabel: `Зміцнення Ворфрейма`,
detailedView_invigorationLabel: `Зміцнення`,
detailedView_loadoutLabel: `Конфігурації`,
invigorations_offensive_AbilityStrength: `+200% до потужності здібностей.`,
@ -172,9 +178,9 @@ dict = {
invigorations_utility_Jumps: `+5 Оновлень стрибків.`,
invigorations_utility_EnergyRegen: `+2 до відновлення енергії на секунду.`,
invigorations_offensiveLabel: `Атакуюче вдосконалення`,
invigorations_defensiveLabel: `Допоміжне вдосконалення`,
invigorations_expiryLabel: `Термін дії Зміцнення (необов'язково)`,
detailedView_invigorationOffensiveLabel: `Атакуюче вдосконалення`,
detailedView_invigorationUtilityLabel: `Допоміжне вдосконалення`,
detailedView_invigorationExpiryLabel: `Термін дії Зміцнення (необов'язково)`,
abilityOverride_label: `Перевизначення здібностей`,
abilityOverride_onSlot: `у комірці`,
@ -227,7 +233,7 @@ dict = {
cheats_baroFullyStocked: `Баро повністю укомплектований`,
cheats_syndicateMissionsRepeatable: `Повторювати місії синдиката`,
cheats_unlockAllProfitTakerStages: `Розблокувати всі етапи Привласнювачки`,
cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди /sync в програмі bootstrapper, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
cheats_unlockSuccInventory: `Успішно. Зверніть увагу, що вам потрібно буде повторно синхронізувати своє спорядження, наприклад, за допомогою команди завантажувача /sync у чаті гри, відвідавши Доджьо/Реле або повторно увійшовши в гру.`,
cheats_instantFinishRivenChallenge: `Миттєве завершення випробування модифікатора Розколу`,
cheats_instantResourceExtractorDrones: `Миттєво добуваючі Дрони-видобувачі`,
cheats_noResourceExtractorDronesDamage: `Без шкоди по Дронам-видобувачам`,
@ -257,6 +263,12 @@ dict = {
cheats_changeButton: `Змінити`,
cheats_markAllAsRead: `Помітити всі вхідні як прочитані`,
cheats_finishInvasionsInOneMission: `Завершувати вторгнення за одну місію`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `Множник прогресу люті (Ґрінери)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `Множник прогресу люті (Корпус)`,
cheats_nemesisAntivirusGainMultiplier: `Мультиплікатор прогресу антивіруса`,
cheats_nemesisHintProgressMultiplierGrineer: `Множник прогресу підсказки (Ґрінери)`,
cheats_nemesisHintProgressMultiplierCorpus: `Множник прогресу підсказки (Корпус)`,
cheats_nemesisExtraWeapon: `Додаткова зброя/Жива сердцевина за перемогу над Недругом (0 для вимкнення)`,
worldState: `Стан світу`,
worldState_creditBoost: `Глобальне посилення Кредитів`,
@ -273,11 +285,13 @@ dict = {
worldState_dogDays: `Спекотні дні`,
worldState_dogDaysRewards: `Нагороди Спекотних днів`,
worldState_wolfHunt: `Полювання на Вовка (2025)`,
worldState_voidCorruption: `Викривлення Порожнечі (|VAL|)`,
worldState_orphixVenom: `Орфіксова отрута`,
worldState_longShadow: `Довга тінь`,
worldState_hallowedFlame: `Священне полум'я`,
worldState_hallowedNightmares: `Священні жахіття`,
worldState_hallowedNightmaresRewards: `Нагороди Священних жахіть`,
worldState_naberusNights: `Наберові ночі`,
worldState_proxyRebellion: `Повстання роботів`,
worldState_proxyRebellionRewards: `Нагороди Повстання роботів`,
worldState_bellyOfTheBeast: `У лігві звіра`,
@ -286,6 +300,7 @@ dict = {
worldState_eightClawProgressOverride: `Прогрес Восьми кігтів`,
worldState_thermiaFractures: `Розломи термії`,
worldState_thermiaFracturesProgressOverride: `Прогрес Розломів термії`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `з |VAL|`,
worldState_pre_year: `до |VAL|`,
worldState_week: `Тиждень |VAL|`,

View File

@ -1,4 +1,4 @@
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, & qingchun
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang, Corvus, qingchun
dict = {
general_inventoryUpdateNote: `注意: 要在游戏中查看更改,您需要重新同步库存,例如使用客户端的 /sync 命令,访问道场/中继站或重新登录.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`,
@ -75,8 +75,12 @@ dict = {
code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `追猎者`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `漂泊者胡须 |INDEX|`,
code_drifterFaceName: `漂泊者面部 |INDEX|`,
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,
@ -109,9 +113,10 @@ dict = {
inventory_moaPets: `恐鸟`,
inventory_kubrowPets: `动物同伴`,
inventory_evolutionProgress: `灵化之源进度`,
inventory_Boosters: `加成器`,
inventory_boosters: `加成器`,
inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
inventory_shipDecorations: `飞船装饰`,
inventory_weaponSkins: `外观`,
inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失载具`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
@ -147,7 +153,7 @@ dict = {
detailedView_valenceBonusLabel: `效价加成`,
detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成.`,
detailedView_modularPartsLabel: `更换部件`,
detailedView_suitInvigorationLabel: `编辑战甲活化属性`,
detailedView_invigorationLabel: `活化`,
detailedView_loadoutLabel: `配置`,
invigorations_offensive_AbilityStrength: `+200%技能强度`,
@ -172,9 +178,9 @@ dict = {
invigorations_utility_Jumps: `+5跳跃次数`,
invigorations_utility_EnergyRegen: `+2/秒能量恢复`,
invigorations_offensiveLabel: `进攻型属性`,
invigorations_defensiveLabel: `功能型属性`,
invigorations_expiryLabel: `活化时效(可选)`,
detailedView_invigorationOffensiveLabel: `进攻型属性`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`,
detailedView_invigorationExpiryLabel: `活化时效(可选)`,
abilityOverride_label: `技能替换`,
abilityOverride_onSlot: `槽位`,
@ -193,7 +199,7 @@ dict = {
cheats_skipTutorial: `跳过教程`,
cheats_skipAllDialogue: `跳过所有对话`,
cheats_unlockAllScans: `解锁所有扫描`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please that you'll need to relog for the client to refresh this.`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`,
cheats_unlockAllMissions: `解锁所有星图`,
cheats_unlockAllMissions_ok: `操作成功.请注意,您需要进入道场/中继站或重新登录以刷新星图数据.`,
cheats_infiniteCredits: `无限现金`,
@ -227,7 +233,7 @@ dict = {
cheats_baroFullyStocked: `虚空商人贩卖所有商品`,
cheats_syndicateMissionsRepeatable: `集团任务可重复完成`,
cheats_unlockAllProfitTakerStages: `解锁利润收割者圆蛛所有阶段`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging..`,
cheats_unlockSuccInventory: `[UNTRANSLATED] Success. Please note that you'll need to resync your inventory, e.g. using the bootstrapper's /sync command in game chat, visiting a dojo/relay, or relogging.`,
cheats_instantFinishRivenChallenge: `立即完成裂罅挑战`,
cheats_instantResourceExtractorDrones: `资源无人机即时完成`,
cheats_noResourceExtractorDronesDamage: `资源无人机不会损毁`,
@ -257,6 +263,12 @@ dict = {
cheats_changeButton: `更改`,
cheats_markAllAsRead: `收件箱全部标记为已读`,
cheats_finishInvasionsInOneMission: `一场任务完成整场入侵`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `玄骸怒气倍率 (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `玄骸怒气倍率 (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `杀毒进度倍率 (科腐者)`,
cheats_nemesisHintProgressMultiplierGrineer: `解密进度倍率 (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `解密进度倍率 (Corpus)`,
cheats_nemesisExtraWeapon: `额外玄骸武器/代币 (0为禁用)`,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,
@ -273,11 +285,13 @@ dict = {
worldState_dogDays: `三伏天`,
worldState_dogDaysRewards: `三伏天奖励设置`,
worldState_wolfHunt: `恶狼狩猎 (2025)`,
worldState_voidCorruption: `虚空堕落 (|VAL|)`,
worldState_orphixVenom: `奥影之毒`,
worldState_longShadow: `暗夜长影`,
worldState_hallowedFlame: `万圣之焰`,
worldState_hallowedNightmares: `万圣噩梦`,
worldState_hallowedNightmaresRewards: `万圣噩梦奖励设置`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`,
worldState_proxyRebellion: `机械叛乱`,
worldState_proxyRebellionRewards: `机械叛乱奖励设置`,
worldState_bellyOfTheBeast: `兽之腹`,
@ -286,6 +300,7 @@ dict = {
worldState_eightClawProgressOverride: `大帝金币收集进度(%)`,
worldState_thermiaFractures: `热美亚裂缝`,
worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `|VAL|`,
worldState_pre_year: `|VAL|之前`,
worldState_week: `第|VAL|周`,