Compare commits

...

60 Commits

Author SHA1 Message Date
dde8ba4d25 merge upstream 2025-10-26 17:18:25 -07:00
3ae535ccbc feat: deepmines bounties (#2933)
Closes #2936

Reviewed-on: OpenWF/SpaceNinjaServer#2933
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-26 06:37:43 -07:00
23abe5de02 fix: junction completion on steel path doesn't save (#2937)
Aka., an alternative approach to fixing the problem in #2866. Junctions don't have RewardInfo and therefore weren't reaching the new call to addMissionComplete.

Reviewed-on: OpenWF/SpaceNinjaServer#2937
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-25 00:27:02 -07:00
0d21c73ab7 fix: set ModQuestTeshinAccess when using cheats to complete mod quest (#2935)
This is required to go to Teshin's relay room after U40.

Reviewed-on: OpenWF/SpaceNinjaServer#2935
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-25 00:26:49 -07:00
482101ccd0 feat: nightcap syndicate (#2934)
Closes #2928
Closes #2931

Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2934
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-25 00:26:36 -07:00
60e87543aa fixup: skipAllDialogue for the prince 2025-10-24 10:43:05 +02:00
c4c17f24d7 chore: add nightcap stuff for skipAllDialogues (#2930)
Reviewed-on: OpenWF/SpaceNinjaServer#2930
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-23 23:21:46 -07:00
43fa1978c0 feat(webui): remove IsNew (#2926)
Closes #2917

Reviewed-on: OpenWF/SpaceNinjaServer#2926
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-23 23:21:37 -07:00
18fafc38b5 feat: invasion additional credits (#2925)
Re #1097

Reviewed-on: OpenWF/SpaceNinjaServer#2925
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-23 07:42:18 -07:00
98a46e51de feat: complete Rising Tide with buying railjack (#2922)
Closes #2754

Reviewed-on: OpenWF/SpaceNinjaServer#2922
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-21 23:50:05 -07:00
2a7767ef4a chore(webui): update fr (#2924)
Reviewed-on: OpenWF/SpaceNinjaServer#2924
Co-authored-by: Vitruvio <vitruvio@noreply.localhost>
Co-committed-by: Vitruvio <vitruvio@noreply.localhost>
2025-10-21 12:23:12 -07:00
e867123f89 fix: correct multiplier for credit blessing (#2921)
Reviewed-on: OpenWF/SpaceNinjaServer#2921
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-21 00:43:25 -07:00
2322a994c6 feat: rewards for overriden enemy caches (#2919)
Closes #2913

Reviewed-on: OpenWF/SpaceNinjaServer#2919
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-21 00:43:15 -07:00
be8e2feae6 fix: exclude SolNode63 from archon hunts (#2918)
Reviewed-on: OpenWF/SpaceNinjaServer#2918
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-21 00:43:03 -07:00
4f8b07322e chore: move int cheats into account section (#2916)
Re #2361

Reviewed-on: OpenWF/SpaceNinjaServer#2916
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-21 00:42:56 -07:00
4b3b1969da chore(webui): disable browser autocompletion for datalist inputs (#2915)
It's just unnecessary clutter, especially if you switch languages in the webui

Reviewed-on: OpenWF/SpaceNinjaServer#2915
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-20 00:56:56 -07:00
a0ce110e7e chore: dont send messages with completeQuest (#2914)
Re #2754

Reviewed-on: OpenWF/SpaceNinjaServer#2914
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-20 00:56:45 -07:00
7fe00da2a4 fix: remove vors prize from questCompletionRewards (#2911)
Because this file overrides the public export, it means The Teacher quest would not be given.

Reviewed-on: OpenWF/SpaceNinjaServer#2911
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-18 23:44:48 -07:00
bac23a8465 fix(webui): use text type for email input (#2910)
We don't need the browser to validate the input because the game accepts emails with nothing before the @ which the browser may not.

Reviewed-on: OpenWF/SpaceNinjaServer#2910
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-18 23:44:39 -07:00
db112ee5ed chore: handle updateQuest request having CompletionDate (#2909)
The client date representation would produce a schema error

Reviewed-on: OpenWF/SpaceNinjaServer#2909
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-18 23:44:28 -07:00
86998b6760 fix: disallow infestation hijack missions in sorties (#2908)
Closes #2907

Reviewed-on: OpenWF/SpaceNinjaServer#2908
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-17 22:38:08 -07:00
be3dd7ab66 chore: use prettier instead of lint:fix for 'npm run fix' (#2906)
e.g., eslint can't fix prettier problems in .json files

Reviewed-on: OpenWF/SpaceNinjaServer#2906
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-17 22:37:59 -07:00
e6fb675e21 chore: update getSkuCatalog for U40 (#2905)
Reviewed-on: OpenWF/SpaceNinjaServer#2905
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-17 22:37:52 -07:00
fb4c42490e fix(webui): use optional chaining operator for maxLevelCap (#2904)
For items that not in itemMap

Reviewed-on: OpenWF/SpaceNinjaServer#2904
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-17 22:37:45 -07:00
96a15e25df chore(webui): update German translation (#2903)
Reviewed-on: OpenWF/SpaceNinjaServer#2903
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-17 08:01:04 -07:00
ff234c9874 chore: update PE+ (#2902)
Reviewed-on: OpenWF/SpaceNinjaServer#2902
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-17 08:00:53 -07:00
7d3915fe05 feat: night of naberus and qtcc flashsales (#2901)
Reviewed-on: OpenWF/SpaceNinjaServer#2901
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-16 00:48:33 -07:00
4b3e2dfc62 chore: update typings for bootstrapper for 0.11.13 (#2900)
Reviewed-on: OpenWF/SpaceNinjaServer#2900
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-16 00:48:12 -07:00
737d013655 feat: focus 2.0 (#2898)
Implemented all the ops we handle for focus 3.0 + activating/deactivating upgrades + the pool mechanic

Reviewed-on: OpenWF/SpaceNinjaServer#2898
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-16 00:48:01 -07:00
1f8d437fad chore: fix unlock all focus schools cheat advising visiting navigation (#2899)
This doesn't work to sync inventory on pre-duviri or post-spider versions.

Reviewed-on: OpenWF/SpaceNinjaServer#2899
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-15 01:14:05 -07:00
9263b8f179 chore: forgot to add one removed/obsolete setting to configRemovedOptionsKeys (#2897)
One setting in the config used to have a typo before #291 and the whole thing here is case sensitive anyway, so I added it here as well.

Reviewed-on: OpenWF/SpaceNinjaServer#2897
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-15 01:13:53 -07:00
875f4b9fa4 chore: more removed/obsolete settings put into configRemovedOptionsKeys (#2896)
Reviewed-on: OpenWF/SpaceNinjaServer#2896
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-14 00:24:43 -07:00
fd7ddd9696 chore(webui): fix inconsistent strings in dropdown menu (#2895)
Reviewed-on: OpenWF/SpaceNinjaServer#2895
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-13 05:21:22 -07:00
065afc0089 chore(webui): move wolf hunt 2025 option up for consistency (#2891)
Reviewed-on: OpenWF/SpaceNinjaServer#2891
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-10-11 23:35:53 -07:00
c1c14b2068 feat(webui): Vault MiscItems and ShipDecorations (#2889)
Closes #2874
Closes #2875

Reviewed-on: OpenWF/SpaceNinjaServer#2889
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-11 23:35:39 -07:00
9e66d22256 feat: wolf hunt 2019 (#2888)
Re #1103

Reviewed-on: OpenWF/SpaceNinjaServer#2888
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-11 05:17:08 -07:00
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
60 changed files with 2678 additions and 645 deletions

View File

@ -14,9 +14,6 @@
"unlockAllSkins": false, "unlockAllSkins": false,
"fullyStockedVendors": false, "fullyStockedVendors": false,
"skipClanKeyCrafting": false, "skipClanKeyCrafting": false,
"spoofMasteryRank": -1,
"relicRewardItemCountMultiplier": 1,
"nightwaveStandingMultiplier": 1,
"unfaithfulBugFixes": { "unfaithfulBugFixes": {
"ignore1999LastRegionPlayed": false, "ignore1999LastRegionPlayed": false,
"fixXtraCheeseTimer": false, "fixXtraCheeseTimer": false,
@ -31,7 +28,7 @@
"baroAlwaysAvailable": false, "baroAlwaysAvailable": false,
"baroFullyStocked": false, "baroFullyStocked": false,
"varziaFullyStocked": false, "varziaFullyStocked": false,
"wolfHunt": false, "wolfHunt": null,
"orphixVenom": false, "orphixVenom": false,
"longShadow": false, "longShadow": false,
"hallowedFlame": false, "hallowedFlame": false,
@ -41,6 +38,11 @@
"naberusNightsOverride": null, "naberusNightsOverride": null,
"proxyRebellion": false, "proxyRebellion": false,
"proxyRebellionRewardsOverride": 0, "proxyRebellionRewardsOverride": 0,
"voidCorruption2025Week1": false,
"voidCorruption2025Week2": false,
"voidCorruption2025Week3": false,
"voidCorruption2025Week4": false,
"qtccAlerts": false,
"galleonOfGhouls": 0, "galleonOfGhouls": 0,
"ghoulEmergenceOverride": null, "ghoulEmergenceOverride": null,
"plagueStarOverride": null, "plagueStarOverride": null,

10
package-lock.json generated
View File

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

View File

@ -22,11 +22,12 @@
"lint:fix": "eslint --fix --ext .ts .", "lint:fix": "eslint --fix --ext .ts .",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"update-translations": "cd scripts && node update-translations.cjs", "update-translations": "cd scripts && node update-translations.cjs",
"fix": "npm run update-translations && npm run lint:fix" "fix": "npm run update-translations && npm run prettier"
}, },
"license": "GNU", "license": "GNU",
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"body-parser": "^2.2.0",
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"crc-32": "^1.2.2", "crc-32": "^1.2.2",
"express": "^5", "express": "^5",
@ -35,13 +36,14 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"undici": "^7.10.0", "undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.89", "warframe-public-export-plus": "^0.5.93",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0", "winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2" "ws": "^8.18.2"
}, },
"optionalDependencies": { "optionalDependencies": {
"@types/body-parser": "^1.19.6",
"@types/express": "^5", "@types/express": "^5",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10", "@types/websocket": "^1.0.10",

View File

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

View File

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

View File

@ -0,0 +1,62 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { logger } from "../../utils/logger.ts";
import type { IInventoryChanges } from "../../types/purchaseTypes.ts";
export const feedPrinceController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems NokkoColony NodeIntrosCompleted");
const payload = getJSONfromString<IFeedPrinceRequest>(String(req.body));
switch (payload.Mode) {
case "r": {
inventory.NokkoColony ??= {
FeedLevel: 0,
JournalEntries: []
};
const InventoryChanges: IInventoryChanges = {};
inventory.NokkoColony.FeedLevel += payload.Amount;
if (
(!inventory.NodeIntrosCompleted.includes("CompletedVision1") && inventory.NokkoColony.FeedLevel > 20) ||
(!inventory.NodeIntrosCompleted.includes("CompletedVision2") && inventory.NokkoColony.FeedLevel > 60)
) {
res.json({
FeedSucceeded: false,
FeedLevel: inventory.NokkoColony.FeedLevel - payload.Amount,
InventoryChanges
} satisfies IFeedPrinceResponse);
} else {
addMiscItem(
inventory,
"/Lotus/Types/Items/MiscItems/MushroomFood",
payload.Amount * -1,
InventoryChanges
);
await inventory.save();
res.json({
FeedSucceeded: true,
FeedLevel: inventory.NokkoColony.FeedLevel,
InventoryChanges
} satisfies IFeedPrinceResponse);
}
break;
}
default:
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown feedPrince mode: ${payload.Mode}`);
}
};
interface IFeedPrinceRequest {
Mode: string; // r
Amount: number;
}
interface IFeedPrinceResponse {
FeedSucceeded: boolean;
FeedLevel: number;
InventoryChanges: IInventoryChanges;
}

View File

@ -1,23 +1,91 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts"; import { getAccountForRequest } from "../../services/loginService.ts";
import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts"; import { getInventory, addMiscItems, addEquipment, occupySlot } from "../../services/inventoryService.ts";
import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts"; import type { IMiscItem, TFocusPolarity, TEquipmentKey } from "../../types/inventoryTypes/inventoryTypes.ts";
import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts"; import { InventorySlot } from "../../types/inventoryTypes/inventoryTypes.ts";
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
import { ExportFocusUpgrades } from "warframe-public-export-plus"; import { ExportFocusUpgrades } from "warframe-public-export-plus";
import { Inventory } from "../../models/inventoryModels/inventoryModel.ts"; import { Inventory } from "../../models/inventoryModels/inventoryModel.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts";
export const focusController: RequestHandler = async (req, res) => { export const focusController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const account = await getAccountForRequest(req);
switch (req.query.op) {
let op = req.query.op as string;
const focus2 = account.BuildLabel && version_compare(account.BuildLabel, "2022.04.29.12.53") < 0;
if (focus2) {
// Focus 2.0
switch (req.query.op) {
case Focus2Operation.InstallLens:
op = "InstallLens";
break;
case Focus2Operation.UnlockWay:
op = "UnlockWay";
break;
case Focus2Operation.UnlockUpgrade:
op = "UnlockUpgrade";
break;
case Focus2Operation.IncreasePool:
op = "IncreasePool";
break;
case Focus2Operation.LevelUpUpgrade:
op = "LevelUpUpgrade";
break;
case Focus2Operation.ActivateWay:
op = "ActivateWay";
break;
case Focus2Operation.UpdateUpgrade:
op = "UpdateUpgrade";
break;
case Focus2Operation.SentTrainingAmplifier:
op = "SentTrainingAmplifier";
break;
case Focus2Operation.UnbindUpgrade:
op = "UnbindUpgrade";
break;
case Focus2Operation.ConvertShard:
op = "ConvertShard";
break;
}
} else {
// Focus 3.0
switch (req.query.op) {
case Focus3Operation.InstallLens:
op = "InstallLens";
break;
case Focus3Operation.UnlockWay:
op = "UnlockWay";
break;
case Focus3Operation.UnlockUpgrade:
op = "UnlockUpgrade";
break;
case Focus3Operation.LevelUpUpgrade:
op = "LevelUpUpgrade";
break;
case Focus3Operation.ActivateWay:
op = "ActivateWay";
break;
case Focus3Operation.SentTrainingAmplifier:
op = "SentTrainingAmplifier";
break;
case Focus3Operation.UnbindUpgrade:
op = "UnbindUpgrade";
break;
case Focus3Operation.ConvertShard:
op = "ConvertShard";
break;
}
}
switch (op) {
default: default:
logger.error("Unhandled focus op type: " + String(req.query.op)); logger.error("Unhandled focus op type: " + String(req.query.op));
logger.debug(String(req.body)); logger.debug(String(req.body));
res.end(); res.end();
break; break;
case FocusOperation.InstallLens: { case "InstallLens": {
const request = JSON.parse(String(req.body)) as ILensInstallRequest; const request = JSON.parse(String(req.body)) as ILensInstallRequest;
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
const item = inventory[request.Category].id(request.WeaponId); const item = inventory[request.Category].id(request.WeaponId);
if (item) { if (item) {
item.FocusLens = request.LensType; item.FocusLens = request.LensType;
@ -35,10 +103,10 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case FocusOperation.UnlockWay: { case "UnlockWay": {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
const focusPolarity = focusTypeToPolarity(focusType); const focusPolarity = focusTypeToPolarity(focusType);
const inventory = await getInventory(accountId, "FocusAbility FocusUpgrades FocusXP"); const inventory = await getInventory(account._id.toString(), "FocusAbility FocusUpgrades FocusXP");
const cost = inventory.FocusAbility ? 50_000 : 0; const cost = inventory.FocusAbility ? 50_000 : 0;
inventory.FocusAbility ??= focusType; inventory.FocusAbility ??= focusType;
inventory.FocusUpgrades.push({ ItemType: focusType }); inventory.FocusUpgrades.push({ ItemType: focusType });
@ -52,12 +120,29 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case FocusOperation.ActivateWay: { case "IncreasePool": {
const request = JSON.parse(String(req.body)) as IIncreasePoolRequest;
const focusPolarity = focusTypeToPolarity(request.FocusType);
const inventory = await getInventory(account._id.toString(), "FocusXP FocusCapacity");
let cost = 0;
for (let capacity = request.CurrentTotalCapacity; capacity != request.NewTotalCapacity; ++capacity) {
cost += increasePoolCost[capacity - 5];
}
inventory.FocusXP![focusPolarity]! -= cost;
inventory.FocusCapacity = request.NewTotalCapacity;
await inventory.save();
res.json({
TotalCapacity: request.NewTotalCapacity,
FocusPointCosts: { [focusPolarity]: cost }
});
break;
}
case "ActivateWay": {
const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType; const focusType = (JSON.parse(String(req.body)) as IWayRequest).FocusType;
await Inventory.updateOne( await Inventory.updateOne(
{ {
accountOwnerId: accountId accountOwnerId: account._id.toString()
}, },
{ {
FocusAbility: focusType FocusAbility: focusType
@ -69,13 +154,20 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case FocusOperation.UnlockUpgrade: { case "UnlockUpgrade": {
const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest; const request = JSON.parse(String(req.body)) as IUnlockUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
let cost = 0; let cost = 0;
for (const focusType of request.FocusTypes) { for (const focusType of request.FocusTypes) {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost; if (focusType in ExportFocusUpgrades) {
cost += ExportFocusUpgrades[focusType].baseFocusPointCost;
} else if (focusType == "/Lotus/Upgrades/Focus/Power/Residual/ChannelEfficiencyFocusUpgrade") {
// Zenurik's Inner Might (Focus 2.0)
cost += 50_000;
} else {
logger.warn(`unknown focus upgrade ${focusType}, will unlock it for free`);
}
inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 }); inventory.FocusUpgrades.push({ ItemType: focusType, Level: 0 });
} }
inventory.FocusXP![focusPolarity]! -= cost; inventory.FocusXP![focusPolarity]! -= cost;
@ -86,15 +178,20 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case FocusOperation.LevelUpUpgrade: { case "LevelUpUpgrade":
case "UpdateUpgrade": {
const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest; const request = JSON.parse(String(req.body)) as ILevelUpUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType); const focusPolarity = focusTypeToPolarity(request.FocusInfos[0].ItemType);
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
let cost = 0; let cost = 0;
for (const focusUpgrade of request.FocusInfos) { for (const focusUpgrade of request.FocusInfos) {
cost += focusUpgrade.FocusXpCost; cost += focusUpgrade.FocusXpCost;
const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!; const focusUpgradeDb = inventory.FocusUpgrades.find(entry => entry.ItemType == focusUpgrade.ItemType)!;
focusUpgradeDb.Level = focusUpgrade.Level; if (op == "UpdateUpgrade") {
focusUpgradeDb.IsActive = focusUpgrade.IsActive;
} else {
focusUpgradeDb.Level = focusUpgrade.Level;
}
} }
inventory.FocusXP![focusPolarity]! -= cost; inventory.FocusXP![focusPolarity]! -= cost;
await inventory.save(); await inventory.save();
@ -104,9 +201,9 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case FocusOperation.SentTrainingAmplifier: { case "SentTrainingAmplifier": {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, { const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
ModularParts: [ ModularParts: [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
@ -119,10 +216,10 @@ export const focusController: RequestHandler = async (req, res) => {
res.json(inventoryChanges.OperatorAmps![0]); res.json(inventoryChanges.OperatorAmps![0]);
break; break;
} }
case FocusOperation.UnbindUpgrade: { case "UnbindUpgrade": {
const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest; const request = JSON.parse(String(req.body)) as IUnbindUpgradeRequest;
const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]); const focusPolarity = focusTypeToPolarity(request.FocusTypes[0]);
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length; inventory.FocusXP![focusPolarity]! -= 750_000 * request.FocusTypes.length;
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
@ -149,7 +246,7 @@ export const focusController: RequestHandler = async (req, res) => {
}); });
break; break;
} }
case FocusOperation.ConvertShard: { case "ConvertShard": {
const request = JSON.parse(String(req.body)) as IConvertShardRequest; const request = JSON.parse(String(req.body)) as IConvertShardRequest;
// Tally XP // Tally XP
let xp = 0; let xp = 0;
@ -167,7 +264,7 @@ export const focusController: RequestHandler = async (req, res) => {
for (const shard of request.Shards) { for (const shard of request.Shards) {
shard.ItemCount *= -1; shard.ItemCount *= -1;
} }
const inventory = await getInventory(accountId); const inventory = await getInventory(account._id.toString());
const polarity = request.Polarity; const polarity = request.Polarity;
inventory.FocusXP ??= {}; inventory.FocusXP ??= {};
inventory.FocusXP[polarity] ??= 0; inventory.FocusXP[polarity] ??= 0;
@ -179,7 +276,8 @@ export const focusController: RequestHandler = async (req, res) => {
} }
}; };
enum FocusOperation { // Focus 3.0
enum Focus3Operation {
InstallLens = "1", InstallLens = "1",
UnlockWay = "2", UnlockWay = "2",
UnlockUpgrade = "3", UnlockUpgrade = "3",
@ -190,6 +288,20 @@ enum FocusOperation {
ConvertShard = "9" ConvertShard = "9"
} }
// Focus 2.0
enum Focus2Operation {
InstallLens = "1",
UnlockWay = "2",
UnlockUpgrade = "3",
IncreasePool = "4",
LevelUpUpgrade = "5",
ActivateWay = "6",
UpdateUpgrade = "7", // used to change the IsActive state, same format as ILevelUpUpgradeRequest
SentTrainingAmplifier = "9",
UnbindUpgrade = "10",
ConvertShard = "11"
}
// For UnlockWay & ActivateWay // For UnlockWay & ActivateWay
interface IWayRequest { interface IWayRequest {
FocusType: string; FocusType: string;
@ -199,6 +311,13 @@ interface IUnlockUpgradeRequest {
FocusTypes: string[]; FocusTypes: string[];
} }
// Focus 2.0
interface IIncreasePoolRequest {
FocusType: string;
CurrentTotalCapacity: number;
NewTotalCapacity: number;
}
interface ILevelUpUpgradeRequest { interface ILevelUpUpgradeRequest {
FocusInfos: { FocusInfos: {
ItemType: string; ItemType: string;
@ -206,6 +325,7 @@ interface ILevelUpUpgradeRequest {
IsUniversal: boolean; IsUniversal: boolean;
Level: number; Level: number;
IsActiveAbility: boolean; IsActiveAbility: boolean;
IsActive?: number; // Focus 2.0
}[]; }[];
} }
@ -240,3 +360,19 @@ const shardValues = {
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000, "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantItem": 25_000,
"/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000 "/Lotus/Types/Gameplay/Eidolon/Resources/SentientShards/SentientShardBrilliantTierTwoItem": 40_000
}; };
// Starting at a capacity of 5 (Source: https://wiki.warframe.com/w/Focus_2.0)
const increasePoolCost = [
2576, 3099, 3638, 4190, 4755, 5331, 5918, 6514, 7120, 7734, 8357, 8988, 9626, 10271, 10923, 11582, 12247, 12918,
13595, 14277, 14965, 15659, 16357, 17061, 17769, 18482, 19200, 19922, 20649, 21380, 22115, 22854, 23597, 24344,
25095, 25850, 26609, 27371, 28136, 28905, 29678, 30454, 31233, 32015, 32801, 33590, 34382, 35176, 35974, 36775,
37579, 38386, 39195, 40008, 40823, 41641, 42461, 43284, 44110, 44938, 45769, 46603, 47439, 48277, 49118, 49961,
50807, 51655, 52505, 53357, 54212, 55069, 55929, 56790, 57654, 58520, 59388, 60258, 61130, 62005, 62881, 63759,
64640, 65522, 66407, 67293, 68182, 69072, 69964, 70858, 71754, 72652, 73552, 74453, 75357, 76262, 77169, 78078,
78988, 79900, 80814, 81730, 82648, 83567, 84488, 85410, 86334, 87260, 88188, 89117, 90047, 90980, 91914, 92849,
93786, 94725, 95665, 96607, 97550, 98495, 99441, 100389, 101338, 102289, 103241, 104195, 105150, 106107, 107065,
108024, 108985, 109948, 110911, 111877, 112843, 113811, 114780, 115751, 116723, 117696, 118671, 119647, 120624,
121603, 122583, 123564, 124547, 125531, 126516, 127503, 128490, 129479, 130470, 131461, 132454, 133448, 134443,
135440, 136438, 137437, 138437, 139438, 140441, 141444, 142449, 143455, 144463, 145471, 146481, 147492, 148503,
149517
];

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

@ -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 inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
//await inventory.save(); //await inventory.save();
@ -348,12 +348,12 @@ export const getInventoryResponse = async (
} }
} }
if (typeof config.spoofMasteryRank === "number" && config.spoofMasteryRank >= 0) { if (inventory.spoofMasteryRank && inventory.spoofMasteryRank >= 0) {
inventoryResponse.PlayerLevel = config.spoofMasteryRank; inventoryResponse.PlayerLevel = inventory.spoofMasteryRank;
if (!xpBasedLevelCapDisabled) { if (!xpBasedLevelCapDisabled) {
// This client has not been patched to accept any mastery rank, need to fake the XP. // This client has not been patched to accept any mastery rank, need to fake the XP.
inventoryResponse.XPInfo = []; inventoryResponse.XPInfo = [];
let numFrames = getExpRequiredForMr(Math.min(config.spoofMasteryRank, 5030)) / 6000; let numFrames = getExpRequiredForMr(Math.min(inventory.spoofMasteryRank, 5030)) / 6000;
while (numFrames-- > 0) { while (numFrames-- > 0) {
inventoryResponse.XPInfo.push({ inventoryResponse.XPInfo.push({
ItemType: "/Lotus/Powersuits/Mag/Mag", ItemType: "/Lotus/Powersuits/Mag/Mag",
@ -461,6 +461,9 @@ export const getInventoryResponse = async (
toLegacyOid(id); toLegacyOid(id);
} }
} }
if (inventoryResponse.GuildId) {
toLegacyOid(inventoryResponse.GuildId);
}
} }
} }
} }

View File

@ -129,14 +129,22 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
res.json(deltas); res.json(deltas);
} else if (missionReport.RewardInfo) { } else if (missionReport.RewardInfo) {
logger.debug(`classic mission completion, sending everything`); 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({ res.json({
InventoryJson: JSON.stringify(inventoryResponse), InventoryJson: JSON.stringify(inventoryResponse),
...deltas ...deltas
} satisfies IMissionInventoryUpdateResponse); } satisfies IMissionInventoryUpdateResponse);
} else { } else {
logger.debug(`no reward info, assuming this wasn't a mission completion and we should just sync inventory`); 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({ res.json({
InventoryJson: JSON.stringify(inventoryResponse) InventoryJson: JSON.stringify(inventoryResponse)
} satisfies IMissionInventoryUpdateResponseBackToDryDock); } 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 type { IKnifeResponse } from "../../helpers/nemesisHelpers.ts";
import { import {
antivirusMods, antivirusMods,
@ -310,6 +310,26 @@ export const nemesisController: RequestHandler = async (req, res) => {
res.json({ res.json({
target: inventory.toJSON().Nemesis 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") { } else if ((req.query.mode as string) == "w") {
const inventory = await getInventory(account._id.toString(), "Nemesis"); const inventory = await getInventory(account._id.toString(), "Nemesis");
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body)); //const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
@ -447,3 +467,15 @@ const consumeModCharge = (
response.UpgradeNew.push(true); 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 { createMessage } from "../../services/inboxService.ts";
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts"; import { getAccountForRequest, getSuffixedName } from "../../services/loginService.ts";
import { sendWsBroadcastTo } from "../../services/wsService.ts";
import { GuildPermission } from "../../types/guildTypes.ts"; import { GuildPermission } from "../../types/guildTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
@ -85,6 +86,7 @@ export const removeFromGuildController: RequestHandler = async (req, res) => {
ItemToRemove: "/Lotus/Types/Keys/DojoKey", ItemToRemove: "/Lotus/Types/Keys/DojoKey",
RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint" RecipeToRemove: "/Lotus/Types/Keys/DojoKeyBlueprint"
}); });
sendWsBroadcastTo(payload.userId, { update_inventory: true });
}; };
interface IRemoveFromGuildRequest { interface IRemoveFromGuildRequest {

View File

@ -0,0 +1,117 @@
import type { RequestHandler } from "express";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { addMiscItem, getInventory } from "../../services/inventoryService.ts";
import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { logger } from "../../utils/logger.ts";
import type { IJournalEntry } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IAffiliationMods } from "../../types/purchaseTypes.ts";
export const researchMushroomController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "MiscItems NokkoColony Affiliations");
const payload = getJSONfromString<IResearchMushroom>(String(req.body));
switch (payload.Mode) {
case "r": {
const InventoryChanges = {};
const AffiliationMods: IAffiliationMods[] = [];
addMiscItem(inventory, payload.MushroomItem, payload.Amount * -1, InventoryChanges);
if (payload.Convert) {
addMiscItem(inventory, "/Lotus/Types/Items/MiscItems/MushroomFood", payload.Amount, InventoryChanges);
}
inventory.NokkoColony ??= {
FeedLevel: 0,
JournalEntries: []
};
let journalEntry = inventory.NokkoColony.JournalEntries.find(x => x.EntryType == payload.MushroomItem);
if (!journalEntry) {
journalEntry = { EntryType: payload.MushroomItem, Progress: 0 };
inventory.NokkoColony.JournalEntries.push(journalEntry);
}
let syndicate = inventory.Affiliations.find(x => x.Tag == "NightcapJournalSyndicate");
if (!syndicate) {
syndicate = { Tag: "NightcapJournalSyndicate", Title: 0, Standing: 0 };
inventory.Affiliations.push(syndicate);
}
const completedBefore = inventory.NokkoColony.JournalEntries.filter(
entry => getJournalRank(entry) === 3
).length;
const PrevRank = syndicateTitleThresholds.reduce(
(rank, threshold, i) => (completedBefore >= threshold ? i : rank),
0
);
if (getJournalRank(journalEntry) < 3) journalEntry.Progress += payload.Amount;
const completedAfter = inventory.NokkoColony.JournalEntries.filter(
entry => getJournalRank(entry) === 3
).length;
const NewRank = syndicateTitleThresholds.reduce(
(rank, threshold, i) => (completedAfter >= threshold ? i : rank),
0
);
if (NewRank > (syndicate.Title ?? 0)) {
syndicate.Title = NewRank;
AffiliationMods.push({ Tag: "NightcapJournalSyndicate", Title: NewRank });
}
await inventory.save();
res.json({
PrevRank,
NewRank,
Progress: journalEntry.Progress,
InventoryChanges,
AffiliationMods
});
break;
}
default:
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
throw new Error(`unknown researchMushroom mode: ${payload.Mode}`);
}
};
interface IResearchMushroom {
Mode: string; // r
MushroomItem: string;
Amount: number;
Convert: boolean;
}
const journalEntriesRank: Record<string, number> = {
"/Lotus/Types/Items/MushroomJournal/PlainMushroomJournalItem": 1,
"/Lotus/Types/Items/MushroomJournal/GasMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/ToxinMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/ViralMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/MagneticMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/ElectricMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/TauMushroomJournalItem": 5,
"/Lotus/Types/Items/MushroomJournal/SlashMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/BlastMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/ImpactMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/ColdMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/CorrosiveMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/PunctureMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/HeatMushroomJournalItem": 3,
"/Lotus/Types/Items/MushroomJournal/RadiationMushroomJournalItem": 4,
"/Lotus/Types/Items/MushroomJournal/VoidMushroomJournalItem": 5
};
const syndicateTitleThresholds = [0, 1, 2, 6, 12, 16];
const getJournalRank = (journalEntry: IJournalEntry): number => {
const k = journalEntriesRank[journalEntry.EntryType];
if (!k) return 0;
const thresholds = [k * 1, k * 3, k * 6];
if (journalEntry.Progress >= thresholds[2]) return 3;
if (journalEntry.Progress >= thresholds[1]) return 2;
if (journalEntry.Progress >= thresholds[0]) return 1;
return 0;
};

View File

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

View File

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

View File

@ -1,11 +1,20 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { updateShipFeature } from "../../services/personalRoomsService.ts"; import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { IUnlockShipFeatureRequest } from "../../types/requestTypes.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { parseString } from "../../helpers/general.ts"; import { getJSONfromString } from "../../helpers/stringHelpers.ts";
import { unlockShipFeature } from "../../services/personalRoomsService.ts";
export const unlockShipFeatureController: RequestHandler = async (req, res) => { export const unlockShipFeatureController: RequestHandler = async (req, res) => {
const accountId = parseString(req.query.accountId); const accountId = await getAccountIdForRequest(req);
const shipFeatureRequest = JSON.parse((req.body as string).toString()) as IUnlockShipFeatureRequest; const inventory = await getInventory(accountId, "MiscItems accountOwnerId");
await updateShipFeature(accountId, shipFeatureRequest.Feature); const request = getJSONfromString<IUnlockShipFeatureRequest>(String(req.body));
await unlockShipFeature(inventory, request.Feature);
await inventory.save();
res.send([]); res.send([]);
}; };
interface IUnlockShipFeatureRequest {
Feature: string;
KeyChain: string;
ChainStage: number;
}

View File

@ -14,7 +14,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
const inventory = await getInventory( const inventory = await getInventory(
account._id.toString(), account._id.toString(),
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress" "ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress nightwaveStandingMultiplier"
); );
let affiliationMods: IAffiliationMods[] = []; let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) { if (challenges.ChallengeProgress) {

View File

@ -1,36 +0,0 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
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, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
res.status(400).send("-1").end();
return;
}
guild.VaultDecoRecipes ??= [];
for (const request of requests) {
const index = guild.VaultDecoRecipes.findIndex(x => x.ItemType === request.ItemType);
if (index == -1) {
guild.VaultDecoRecipes.push({
ItemType: request.ItemType,
ItemCount: request.ItemCount
});
} else {
guild.VaultDecoRecipes[index].ItemCount += request.ItemCount;
if (guild.VaultDecoRecipes[index].ItemCount < 1) {
guild.VaultDecoRecipes.splice(index, 1);
}
}
}
await guild.save();
res.end();
};

View File

@ -0,0 +1,43 @@
import { getAccountIdForRequest } from "../../services/loginService.ts";
import { getInventory } from "../../services/inventoryService.ts";
import type { RequestHandler } from "express";
import { getGuildForRequestEx, hasGuildPermission } from "../../services/guildService.ts";
import { GuildPermission } from "../../types/guildTypes.ts";
import type { ITypeCount } from "../../types/commonTypes.ts";
export const addVaultTypeCountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const { vaultType, items } = req.body as {
vaultType: keyof typeof vaultConfig;
items: ITypeCount[];
};
const inventory = await getInventory(accountId, "GuildId");
const guild = await getGuildForRequestEx(req, inventory);
if (!(await hasGuildPermission(guild, accountId, vaultConfig[vaultType]))) {
res.status(400).send("-1").end();
return;
}
guild[vaultType] ??= [];
for (const item of items) {
const index = guild[vaultType].findIndex(x => x.ItemType === item.ItemType);
if (index === -1) {
guild[vaultType].push({
ItemType: item.ItemType,
ItemCount: item.ItemCount
});
} else {
guild[vaultType][index].ItemCount += item.ItemCount;
if (guild[vaultType][index].ItemCount < 1) {
guild[vaultType].splice(index, 1);
}
}
}
await guild.save();
res.end();
};
const vaultConfig = {
VaultShipDecorations: GuildPermission.Treasurer,
VaultMiscItems: GuildPermission.Treasurer,
VaultDecoRecipes: GuildPermission.Architect
} as const;

View File

@ -38,6 +38,7 @@ interface ListedItem {
parazon?: boolean; parazon?: boolean;
alwaysAvailable?: boolean; alwaysAvailable?: boolean;
maxLevelCap?: number; maxLevelCap?: number;
eligibleForVault?: boolean;
} }
interface ItemLists { interface ItemLists {
@ -66,6 +67,7 @@ interface ItemLists {
VaultDecoRecipes: ListedItem[]; VaultDecoRecipes: ListedItem[];
FlavourItems: ListedItem[]; FlavourItems: ListedItem[];
ShipDecorations: ListedItem[]; ShipDecorations: ListedItem[];
WeaponSkins: ListedItem[];
//circuitGameModes: ListedItem[]; //circuitGameModes: ListedItem[];
} }
@ -107,7 +109,8 @@ const getItemListsController: RequestHandler = (req, response) => {
TechProjects: [], TechProjects: [],
VaultDecoRecipes: [], VaultDecoRecipes: [],
FlavourItems: [], FlavourItems: [],
ShipDecorations: [] ShipDecorations: [],
WeaponSkins: []
/*circuitGameModes: [ /*circuitGameModes: [
{ {
uniqueName: "Survival", uniqueName: "Survival",
@ -139,6 +142,12 @@ const getItemListsController: RequestHandler = (req, response) => {
} }
]*/ ]*/
}; };
const eligibleForVault = new Set<string>([
...Object.values(ExportDojoRecipes.research).flatMap(r => r.ingredients.map(i => i.ItemType)),
...Object.values(ExportDojoRecipes.fabrications).flatMap(f => f.ingredients.map(i => i.ItemType)),
...Object.values(ExportDojoRecipes.rooms).flatMap(r => r.ingredients.map(i => i.ItemType)),
...Object.values(ExportDojoRecipes.decos).flatMap(d => d.ingredients.map(i => i.ItemType))
]);
for (const [uniqueName, item] of Object.entries(ExportWarframes)) { for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({ res[item.productCategory].push({
uniqueName, uniqueName,
@ -244,7 +253,8 @@ const getItemListsController: RequestHandler = (req, response) => {
res.miscitems.push({ res.miscitems.push({
uniqueName: uniqueName, uniqueName: uniqueName,
name: name, name: name,
subtype: "Resource" subtype: "Resource",
...(eligibleForVault.has(uniqueName) && { eligibleForVault: true })
}); });
} }
} }
@ -298,10 +308,18 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const [uniqueName, item] of Object.entries(ExportCustoms)) { for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
res.miscitems.push({ if (
uniqueName: uniqueName, item.productCategory == "WeaponSkins" &&
name: getString(item.name, lang) !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)) { for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {

View File

@ -136,7 +136,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
if (currentStage + 1 == questManifest.chainStages?.length) { if (currentStage + 1 == questManifest.chainStages?.length) {
logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`); logger.debug(`Trying to complete last stage with nextStage, calling completeQuest instead`);
await completeQuest(inventory, questKey.ItemType); await completeQuest(inventory, questKey.ItemType, true);
} else { } else {
if (run > 0) { if (run > 0) {
questKey.Progress[currentStage + 1].c = run; questKey.Progress[currentStage + 1].c = run;

View File

@ -0,0 +1,24 @@
import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.ts";
import type { RequestHandler } from "express";
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
import { broadcastInventoryUpdate } from "../../services/wsService.ts";
export const removeIsNewController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const filteredEquipmentKeys = equipmentKeys.filter(k => k !== "CrewShipWeapons" && k !== "CrewShipSalvagedWeapons");
const inventory = await getInventory(accountId, [...filteredEquipmentKeys, "WeaponSkins"].join(" "));
for (const key of filteredEquipmentKeys) {
if (key in inventory) {
for (const equipment of inventory[key]) {
if (equipment.IsNew) equipment.IsNew = false;
}
}
}
for (const equipment of inventory.WeaponSkins) {
if (equipment.IsNew) equipment.IsNew = false;
}
await inventory.save();
res.end();
broadcastInventoryUpdate(req);
};

View File

@ -1,6 +1,6 @@
import { getInventory } from "../../services/inventoryService.ts"; import { getInventory } from "../../services/inventoryService.ts";
import { getAccountIdForRequest } from "../../services/loginService.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 { IAccountCheats } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { logger } from "../../utils/logger.ts"; import { logger } from "../../utils/logger.ts";
@ -20,6 +20,8 @@ export const setAccountCheatController: RequestHandler = async (req, res) => {
res.end(); res.end();
if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) { if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(payload.key) != -1) {
sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true }); sendWsBroadcastTo(accountId, { update_inventory: true, sync_inventory: true });
} else {
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
} }
}; };

View File

@ -1,16 +1,9 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import type { ITunables } from "../../types/bootstrapperTypes.ts";
// This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual // This endpoint is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
interface ITunables { export const tunablesController: RequestHandler = (_req, res) => {
prohibit_skip_mission_start_timer?: boolean;
prohibit_fov_override?: boolean;
prohibit_freecam?: boolean;
prohibit_teleport?: boolean;
prohibit_scripts?: boolean;
}
const tunablesController: RequestHandler = (_req, res) => {
const tunables: ITunables = {}; const tunables: ITunables = {};
//tunables.prohibit_skip_mission_start_timer = true; //tunables.prohibit_skip_mission_start_timer = true;
//tunables.prohibit_fov_override = true; //tunables.prohibit_fov_override = true;
@ -19,5 +12,3 @@ const tunablesController: RequestHandler = (_req, res) => {
//tunables.prohibit_scripts = true; //tunables.prohibit_scripts = true;
res.json(tunables); res.json(tunables);
}; };
export { tunablesController };

View File

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

View File

@ -1,5 +1,12 @@
import type { RequestHandler } from "express"; import type { RequestHandler } from "express";
import { getAccountForRequest } from "../../services/loginService.ts";
import { version_compare } from "../../helpers/inventoryHelpers.ts";
export const getSkuCatalogController: RequestHandler = (_req, res) => { export const getSkuCatalogController: RequestHandler = async (req, res) => {
res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" }); const account = await getAccountForRequest(req);
if (!account.BuildLabel || version_compare(account.BuildLabel, "2025.10.14.16.10") >= 0) {
res.sendFile("static/fixed_responses/getSkuCatalogU40.json", { root: "./" });
} else {
res.sendFile("static/fixed_responses/getSkuCatalog.json", { root: "./" });
}
}; };

View File

@ -8,7 +8,6 @@ import { logger } from "../utils/logger.ts";
import { addMiscItems, combineInventoryChanges } from "../services/inventoryService.ts"; import { addMiscItems, combineInventoryChanges } from "../services/inventoryService.ts";
import { handleStoreItemAcquisition } from "../services/purchaseService.ts"; import { handleStoreItemAcquisition } from "../services/purchaseService.ts";
import type { IInventoryChanges } from "../types/purchaseTypes.ts"; import type { IInventoryChanges } from "../types/purchaseTypes.ts";
import { config } from "../services/configService.ts";
export const crackRelic = async ( export const crackRelic = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
@ -29,10 +28,10 @@ export const crackRelic = async (
ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics ExportRewards[relic.rewardManifest][0] as { type: string; itemCount: number; rarity: TRarity }[], // rarity is nullable in PE+ typings, but always present for relics
weights weights
)!; )!;
if (config.relicRewardItemCountMultiplier !== undefined && (config.relicRewardItemCountMultiplier ?? 1) != 1) { if (inventory.relicRewardItemCountMultiplier && inventory.relicRewardItemCountMultiplier != 1) {
reward = { reward = {
...reward, ...reward,
itemCount: reward.itemCount * config.relicRewardItemCountMultiplier itemCount: reward.itemCount * inventory.relicRewardItemCountMultiplier
}; };
} }
logger.debug(`relic rolled`, reward); logger.debug(`relic rolled`, 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

@ -88,7 +88,9 @@ import type {
IGoalProgressDatabase, IGoalProgressDatabase,
IGoalProgressClient, IGoalProgressClient,
IKubrowPetPrintClient, IKubrowPetPrintClient,
IKubrowPetPrintDatabase IKubrowPetPrintDatabase,
INokkoColony,
IJournalEntry
} from "../../types/inventoryTypes/inventoryTypes.ts"; } from "../../types/inventoryTypes/inventoryTypes.ts";
import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts"; import { equipmentKeys } from "../../types/inventoryTypes/inventoryTypes.ts";
import type { IOid, ITypeCount } from "../../types/commonTypes.ts"; import type { IOid, ITypeCount } from "../../types/commonTypes.ts";
@ -146,7 +148,8 @@ const focusUpgradeSchema = new Schema<IFocusUpgrade>(
{ {
ItemType: String, ItemType: String,
Level: Number, Level: Number,
IsUniversal: Boolean IsUniversal: Boolean,
IsActive: Number
}, },
{ _id: false } { _id: false }
); );
@ -1423,6 +1426,22 @@ const hubNpcCustomizationSchema = new Schema<IHubNpcCustomization>(
{ _id: false } { _id: false }
); );
const journalEntrySchema = new Schema<IJournalEntry>(
{
EntryType: String,
Progress: Number
},
{ _id: false }
);
const nokkoColonySchema = new Schema<INokkoColony>(
{
FeedLevel: Number,
JournalEntries: [journalEntrySchema]
},
{ _id: false }
);
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>( const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
{ {
accountOwnerId: Schema.Types.ObjectId, accountOwnerId: Schema.Types.ObjectId,
@ -1468,6 +1487,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
nemesisHintProgressMultiplierGrineer: Number, nemesisHintProgressMultiplierGrineer: Number,
nemesisHintProgressMultiplierCorpus: Number, nemesisHintProgressMultiplierCorpus: Number,
nemesisExtraWeapon: Number, nemesisExtraWeapon: Number,
spoofMasteryRank: { type: Number, default: -1 },
relicRewardItemCountMultiplier: { type: Number, default: 1 },
nightwaveStandingMultiplier: { type: Number, default: 1 },
SubscribedToEmails: { type: Number, default: 0 }, SubscribedToEmails: { type: Number, default: 0 },
SubscribedToEmailsPersonalized: { type: Number, default: 0 }, SubscribedToEmailsPersonalized: { type: Number, default: 0 },
@ -1542,6 +1564,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
FocusAbility: String, FocusAbility: String,
//The treeways of the Focus school.(Active and passive Ability) //The treeways of the Focus school.(Active and passive Ability)
FocusUpgrades: [focusUpgradeSchema], FocusUpgrades: [focusUpgradeSchema],
//Focus 2.0 Pool
FocusCapacity: Number,
//Achievement //Achievement
ChallengeProgress: [challengeProgressSchema], ChallengeProgress: [challengeProgressSchema],
@ -1743,7 +1767,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
NemesisAbandonedRewards: { type: [String], default: [] }, NemesisAbandonedRewards: { type: [String], default: [] },
Nemesis: nemesisSchema, Nemesis: nemesisSchema,
NemesisHistory: { type: [nemesisSchema], default: undefined }, NemesisHistory: { type: [nemesisSchema], default: undefined },
//LastNemesisAllySpawnTime: Schema.Types.Mixed, LastNemesisAllySpawnTime: { type: Date, default: undefined },
//TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social) //TradingRulesConfirmed,ShowFriendInvNotifications(Option->Social)
Settings: settingsSchema, Settings: settingsSchema,
@ -1834,7 +1858,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
ClaimedJunctionChallengeRewards: { type: [String], default: undefined }, ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined } SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
NokkoColony: { type: nokkoColonySchema, default: undefined }
}, },
{ timestamps: { createdAt: "Created", updatedAt: false } } { timestamps: { createdAt: "Created", updatedAt: false } }
); );
@ -1864,6 +1890,9 @@ inventorySchema.set("toJSON", {
if (inventoryDatabase.BlessingCooldown) { if (inventoryDatabase.BlessingCooldown) {
inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown); inventoryResponse.BlessingCooldown = toMongoDate(inventoryDatabase.BlessingCooldown);
} }
if (inventoryDatabase.LastNemesisAllySpawnTime) {
inventoryResponse.LastNemesisAllySpawnTime = toMongoDate(inventoryDatabase.LastNemesisAllySpawnTime);
}
if (inventoryDatabase.NextRefill) { if (inventoryDatabase.NextRefill) {
inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill); inventoryResponse.NextRefill = toMongoDate(inventoryDatabase.NextRefill);
} }

View File

@ -50,9 +50,11 @@ import { dronesController } from "../controllers/api/dronesController.ts";
import { endlessXpController } from "../controllers/api/endlessXpController.ts"; import { endlessXpController } from "../controllers/api/endlessXpController.ts";
import { entratiLabConquestModeController } from "../controllers/api/entratiLabConquestModeController.ts"; import { entratiLabConquestModeController } from "../controllers/api/entratiLabConquestModeController.ts";
import { evolveWeaponController } from "../controllers/api/evolveWeaponController.ts"; import { evolveWeaponController } from "../controllers/api/evolveWeaponController.ts";
import { feedPrinceController } from "../controllers/api/feedPrinceController.ts";
import { findSessionsController } from "../controllers/api/findSessionsController.ts"; import { findSessionsController } from "../controllers/api/findSessionsController.ts";
import { fishmongerController } from "../controllers/api/fishmongerController.ts"; import { fishmongerController } from "../controllers/api/fishmongerController.ts";
import { focusController } from "../controllers/api/focusController.ts"; import { focusController } from "../controllers/api/focusController.ts";
import { forceRemoveItemController } from "../controllers/api/forceRemoveItemController.ts";
import { fusionTreasuresController } from "../controllers/api/fusionTreasuresController.ts"; import { fusionTreasuresController } from "../controllers/api/fusionTreasuresController.ts";
import { gardeningController } from "../controllers/api/gardeningController.ts"; import { gardeningController } from "../controllers/api/gardeningController.ts";
import { genericUpdateController } from "../controllers/api/genericUpdateController.ts"; import { genericUpdateController } from "../controllers/api/genericUpdateController.ts";
@ -66,8 +68,8 @@ import { getGuildEventScoreController } from "../controllers/api/getGuildEventSc
import { getGuildLogController } from "../controllers/api/getGuildLogController.ts"; import { getGuildLogController } from "../controllers/api/getGuildLogController.ts";
import { getIgnoredUsersController } from "../controllers/api/getIgnoredUsersController.ts"; import { getIgnoredUsersController } from "../controllers/api/getIgnoredUsersController.ts";
import { getNewRewardSeedController } from "../controllers/api/getNewRewardSeedController.ts"; import { getNewRewardSeedController } from "../controllers/api/getNewRewardSeedController.ts";
import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts";
import { getPastWeeklyChallengesController } from "../controllers/api/getPastWeeklyChallengesController.ts"; import { getPastWeeklyChallengesController } from "../controllers/api/getPastWeeklyChallengesController.ts";
import { getProfileViewingDataPostController } from "../controllers/dynamic/getProfileViewingDataController.ts";
import { getShipController } from "../controllers/api/getShipController.ts"; import { getShipController } from "../controllers/api/getShipController.ts";
import { getVendorInfoController } from "../controllers/api/getVendorInfoController.ts"; import { getVendorInfoController } from "../controllers/api/getVendorInfoController.ts";
import { getVoidProjectionRewardsController } from "../controllers/api/getVoidProjectionRewardsController.ts"; import { getVoidProjectionRewardsController } from "../controllers/api/getVoidProjectionRewardsController.ts";
@ -115,6 +117,7 @@ import { removeFromGuildController } from "../controllers/api/removeFromGuildCon
import { removeIgnoredUserController } from "../controllers/api/removeIgnoredUserController.ts"; import { removeIgnoredUserController } from "../controllers/api/removeIgnoredUserController.ts";
import { renamePetController } from "../controllers/api/renamePetController.ts"; import { renamePetController } from "../controllers/api/renamePetController.ts";
import { rerollRandomModController } from "../controllers/api/rerollRandomModController.ts"; import { rerollRandomModController } from "../controllers/api/rerollRandomModController.ts";
import { researchMushroomController } from "../controllers/api/researchMushroomController.ts";
import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts"; import { resetQuestProgressController } from "../controllers/api/resetQuestProgressController.ts";
import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts"; import { retrievePetFromStasisController } from "../controllers/api/retrievePetFromStasisController.ts";
import { saveDialogueController } from "../controllers/api/saveDialogueController.ts"; import { saveDialogueController } from "../controllers/api/saveDialogueController.ts";
@ -270,9 +273,11 @@ apiRouter.post("/drones.php", dronesController);
apiRouter.post("/endlessXp.php", endlessXpController); apiRouter.post("/endlessXp.php", endlessXpController);
apiRouter.post("/entratiLabConquestMode.php", entratiLabConquestModeController); apiRouter.post("/entratiLabConquestMode.php", entratiLabConquestModeController);
apiRouter.post("/evolveWeapon.php", evolveWeaponController); apiRouter.post("/evolveWeapon.php", evolveWeaponController);
apiRouter.post("/feedPrince.php", feedPrinceController);
apiRouter.post("/findSessions.php", findSessionsController); apiRouter.post("/findSessions.php", findSessionsController);
apiRouter.post("/fishmonger.php", fishmongerController); apiRouter.post("/fishmonger.php", fishmongerController);
apiRouter.post("/focus.php", focusController); apiRouter.post("/focus.php", focusController);
apiRouter.post("/forceRemoveItem.php", forceRemoveItemController);
apiRouter.post("/fusionTreasures.php", fusionTreasuresController); apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
apiRouter.post("/gardening.php", gardeningController); apiRouter.post("/gardening.php", gardeningController);
apiRouter.post("/genericUpdate.php", genericUpdateController); apiRouter.post("/genericUpdate.php", genericUpdateController);
@ -316,6 +321,7 @@ apiRouter.post("/removeFromGuild.php", removeFromGuildController);
apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController); apiRouter.post("/removeIgnoredUser.php", removeIgnoredUserController);
apiRouter.post("/renamePet.php", renamePetController); apiRouter.post("/renamePet.php", renamePetController);
apiRouter.post("/rerollRandomMod.php", rerollRandomModController); apiRouter.post("/rerollRandomMod.php", rerollRandomModController);
apiRouter.post("/researchMushroom.php", researchMushroomController);
apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController); apiRouter.post("/retrievePetFromStasis.php", retrievePetFromStasisController);
apiRouter.post("/saveDialogue.php", saveDialogueController); apiRouter.post("/saveDialogue.php", saveDialogueController);
apiRouter.post("/saveLoadout.php", saveLoadoutController); apiRouter.post("/saveLoadout.php", saveLoadoutController);

View File

@ -22,6 +22,7 @@ import { unlockAllScansController } from "../controllers/custom/unlockAllScansCo
import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts"; import { unlockAllShipFeaturesController } from "../controllers/custom/unlockAllShipFeaturesController.ts";
import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts"; import { unlockAllCapturaScenesController } from "../controllers/custom/unlockAllCapturaScenesController.ts";
import { removeCustomizationController } from "../controllers/custom/removeCustomizationController.ts"; import { removeCustomizationController } from "../controllers/custom/removeCustomizationController.ts";
import { removeIsNewController } from "../controllers/custom/removeIsNewController.ts";
import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts"; import { abilityOverrideController } from "../controllers/custom/abilityOverrideController.ts";
import { createAccountController } from "../controllers/custom/createAccountController.ts"; import { createAccountController } from "../controllers/custom/createAccountController.ts";
@ -34,7 +35,7 @@ import {
fundTechProjectController, fundTechProjectController,
removeTechProjectController removeTechProjectController
} from "../controllers/custom/techProjectController.ts"; } from "../controllers/custom/techProjectController.ts";
import { addVaultDecoRecipeController } from "../controllers/custom/addVaultDecoRecipeController.ts"; import { addVaultTypeCountController } from "../controllers/custom/addVaultTypeCountController.ts";
import { addXpController } from "../controllers/custom/addXpController.ts"; import { addXpController } from "../controllers/custom/addXpController.ts";
import { importController } from "../controllers/custom/importController.ts"; import { importController } from "../controllers/custom/importController.ts";
import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts"; import { manageQuestsController } from "../controllers/custom/manageQuestsController.ts";
@ -73,6 +74,7 @@ customRouter.get("/unlockAllScans", unlockAllScansController);
customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController); customRouter.get("/unlockAllShipFeatures", unlockAllShipFeaturesController);
customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController); customRouter.get("/unlockAllCapturaScenes", unlockAllCapturaScenesController);
customRouter.get("/removeCustomization", removeCustomizationController); customRouter.get("/removeCustomization", removeCustomizationController);
customRouter.get("/removeIsNew", removeIsNewController);
customRouter.post("/abilityOverride", abilityOverrideController); customRouter.post("/abilityOverride", abilityOverrideController);
customRouter.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
@ -81,7 +83,7 @@ customRouter.post("/addCurrency", addCurrencyController);
customRouter.post("/addItems", addItemsController); customRouter.post("/addItems", addItemsController);
customRouter.post("/addTechProject", addTechProjectController); customRouter.post("/addTechProject", addTechProjectController);
customRouter.post("/removeTechProject", removeTechProjectController); customRouter.post("/removeTechProject", removeTechProjectController);
customRouter.post("/addVaultDecoRecipe", addVaultDecoRecipeController); customRouter.post("/addVaultTypeCount", addVaultTypeCountController);
customRouter.post("/fundTechProject", fundTechProjectController); customRouter.post("/fundTechProject", fundTechProjectController);
customRouter.post("/completeTechProject", completeTechProjectsController); customRouter.post("/completeTechProject", completeTechProjectsController);
customRouter.post("/addXp", addXpController); customRouter.post("/addXp", addXpController);

View File

@ -24,9 +24,6 @@ export interface IConfig {
unlockAllSkins?: boolean; unlockAllSkins?: boolean;
fullyStockedVendors?: boolean; fullyStockedVendors?: boolean;
skipClanKeyCrafting?: boolean; skipClanKeyCrafting?: boolean;
spoofMasteryRank?: number;
relicRewardItemCountMultiplier?: number;
nightwaveStandingMultiplier?: number;
unfaithfulBugFixes?: { unfaithfulBugFixes?: {
ignore1999LastRegionPlayed?: boolean; ignore1999LastRegionPlayed?: boolean;
fixXtraCheeseTimer?: boolean; fixXtraCheeseTimer?: boolean;
@ -41,7 +38,7 @@ export interface IConfig {
baroAlwaysAvailable?: boolean; baroAlwaysAvailable?: boolean;
baroFullyStocked?: boolean; baroFullyStocked?: boolean;
varziaFullyStocked?: boolean; varziaFullyStocked?: boolean;
wolfHunt?: boolean; wolfHunt?: number;
orphixVenom?: boolean; orphixVenom?: boolean;
longShadow?: boolean; longShadow?: boolean;
hallowedFlame?: boolean; hallowedFlame?: boolean;
@ -51,6 +48,11 @@ export interface IConfig {
naberusNightsOverride?: boolean; naberusNightsOverride?: boolean;
proxyRebellion?: boolean; proxyRebellion?: boolean;
proxyRebellionRewardsOverride?: number; proxyRebellionRewardsOverride?: number;
voidCorruption2025Week1?: boolean;
voidCorruption2025Week2?: boolean;
voidCorruption2025Week3?: boolean;
voidCorruption2025Week4?: boolean;
qtccAlerts?: boolean;
galleonOfGhouls?: number; galleonOfGhouls?: number;
ghoulEmergenceOverride?: boolean; ghoulEmergenceOverride?: boolean;
plagueStarOverride?: boolean; plagueStarOverride?: boolean;
@ -78,6 +80,21 @@ export interface IConfig {
} }
export const configRemovedOptionsKeys = [ export const configRemovedOptionsKeys = [
"unlockallShipFeatures",
"testQuestKey",
"lockTime",
"starDays",
"platformCDNs",
"completeAllQuests",
"worldSeed",
"unlockAllQuests",
"unlockAllMissions",
"version",
"matchmakingBuildId",
"buildLabel",
"infiniteResources",
"testMission",
"skipStoryModeChoice",
"NRS", "NRS",
"myIrcAddresses", "myIrcAddresses",
"skipAllDialogue", "skipAllDialogue",
@ -129,7 +146,10 @@ export const configRemovedOptionsKeys = [
"fastClanAscension", "fastClanAscension",
"unlockAllFlavourItems", "unlockAllFlavourItems",
"unlockAllShipDecorations", "unlockAllShipDecorations",
"unlockAllDecoRecipes" "unlockAllDecoRecipes",
"spoofMasteryRank",
"relicRewardItemCountMultiplier",
"nightwaveStandingMultiplier"
]; ];
export const configPath = path.join(repoDir, args.configPath ?? "config.json"); export const configPath = path.join(repoDir, args.configPath ?? "config.json");

View File

@ -1,6 +1,5 @@
import type { IFriendInfo } from "../types/friendTypes.ts"; import type { IFriendInfo } from "../types/friendTypes.ts";
import { getInventory } from "./inventoryService.ts"; import { getInventory } from "./inventoryService.ts";
import { config } from "./configService.ts";
import { Account } from "../models/loginModel.ts"; import { Account } from "../models/loginModel.ts";
import type { Types } from "mongoose"; import type { Types } from "mongoose";
import { Friendship } from "../models/friendModel.ts"; import { Friendship } from "../models/friendModel.ts";
@ -13,8 +12,8 @@ export const addAccountDataToFriendInfo = async (info: IFriendInfo): Promise<voi
}; };
export const addInventoryDataToFriendInfo = async (info: IFriendInfo): Promise<void> => { export const addInventoryDataToFriendInfo = async (info: IFriendInfo): Promise<void> => {
const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType"); const inventory = await getInventory(fromOid(info._id), "PlayerLevel ActiveAvatarImageType spoofMasteryRank");
info.PlayerLevel = config.spoofMasteryRank == -1 ? inventory.PlayerLevel : config.spoofMasteryRank; info.PlayerLevel = inventory.spoofMasteryRank == -1 ? inventory.PlayerLevel : inventory.spoofMasteryRank;
info.ActiveAvatarImageType = inventory.ActiveAvatarImageType; info.ActiveAvatarImageType = inventory.ActiveAvatarImageType;
}; };

View File

@ -22,7 +22,7 @@ import type {
ITechProjectDatabase ITechProjectDatabase
} from "../types/guildTypes.ts"; } from "../types/guildTypes.ts";
import { GuildPermission } 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 { Types } from "mongoose";
import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus"; import type { IDojoBuild, IDojoResearch } from "warframe-public-export-plus";
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus"; import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
@ -68,9 +68,15 @@ export const getGuildClient = async (
let missingEntry = true; let missingEntry = true;
const dataFillInPromises: Promise<void>[] = []; const dataFillInPromises: Promise<void>[] = [];
for (const guildMember of guildMembers) { 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 = { const member: IGuildMemberClient = {
_id: toOid2(guildMember.accountId, account.BuildLabel), _id: toOid2(guildMember.accountId, account.BuildLabel),
Rank: guildMember.rank, Rank: guildMember.rank + rankBase,
Status: guildMember.status, Status: guildMember.status,
Note: guildMember.RequestMsg, Note: guildMember.RequestMsg,
RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined RequestExpiry: guildMember.RequestExpiry ? toMongoDate(guildMember.RequestExpiry) : undefined

View File

@ -73,6 +73,7 @@ import type {
ITailorShop, ITailorShop,
ITailorShopDatabase ITailorShopDatabase
} from "../types/personalRoomsTypes.ts"; } from "../types/personalRoomsTypes.ts";
import { fromMongoDate } from "../helpers/inventoryHelpers.ts";
const convertDate = (value: IMongoDate): Date => { const convertDate = (value: IMongoDate): Date => {
return new Date(parseInt(value.$date.$numberLong)); return new Date(parseInt(value.$date.$numberLong));
@ -326,7 +327,15 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"GiftsRemaining", "GiftsRemaining",
"ChallengesFixVersion", "ChallengesFixVersion",
"Founder", "Founder",
"Guide" "Guide",
"BountyScore",
"EntratiVaultCountLastPeriod",
"EntratiLabConquestUnlocked",
"EntratiLabConquestHardModeStatus",
"EntratiLabConquestCacheScoreMission",
"EchoesHexConquestUnlocked",
"EchoesHexConquestHardModeStatus",
"EchoesHexConquestCacheScoreMission"
] as const) { ] as const) {
if (client[key] !== undefined) { if (client[key] !== undefined) {
db[key] = client[key]; db[key] = client[key];
@ -354,12 +363,28 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
"NodeIntrosCompleted", "NodeIntrosCompleted",
"DeathMarks", "DeathMarks",
"Wishlist", "Wishlist",
"NemesisAbandonedRewards" "NemesisAbandonedRewards",
"EntratiLabConquestActiveFrameVariants",
"EchoesHexConquestActiveFrameVariants",
"EchoesHexConquestActiveStickers"
] as const) { ] as const) {
if (client[key] !== undefined) { if (client[key] !== undefined) {
db[key] = client[key]; 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[] // IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) { for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) { if (client[key] !== undefined) {

View File

@ -92,6 +92,7 @@ import type {
} from "../types/equipmentTypes.ts"; } from "../types/equipmentTypes.ts";
import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts"; import { EquipmentFeatures, Status } from "../types/equipmentTypes.ts";
import type { ITypeCount } from "../types/commonTypes.ts"; import type { ITypeCount } from "../types/commonTypes.ts";
import { skinLookupTable } from "../helpers/skinLookupTable.ts";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -116,6 +117,7 @@ export const createInventory = async (
inventory.PlayedParkourTutorial = true; inventory.PlayedParkourTutorial = true;
await addStartingGear(inventory); await addStartingGear(inventory);
await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"); await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
await completeQuest(inventory, "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain");
const completedMissions = ["SolNode27", "SolNode89", "SolNode63", "SolNode85", "SolNode15", "SolNode79"]; const completedMissions = ["SolNode27", "SolNode89", "SolNode63", "SolNode85", "SolNode15", "SolNode79"];
@ -133,16 +135,6 @@ export const createInventory = async (
} }
}; };
//TODO: RawUpgrades might need to return a LastAdded
const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
"/Lotus/Types/Restoratives/LisetAutoHack",
"/Lotus/Upgrades/Mods/Warframe/AvatarShieldMaxMod"
];
export const addStartingGear = async ( export const addStartingGear = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
startingGear?: TPartialStartingGear startingGear?: TPartialStartingGear
@ -195,6 +187,14 @@ export const addStartingGear = async (
inventory.RegularCredits = 3000; inventory.RegularCredits = 3000;
inventoryChanges.RegularCredits = 3000; inventoryChanges.RegularCredits = 3000;
const awakeningRewards = [
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem1",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem2",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem3",
"/Lotus/Types/StoreItems/AvatarImages/AvatarImageItem4",
"/Lotus/Types/Restoratives/LisetAutoHack"
];
for (const item of awakeningRewards) { for (const item of awakeningRewards) {
const inventoryDelta = await addItem(inventory, item); const inventoryDelta = await addItem(inventory, item);
combineInventoryChanges(inventoryChanges, inventoryDelta); combineInventoryChanges(inventoryChanges, inventoryDelta);
@ -388,7 +388,7 @@ export const addItem = async (
}; };
} else if (ExportResources[typeName].productCategory == "CrewShips") { } else if (ExportResources[typeName].productCategory == "CrewShips") {
return { return {
...addCrewShip(inventory, typeName), ...(await addCrewShip(inventory, typeName)),
// fix to unlock railjack modding, item bellow supposed to be obtained from archwing quest // fix to unlock railjack modding, item bellow supposed to be obtained from archwing quest
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
...(!inventory.CrewShipHarnesses?.length ...(!inventory.CrewShipHarnesses?.length
@ -1391,7 +1391,10 @@ export const addStanding = (
// TODO: AffiliationMods support (Nightwave). // TODO: AffiliationMods support (Nightwave).
export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise<IUpdateNodeIntrosResponse> => { export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise<IUpdateNodeIntrosResponse> => {
const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems ShipDecorations"); const inventory = await getInventory(
accountId,
"NodeIntrosCompleted MiscItems ShipDecorations FlavourItems WeaponSkins"
);
// Make it an array for easier parsing. // Make it an array for easier parsing.
if (typeof data.NodeIntrosCompleted === "string") { if (typeof data.NodeIntrosCompleted === "string") {
@ -1400,6 +1403,12 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
for (const node of data.NodeIntrosCompleted) { for (const node of data.NodeIntrosCompleted) {
if (inventory.NodeIntrosCompleted.indexOf(node) != -1) {
continue;
}
inventory.NodeIntrosCompleted.push(node);
logger.debug(`completed dialogue/cutscene for ${node}`);
if (node == "TC2025") { if (node == "TC2025") {
inventoryChanges.ShipDecorations = [ inventoryChanges.ShipDecorations = [
{ {
@ -1420,16 +1429,15 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/BeatCaliberChicksEmailItem", inventoryChanges); await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/BeatCaliberChicksEmailItem", inventoryChanges);
} else if (node == "ClearedFiveLoops") { } else if (node == "ClearedFiveLoops") {
await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/ClearedFiveLoopsEmailItem", inventoryChanges); await addEmailItem(inventory, "/Lotus/Types/Items/EmailItems/ClearedFiveLoopsEmailItem", inventoryChanges);
} else if (node == "NokkoVisions_AllCompleted") {
addCustomization(
inventory,
"/Lotus/Types/StoreItems/AvatarImages/Warframes/NokkoBabySecretGlyph",
inventoryChanges
);
addSkin(inventory, "/Lotus/Upgrades/Skins/Clan/NokkoBabySecretEmblemItem", inventoryChanges);
} }
} }
// Combine the two arrays into one.
data.NodeIntrosCompleted = inventory.NodeIntrosCompleted.concat(data.NodeIntrosCompleted);
// Remove duplicate entries.
const nodes = [...new Set(data.NodeIntrosCompleted)];
inventory.NodeIntrosCompleted = nodes;
await inventory.save(); await inventory.save();
return { return {
@ -1489,7 +1497,13 @@ export const addSkin = (
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => { ): IInventoryChanges => {
if (inventory.WeaponSkins.some(x => x.ItemType == typeName)) { 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 { } else {
const index = const index =
inventory.WeaponSkins.push({ inventory.WeaponSkins.push({
@ -1537,17 +1551,31 @@ export const addCrewShipSalvagedWeaponSkin = (
return inventoryChanges; return inventoryChanges;
}; };
const addCrewShip = ( const addCrewShip = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
typeName: string, typeName: string,
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => { ): Promise<IInventoryChanges> => {
if (inventory.CrewShips.length != 0) { if (inventory.CrewShips.length != 0) {
logger.warn("refusing to add CrewShip because account already has one"); logger.warn("refusing to add CrewShip because account already has one");
} else { } else {
const index = inventory.CrewShips.push({ ItemType: typeName }) - 1; const index = inventory.CrewShips.push({ ItemType: typeName }) - 1;
inventoryChanges.CrewShips ??= []; inventoryChanges.CrewShips ??= [];
inventoryChanges.CrewShips.push(inventory.CrewShips[index].toJSON<IEquipmentClient>()); inventoryChanges.CrewShips.push(inventory.CrewShips[index].toJSON<IEquipmentClient>());
const railjackQuest = inventory.QuestKeys.find(
qk => qk.ItemType === "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestKeyChain"
);
if (!railjackQuest || !railjackQuest.Completed) {
const questChanges = await completeQuest(
inventory,
"/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestKeyChain",
false
);
if (questChanges) {
inventoryChanges.QuestKeys ??= [];
inventoryChanges.QuestKeys.push(questChanges);
}
}
} }
return inventoryChanges; return inventoryChanges;
}; };
@ -2124,7 +2152,7 @@ export const addChallenges = async (
]; ];
} }
const standingToAdd = meta.standing! * (config.nightwaveStandingMultiplier ?? 1); const standingToAdd = meta.standing! * (inventory.nightwaveStandingMultiplier ?? 1);
affiliation.Standing += standingToAdd; affiliation.Standing += standingToAdd;
if (affiliationMods.length == 0) { if (affiliationMods.length == 0) {
affiliationMods.push({ Tag: nightwaveSyndicateTag }); affiliationMods.push({ Tag: nightwaveSyndicateTag });
@ -2152,7 +2180,7 @@ export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag,
if (itemIndex !== -1) { if (itemIndex !== -1) {
Missions[itemIndex].Completes += Completes; Missions[itemIndex].Completes += Completes;
if (Tier) { if (Completes && Tier) {
Missions[itemIndex].Tier = Tier; Missions[itemIndex].Tier = Tier;
} }
} else { } else {
@ -2254,7 +2282,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); inventory.CurrentLoadOutIds = inventory.CurrentLoadOutIds.map(fromDbOid);
let index = inventory.MiscItems.findIndex(x => x.ItemType == ""); let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
@ -2308,6 +2336,99 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
logger.debug(`removed ModularParts from ${numFixed} non-modular items`); 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 => { export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {

View File

@ -81,7 +81,8 @@ export const getRecipe = (uniqueName: string): IRecipe | undefined => {
ItemCount: 600 ItemCount: 600
} }
], ],
excludeFromMarket: true excludeFromMarket: true,
tradable: false
}; };
} }

View File

@ -65,7 +65,7 @@ export const createPersonalRooms = async (accountId: Types.ObjectId, shipId: Typ
activeShipId: shipId activeShipId: shipId
}); });
if (config.skipTutorial) { if (config.skipTutorial) {
// unlocked during Vor's Prize // unlocked during Vor's Prize and The Teacher quests
const defaultFeatures = [ const defaultFeatures = [
"/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/MercuryNavigationFeatureItem",
"/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem", "/Lotus/Types/Items/ShipFeatureItems/ArsenalFeatureItem",

View File

@ -8,6 +8,7 @@ import {
ExportAnimals, ExportAnimals,
ExportEnemies, ExportEnemies,
ExportFusionBundles, ExportFusionBundles,
ExportKeys,
ExportRegions, ExportRegions,
ExportRelics, ExportRelics,
ExportRewards ExportRewards
@ -242,7 +243,7 @@ export const addMissionInventoryUpdates = async (
} }
} }
} }
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) { if (inventoryUpdates.RewardInfo.jobId) {
// e.g. for Profit-Taker Phase 1: // e.g. for Profit-Taker Phase 1:
// JobTier: -6, // JobTier: -6,
// jobId: '/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne_-6_SolarisUnitedHub1_663a71c80000000000000025_EudicoHeists', // jobId: '/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne_-6_SolarisUnitedHub1_663a71c80000000000000025_EudicoHeists',
@ -250,7 +251,10 @@ export const addMissionInventoryUpdates = async (
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [bounty, tier, hub, id, tag] = inventoryUpdates.RewardInfo.jobId.split("_"); const [bounty, tier, hub, id, tag] = inventoryUpdates.RewardInfo.jobId.split("_");
if (tag == "EudicoHeists") { if (
(tag == "EudicoHeists" && inventoryUpdates.MissionStatus == "GS_SUCCESS") ||
(tag == "NokkoColony" && inventoryUpdates.RewardInfo.JobStage == 4)
) {
inventory.CompletedJobChains ??= []; inventory.CompletedJobChains ??= [];
let chain = inventory.CompletedJobChains.find(x => x.LocationTag == tag); let chain = inventory.CompletedJobChains.find(x => x.LocationTag == tag);
if (!chain) { if (!chain) {
@ -1128,7 +1132,8 @@ export const addMissionRewards = async (
RegularCredits: creditDrops, RegularCredits: creditDrops,
VoidTearParticipantsCurrWave: voidTearWave, VoidTearParticipantsCurrWave: voidTearWave,
StrippedItems: strippedItems, StrippedItems: strippedItems,
AffiliationChanges: AffiliationMods AffiliationChanges: AffiliationMods,
InvasionProgress: invasionProgress
}: IMissionInventoryUpdateRequest, }: IMissionInventoryUpdateRequest,
firstCompletion: boolean firstCompletion: boolean
): Promise<AddMissionRewardsReturnType> => { ): Promise<AddMissionRewardsReturnType> => {
@ -1144,6 +1149,7 @@ export const addMissionRewards = async (
const MissionRewards: IMissionReward[] = getRandomMissionDrops( const MissionRewards: IMissionReward[] = getRandomMissionDrops(
inventory, inventory,
rewardInfo, rewardInfo,
levelKeyName,
missions, missions,
wagerTier, wagerTier,
firstCompletion firstCompletion
@ -1154,8 +1160,42 @@ export const addMissionRewards = async (
let ConquestCompletedMissionsCount; let ConquestCompletedMissionsCount;
let missionCompletionCredits = 0; 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 //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (invasionProgress) {
for (const clientProgress of invasionProgress) {
const dbProgress = inventory.QualifyingInvasions.find(x => x.invasionId.equals(clientProgress._id.$oid));
if (dbProgress) {
const run =
(clientProgress.AttackerScore > clientProgress.DefenderScore
? dbProgress.AttackerScore
: dbProgress.DefenderScore) - 1;
missionCompletionCredits += 1000 * Math.min(run, 10);
}
}
}
if (rewardInfo.goalId) { if (rewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId); const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
if (goal) { if (goal) {
@ -1477,7 +1517,7 @@ export const addMissionRewards = async (
syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId); syndicateEntry = Goals.find(m => m._id.$oid === syndicateMissionId);
if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!; if (syndicateEntry) syndicateEntry.Tag = syndicateEntry.JobAffiliationTag!;
} }
if (syndicateEntry && syndicateEntry.Jobs) { if (syndicateEntry && syndicateEntry.Jobs && !jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs")) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!]; let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
if ( if (
[ [
@ -1667,8 +1707,8 @@ export const addCredits = async (
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
} }
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) { if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1]; inventory.RegularCredits += finalCredits.TotalCredits[1] * 0.25;
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1]; finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1] * 0.25;
} }
return finalCredits; return finalCredits;
@ -1735,6 +1775,7 @@ function getLevelCreditRewards(node: IRegion): number {
function getRandomMissionDrops( function getRandomMissionDrops(
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
RewardInfo: IRewardInfo, RewardInfo: IRewardInfo,
levelKeyName: string | undefined,
mission: IMission | undefined, mission: IMission | undefined,
tierOverride: number | undefined, tierOverride: number | undefined,
firstCompletion: boolean firstCompletion: boolean
@ -1984,6 +2025,19 @@ function getRandomMissionDrops(
xpAmounts: [1000] xpAmounts: [1000]
}; };
RewardInfo.Q = false; // Just in case RewardInfo.Q = false; // Just in case
} else if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) {
job = {
rewards: jobType
.replace("SteelPath", "Steel")
.replace(
"/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob",
"Lotus/Types/Game/MissionDecks/NokkoColonyRewards/NokkoColonyRewards"
),
masteryReq: 0,
minEnemyLevel: 30,
maxEnemyLevel: 40,
xpAmounts: [0, 0, 0, 0, 0]
};
} else { } else {
const tierMap = { const tierMap = {
Two: "B", Two: "B",
@ -2027,14 +2081,22 @@ function getRandomMissionDrops(
} else if (totalStage == 5 && curentStage == 4) { } else if (totalStage == 5 && curentStage == 4) {
tableIndex = 2; tableIndex = 2;
} }
if (jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob")) {
rotations = [tableIndex]; if (RewardInfo.JobStage === job.xpAmounts.length - 1) {
rotations = [0, 1, 2];
} else {
rewardManifests = [];
}
} else {
rotations = [tableIndex];
}
} else { } else {
rotations = [0]; rotations = [0];
} }
if ( if (
RewardInfo.Q && RewardInfo.Q &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) && (RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) &&
!jobType.startsWith("/Lotus/Types/Gameplay/NokkoColony/Jobs/NokkoJob") &&
!isEndlessJob !isEndlessJob
) { ) {
rotations.push(ExportRewards[job.rewards].length - 1); rotations.push(ExportRewards[job.rewards].length - 1);
@ -2172,7 +2234,7 @@ function getRandomMissionDrops(
} }
} }
if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound) { if (region.cacheRewardManifest && RewardInfo.EnemyCachesFound && !RewardInfo.goalId) {
const deck = ExportRewards[region.cacheRewardManifest]; const deck = ExportRewards[region.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) { for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]); const drop = getRandomRewardByChance(deck[rotation]);
@ -2238,6 +2300,71 @@ function getRandomMissionDrops(
} }
} }
if (RewardInfo.EnemyCachesFound) {
if (RewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == RewardInfo.goalId);
if (goal) {
let currentMissionKey: string | undefined;
if (RewardInfo.node == goal.Node) {
currentMissionKey = goal.MissionKeyName;
} else if (goal.ConcurrentNodes && goal.ConcurrentMissionKeyNames) {
for (let i = 0; i < goal.ConcurrentNodes.length; i++) {
if (RewardInfo.node == goal.ConcurrentNodes[i]) {
currentMissionKey = goal.ConcurrentMissionKeyNames[i];
break;
}
}
}
if (currentMissionKey) {
const keyMeta = ExportKeys[currentMissionKey];
if (keyMeta.cacheRewardManifest) {
const deck = ExportRewards[keyMeta.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
if (drop) {
drops.push({
StoreItem: drop.type,
ItemCount: drop.itemCount,
FromEnemyCache: true
});
}
}
}
}
}
} else if (RewardInfo.alertId) {
const alert = getWorldState().Alerts.find(x => x._id.$oid == RewardInfo.alertId);
if (alert && alert.MissionInfo.enemyCacheOverride) {
const deck = ExportRewards[alert.MissionInfo.enemyCacheOverride];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
if (drop) {
drops.push({
StoreItem: drop.type,
ItemCount: drop.itemCount,
FromEnemyCache: true
});
}
}
}
} else if (levelKeyName) {
const keyMeta = ExportKeys[levelKeyName];
if (keyMeta.cacheRewardManifest) {
const deck = ExportRewards[keyMeta.cacheRewardManifest];
for (let rotation = 0; rotation != RewardInfo.EnemyCachesFound; ++rotation) {
const drop = getRandomRewardByChance(deck[rotation]);
if (drop) {
drops.push({
StoreItem: drop.type,
ItemCount: drop.itemCount,
FromEnemyCache: true
});
}
}
}
}
}
if (inventory.missionsCanGiveAllRelics) { if (inventory.missionsCanGiveAllRelics) {
for (const drop of drops) { for (const drop of drops) {
const itemType = fromStoreItem(drop.StoreItem); const itemType = fromStoreItem(drop.StoreItem);
@ -2458,6 +2585,18 @@ const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string
sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectD", sub: "/Lotus/Language/Inbox/WaterFightRewardSubjectD",
icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png" icon: "/Lotus/Interface/Icons/Npcs/Grineer/KelaDeThaym.png"
}, },
"/Lotus/Types/Keys/WolfTacAlertA": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertxB": {
sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertHardBody",
sub: "/Lotus/Language/Inbox/WolfTacAlertHardTitle",
icon: "/Lotus/Interface/Icons/Npcs/Seasonal/NoraNight.png"
},
"/Lotus/Types/Keys/WolfTacAlertReduxA": { "/Lotus/Types/Keys/WolfTacAlertReduxA": {
sndr: "/Lotus/Language/Bosses/NoraNight", sndr: "/Lotus/Language/Bosses/NoraNight",
msg: "/Lotus/Language/Inbox/WolfTacAlertBody", msg: "/Lotus/Language/Inbox/WolfTacAlertBody",

View File

@ -1,7 +1,9 @@
import { PersonalRooms } from "../models/personalRoomsModel.ts"; import { PersonalRooms } from "../models/personalRoomsModel.ts";
import { addItem, getInventory } from "./inventoryService.ts"; import { addItem } from "./inventoryService.ts";
import type { IGardeningDatabase, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes.ts"; import type { IGardeningDatabase, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes.ts";
import { getRandomElement } from "./rngService.ts"; import { getRandomElement } from "./rngService.ts";
import type { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel.ts";
import { logger } from "../utils/logger.ts";
export const getPersonalRooms = async ( export const getPersonalRooms = async (
accountId: string, accountId: string,
@ -15,19 +17,17 @@ export const getPersonalRooms = async (
return personalRooms; return personalRooms;
}; };
export const updateShipFeature = async (accountId: string, shipFeature: string): Promise<void> => { export const unlockShipFeature = async (inventory: TInventoryDatabaseDocument, shipFeature: string): Promise<void> => {
const personalRooms = await getPersonalRooms(accountId); const personalRooms = await getPersonalRooms(inventory.accountOwnerId.toString());
if (personalRooms.Ship.Features.includes(shipFeature)) { if (personalRooms.Ship.Features.includes(shipFeature)) {
throw new Error(`ship feature ${shipFeature} already unlocked`); logger.warn(`ship feature ${shipFeature} already unlocked`);
} else {
personalRooms.Ship.Features.push(shipFeature);
await personalRooms.save();
} }
const miscItem = inventory.MiscItems.find(x => x.ItemType === shipFeature);
personalRooms.Ship.Features.push(shipFeature); if (miscItem && miscItem.ItemCount > 0) await addItem(inventory, shipFeature, miscItem.ItemCount * -1);
await personalRooms.save();
const inventory = await getInventory(accountId);
await addItem(inventory, shipFeature, -1);
await inventory.save();
}; };
export const createGarden = (): IGardeningDatabase => { export const createGarden = (): IGardeningDatabase => {

View File

@ -11,9 +11,10 @@ import { addFixedLevelRewards } from "./missionInventoryUpdateService.ts";
import type { IInventoryChanges } from "../types/purchaseTypes.ts"; import type { IInventoryChanges } from "../types/purchaseTypes.ts";
import questCompletionItems from "../../static/fixed_responses/questCompletionRewards.json" with { type: "json" }; import questCompletionItems from "../../static/fixed_responses/questCompletionRewards.json" with { type: "json" };
import type { ITypeCount } from "../types/commonTypes.ts"; import type { ITypeCount } from "../types/commonTypes.ts";
import { addString } from "../helpers/stringHelpers.ts";
export interface IUpdateQuestRequest { export interface IUpdateQuestRequest {
QuestKeys: Omit<IQuestKeyDatabase, "CompletionDate">[]; QuestKeys: IQuestKeyClient[];
PS: string; PS: string;
questCompletion: boolean; questCompletion: boolean;
PlayerShipEvents: unknown[]; PlayerShipEvents: unknown[];
@ -36,6 +37,7 @@ export const updateQuestKey = async (
throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`); throw new Error(`quest key ${questKeyUpdate[0].ItemType} not found`);
} }
delete questKeyUpdate[0].CompletionDate;
inventory.QuestKeys[questKeyIndex].overwrite(questKeyUpdate[0]); inventory.QuestKeys[questKeyIndex].overwrite(questKeyUpdate[0]);
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
@ -115,7 +117,11 @@ export const addQuestKey = (
return inventory.QuestKeys[index - 1].toJSON<IQuestKeyClient>(); return inventory.QuestKeys[index - 1].toJSON<IQuestKeyClient>();
}; };
export const completeQuest = async (inventory: TInventoryDatabaseDocument, questKey: string): Promise<void> => { export const completeQuest = async (
inventory: TInventoryDatabaseDocument,
questKey: string,
sendMessages: boolean = false
): Promise<void | IQuestKeyClient> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[questKey]?.chainStages; const chainStages = ExportKeys[questKey]?.chainStages;
@ -133,8 +139,8 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
unlock: true, unlock: true,
Progress: Array.from({ length: chainStageTotal }, () => ({ Progress: Array.from({ length: chainStageTotal }, () => ({
c: 0, c: 0,
i: false, i: true,
m: false, m: true,
b: [] b: []
})) }))
}; };
@ -161,7 +167,7 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
const stage = existingQuestKey.Progress[i]; const stage = existingQuestKey.Progress[i];
if (stage.c <= run) { if (stage.c <= run) {
stage.c = run; stage.c = run;
await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i }); await giveKeyChainStageTriggered(inventory, { KeyChain: questKey, ChainStage: i }, sendMessages);
await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i }); await giveKeyChainMissionReward(inventory, { KeyChain: questKey, ChainStage: i });
} }
} }
@ -170,7 +176,14 @@ export const completeQuest = async (inventory: TInventoryDatabaseDocument, quest
existingQuestKey.Completed = true; existingQuestKey.Completed = true;
existingQuestKey.CompletionDate = new Date(); existingQuestKey.CompletionDate = new Date();
await handleQuestCompletion(inventory, questKey, undefined, run > 0); await handleQuestCompletion(inventory, questKey, undefined, run > 0);
if (questKey == "/Lotus/Types/Keys/ModQuest/ModQuestKeyChain") {
// This would be set by the client during the equilogue, but since we're cheating through, we have to do it ourselves.
addString(inventory.NodeIntrosCompleted, "ModQuestTeshinAccess");
}
} }
return existingQuestKey.toJSON<IQuestKeyClient>();
}; };
const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined => { const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined => {
@ -327,7 +340,8 @@ export const giveKeyChainItem = async (
export const giveKeyChainMessage = async ( export const giveKeyChainMessage = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest, keyChainInfo: IKeyChainRequest,
questKey: IQuestKeyDatabase questKey: IQuestKeyDatabase,
sendMessage: boolean = true
): Promise<void> => { ): Promise<void> => {
const keyChainMessage = getKeyChainMessage(keyChainInfo); const keyChainMessage = getKeyChainMessage(keyChainInfo);
@ -336,7 +350,12 @@ export const giveKeyChainMessage = async (
keyChainMessage.countedAtt = []; keyChainMessage.countedAtt = [];
} }
await createMessage(inventory.accountOwnerId, [keyChainMessage]); if (sendMessage) {
await createMessage(inventory.accountOwnerId, [keyChainMessage]);
} else {
if (keyChainMessage.countedAtt?.length) await addItems(inventory, keyChainMessage.countedAtt);
if (keyChainMessage.att?.length) await addItems(inventory, keyChainMessage.att);
}
updateQuestStage(inventory, keyChainInfo, { m: true }); updateQuestStage(inventory, keyChainInfo, { m: true });
}; };
@ -384,7 +403,8 @@ export const giveKeyChainMissionReward = async (
export const giveKeyChainStageTriggered = async ( export const giveKeyChainStageTriggered = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
keyChainInfo: IKeyChainRequest keyChainInfo: IKeyChainRequest,
sendMessage: boolean = true
): Promise<void> => { ): Promise<void> => {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages; const chainStages = ExportKeys[keyChainInfo.KeyChain]?.chainStages;
@ -396,7 +416,7 @@ export const giveKeyChainStageTriggered = async (
} }
if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) { if (chainStages[keyChainInfo.ChainStage].messageToSendWhenTriggered) {
await giveKeyChainMessage(inventory, keyChainInfo, questKey); await giveKeyChainMessage(inventory, keyChainInfo, questKey, sendMessage);
} }
} }
}; };

View File

@ -19,6 +19,7 @@ import type {
ICalendarDay, ICalendarDay,
ICalendarEvent, ICalendarEvent,
ICalendarSeason, ICalendarSeason,
IAlert,
IGoal, IGoal,
IInvasion, IInvasion,
ILiteSortie, ILiteSortie,
@ -34,7 +35,8 @@ import type {
IVoidTrader, IVoidTrader,
IVoidTraderOffer, IVoidTraderOffer,
IWorldState, IWorldState,
TCircuitGameMode TCircuitGameMode,
IFlashSale
} from "../types/worldStateTypes.ts"; } from "../types/worldStateTypes.ts";
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers.ts"; import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers.ts";
import { logger } from "../utils/logger.ts"; import { logger } from "../utils/logger.ts";
@ -61,7 +63,7 @@ const sortieBosses = [
type TSortieBoss = (typeof sortieBosses)[number]; type TSortieBoss = (typeof sortieBosses)[number];
const sortieBossToFaction: Record<TSortieBoss, string> = { const sortieBossToFaction: Record<TSortieBoss, TFaction> = {
SORTIE_BOSS_HYENA: "FC_CORPUS", SORTIE_BOSS_HYENA: "FC_CORPUS",
SORTIE_BOSS_KELA: "FC_GRINEER", SORTIE_BOSS_KELA: "FC_GRINEER",
SORTIE_BOSS_VOR: "FC_GRINEER", SORTIE_BOSS_VOR: "FC_GRINEER",
@ -112,6 +114,89 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
SORTIE_BOSS_VOR: "SolNode108" 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[] = [ const eidolonJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss", "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap", "/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
@ -291,6 +376,11 @@ export const getSortie = (day: number): ISortie => {
const selectedNodes: ISortieMission[] = []; const selectedNodes: ISortieMission[] = [];
const missionTypes = new Set(); const missionTypes = new Set();
if (enemyFaction == "FC_INFESTATION") {
// MT_RETRIEVAL may not be chosen for infested enemies (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2907)
missionTypes.add("MT_RETRIEVAL");
}
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const randomIndex = rng.randomInt(0, nodes.length - 1); const randomIndex = rng.randomInt(0, nodes.length - 1);
const node = nodes[randomIndex]; const node = nodes[randomIndex];
@ -1392,6 +1482,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const date = new Date(timeMs); const date = new Date(timeMs);
const defenseWavesPerRotation = buildLabel && version_compare(buildLabel, "2025.03.18.16.07") < 0 ? 5 : 3;
const worldState: IWorldState = { const worldState: IWorldState = {
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel, BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
@ -1481,6 +1572,168 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.VoidTraders.push(vt); worldState.VoidTraders.push(vt);
fullyStockBaro(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) {
const activationTimeStamp = "1759327200000";
const expiryTimeStamp = "2000000000000";
worldState.Alerts.push(
{
_id: {
$oid: "68dc23c42e9d3acfa708ff3b"
},
Activation: { $date: { $numberLong: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
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: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
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: activationTimeStamp } },
Expiry: { $date: { $numberLong: expiryTimeStamp } },
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 storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021D", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoI", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Operator/Tattoos/TattooTennoH", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Clan/QTCC2024EmblemItem", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2024Display", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVII", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphVI", RegularOverride: 1 },
{ TypeName: "/Lotus/Interface/Graphics/CustomUI/ConqueraStyle", RegularOverride: 1 },
{ TypeName: "/Lotus/Interface/Graphics/CustomUI/Backgrounds/ConqueraBackground", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Conquera2021Deco", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2022A", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021B", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021A", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/TnCharityRibbonSyandana", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera2021C", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Venus/Conquera2023CommunityDisplay", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageConqueraGlyphUpdated", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImageConquera", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Sigils/QTCC2023ConqueraSigil", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Sigils/ConqueraSigil", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Effects/Conquera2022Ephemera", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Effects/ConqueraEphemera", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorL", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraArmorA", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/TnCharityRibbonArmor/ConqueraChestRibbon", RegularOverride: 1 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Plushies/PlushyProtectorStalker", PremiumOverride: 35 }
];
worldState.FlashSales.push(
...storeItems.map(item => ({
...{
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } },
ShowInMarket: item.ShowInMarket ?? true,
HideFromMarket: item.HideFromMarket ?? false,
SupporterPack: item.SupporterPack ?? false,
Discount: item.Discount ?? 0,
BogoBuy: item.BogoBuy ?? 0,
BogoGet: item.BogoGet ?? 0,
RegularOverride: item.RegularOverride ?? 0,
PremiumOverride: item.PremiumOverride ?? 0
},
...item
}))
);
const seasonalItems = storeItems.map(item => item.TypeName);
const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(
c => c.CategoryName == "COMMUNITY"
);
if (seasonalCategory) {
seasonalCategory.Items ??= [];
seasonalCategory.Items.push(...seasonalItems);
} else {
worldState.InGameMarket.LandingPage.Categories.push({
CategoryName: "COMMUNITY",
Name: "/Lotus/Language/Store/CommunityCategoryTitle",
Icon: "community",
AddToMenu: true,
Items: seasonalItems
});
}
}
const isFebruary = date.getUTCMonth() == 1; const isFebruary = date.getUTCMonth() == 1;
if (config.worldState?.starDaysOverride ?? isFebruary) { if (config.worldState?.starDaysOverride ?? isFebruary) {
worldState.Goals.push({ worldState.Goals.push({
@ -1545,7 +1798,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Personal: true, Personal: true,
Bounty: true, Bounty: true,
ClampNodeScores: true, ClampNodeScores: true,
Node: "EventNode28", // Incompatible with Wolf Hunt (2025), Orphix Venom, Warframe Anniversary Node: "EventNode28", // Incompatible with Wolf Hunt, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB", MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB",
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle", Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png", Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
@ -1846,82 +2099,39 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
NightLevel: "/Lotus/Levels/GrineerBeach/GrineerBeachEventNight.level" NightLevel: "/Lotus/Levels/GrineerBeach/GrineerBeachEventNight.level"
}); });
const baseStoreItem = { const storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
ShowInMarket: true, { TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle", PremiumOverride: 240 },
HideFromMarket: false, { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead", PremiumOverride: 35 },
SupporterPack: false, { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead", PremiumOverride: 35 },
Discount: 0, { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead", PremiumOverride: 35 },
BogoBuy: 0, { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead", PremiumOverride: 35 },
BogoGet: 0, { TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple", RegularOverride: 1 },
StartDate: { $date: { $numberLong: activationTimeStamp } }, { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead", PremiumOverride: 35 },
EndDate: { $date: { $numberLong: expiryTimeStamp } }, { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead", PremiumOverride: 35 },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } } { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead", PremiumOverride: 35 },
}; { TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead", PremiumOverride: 35 },
const storeItems = [ { TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers", PremiumOverride: 75 }
{
TypeName: "/Lotus/Types/StoreItems/Packages/WaterFightNoggleBundle",
PremiumOverride: 240,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFBeastMasterBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFChargerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFEngineerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFGruntBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/ImagePopsicleGrineerPurple",
PremiumOverride: 0,
RegularOverride: 1
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHealerBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHeavyBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFHellionBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFSniperBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/Items/ShipDecos/Events/WFTankBobbleHead",
PremiumOverride: 35,
RegularOverride: 0
},
{
TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerRollers",
PremiumOverride: 75,
RegularOverride: 0
}
]; ];
worldState.FlashSales.push(...storeItems.map(item => ({ ...baseStoreItem, ...item }))); worldState.FlashSales.push(
...storeItems.map(item => ({
...{
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } },
ShowInMarket: item.ShowInMarket ?? true,
HideFromMarket: item.HideFromMarket ?? false,
SupporterPack: item.SupporterPack ?? false,
Discount: item.Discount ?? 0,
BogoBuy: item.BogoBuy ?? 0,
BogoGet: item.BogoGet ?? 0,
RegularOverride: item.RegularOverride ?? 0,
PremiumOverride: item.PremiumOverride ?? 0
},
...item
}))
);
const seasonalItems = storeItems.map(item => item.TypeName); const seasonalItems = storeItems.map(item => item.TypeName);
@ -1942,7 +2152,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
if (config.worldState?.anniversary != undefined) { if (config.worldState?.anniversary != undefined) {
// Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt (2025), Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow // Incompatible with: Use Tag from Warframe Anniversary for old Events, Wolf Hunt, Galleon Of Ghouls, Hallowed Flame, Hallowed Nightmares, Dog Days, Proxy Rebellion, Long Shadow
const goalsByWeek: Partial<IGoal>[][] = [ const goalsByWeek: Partial<IGoal>[][] = [
[ [
{ {
@ -2161,65 +2371,108 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}); });
} }
if (config.worldState?.wolfHunt) { if (config.worldState?.wolfHunt != undefined) {
worldState.Goals.push({ if (config.worldState.wolfHunt == 0) {
_id: { worldState.Goals.push({
$oid: "67ed7672798d6466172e3b9d" _id: {
}, $oid: "67ed7672798d6466172e3b9c"
Activation: {
$date: {
$numberLong: "1743616800000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
Count: 0,
Goal: 3,
InterimGoals: [1, 2],
BonusGoal: 4,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode29",
ConcurrentMissionKeyNames: [
"/Lotus/Types/Keys/WolfTacAlertReduxB",
"/Lotus/Types/Keys/WolfTacAlertReduxC",
"/Lotus/Types/Keys/WolfTacAlertReduxD"
],
ConcurrentNodeReqs: [1, 2, 3],
ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA",
Faction: "FC_GRINEER",
Desc: "/Lotus/Language/Alerts/WolfAlert",
Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png",
Tag: "WolfHuntRedux",
InterimRewards: [
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"]
}, },
{ Activation: {
credits: 50000, $date: {
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"] $numberLong: "1743616800000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
Count: 0,
Goal: 1,
BonusGoal: 2,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode29",
ConcurrentMissionKeyNames: ["/Lotus/Types/Keys/WolfTacAlertB"],
ConcurrentNodeReqs: [1],
ConcurrentNodes: ["EventNode28"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertA",
Faction: "FC_GRINEER",
Desc: "/Lotus/Language/Alerts/WolfAlert",
Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png",
Tag: "WolfHuntRedux", // unfaithful
Reward: {
countedItems: [{ ItemType: "/Lotus/Types/Items/MiscItems/Alertium", ItemCount: 10 }]
},
BonusReward: {
items: [
"/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
} }
], });
Reward: { } else if (config.worldState.wolfHunt == 1) {
credits: 50000, worldState.Goals.push({
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"] _id: {
}, $oid: "67ed7672798d6466172e3b9d"
BonusReward: { },
credits: 50000, Activation: {
items: [ $date: {
"/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint", $numberLong: "1743616800000"
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst", }
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" },
] Expiry: {
} $date: {
}); $numberLong: "2000000000000"
}
},
Count: 0,
Goal: 3,
InterimGoals: [1, 2],
BonusGoal: 4,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode29",
ConcurrentMissionKeyNames: [
"/Lotus/Types/Keys/WolfTacAlertReduxB",
"/Lotus/Types/Keys/WolfTacAlertReduxC",
"/Lotus/Types/Keys/WolfTacAlertReduxD"
],
ConcurrentNodeReqs: [1, 2, 3],
ConcurrentNodes: ["EventNode28", "EventNode39", "EventNode40"], // Incompatible with Galleon Of Ghouls, Orphix Venom, Warframe Anniversary
MissionKeyName: "/Lotus/Types/Keys/WolfTacAlertReduxA",
Faction: "FC_GRINEER",
Desc: "/Lotus/Language/Alerts/WolfAlert",
Icon: "/Lotus/Interface/Icons/Npcs/Seasonal/WolfStalker.png",
Tag: "WolfHuntRedux",
InterimRewards: [
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHandle"]
},
{
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerHead"]
}
],
Reward: {
credits: 50000,
items: ["/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/ThrowingHammerMotor"]
},
BonusReward: {
credits: 50000,
items: [
"/Lotus/StoreItems/Types/Recipes/Weapons/ThrowingHammerBlueprint",
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
}
});
}
} }
const tagsForOlderGoals: string[] = [ const tagsForOlderGoals: string[] = [
@ -2316,22 +2569,22 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// 2016 // 2016
[ [
{ {
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"] items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
},
{
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil"]
}, },
{ {
items: [ items: [
"/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", "/Lotus/StoreItems/Upgrades/Skins/Sigils/DotD2016Sigil",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem" "/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
] ]
},
{
items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor"]
} }
], ],
// 2015 // 2015
[ [
{ {
items: ["/Lotus/StoreItems/Upgrades/Skins/Sigils/OrokinCatalyst"] items: ["/Lotus/StoreItems/Types/Items/MiscItems/OrokinCatalyst"]
}, },
{ {
items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"] items: ["/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"]
@ -2517,22 +2770,18 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const isOctober = date.getUTCMonth() == 9; // October = month index 9 const isOctober = date.getUTCMonth() == 9; // October = month index 9
if (config.worldState?.naberusNightsOverride ?? isOctober) { if (config.worldState?.naberusNightsOverride ?? isOctober) {
const activationTimeStamp = config.worldState?.naberusNightsOverride
? "1727881200000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString();
const expiryTimeStamp = config.worldState?.naberusNightsOverride
? "2000000000000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString();
worldState.Goals.push({ worldState.Goals.push({
_id: { $oid: "66fd602de1778d583419e8e7" }, _id: { $oid: "66fd602de1778d583419e8e7" },
Activation: { Activation: { $date: { $numberLong: activationTimeStamp } },
$date: { Expiry: { $date: { $numberLong: expiryTimeStamp } },
$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, Count: 0,
Goal: 0, Goal: 0,
Success: 0, Success: 0,
@ -2543,6 +2792,181 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Tag: "DeimosHalloween", Tag: "DeimosHalloween",
Node: "DeimosHub" Node: "DeimosHub"
}); });
const storeItems: (Partial<IFlashSale> & { TypeName: string })[] = [
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023GlyphBundleA", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2021GlyphBundle", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleA", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2019GlyphBundleB", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenGlyphBundle", PremiumOverride: 65 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/Halloween2023ArmorBundle", PremiumOverride: 125 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenCrpCircArmorPack", PremiumOverride: 100 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundleB", PremiumOverride: 80 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPack", PremiumOverride: 175 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenShipSkinBundle", PremiumOverride: 80 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackC", PremiumOverride: 175 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackII", PremiumOverride: 145 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenScarfBundle", PremiumOverride: 130 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/AcolyteNoggleBundle", PremiumOverride: 160 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteAreaCasterBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteDuellistBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteControlBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteHeavyBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteRogueBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/Items/ShipDecos/AcolyteStrikerBobbleHead", PremiumOverride: 35 },
{ TypeName: "/Lotus/Types/StoreItems/SuitCustomizations/ColourPickerHalloweenItemA", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/Halloween2014Wings/Halloween2014ArmArmor", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Festivities/PumpkinHead", RegularOverride: 1 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenRegorAxeShield", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CheshireKavat",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAkvasto", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAngstrum", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBoltor", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhostChibiWisp",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBraton", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016A", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016C", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016B", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenBuzlok", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/AvatarImageHalloween2016D", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019CreepyClem", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDaikyu", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Dethcube",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDragonNikana", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDualZoren", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019FrankenCorpus",
PremiumOverride: 20
},
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Grineer",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaive", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGalatine", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrakata", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGorgon", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGlaxion", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGremlins", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenGrinlok", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenFireFlyScarf", PremiumOverride: 90 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenImperator", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKronen", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Lotus",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenJatKittag", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenKyropteraScarf", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenKunai", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Liset/LisetSkinHalloween", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Liset/LisetInsectSkinHalloween", PremiumOverride: 50 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphFour",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenMarelok", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNikana", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenNukor", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Loid",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOpticor", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenOrthos", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenParis", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphTwo",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircC", PremiumOverride: 45 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircA", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Armor/CrpCircleArmour/HalloweenCrpCircL", PremiumOverride: 35 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenScindo", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GhoulGrave", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageHalloween2021Pumpkin",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSarpa", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphThree",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSilvaAndAegis", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/AvatarImageChillingGlyphOne",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSoma", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSkana", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019SlimeLoki", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSobek", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSonicor", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSimulor", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTonkor", PremiumOverride: 20 },
{
TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019TrickOrBalas",
PremiumOverride: 20
},
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenSpira", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenStradavar", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenTwinGrakatas", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenArchSword", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenLato", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/AvatarImages/Seasonal/Halloween2019Werefested", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenErosionCape", PremiumOverride: 50 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenVasto", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkSplitSword", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenDarkDagger", PremiumOverride: 20 },
{ TypeName: "/Lotus/Upgrades/Skins/Scarves/HalloweenGrnBannerScarf", PremiumOverride: 75 },
{ TypeName: "/Lotus/Upgrades/Skins/Halloween/HalloweenAmprex", PremiumOverride: 20 },
{ TypeName: "/Lotus/Types/StoreItems/Packages/HalloweenSkinPackD", PremiumOverride: 180 }
];
worldState.FlashSales.push(
...storeItems.map(item => ({
...{
StartDate: { $date: { $numberLong: activationTimeStamp } },
EndDate: { $date: { $numberLong: expiryTimeStamp } },
ProductExpiryOverride: { $date: { $numberLong: expiryTimeStamp } },
ShowInMarket: item.ShowInMarket ?? true,
HideFromMarket: item.HideFromMarket ?? false,
SupporterPack: item.SupporterPack ?? false,
Discount: item.Discount ?? 0,
BogoBuy: item.BogoBuy ?? 0,
BogoGet: item.BogoGet ?? 0,
RegularOverride: item.RegularOverride ?? 0,
PremiumOverride: item.PremiumOverride ?? 0
},
...item
}))
);
const seasonalItems = storeItems.map(item => item.TypeName);
const seasonalCategory = worldState.InGameMarket.LandingPage.Categories.find(c => c.CategoryName == "SEASONAL");
if (seasonalCategory) {
seasonalCategory.Items ??= [];
seasonalCategory.Items.push(...seasonalItems);
} else {
worldState.InGameMarket.LandingPage.Categories.push({
CategoryName: "SEASONAL",
Name: "/Lotus/Language/Store/SeasonalCategoryTitle",
Icon: "seasonal",
AddToMenu: true,
Items: seasonalItems
});
}
} }
if (config.worldState?.bellyOfTheBeast) { if (config.worldState?.bellyOfTheBeast) {
@ -2620,7 +3044,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Success: 0, Success: 0,
Personal: true, Personal: true,
Best: true, Best: true,
Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt (2025) Node: "EventNode28", // Incompatible with Galleon Of Ghouls, Wolf Hunt
MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGrineerGalleon", MissionKeyName: "/Lotus/Types/Keys/MechSurvivalGrineerGalleon",
Faction: "FC_SENTIENT", Faction: "FC_SENTIENT",
Desc: "/Lotus/Language/Events/MechEventMissionTier2", Desc: "/Lotus/Language/Events/MechEventMissionTier2",
@ -3279,7 +3703,8 @@ export const getLiteSortie = (week: number): ILiteSortie => {
value.missionType != "MT_ASSASSINATION" && value.missionType != "MT_ASSASSINATION" &&
value.missionType != "MT_JUNCTION" && value.missionType != "MT_JUNCTION" &&
value.missionType != "MT_LANDSCAPE" && value.missionType != "MT_LANDSCAPE" &&
value.missionType != "MT_RAILJACK" value.missionType != "MT_RAILJACK" &&
key != "SolNode63" // This node uses GrineerForestTilesetCaves which only supports MT_CAPTURE, which is not valid for LiteSorties.
) { ) {
nodes.push(key); nodes.push(key);
} }

View File

@ -8,6 +8,7 @@ import type { IDatabaseAccountJson } from "../types/loginTypes.ts";
import type { HydratedDocument } from "mongoose"; import type { HydratedDocument } from "mongoose";
import { logError, logger } from "../utils/logger.ts"; import { logError, logger } from "../utils/logger.ts";
import type { Request } from "express"; import type { Request } from "express";
import type { ITunables } from "../types/bootstrapperTypes.ts";
let wsServer: WebSocketServer | undefined; let wsServer: WebSocketServer | undefined;
let wssServer: WebSocketServer | undefined; let wssServer: WebSocketServer | undefined;
@ -89,8 +90,9 @@ interface IWsMsgToClient {
logged_out?: boolean; logged_out?: boolean;
have_game_ws?: boolean; have_game_ws?: boolean;
// to game // to game/bootstrapper (https://openwf.io/bootstrapper-manual)
sync_inventory?: boolean; sync_inventory?: boolean;
tunables?: ITunables;
} }
const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => { const wsOnConnect = (ws: WebSocket, req: http.IncomingMessage): void => {

View File

@ -0,0 +1,9 @@
// This is specific to the OpenWF Bootstrapper: https://openwf.io/bootstrapper-manual
export interface ITunables {
token?: string;
prohibit_skip_mission_start_timer?: boolean;
prohibit_fov_override?: boolean;
prohibit_freecam?: boolean;
prohibit_teleport?: boolean;
prohibit_scripts?: boolean;
}

View File

@ -61,6 +61,9 @@ export interface IAccountCheats {
nemesisHintProgressMultiplierGrineer?: number; nemesisHintProgressMultiplierGrineer?: number;
nemesisHintProgressMultiplierCorpus?: number; nemesisHintProgressMultiplierCorpus?: number;
nemesisExtraWeapon?: number; nemesisExtraWeapon?: number;
spoofMasteryRank?: number;
relicRewardItemCountMultiplier?: number;
nightwaveStandingMultiplier?: number;
} }
export interface IInventoryDatabase export interface IInventoryDatabase
@ -92,6 +95,7 @@ export interface IInventoryDatabase
| "NextRefill" | "NextRefill"
| "Nemesis" | "Nemesis"
| "NemesisHistory" | "NemesisHistory"
| "LastNemesisAllySpawnTime"
| "EntratiVaultCountResetDate" | "EntratiVaultCountResetDate"
| "BrandedSuits" | "BrandedSuits"
| "LockedWeaponGroup" | "LockedWeaponGroup"
@ -136,6 +140,7 @@ export interface IInventoryDatabase
NextRefill?: Date; NextRefill?: Date;
Nemesis?: INemesisDatabase; Nemesis?: INemesisDatabase;
NemesisHistory?: INemesisBaseDatabase[]; NemesisHistory?: INemesisBaseDatabase[];
LastNemesisAllySpawnTime?: Date;
EntratiVaultCountResetDate?: Date; EntratiVaultCountResetDate?: Date;
BrandedSuits?: Types.ObjectId[]; BrandedSuits?: Types.ObjectId[];
LockedWeaponGroup?: ILockedWeaponGroupDatabase; LockedWeaponGroup?: ILockedWeaponGroupDatabase;
@ -338,6 +343,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EmailItems: ITypeCount[]; EmailItems: ITypeCount[];
CompletedSyndicates: string[]; CompletedSyndicates: string[];
FocusXP?: IFocusXP; FocusXP?: IFocusXP;
FocusCapacity?: number;
Wishlist: string[]; Wishlist: string[];
Alignment?: IAlignment; Alignment?: IAlignment;
CompletedSorties: string[]; CompletedSorties: string[];
@ -360,7 +366,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ThemeStyle: string; ThemeStyle: string;
ThemeBackground: string; ThemeBackground: string;
ThemeSounds: string; ThemeSounds: string;
BountyScore: number; BountyScore?: number;
//ChallengeInstanceStates: IChallengeInstanceState[]; //ChallengeInstanceStates: IChallengeInstanceState[];
LoginMilestoneRewards: string[]; LoginMilestoneRewards: string[];
RecentVendorPurchases?: IRecentVendorPurchaseClient[]; RecentVendorPurchases?: IRecentVendorPurchaseClient[];
@ -372,7 +378,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
//InvasionChainProgress: IInvasionChainProgress[]; //InvasionChainProgress: IInvasionChainProgress[];
Nemesis?: INemesisClient; Nemesis?: INemesisClient;
NemesisHistory?: INemesisBaseClient[]; NemesisHistory?: INemesisBaseClient[];
//LastNemesisAllySpawnTime?: IMongoDate; LastNemesisAllySpawnTime?: IMongoDate;
Settings?: ISettings; Settings?: ISettings;
PersonalTechProjects: IPersonalTechProjectClient[]; PersonalTechProjects: IPersonalTechProjectClient[];
PlayerSkills: IPlayerSkills; PlayerSkills: IPlayerSkills;
@ -430,6 +436,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Ship?: IOrbiterClient; // U22 and below, response only Ship?: IOrbiterClient; // U22 and below, response only
ClaimedJunctionChallengeRewards?: string[]; // U39 ClaimedJunctionChallengeRewards?: string[]; // U39
SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
NokkoColony?: INokkoColony; // Field Guide
} }
export interface IAffiliation { export interface IAffiliation {
@ -636,6 +643,7 @@ export interface IFocusUpgrade {
ItemType: string; ItemType: string;
Level?: number; Level?: number;
IsUniversal?: boolean; IsUniversal?: boolean;
IsActive?: number; // Focus 2.0
} }
export interface IFocusXP { export interface IFocusXP {
@ -1213,3 +1221,13 @@ export interface IHubNpcCustomization {
Pattern: string; Pattern: string;
Tag: string; Tag: string;
} }
export interface IJournalEntry {
EntryType: string;
Progress: number;
}
export interface INokkoColony {
FeedLevel: number;
JournalEntries: IJournalEntry[];
}

View File

@ -9,7 +9,8 @@ import type {
TEquipmentKey, TEquipmentKey,
ICrewMemberClient, ICrewMemberClient,
IKubrowPetPrintClient, IKubrowPetPrintClient,
IUpgradeClient IUpgradeClient,
IQuestKeyClient
} from "./inventoryTypes/inventoryTypes.ts"; } from "./inventoryTypes/inventoryTypes.ts";
export enum PurchaseSource { export enum PurchaseSource {
@ -83,6 +84,7 @@ export type IInventoryChanges = {
CrewMembers?: ICrewMemberClient[]; CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[]; KubrowPetPrints?: IKubrowPetPrintClient[];
Upgrades?: IUpgradeClient[]; // TOVERIFY Upgrades?: IUpgradeClient[]; // TOVERIFY
QuestKeys?: IQuestKeyClient[];
} & Record< } & Record<
Exclude< Exclude<
string, string,

View File

@ -178,6 +178,7 @@ export interface IRewardInfo {
goalManifest?: string; goalManifest?: string;
invasionId?: string; invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS"; invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
alertId?: string;
sortieId?: string; sortieId?: string;
sortieTag?: "Mission1" | "Mission2" | "Final"; sortieTag?: "Mission1" | "Mission2" | "Final";
sortiePrereqs?: string[]; sortiePrereqs?: string[];
@ -233,11 +234,6 @@ export interface IUpgradeOperation {
PolarizeValue: ArtifactPolarity; PolarizeValue: ArtifactPolarity;
PolarityRemap: IPolarity[]; PolarityRemap: IPolarity[];
} }
export interface IUnlockShipFeatureRequest {
Feature: string;
KeyChain: string;
ChainStage: number;
}
export interface IVoidTearParticipantInfo { export interface IVoidTearParticipantInfo {
AccountId: string; AccountId: 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"; import type { IMongoDate, IOid } from "./commonTypes.ts";
export interface IWorldState { export interface IWorldState {
@ -7,7 +7,7 @@ export interface IWorldState {
Time: number; Time: number;
InGameMarket: IInGameMarket; InGameMarket: IInGameMarket;
Goals: IGoal[]; Goals: IGoal[];
Alerts: []; Alerts: IAlert[];
Sorties: ISortie[]; Sorties: ISortie[];
LiteSorties: ILiteSortie[]; LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[]; SyndicateMissions: ISyndicateMissionInfo[];
@ -35,6 +35,34 @@ export interface IWorldState {
Tmp?: string; 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;
enemyCacheOverride?: string;
maxRotations?: number; // SNS specific field
}
export interface IGoal { export interface IGoal {
_id: IOid; _id: IOid;
Activation: IMongoDate; Activation: IMongoDate;

View File

@ -136,5 +136,7 @@
"ConquestSetupIntro", "ConquestSetupIntro",
"EntratiLabConquestHardModeUnlocked", "EntratiLabConquestHardModeUnlocked",
"/Lotus/Language/Npcs/KonzuPostNewWar", "/Lotus/Language/Npcs/KonzuPostNewWar",
"/Lotus/Language/SolarisVenus/EudicoPostNewWar" "/Lotus/Language/SolarisVenus/EudicoPostNewWar",
"/Lotus/Language/NokkoColony/NokkoVendorName",
"NokkoVisions_FirstVisit"
] ]

View File

@ -0,0 +1,409 @@
{
"Skus": [
{
"productId": 17,
"listPrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"basePrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 18,
"listPrice": {
"formatted": "9.99 USD",
"raw": "9.99",
"value": 9.99
},
"basePrice": {
"formatted": "9.99 USD",
"raw": "9.99",
"value": 9.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 160,
"listPrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"basePrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 257,
"listPrice": {
"formatted": "199.99 USD",
"raw": "199.99",
"value": 199.99
},
"basePrice": {
"formatted": "199.99 USD",
"raw": "199.99",
"value": 199.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 258,
"listPrice": {
"formatted": "99.99 USD",
"raw": "99.99",
"value": 99.99
},
"basePrice": {
"formatted": "99.99 USD",
"raw": "99.99",
"value": 99.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 259,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 785,
"listPrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"basePrice": {
"formatted": "19.99 USD",
"raw": "19.99",
"value": 19.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 786,
"listPrice": {
"formatted": "39.99 USD",
"raw": "39.99",
"value": 39.99
},
"basePrice": {
"formatted": "39.99 USD",
"raw": "39.99",
"value": 39.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 787,
"listPrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"basePrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 979,
"listPrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"basePrice": {
"formatted": "4.99 USD",
"raw": "4.99",
"value": 4.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10037,
"listPrice": {
"formatted": "54.99 USD",
"raw": "54.99",
"value": 54.99
},
"basePrice": {
"formatted": "54.99 USD",
"raw": "54.99",
"value": 54.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10054,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10061,
"listPrice": {
"formatted": "0.99 USD",
"raw": "0.99",
"value": 0.99
},
"basePrice": {
"formatted": "0.99 USD",
"raw": "0.99",
"value": 0.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10076,
"listPrice": {
"formatted": "29.99 USD",
"raw": "29.99",
"value": 29.99
},
"basePrice": {
"formatted": "29.99 USD",
"raw": "29.99",
"value": 29.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10078,
"listPrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"basePrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10080,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10081,
"listPrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"basePrice": {
"formatted": "79.99 USD",
"raw": "79.99",
"value": 79.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10082,
"listPrice": {
"formatted": "139.99 USD",
"raw": "139.99",
"value": 139.99
},
"basePrice": {
"formatted": "139.99 USD",
"raw": "139.99",
"value": 139.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10084,
"listPrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"basePrice": {
"formatted": "49.99 USD",
"raw": "49.99",
"value": 49.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10085,
"listPrice": {
"formatted": "30.00 USD",
"raw": "30.00",
"value": 30
},
"basePrice": {
"formatted": "30.00 USD",
"raw": "30.00",
"value": 30
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10086,
"listPrice": {
"formatted": "60.00 USD",
"raw": "60.00",
"value": 60
},
"basePrice": {
"formatted": "60.00 USD",
"raw": "60.00",
"value": 60
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10087,
"listPrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"basePrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10088,
"listPrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"basePrice": {
"formatted": "90.00 USD",
"raw": "90.00",
"value": 90
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10089,
"listPrice": {
"formatted": "40.01 USD",
"raw": "40.01",
"value": 40.01
},
"basePrice": {
"formatted": "40.01 USD",
"raw": "40.01",
"value": 40.01
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10090,
"listPrice": {
"formatted": "10.01 USD",
"raw": "10.01",
"value": 10.01
},
"basePrice": {
"formatted": "10.01 USD",
"raw": "10.01",
"value": 10.01
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10094,
"listPrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"basePrice": {
"formatted": "24.99 USD",
"raw": "24.99",
"value": 24.99
},
"currencyCode": "USD",
"owned": false
},
{
"productId": 10095,
"listPrice": {
"formatted": "14.99 USD",
"raw": "14.99",
"value": 14.99
},
"basePrice": {
"formatted": "14.99 USD",
"raw": "14.99",
"value": 14.99
},
"currencyCode": "USD",
"owned": false
}
]
}

View File

@ -1,10 +1,4 @@
{ {
"/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain": [
{
"ItemType": "/Lotus/Types/NeutralCreatures/ErsatzHorse/ErsatzHorsePowerSuit",
"ItemCount": 1
}
],
"/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": [{ "ItemType": "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", "ItemCount": 1 }], "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain": [{ "ItemType": "/Lotus/Types/Recipes/WarframeRecipes/BrokenFrameBlueprint", "ItemCount": 1 }],
"/Lotus/Types/Keys/OrokinMoonQuest/OrokinMoonQuestKeyChain": [ "/Lotus/Types/Keys/OrokinMoonQuest/OrokinMoonQuestKeyChain": [
{ {

View File

@ -83,7 +83,7 @@
<p data-loc="login_description"></p> <p data-loc="login_description"></p>
<form onsubmit="doLogin();return false;"> <form onsubmit="doLogin();return false;">
<label for="email" data-loc="login_emailLabel"></label> <label for="email" data-loc="login_emailLabel"></label>
<input class="form-control" type="email" id="email" required /> <input class="form-control" type="text" id="email" required />
<br /> <br />
<label for="password" data-loc="login_passwordLabel"></label> <label for="password" data-loc="login_passwordLabel"></label>
<input class="form-control" type="password" id="password" required /> <input class="form-control" type="password" id="password" required />
@ -110,7 +110,7 @@
<div class="tab-pane" id="miscItems-tab-content"> <div class="tab-pane" id="miscItems-tab-content">
<form class="card-body input-group" onsubmit="doAcquireCountItems('miscitems');return false;"> <form class="card-body input-group" onsubmit="doAcquireCountItems('miscitems');return false;">
<input class="form-control" id="miscitems-count" type="number" value="1" /> <input class="form-control" id="miscitems-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-miscitems" list="datalist-miscitems" /> <input class="form-control w-50" id="acquire-type-miscitems" list="datalist-miscitems" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
</div> </div>
@ -182,7 +182,7 @@
<h5 class="card-header" data-loc="inventory_suits"></h5> <h5 class="card-header" data-loc="inventory_suits"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
<input class="form-control" id="acquire-type-Suits" list="datalist-Suits" /> <input class="form-control" id="acquire-type-Suits" list="datalist-Suits" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -198,13 +198,13 @@
<h5 class="card-header" data-loc="inventory_longGuns"></h5> <h5 class="card-header" data-loc="inventory_longGuns"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
<input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" /> <input class="form-control" id="acquire-type-LongGuns" list="datalist-LongGuns" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3 d-none" id="modular-LongGuns"> <form class="input-group mb-3 d-none" id="modular-LongGuns">
<input class="form-control" id="acquire-type-LongGuns-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" /> <input class="form-control" id="acquire-type-LongGuns-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" autocomplete="off" />
<input class="form-control" id="acquire-type-LongGuns-GUN_PRIMARY_HANDLE" list="datalist-ModularParts-GUN_PRIMARY_HANDLE" /> <input class="form-control" id="acquire-type-LongGuns-GUN_PRIMARY_HANDLE" list="datalist-ModularParts-GUN_PRIMARY_HANDLE" autocomplete="off" />
<input class="form-control" id="acquire-type-LongGuns-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" /> <input class="form-control" id="acquire-type-LongGuns-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" autocomplete="off" />
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -221,13 +221,13 @@
<h5 class="card-header" data-loc="inventory_pistols"></h5> <h5 class="card-header" data-loc="inventory_pistols"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
<input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" /> <input class="form-control" id="acquire-type-Pistols" list="datalist-Pistols" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3 d-none" id="modular-Pistols"> <form class="input-group mb-3 d-none" id="modular-Pistols">
<input class="form-control" id="acquire-type-Pistols-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" /> <input class="form-control" id="acquire-type-Pistols-GUN_BARREL" list="datalist-ModularParts-GUN_BARREL" autocomplete="off" />
<input class="form-control" id="acquire-type-Pistols-GUN_SECONDARY_HANDLE" list="datalist-ModularParts-GUN_SECONDARY_HANDLE" /> <input class="form-control" id="acquire-type-Pistols-GUN_SECONDARY_HANDLE" list="datalist-ModularParts-GUN_SECONDARY_HANDLE" autocomplete="off" />
<input class="form-control" id="acquire-type-Pistols-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" /> <input class="form-control" id="acquire-type-Pistols-GUN_CLIP" list="datalist-ModularParts-GUN_CLIP" autocomplete="off" />
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -242,13 +242,13 @@
<h5 class="card-header" data-loc="inventory_melee"></h5> <h5 class="card-header" data-loc="inventory_melee"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
<input class="form-control" id="acquire-type-Melee" list="datalist-Melee" /> <input class="form-control" id="acquire-type-Melee" list="datalist-Melee" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3 d-none" id="modular-Melee"> <form class="input-group mb-3 d-none" id="modular-Melee">
<input class="form-control" id="acquire-type-Melee-BLADE" list="datalist-ModularParts-BLADE" /> <input class="form-control" id="acquire-type-Melee-BLADE" list="datalist-ModularParts-BLADE" autocomplete="off" />
<input class="form-control" id="acquire-type-Melee-HILT" list="datalist-ModularParts-HILT" /> <input class="form-control" id="acquire-type-Melee-HILT" list="datalist-ModularParts-HILT" autocomplete="off" />
<input class="form-control" id="acquire-type-Melee-HILT_WEIGHT" list="datalist-ModularParts-HILT_WEIGHT" /> <input class="form-control" id="acquire-type-Melee-HILT_WEIGHT" list="datalist-ModularParts-HILT_WEIGHT" autocomplete="off" />
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -265,7 +265,7 @@
<h5 class="card-header" data-loc="inventory_spaceSuits"></h5> <h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
<input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" /> <input class="form-control" id="acquire-type-SpaceSuits" list="datalist-SpaceSuits" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -281,7 +281,7 @@
<h5 class="card-header" data-loc="inventory_spaceGuns"></h5> <h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
<input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" /> <input class="form-control" id="acquire-type-SpaceGuns" list="datalist-SpaceGuns" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -299,7 +299,7 @@
<h5 class="card-header" data-loc="inventory_spaceMelee"></h5> <h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
<input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" /> <input class="form-control" id="acquire-type-SpaceMelee" list="datalist-SpaceMelee" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -315,7 +315,7 @@
<h5 class="card-header" data-loc="inventory_mechSuits"></h5> <h5 class="card-header" data-loc="inventory_mechSuits"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
<input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" /> <input class="form-control" id="acquire-type-MechSuits" list="datalist-MechSuits" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -333,7 +333,7 @@
<h5 class="card-header" data-loc="inventory_sentinels"></h5> <h5 class="card-header" data-loc="inventory_sentinels"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
<input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" /> <input class="form-control" id="acquire-type-Sentinels" list="datalist-Sentinels" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -349,20 +349,20 @@
<h5 class="card-header" data-loc="inventory_moaPets"></h5> <h5 class="card-header" data-loc="inventory_moaPets"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
<input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" /> <input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3 d-none" id="modular-MoaPets-Moa"> <form class="input-group mb-3 d-none" id="modular-MoaPets-Moa">
<input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" /> <input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" /> <input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" /> <input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-MOA_LEG" list="datalist-ModularParts-MOA_LEG" /> <input class="form-control" id="acquire-type-MoaPets-MOA_LEG" list="datalist-ModularParts-MOA_LEG" autocomplete="off" />
</form> </form>
<form class="input-group mb-3 d-none" id="modular-MoaPets-Zanuka"> <form class="input-group mb-3 d-none" id="modular-MoaPets-Zanuka">
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_HEAD" list="datalist-ModularParts-ZANUKA_HEAD" /> <input class="form-control" id="acquire-type-MoaPets-ZANUKA_HEAD" list="datalist-ModularParts-ZANUKA_HEAD" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_BODY" list="datalist-ModularParts-ZANUKA_BODY" /> <input class="form-control" id="acquire-type-MoaPets-ZANUKA_BODY" list="datalist-ModularParts-ZANUKA_BODY" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_LEG" list="datalist-ModularParts-ZANUKA_LEG" /> <input class="form-control" id="acquire-type-MoaPets-ZANUKA_LEG" list="datalist-ModularParts-ZANUKA_LEG" autocomplete="off" />
<input class="form-control" id="acquire-type-MoaPets-ZANUKA_TAIL" list="datalist-ModularParts-ZANUKA_TAIL" /> <input class="form-control" id="acquire-type-MoaPets-ZANUKA_TAIL" list="datalist-ModularParts-ZANUKA_TAIL" autocomplete="off" />
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -379,16 +379,16 @@
<h5 class="card-header" data-loc="inventory_kubrowPets"></h5> <h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
<input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" /> <input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3 d-none" id="modular-KubrowPets-Catbrow"> <form class="input-group mb-3 d-none" id="modular-KubrowPets-Catbrow">
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" /> <input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" /> <input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" autocomplete="off" />
</form> </form>
<form class="input-group mb-3 d-none" id="modular-KubrowPets-Kubrow"> <form class="input-group mb-3 d-none" id="modular-KubrowPets-Kubrow">
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" /> <input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" autocomplete="off" />
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" /> <input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" autocomplete="off" />
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -403,7 +403,7 @@
<h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5> <h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
<input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" /> <input class="form-control" id="acquire-type-SentinelWeapons" list="datalist-SentinelWeapons" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -421,13 +421,13 @@
<h5 class="card-header" data-loc="inventory_operatorAmps"></h5> <h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;"> <form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
<input class="form-control" id="acquire-type-OperatorAmps" list="datalist-OperatorAmps" /> <input class="form-control" id="acquire-type-OperatorAmps" list="datalist-OperatorAmps" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3 d-none" id="modular-OperatorAmps"> <form class="input-group mb-3 d-none" id="modular-OperatorAmps">
<input class="form-control" id="acquire-type-OperatorAmps-AMP_OCULUS" list="datalist-ModularParts-AMP_OCULUS" /> <input class="form-control" id="acquire-type-OperatorAmps-AMP_OCULUS" list="datalist-ModularParts-AMP_OCULUS" autocomplete="off" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_CORE" list="datalist-ModularParts-AMP_CORE" /> <input class="form-control" id="acquire-type-OperatorAmps-AMP_CORE" list="datalist-ModularParts-AMP_CORE" autocomplete="off" />
<input class="form-control" id="acquire-type-OperatorAmps-AMP_BRACE" list="datalist-ModularParts-AMP_BRACE" /> <input class="form-control" id="acquire-type-OperatorAmps-AMP_BRACE" list="datalist-ModularParts-AMP_BRACE" autocomplete="off" />
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -442,10 +442,10 @@
<h5 class="card-header" data-loc="inventory_hoverboards"></h5> <h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('Hoverboards');return false;"> <form class="input-group mb-3" onsubmit="doAcquireModularEquipment('Hoverboards');return false;">
<input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" /> <input class="form-control" id="acquire-type-Hoverboards-HB_DECK" list="datalist-ModularParts-HB_DECK" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" /> <input class="form-control" id="acquire-type-Hoverboards-HB_ENGINE" list="datalist-ModularParts-HB_ENGINE" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" /> <input class="form-control" id="acquire-type-Hoverboards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" autocomplete="off" />
<input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" /> <input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -463,7 +463,7 @@
<h5 class="card-header" data-loc="inventory_evolutionProgress"></h5> <h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;"> <form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
<input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" /> <input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -479,7 +479,7 @@
<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"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireBooster();return false;"> <form class="input-group mb-3" onsubmit="doAcquireBooster();return false;">
<input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" /> <input class="form-control" id="acquire-type-Boosters" list="datalist-Boosters" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -497,7 +497,7 @@
<h5 class="card-header" data-loc="inventory_flavourItems"></h5> <h5 class="card-header" data-loc="inventory_flavourItems"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('FlavourItems');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('FlavourItems');return false;">
<input class="form-control" id="acquire-type-FlavourItems" list="datalist-FlavourItems" /> <input class="form-control" id="acquire-type-FlavourItems" list="datalist-FlavourItems" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -514,7 +514,7 @@
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireCountItems('ShipDecorations');return false;"> <form class="input-group mb-3" onsubmit="doAcquireCountItems('ShipDecorations');return false;">
<input class="form-control" id="ShipDecorations-count" type="number" value="1" /> <input class="form-control" id="ShipDecorations-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-ShipDecorations" list="datalist-ShipDecorations" /> <input class="form-control w-50" id="acquire-type-ShipDecorations" list="datalist-ShipDecorations" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -526,6 +526,24 @@
</div> </div>
</div> </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" autocomplete="off" />
<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"> <div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
@ -538,7 +556,9 @@
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button> <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, ['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, ['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> <button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
<button class="btn btn-primary" onclick="removeIsNew();" data-loc="inventory_removeIsNew"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Suits']);" data-loc="inventory_bulkRankUpSuits"></button> <button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
@ -590,7 +610,7 @@
<h5 class="card-header" data-loc="guildView_techProjects"></h5> <h5 class="card-header" data-loc="guildView_techProjects"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form id="techProjects-form" class="input-group mb-3 d-none" onsubmit="addGuildTechProject();return false;"> <form id="techProjects-form" class="input-group mb-3 d-none" onsubmit="addGuildTechProject();return false;">
<input class="form-control" id="acquire-type-TechProjects" list="datalist-TechProjects" /> <input class="form-control" id="acquire-type-TechProjects" list="datalist-TechProjects" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -605,8 +625,8 @@
<div class="card" style="height: 400px;"> <div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="guildView_vaultDecoRecipes"></h5> <h5 class="card-header" data-loc="guildView_vaultDecoRecipes"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form id="vaultDecoRecipes-form" class="input-group mb-3 d-none" onsubmit="addVaultDecoRecipe();return false;"> <form id="vaultDecoRecipes-form" class="input-group mb-3 d-none" onsubmit="addVaultItem('VaultDecoRecipes');return false;">
<input class="form-control" id="acquire-type-VaultDecoRecipes" list="datalist-VaultDecoRecipes" /> <input class="form-control" id="acquire-type-VaultDecoRecipes" list="datalist-VaultDecoRecipes" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -618,6 +638,42 @@
</div> </div>
</div> </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="guildView_vaultMiscItems"></h5>
<div class="card-body d-flex flex-column">
<form id="vaultMiscItems-form" class="input-group mb-3 d-none" onsubmit="addVaultItem('VaultMiscItems');return false;">
<input class="form-control" id="VaultMiscItems-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-VaultMiscItems" list="datalist-VaultMiscItems" autocomplete="off" />
<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="VaultMiscItems-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="guildView_vaultShipDecorations"></h5>
<div class="card-body d-flex flex-column">
<form id="vaultShipDecorations-form" class="input-group mb-3 d-none" onsubmit="addVaultItem('VaultShipDecorations');return false;">
<input class="form-control" id="VaultShipDecorations-count" type="number" value="1" />
<input class="form-control w-50" id="acquire-type-VaultShipDecorations" list="datalist-ShipDecorations" autocomplete="off" />
<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="VaultShipDecorations-list"></tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row g-3 mb-3"> <div class="row g-3 mb-3">
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card" style="height: 400px;"> <div class="card" style="height: 400px;">
@ -647,7 +703,8 @@
<div class="card-body" id="guild-actions"> <div class="card-body" id="guild-actions">
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(addMissingTechProjects);" data-loc="guildView_bulkAddTechProjects"></button> <button class="btn btn-primary" onclick="debounce(addMissingTechProjects);" data-loc="guildView_bulkAddTechProjects"></button>
<button class="btn btn-primary" onclick="debounce(addMissingVaultDecoRecipes);" data-loc="guildView_bulkAddVaultDecoRecipes"></button> <button class="btn btn-primary" onclick="debounce(addMissingVaultItems, ['VaultDecoRecipes']);" data-loc="guildView_bulkAddVaultDecoRecipes"></button>
<button class="btn btn-primary" onclick="debounce(addMissingVaultItems, ['VaultShipDecorations']);" data-loc="guildView_bulkAddVaultShipDecorations"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="debounce(fundAllTechProjects);" data-loc="guildView_bulkFundTechProjects"></button> <button class="btn btn-success" onclick="debounce(fundAllTechProjects);" data-loc="guildView_bulkFundTechProjects"></button>
@ -710,7 +767,7 @@
<form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;"> <form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;">
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" /> <input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
<span class="input-group-text">x</span> <span class="input-group-text">x</span>
<input class="form-control" list="datalist-archonCrystalUpgrades" /> <input class="form-control" list="datalist-archonCrystalUpgrades" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<table class="table table-hover w-100"> <table class="table table-hover w-100">
@ -820,7 +877,7 @@
<div class="card-body"> <div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireMod();return false;"> <form class="input-group mb-3" onsubmit="doAcquireMod();return false;">
<input class="form-control" id="mod-count" type="number" value="1"/> <input class="form-control" id="mod-count" type="number" value="1"/>
<input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" /> <input class="form-control w-50" id="mod-to-acquire" list="datalist-mods" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
<button class="btn btn-success" onclick="window.maxed=true" data-loc="mods_addMax"></button> <button class="btn btn-success" onclick="window.maxed=true" data-loc="mods_addMax"></button>
</form> </form>
@ -848,7 +905,7 @@
<h5 class="card-header" data-loc="quests_list"></h5> <h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;"> <form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
<input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" /> <input class="form-control" id="acquire-type-QuestKeys" list="datalist-QuestKeys" autocomplete="off" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<div class="overflow-auto"> <div class="overflow-auto">
@ -1015,6 +1072,20 @@
<input class="form-check-input" type="checkbox" id="finishInvasionsInOneMission" /> <input class="form-check-input" type="checkbox" id="finishInvasionsInOneMission" />
<label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></label> <label class="form-check-label" for="finishInvasionsInOneMission" data-loc="cheats_finishInvasionsInOneMission"></label>
</div> </div>
<form class="form-group mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<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-secondary" type="submit" data-loc="cheats_changeButton"></button>
</div>
</form>
<form class="form-group mt-2">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2"> <form class="form-group mt-2">
<label class="form-label" for="nemesisHenchmenKillsMultiplierGrineer" data-loc="cheats_nemesisHenchmenKillsMultiplierGrineer"></label> <label class="form-label" for="nemesisHenchmenKillsMultiplierGrineer" data-loc="cheats_nemesisHenchmenKillsMultiplierGrineer"></label>
<div class="input-group"> <div class="input-group">
@ -1057,6 +1128,20 @@
<button class="btn btn-secondary" type="button" data-loc="cheats_save"></button> <button class="btn btn-secondary" type="button" data-loc="cheats_save"></button>
</div> </div>
</form> </form>
<form class="form-group mt-2">
<label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
<div class="input-group">
<input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" 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="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
<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"> <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(doUnlockAllShipFeatures);" data-loc="cheats_unlockAllShipFeatures"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllMissions);" data-loc="cheats_unlockAllMissions"></button> <button class="btn btn-primary" onclick="debounce(unlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
@ -1071,13 +1156,6 @@
<button class="btn btn-primary" onclick="debounce(unlockAllProfitTakerStages);" data-loc="cheats_unlockAllProfitTakerStages"></button> <button class="btn btn-primary" onclick="debounce(unlockAllProfitTakerStages);" data-loc="cheats_unlockAllProfitTakerStages"></button>
<button class="btn btn-primary" onclick="debounce(unlockAllSimarisResearchEntries);" data-loc="cheats_unlockAllSimarisResearchEntries"></button> <button class="btn btn-primary" onclick="debounce(unlockAllSimarisResearchEntries);" data-loc="cheats_unlockAllSimarisResearchEntries"></button>
</div> </div>
<form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<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-secondary" type="submit" data-loc="cheats_changeButton"></button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
@ -1105,27 +1183,6 @@
<input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" /> <input class="form-check-input" type="checkbox" id="skipClanKeyCrafting" />
<label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label> <label class="form-check-label" for="skipClanKeyCrafting" data-loc="cheats_skipClanKeyCrafting"></label>
</div> </div>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" data-default="-1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('relicRewardItemCountMultiplier'); return false;">
<label class="form-label" for="relicRewardItemCountMultiplier" data-loc="cheats_relicRewardItemCountMultiplier"></label>
<div class="input-group">
<input class="form-control" id="relicRewardItemCountMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" data-default="1" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div> </div>
</div> </div>
</div> </div>
@ -1169,11 +1226,6 @@
<label class="form-check-label" for="unfaithfulBugFixes.useAnniversaryTagForOldGoals" data-loc="worldState_useAnniversaryTagForOldGoals"></label> <label class="form-check-label" for="unfaithfulBugFixes.useAnniversaryTagForOldGoals" data-loc="worldState_useAnniversaryTagForOldGoals"></label>
<abbr data-loc-inc="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> <abbr data-loc-inc="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>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.wolfHunt" />
<label class="form-check-label" for="worldState.wolfHunt" data-loc="worldState_wolfHunt"></label>
<abbr data-loc-inc="worldState_galleonOfGhouls|worldState_orphixVenom|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"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.orphixVenom" /> <input class="form-check-input" type="checkbox" id="worldState.orphixVenom" />
<label class="form-check-label" for="worldState.orphixVenom" data-loc="worldState_orphixVenom"></label> <label class="form-check-label" for="worldState.orphixVenom" data-loc="worldState_orphixVenom"></label>
@ -1189,6 +1241,19 @@
<label class="form-check-label" for="worldState.hallowedFlame" data-loc="worldState_hallowedFlame"></label> <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> <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>
<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">
<label class="form-label" for="worldState.wolfHunt" data-loc="worldState_wolfHunt"></label>
<abbr data-loc-inc="worldState_galleonOfGhouls|worldState_orphixVenom|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>
<select class="form-control" id="worldState.wolfHunt" data-default="null">
<option value="null" data-loc="disabled"></option>
<option value="1" data-loc="worldState_from_year" data-loc-replace="2025"></option>
<option value="0" data-loc="worldState_from_year" data-loc-replace="2019"></option>
</select>
</div>
<div class="form-group mt-2 d-flex gap-2"> <div class="form-group mt-2 d-flex gap-2">
<div class="flex-fill"> <div class="flex-fill">
<label class="form-label" for="worldState.hallowedNightmares" data-loc="worldState_hallowedNightmares"></label> <label class="form-label" for="worldState.hallowedNightmares" data-loc="worldState_hallowedNightmares"></label>
@ -1232,6 +1297,27 @@
</select> </select>
</div> </div>
</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"> <div class="form-group mt-2">
<label class="form-label" for="worldState.galleonOfGhouls" data-loc="worldState_galleonOfGhouls"></label> <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> <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>
@ -1421,7 +1507,7 @@
<form class="form-group mt-2" onsubmit="doSaveConfigStringArray('worldState.circuitGameModes'); return false;"> <form class="form-group mt-2" onsubmit="doSaveConfigStringArray('worldState.circuitGameModes'); return false;">
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label> <label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label>
<div class="input-group"> <div class="input-group">
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" /> <input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" autocomplete="off" />
<button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button> <button class="btn btn-secondary" type="submit" data-loc="cheats_save"></button>
</div> </div>
</form> </form>
@ -1503,6 +1589,8 @@
<datalist id="datalist-VaultDecoRecipes"></datalist> <datalist id="datalist-VaultDecoRecipes"></datalist>
<datalist id="datalist-FlavourItems"></datalist> <datalist id="datalist-FlavourItems"></datalist>
<datalist id="datalist-ShipDecorations"></datalist> <datalist id="datalist-ShipDecorations"></datalist>
<datalist id="datalist-WeaponSkins"></datalist>
<datalist id="datalist-VaultMiscItems"></datalist>
<datalist id="datalist-circuitGameModes"> <datalist id="datalist-circuitGameModes">
<option>Survival</option> <option>Survival</option>
<option>VoidFlood</option> <option>VoidFlood</option>

View File

@ -559,7 +559,7 @@ function fetchItemList() {
}); });
} else if (type == "Syndicates") { } else if (type == "Syndicates") {
items.forEach(item => { items.forEach(item => {
if (item.uniqueName === "ConclaveSyndicate") { if (["ConclaveSyndicate", "NightcapJournalSyndicate"].includes(item.uniqueName)) {
return; return;
} }
if (item.uniqueName.startsWith("RadioLegion")) { if (item.uniqueName.startsWith("RadioLegion")) {
@ -599,6 +599,46 @@ function fetchItemList() {
} }
itemMap[item.uniqueName] = { ...item, type }; 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 { } else {
const nameToItems = {}; const nameToItems = {};
items.forEach(item => { items.forEach(item => {
@ -671,6 +711,10 @@ function fetchItemList() {
option.value += " (" + item.subtype + ")"; option.value += " (" + item.subtype + ")";
} }
document.getElementById("datalist-" + type).appendChild(option); document.getElementById("datalist-" + type).appendChild(option);
if (item.eligibleForVault) {
const vaultOption = option.cloneNode(true);
document.getElementById("datalist-VaultMiscItems").appendChild(vaultOption);
}
} else { } else {
//console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`); //console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`);
} }
@ -788,7 +832,7 @@ function updateInventory() {
const td = document.createElement("td"); const td = document.createElement("td");
td.classList = "text-end text-nowrap"; td.classList = "text-end text-nowrap";
let maxXP = Math.pow(itemMap[item.ItemType].maxLevelCap ?? 30, 2) * 1000; let maxXP = Math.pow(itemMap[item.ItemType]?.maxLevelCap ?? 30, 2) * 1000;
if ( if (
category != "Suits" && category != "Suits" &&
category != "SpaceSuits" && category != "SpaceSuits" &&
@ -815,7 +859,7 @@ function updateInventory() {
} }
} }
if ( if (
itemMap[item.ItemType].maxLevelCap > 30 && itemMap[item.ItemType]?.maxLevelCap > 30 &&
(item.Polarized ?? 0) < (itemMap[item.ItemType].maxLevelCap - 30) / 2 (item.Polarized ?? 0) < (itemMap[item.ItemType].maxLevelCap - 30) / 2
) { ) {
const a = document.createElement("a"); const a = document.createElement("a");
@ -1103,6 +1147,44 @@ function updateInventory() {
document.getElementById("FlavourItems-list").appendChild(tr); 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 datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]'); const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
@ -1202,7 +1284,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("setInactive", item.ItemType); debounce(doQuestUpdate, "setInactive", item.ItemType);
}; };
a.title = loc("code_setInactive"); 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>`; 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>`;
@ -1213,7 +1295,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("resetKey", item.ItemType); debounce(doQuestUpdate, "resetKey", item.ItemType);
}; };
a.title = loc("code_reset"); 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>`; 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>`;
@ -1224,7 +1306,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("completeKey", item.ItemType); debounce(doQuestUpdate, "completeKey", item.ItemType);
}; };
a.title = loc("code_complete"); 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>`; 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>`;
@ -1235,7 +1317,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("prevStage", item.ItemType); debounce(doQuestUpdate, "prevStage", item.ItemType);
}; };
a.title = loc("code_prevStage"); 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>`; 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>`;
@ -1250,7 +1332,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
doQuestUpdate("nextStage", item.ItemType); debounce(doQuestUpdate, "nextStage", item.ItemType);
}; };
a.title = loc("code_nextStage"); 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>`; 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>`;
@ -1262,7 +1344,7 @@ function updateInventory() {
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
reAddToItemList(itemMap, "QuestKeys", item.ItemType); reAddToItemList(itemMap, "QuestKeys", item.ItemType);
doQuestUpdate("deleteKey", item.ItemType); debounce(doQuestUpdate, "deleteKey", item.ItemType);
}; };
a.title = loc("code_remove"); 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>`; 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>`;
@ -1724,6 +1806,8 @@ function updateInventory() {
document.getElementById("VaultRegularCredits-owned").classList.remove("mb-0"); document.getElementById("VaultRegularCredits-owned").classList.remove("mb-0");
document.getElementById("vaultPremiumCredits-form").classList.remove("d-none"); document.getElementById("vaultPremiumCredits-form").classList.remove("d-none");
document.getElementById("VaultPremiumCredits-owned").classList.remove("mb-0"); document.getElementById("VaultPremiumCredits-owned").classList.remove("mb-0");
document.getElementById("vaultMiscItems-form").classList.remove("d-none");
document.getElementById("vaultShipDecorations-form").classList.remove("d-none");
} }
if (userGuildMember.Rank <= 1) { if (userGuildMember.Rank <= 1) {
document.querySelectorAll("#guild-actions button").forEach(btn => { document.querySelectorAll("#guild-actions button").forEach(btn => {
@ -1771,7 +1855,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
fundGuildTechProject(item.ItemType); debounce(fundGuildTechProject, item.ItemType);
}; };
a.title = loc("code_fund"); 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>`; 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 +1872,7 @@ function updateInventory() {
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
completeGuildTechProject(item.ItemType); debounce(completeGuildTechProject, item.ItemType);
}; };
a.title = loc("code_complete"); 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>`; 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 +1885,7 @@ function updateInventory() {
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
reAddToItemList(itemMap, "TechProjects", item.ItemType); reAddToItemList(itemMap, "TechProjects", item.ItemType);
removeGuildTechProject(item.ItemType); debounce(removeGuildTechProject, item.ItemType);
}; };
a.title = loc("code_remove"); 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>`; 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>`;
@ -1814,42 +1898,55 @@ function updateInventory() {
document.getElementById("TechProjects-list").appendChild(tr); document.getElementById("TechProjects-list").appendChild(tr);
}); });
document.getElementById("VaultDecoRecipes-list").innerHTML = ""; ["VaultDecoRecipes", "VaultMiscItems", "VaultShipDecorations"].forEach(vaultKey => {
guildData.VaultDecoRecipes ??= []; document.getElementById(vaultKey + "-list").innerHTML = "";
guildData.VaultDecoRecipes.forEach(item => { (guildData[vaultKey] ??= []).forEach(item => {
const datalist = document.getElementById("datalist-VaultDecoRecipes"); if (vaultKey == "VaultDecoRecipes") {
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`); const datalist = document.getElementById("datalist-VaultDecoRecipes");
if (optionToRemove) { const optionToRemove = datalist.querySelector(
datalist.removeChild(optionToRemove); `option[data-key="${item.ItemType}"]`
} );
const tr = document.createElement("tr"); if (optionToRemove) {
tr.setAttribute("data-item-type", item.ItemType); datalist.removeChild(optionToRemove);
{ }
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";
if (userGuildMember && userGuildMember.Rank <= 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
reAddToItemList(itemMap, "VaultDecoRecipes", item.ItemType);
removeVaultDecoRecipe(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>`;
td.appendChild(a);
} }
tr.appendChild(td); const tr = document.createElement("tr");
} tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.ItemCount > 1) {
td.innerHTML += ` <span title='${loc("code_count")}'>🗍 ${parseInt(item.ItemCount)}</span>`;
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
const canRemove =
vaultKey === "VaultDecoRecipes"
? userGuildMember && userGuildMember.Rank <= 1
: userGuildPermissions && userGuildPermissions & 64;
if (canRemove) {
const a = document.createElement("a");
a.href = "#";
a.title = loc("code_remove");
a.onclick = e => {
e.preventDefault();
if (vaultKey == "VaultDecoRecipes") {
reAddToItemList(itemMap, vaultKey, item.ItemType);
}
removeVaultItem(vaultKey, item.ItemType, item.ItemCount * -1);
};
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><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("VaultDecoRecipes-list").appendChild(tr); document.getElementById(vaultKey + "-list").appendChild(tr);
});
}); });
document.getElementById("Members-list").innerHTML = ""; document.getElementById("Members-list").innerHTML = "";
@ -2080,6 +2177,15 @@ function removeCustomization(uniqueName) {
}); });
} }
function removeIsNew() {
revalidateAuthz().then(() => {
const req = $.get("/custom/removeIsNew?" + window.authz);
req.done(() => {
updateInventory();
});
});
}
function getRequiredParts(category, WeaponType) { function getRequiredParts(category, WeaponType) {
switch (category) { switch (category) {
case "Hoverboards": case "Hoverboards":
@ -2268,41 +2374,52 @@ function addMissingEquipment(categories) {
} }
} }
function addVaultDecoRecipe() { function addVaultItem(vaultType) {
const uniqueName = getKey(document.getElementById("acquire-type-VaultDecoRecipes")); const ItemType = getKey(document.getElementById(`acquire-type-${vaultType}`));
if (!uniqueName) { if (!ItemType) {
$("#acquire-type-VaultDecoRecipes").addClass("is-invalid").focus(); $(`#acquire-type-${vaultType}`).addClass("is-invalid").focus();
return; return;
} }
revalidateAuthz().then(() => { const ItemCount = ["VaultMiscItems", "VaultShipDecorations"].includes(vaultType)
const req = $.post({ ? parseInt($(`#${vaultType}-count`).val())
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, : 1;
contentType: "application/json", if (ItemCount != 0) {
data: JSON.stringify([ revalidateAuthz().then(() => {
{ const req = $.post({
ItemType: uniqueName, url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
ItemCount: 1 contentType: "application/json",
} data: JSON.stringify({
]) vaultType,
items: [
{
ItemType,
ItemCount
}
]
})
});
req.done(() => {
document.getElementById(`acquire-type-${vaultType}`).value = "";
updateInventory();
});
}); });
req.done(() => { }
document.getElementById("acquire-type-VaultDecoRecipes").value = "";
updateInventory();
});
});
} }
function removeVaultDecoRecipe(uniqueName) { function removeVaultItem(vaultType, ItemType, ItemCount) {
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
const req = $.post({ const req = $.post({
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, url: "/custom/addVaultTypeCount?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify([ data: JSON.stringify({
{ vaultType,
ItemType: uniqueName, items: [
ItemCount: -1 {
} ItemType,
]) ItemCount
}
]
})
}); });
req.done(() => { req.done(() => {
updateInventory(); updateInventory();
@ -2384,13 +2501,16 @@ function fundGuildTechProject(uniqueName) {
}); });
} }
function dispatchAddVaultDecoRecipesBatch(requests) { function dispatchAddVaultItemsBatch(requests, vaultType) {
return new Promise(resolve => { return new Promise(resolve => {
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
const req = $.post({ const req = $.post({
url: "/custom/addVaultDecoRecipe?" + window.authz + "&guildId=" + window.guildId, url: "/custom/addVaultItems?" + window.authz + "&guildId=" + window.guildId,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify(requests) data: JSON.stringify({
vaultType,
items: requests
})
}); });
req.done(() => { req.done(() => {
updateInventory(); updateInventory();
@ -2400,20 +2520,23 @@ function dispatchAddVaultDecoRecipesBatch(requests) {
}); });
} }
function addMissingVaultDecoRecipes() { function addMissingVaultItems(vaultType) {
const requests = []; const requests = [];
document.querySelectorAll("#datalist-VaultDecoRecipes" + " option").forEach(elm => { document.querySelectorAll(`#datalist-${vaultType} option`).forEach(elm => {
if (!document.querySelector("#VaultDecoRecipes-list [data-item-type='" + elm.getAttribute("data-key") + "']")) { const datalist = vaultType === "VaultShipDecorations" ? "ShipDecorations" : vaultType;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: 1 }); if (!document.querySelector(`#${datalist}-list [data-item-type='${elm.getAttribute("data-key")}']`)) {
let ItemCount = 1;
if (category == "VaultShipDecorations") ItemCount = 999999;
requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount });
} }
}); });
if ( if (
requests.length != 0 && requests.length != 0 &&
window.confirm(loc("code_addDecoRecipesConfirm").split("|COUNT|").join(requests.length)) window.confirm(loc("code_addVaultItemsConfirm").split("|COUNT|").join(requests.length))
) { ) {
return dispatchAddVaultDecoRecipesBatch(requests); return dispatchAddVaultItemsBatch(requests, vaultType);
} }
} }
@ -2844,8 +2967,8 @@ function removeCountItems(uniqueName, count) {
function addItemByItemType() { function addItemByItemType() {
const ItemType = document.getElementById("typeName-type").value; const ItemType = document.getElementById("typeName-type").value;
// Must start with "/Lotus/", contain only AZ letters, no "//", and not end with "/" // Must start with "/Lotus/", contain only letters AZ, digits 09, no "//", and not end with "/"
if (!ItemType || !/^\/Lotus\/(?:[A-Za-z]+(?:\/[A-Za-z]+)*)$/.test(ItemType)) { if (!ItemType || !/^\/Lotus\/(?:[A-Za-z0-9]+(?:\/[A-Za-z0-9]+)*)$/.test(ItemType)) {
$("#typeName-type").addClass("is-invalid").focus(); $("#typeName-type").addClass("is-invalid").focus();
return; return;
} }
@ -3162,13 +3285,13 @@ function unlockFocusSchool(upgradeType) {
return new Promise(resolve => { return new Promise(resolve => {
// Deselect current FocusAbility so we will be able to unlock the way for free // Deselect current FocusAbility so we will be able to unlock the way for free
$.post({ $.post({
url: "/api/focus.php?" + window.authz + "&op=5", url: "/api/focus.php?" + window.authz + "&op=ActivateWay",
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify({ FocusType: null }) data: JSON.stringify({ FocusType: null })
}).done(function () { }).done(function () {
// Unlock the way now // Unlock the way now
$.post({ $.post({
url: "/api/focus.php?" + window.authz + "&op=2", url: "/api/focus.php?" + window.authz + "&op=UnlockWay",
contentType: "text/plain", contentType: "text/plain",
data: JSON.stringify({ data: JSON.stringify({
FocusType: upgradeType FocusType: upgradeType
@ -3195,13 +3318,16 @@ function doIntrinsicsUnlockAll() {
document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => { document.querySelectorAll("#account-cheats input[type=checkbox]").forEach(elm => {
elm.onchange = function () { elm.onchange = function () {
revalidateAuthz().then(() => { revalidateAuthz().then(() => {
const value = elm.checked;
$.post({ $.post({
url: "/custom/setAccountCheat?" + window.authz, url: "/custom/setAccountCheat?" + window.authz,
contentType: "application/json", contentType: "application/json",
data: JSON.stringify({ data: JSON.stringify({
key: elm.id, key: elm.id,
value: elm.checked value: value
}) })
}).done(() => {
elm.checked = value;
}); });
}); });
}; };
@ -3237,6 +3363,8 @@ document.querySelectorAll("#account-cheats .input-group").forEach(grp => {
key: input.id, key: input.id,
value: parseInt(value) value: parseInt(value)
}) })
}).done(() => {
btn.value = value;
}); });
}); });
}; };
@ -3362,6 +3490,12 @@ single.getRoute("#guild-route").on("beforeload", function () {
document.getElementById("VaultDecoRecipes-list").innerHTML = ""; document.getElementById("VaultDecoRecipes-list").innerHTML = "";
document.getElementById("vaultDecoRecipes-form").classList.add("d-none"); document.getElementById("vaultDecoRecipes-form").classList.add("d-none");
document.getElementById("acquire-type-VaultDecoRecipes").value = ""; document.getElementById("acquire-type-VaultDecoRecipes").value = "";
document.getElementById("VaultMiscItems-list").innerHTML = "";
document.getElementById("vaultMiscItems-form").classList.add("d-none");
document.getElementById("acquire-type-VaultMiscItems").value = "";
document.getElementById("VaultShipDecorations-list").innerHTML = "";
document.getElementById("vaultShipDecorations-form").classList.add("d-none");
document.getElementById("acquire-type-VaultShipDecorations").value = "";
document.getElementById("Alliance-list").innerHTML = ""; document.getElementById("Alliance-list").innerHTML = "";
document.getElementById("guildView-alliance").textContent = ""; document.getElementById("guildView-alliance").textContent = "";
document.getElementById("Members-list").innerHTML = ""; document.getElementById("Members-list").innerHTML = "";

View File

@ -1,17 +1,17 @@
// German translation by Animan8000 // German translation by Animan8000
dict = { dict = {
general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`, general_inventoryUpdateNote: `Hinweis: Um Änderungen im Spiel zu sehen, musst du dein Inventar neu synchronisieren, z. B. mit dem /sync Befehl des Bootstrappers im Spielchat, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
general_inventoryUpdateNoteGameWs: `[UNTRANSLATED] Note: You may need to reopen any menu you are on for changes to be reflected.`, general_inventoryUpdateNoteGameWs: `Hinweis: Möglicherweise musst du ein Menü neu öffnen, damit die Änderungen sichtbar werden.`,
general_addButton: `Hinzufügen`, general_addButton: `Hinzufügen`,
general_setButton: `Festlegen`, general_setButton: `Festlegen`,
general_none: `Keines`, general_none: `Nichts`,
general_bulkActions: `Massenaktionen`, general_bulkActions: `Massenaktionen`,
general_loading: `Lädt...`, general_loading: `Lädt...`,
code_loginFail: `Anmeldung fehlgeschlagen. Bitte überprüfe deine Angaben.`, code_loginFail: `Anmeldung fehlgeschlagen. Bitte überprüfe deine Angaben.`,
code_regFail: `Registrierung fehlgeschlagen. Account existiert bereits?`, code_regFail: `Registrierung fehlgeschlagen. Account existiert bereits?`,
code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`, code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`,
code_changeNameRetry: `[UNTRANSLATED] |NAME| is already taken.`, code_changeNameRetry: `|NAME| ist bereits vergeben.`,
code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`, code_deleteAccountConfirm: `Bist du sicher, dass du deinen Account |DISPLAYNAME| (|EMAIL|) löschen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.`,
code_archgun: `Arch-Gewehr`, code_archgun: `Arch-Gewehr`,
code_melee: `Nahkampf`, code_melee: `Nahkampf`,
@ -32,8 +32,8 @@ dict = {
code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`, code_renamePrompt: `Neuen benutzerdefinierten Namen eingeben:`,
code_remove: `Entfernen`, code_remove: `Entfernen`,
code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`, code_addItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Account hinzufügen möchtest?`,
code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, code_addTechProjectsConfirm: `Bist du sicher, dass du |COUNT| Forschungen zu deinem Clan hinzufügen möchtest?`,
code_addDecoRecipesConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| deco recipes to your clan?`, code_addVaultItemsConfirm: `Bist du sicher, dass du |COUNT| Gegenstände zu deinem Clan-Tresor hinzufügen möchtest?`,
code_succRankUp: `Erfolgreich aufgestiegen.`, code_succRankUp: `Erfolgreich aufgestiegen.`,
code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`, code_noEquipmentToRankUp: `Keine Ausstattung zum Rangaufstieg verfügbar.`,
code_succAdded: `Erfolgreich hinzugefügt.`, code_succAdded: `Erfolgreich hinzugefügt.`,
@ -45,10 +45,10 @@ dict = {
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `Rang erhöhen`, code_rankUp: `Rang erhöhen`,
code_rankDown: `Rang verringern`, code_rankDown: `Rang verringern`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, code_unlockLevelCap: `Level-Cap freischalten`,
code_count: `Anzahl`, code_count: `Anzahl`,
code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden.`,
code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`, code_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`,
code_succImport: `Erfolgreich importiert.`, code_succImport: `Erfolgreich importiert.`,
code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`, code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`,
@ -65,18 +65,22 @@ dict = {
code_completed: `Abgeschlossen`, code_completed: `Abgeschlossen`,
code_active: `Aktiv`, code_active: `Aktiv`,
code_pigment: `Pigment`, code_pigment: `Pigment`,
code_controller: `[UNTRANSLATED] Controller cursor`, code_controller: `Controller-Cursor`,
code_mouseLine: `[UNTRANSLATED] Line cursor`, code_mouseLine: `Linien-Cursor`,
code_mouse: `[UNTRANSLATED] Cursor`, code_mouse: `Cursor`,
code_itemColorPalette: `|ITEM| Farbpalette`, code_itemColorPalette: `|ITEM| Farbpalette`,
code_mature: `Für den Kampf auswachsen lassen`, code_mature: `Für den Kampf auswachsen lassen`,
code_unmature: `Genetisches Altern zurücksetzen`, code_unmature: `Genetisches Altern zurücksetzen`,
code_fund: `[UNTRANSLATED] Fund`, code_fund: `Spenden`,
code_funded: `[UNTRANSLATED] Funded`, code_funded: `Gespendet`,
code_replays: `[UNTRANSLATED] Replays`, code_replays: `Wiederholungen`,
code_stalker: `Stalker`, 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_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, code_requiredInvigorationUpgrade: `Du musst sowohl ein Offensiv- als auch ein Support-Upgrade auswählen.`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`, 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_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`, login_passwordLabel: `Passwort`,
@ -92,8 +96,8 @@ dict = {
navbar_cheats: `Cheats`, navbar_cheats: `Cheats`,
navbar_import: `Importieren`, navbar_import: `Importieren`,
inventory_addItems: `Gegenstände hinzufügen`, inventory_addItems: `Gegenstände hinzufügen`,
inventory_addItemByItemType: `[UNTRANSLATED] Raw`, inventory_addItemByItemType: `Roh`,
inventory_addItemByItemType_warning: `[UNTRANSLATED] Use this feature at your own risk. It may break your inventory, and you will need to remove items manually if something goes wrong.`, inventory_addItemByItemType_warning: `Verwende diese Funktion auf eigene Gefahr. Sie kann dein Inventar beschädigen und du musst Gegenstände manuell entfernen, falls etwas schiefgeht.`,
inventory_suits: `Warframes`, inventory_suits: `Warframes`,
inventory_longGuns: `Primärwaffen`, inventory_longGuns: `Primärwaffen`,
inventory_pistols: `Sekundärwaffen`, inventory_pistols: `Sekundärwaffen`,
@ -112,15 +116,17 @@ dict = {
inventory_boosters: `Booster`, inventory_boosters: `Booster`,
inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`, inventory_flavourItems: `<abbr title="Animationssets, Glyphen, Farbpaletten usw.">Sammlerstücke</abbr>`,
inventory_shipDecorations: `Schiffsdekorationen`, inventory_shipDecorations: `Schiffsdekorationen`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`, inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`, inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`, inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `Fehlende Sammlerstücke hinzufügen`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `Fehlende Schiffsdekorationen hinzufügen`,
inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`, inventory_bulkAddEvolutionProgress: `Fehlende Incarnon-Entwicklungsfortschritte hinzufügen`,
inventory_bulkAddWeaponSkins: `Fehlende Skins hinzufügen`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
@ -129,6 +135,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`, inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
inventory_maxPlexus: `Plexus auf Max. Rang`, inventory_maxPlexus: `Plexus auf Max. Rang`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`, quests_completeAll: `Alle Quests abschließen`,
@ -173,8 +180,8 @@ dict = {
invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`, invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`,
detailedView_invigorationOffensiveLabel: `Offensives Upgrade`, detailedView_invigorationOffensiveLabel: `Offensives Upgrade`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`, detailedView_invigorationUtilityLabel: `Support Upgrade`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`, detailedView_invigorationExpiryLabel: `Kräftigungs-Ablaufdatum (optional)`,
abilityOverride_label: `Fähigkeitsüberschreibung`, abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`, abilityOverride_onSlot: `auf Slot`,
@ -193,7 +200,7 @@ dict = {
cheats_skipTutorial: `Tutorial überspringen`, cheats_skipTutorial: `Tutorial überspringen`,
cheats_skipAllDialogue: `Alle Dialoge überspringen`, cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`, cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockSuccRelog: `[UNTRANSLATED] Success. Please note that you'll need to relog for the client to refresh this.`, cheats_unlockSuccRelog: `Erfolgreich. Bitte beachte, dass du dich neu einloggen musst, damit der Client dies aktualisiert.`,
cheats_unlockAllMissions: `Alle Missionen freischalten`, 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_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`, cheats_infiniteCredits: `Unendlich Credits`,
@ -227,7 +234,7 @@ dict = {
cheats_baroFullyStocked: `Baro hat volles Inventar`, cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`, cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`, 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 in game chat, visiting a dojo/relay, or relogging.`, cheats_unlockSuccInventory: `Erfolgreich. Bitte beachte, dass du dein Inventar neu synchronisieren musst, z. B. mit dem /sync Befehl des Bootstrappers im Spielchat, durch Besuch eines Dojo/Relais oder durch erneutes Einloggen.`,
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`, cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`, cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`, cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -256,47 +263,49 @@ dict = {
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`, cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`, cheats_changeButton: `Ändern`,
cheats_markAllAsRead: `Posteingang als gelesen markieren`, cheats_markAllAsRead: `Posteingang als gelesen markieren`,
cheats_finishInvasionsInOneMission: `[UNTRANSLATED] Finish Invasions in One Mission`, cheats_finishInvasionsInOneMission: `Invasionen in einer Mission abschließen`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`, cheats_nemesisHenchmenKillsMultiplierGrineer: `Wut-Fortschrittsmultiplikator (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`, cheats_nemesisHenchmenKillsMultiplierCorpus: `Wut-Fortschrittsmultiplikator (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`, cheats_nemesisAntivirusGainMultiplier: `Antivirus-Fortschrittsmultiplikator`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`, cheats_nemesisHintProgressMultiplierGrineer: `Hinweis-Fortschrittsmultiplikator (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`, cheats_nemesisHintProgressMultiplierCorpus: `Hinweis-Fortschrittsmultiplikator (Corpus)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`, cheats_nemesisExtraWeapon: `Zusätzliche Nemesis-Waffe/-Symbol bei Besiegung (0 zum deaktivieren)`,
worldState: `Weltstatus`, worldState: `Weltstatus`,
worldState_creditBoost: `Event Booster: Credit`, worldState_creditBoost: `Event Booster: Credit`,
worldState_affinityBoost: `Event Booster: Erfahrung`, worldState_affinityBoost: `Event Booster: Erfahrung`,
worldState_resourceBoost: `Event Booster: Ressourcen`, worldState_resourceBoost: `Event Booster: Ressourcen`,
worldState_tennoLiveRelay: `TennoLive Relais`, worldState_tennoLiveRelay: `TennoLive Relais`,
worldState_baroTennoConRelay: `Baros TennoCon Relais`, worldState_baroTennoConRelay: `Baros TennoCon-Relais`,
worldState_starDays: `Sternen-Tage`, worldState_starDays: `Sternen-Tage`,
worldState_galleonOfGhouls: `Galeone der Ghule`, worldState_galleonOfGhouls: `Galeone der Ghule`,
worldState_anniversary: `[UNTRANSLATED] Warframe Anniversary`, worldState_anniversary: `Warframe Jubiläum`,
worldState_useAnniversaryTagForOldGoals: `[UNTRANSLATED] Use <code>Tag</code> from Warframe Anniversary for old Events`, worldState_useAnniversaryTagForOldGoals: `Verwende <code>Tag</code> vom Warframe Jubiläum für alte Events`,
worldState_ghoulEmergence: `Ghul Ausrottung`, worldState_ghoulEmergence: `Ghul Ausrottung`,
worldState_plagueStar: `Plagenstern`, worldState_plagueStar: `Plagenstern`,
worldState_dogDays: `Hitzefrei`, worldState_dogDays: `Hitzefrei`,
worldState_dogDaysRewards: `[UNTRANSLATED] Dog Days Rewards`, worldState_dogDaysRewards: `Hitzefrei-Belohnungen`,
worldState_wolfHunt: `Wolfsjagd (2025)`, worldState_wolfHunt: `Wolfsjagd`,
worldState_voidCorruption: `Void-Korruption (|VAL|)`,
worldState_orphixVenom: `Orphix Gift`, worldState_orphixVenom: `Orphix Gift`,
worldState_longShadow: `Lange Schatten`, worldState_longShadow: `Lange Schatten`,
worldState_hallowedFlame: `Geweihte Flamme`, worldState_hallowedFlame: `Geweihte Flamme`,
worldState_hallowedNightmares: `Geweihte Albträume`, worldState_hallowedNightmares: `Geweihte Albträume`,
worldState_hallowedNightmaresRewards: `[UNTRANSLATED] Hallowed Nightmares Rewards`, worldState_hallowedNightmaresRewards: `Geweihte Albträume-Belohnungen`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`, worldState_naberusNights: `Naberus Nacht`,
worldState_proxyRebellion: `Proxy-Rebellion`, worldState_proxyRebellion: `Proxy-Rebellion`,
worldState_proxyRebellionRewards: `[UNTRANSLATED] Proxy Rebellion Rewards`, worldState_proxyRebellionRewards: `Proxy-Rebellion-Belohnungen`,
worldState_bellyOfTheBeast: `Das Innere der Bestie`, worldState_bellyOfTheBeast: `Das Innere der Bestie`,
worldState_bellyOfTheBeastProgressOverride: `[UNTRANSLATED] Belly of the Beast Progress`, worldState_bellyOfTheBeastProgressOverride: `Das Innere der Bestie-Fortschritt`,
worldState_eightClaw: `Acht Klauen`, worldState_eightClaw: `Acht Klauen`,
worldState_eightClawProgressOverride: `[UNTRANSLATED] Eight Claw Progress`, worldState_eightClawProgressOverride: `Acht Klauen-Fortschritt`,
worldState_thermiaFractures: `Thermische Risse`, worldState_thermiaFractures: `Thermische Risse`,
worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`, worldState_thermiaFracturesProgressOverride: `Thermische Risse-Fortschritt`,
worldState_from_year: `[UNTRANSLATED] from |VAL|`, worldState_qtccAlerts: `Quest zum Kampf gegen Krebs-Alarmierungen`,
worldState_pre_year: `[UNTRANSLATED] pre |VAL|`, worldState_from_year: `Von |VAL|`,
worldState_week: `[UNTRANSLATED] Week |VAL|`, worldState_pre_year: `Vor |VAL|`,
worldState_incompatibleWith: `[UNTRANSLATED] Incompatible with:`, worldState_week: `Woche |VAL|`,
worldState_incompatibleWith: `Inkompatibel mit:`,
enabled: `Aktiviert`, enabled: `Aktiviert`,
disabled: `Deaktiviert`, disabled: `Deaktiviert`,
worldState_we1: `Wochenende 1`, worldState_we1: `Wochenende 1`,
@ -340,7 +349,7 @@ dict = {
worldState_varziaFullyStocked: `Varzia hat volles Inventar`, worldState_varziaFullyStocked: `Varzia hat volles Inventar`,
worldState_varziaOverride: `Varzia-Angebotsüberschreibung`, worldState_varziaOverride: `Varzia-Angebotsüberschreibung`,
import_importNote: `[UNTRANSLATED] You can provide a full or partial <code>inventory.php</code> or <code>getShip.php</code> response (client representation) here.`, import_importNote: `Du kannst hier eine vollständige oder teilweise <code>inventory.php</code> oder <code>getShip.php</code> Antwort (Client-Darstellung) einfügen.`,
import_importNote2: `Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`, import_importNote2: `Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
import_submit: `Absenden`, import_submit: `Absenden`,
import_samples: `Beispiele:`, import_samples: `Beispiele:`,
@ -408,9 +417,11 @@ dict = {
theme_dark: `Dunkles Design`, theme_dark: `Dunkles Design`,
theme_light: `Helles Design`, theme_light: `Helles Design`,
guildView_cheats: `[UNTRANSLATED] Clan Cheats`, guildView_cheats: `Clan-Cheats`,
guildView_techProjects: `Forschung`, guildView_techProjects: `Forschung`,
guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, guildView_vaultDecoRecipes: `Dojo-Deko-Baupläne`,
guildView_vaultMiscItems: `Ressourcen im Tresor`,
guildView_vaultShipDecorations: `Dekorationen im Tresor`,
guildView_alliance: `Allianz`, guildView_alliance: `Allianz`,
guildView_members: `Mitglieder`, guildView_members: `Mitglieder`,
guildView_pending: `Ausstehend`, guildView_pending: `Ausstehend`,
@ -430,11 +441,12 @@ dict = {
guildView_rank_soldier: `Soldat`, guildView_rank_soldier: `Soldat`,
guildView_rank_utility: `Versorger`, guildView_rank_utility: `Versorger`,
guildView_rank_warlord: `Kriegsherr`, guildView_rank_warlord: `Kriegsherr`,
guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, guildView_currency_owned: `|COUNT| im Tresor.`,
guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, guildView_bulkAddTechProjects: `Fehlende Forschungen hinzufügen`,
guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, guildView_bulkAddVaultDecoRecipes: `Fehlende Dojo-Deko-Baupläne hinzufügen`,
guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, guildView_bulkAddVaultShipDecorations: `Fehlende Dekorationen im Tresor hinzufügen`,
guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, guildView_bulkFundTechProjects: `Alle Forschungen spenden`,
guildView_bulkCompleteTechProjects: `Alle Forschungen abschließen`,
guildView_promote: `Befördern`, guildView_promote: `Befördern`,
guildView_demote: `Degradieren`, guildView_demote: `Degradieren`,

View File

@ -32,7 +32,7 @@ dict = {
code_remove: `Remove`, code_remove: `Remove`,
code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`, code_addItemsConfirm: `Are you sure you want to add |COUNT| items to your account?`,
code_addTechProjectsConfirm: `Are you sure you want to add |COUNT| research to your clan?`, code_addTechProjectsConfirm: `Are you sure you want to add |COUNT| research to your clan?`,
code_addDecoRecipesConfirm: `Are you sure you want to add |COUNT| deco recipes to your clan?`, code_addVaultItemsConfirm: `Are you sure you want to add |COUNT| items to your clan vault?`,
code_succRankUp: `Successfully ranked up.`, code_succRankUp: `Successfully ranked up.`,
code_noEquipmentToRankUp: `No equipment to rank up.`, code_noEquipmentToRankUp: `No equipment to rank up.`,
code_succAdded: `Successfully added.`, code_succAdded: `Successfully added.`,
@ -47,7 +47,7 @@ dict = {
code_unlockLevelCap: `Unlock level cap`, code_unlockLevelCap: `Unlock level cap`,
code_count: `Count`, code_count: `Count`,
code_focusAllUnlocked: `All focus schools are already unlocked.`, code_focusAllUnlocked: `All focus schools are already unlocked.`,
code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game.`,
code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`, code_addModsConfirm: `Are you sure you want to add |COUNT| mods to your account?`,
code_succImport: `Successfully imported.`, code_succImport: `Successfully imported.`,
code_succRelog: `Done. Please note that you'll need to relog to see a difference in-game.`, code_succRelog: `Done. Please note that you'll need to relog to see a difference in-game.`,
@ -74,6 +74,10 @@ dict = {
code_funded: `Funded`, code_funded: `Funded`,
code_replays: `Replays`, code_replays: `Replays`,
code_stalker: `Stalker`, 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_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & utility 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_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
@ -111,6 +115,7 @@ dict = {
inventory_boosters: `Boosters`, inventory_boosters: `Boosters`,
inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`, inventory_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Flavour Items</abbr>`,
inventory_shipDecorations: `Ship Decorations`, inventory_shipDecorations: `Ship Decorations`,
inventory_weaponSkins: `Skins`,
inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`, inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`, inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
@ -120,6 +125,7 @@ dict = {
inventory_bulkAddFlavourItems: `Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`, inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
inventory_bulkAddWeaponSkins: `Add Missing Skins`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`, inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`, inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
@ -128,6 +134,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`, inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
inventory_maxPlexus: `Max Rank Plexus`, inventory_maxPlexus: `Max Rank Plexus`,
inventory_removeIsNew: `Remove New Equipment Exclamation Icon`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Complete All Quests`, quests_completeAll: `Complete All Quests`,
@ -277,7 +284,8 @@ dict = {
worldState_plagueStar: `Plague Star`, worldState_plagueStar: `Plague Star`,
worldState_dogDays: `Dog Days`, worldState_dogDays: `Dog Days`,
worldState_dogDaysRewards: `Dog Days Rewards`, worldState_dogDaysRewards: `Dog Days Rewards`,
worldState_wolfHunt: `Wolf Hunt (2025)`, worldState_wolfHunt: `Wolf Hunt`,
worldState_voidCorruption: `Void Corruption (|VAL|)`,
worldState_orphixVenom: `Orphix Venom`, worldState_orphixVenom: `Orphix Venom`,
worldState_longShadow: `Long Shadow`, worldState_longShadow: `Long Shadow`,
worldState_hallowedFlame: `Hallowed Flame`, worldState_hallowedFlame: `Hallowed Flame`,
@ -292,8 +300,9 @@ dict = {
worldState_eightClawProgressOverride: `Eight Claw Progress`, worldState_eightClawProgressOverride: `Eight Claw Progress`,
worldState_thermiaFractures: `Thermia Fractures`, worldState_thermiaFractures: `Thermia Fractures`,
worldState_thermiaFracturesProgressOverride: `Thermia Fractures Progress`, worldState_thermiaFracturesProgressOverride: `Thermia Fractures Progress`,
worldState_from_year: `from |VAL|`, worldState_qtccAlerts: `Quest to Conquer Cancer Alerts`,
worldState_pre_year: `pre |VAL|`, worldState_from_year: `From |VAL|`,
worldState_pre_year: `Pre-|VAL|`,
worldState_week: `Week |VAL|`, worldState_week: `Week |VAL|`,
worldState_incompatibleWith: `Incompatible with:`, worldState_incompatibleWith: `Incompatible with:`,
enabled: `Enabled`, enabled: `Enabled`,
@ -410,6 +419,8 @@ dict = {
guildView_cheats: `Clan Cheats`, guildView_cheats: `Clan Cheats`,
guildView_techProjects: `Research`, guildView_techProjects: `Research`,
guildView_vaultDecoRecipes: `Dojo Deco Recipes`, guildView_vaultDecoRecipes: `Dojo Deco Recipes`,
guildView_vaultMiscItems: `Resources in Vault`,
guildView_vaultShipDecorations: `Decorations in Vault`,
guildView_alliance: `Alliance`, guildView_alliance: `Alliance`,
guildView_members: `Members`, guildView_members: `Members`,
guildView_pending: `Pending`, guildView_pending: `Pending`,
@ -432,6 +443,7 @@ dict = {
guildView_currency_owned: `|COUNT| in Vault.`, guildView_currency_owned: `|COUNT| in Vault.`,
guildView_bulkAddTechProjects: `Add Missing Research`, guildView_bulkAddTechProjects: `Add Missing Research`,
guildView_bulkAddVaultDecoRecipes: `Add Missing Dojo Deco Recipes`, guildView_bulkAddVaultDecoRecipes: `Add Missing Dojo Deco Recipes`,
guildView_bulkAddVaultShipDecorations: `Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `Fund All Research`, guildView_bulkFundTechProjects: `Fund All Research`,
guildView_bulkCompleteTechProjects: `Complete All Research`, guildView_bulkCompleteTechProjects: `Complete All Research`,
guildView_promote: `Promote`, guildView_promote: `Promote`,

View File

@ -33,7 +33,7 @@ dict = {
code_remove: `Quitar`, code_remove: `Quitar`,
code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`, code_addItemsConfirm: `¿Estás seguro de que deseas agregar |COUNT| objetos a tu cuenta?`,
code_addTechProjectsConfirm: `¿Estás seguro de que quieres añadir |COUNT| proyectos de investigación a tu 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_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`,
code_succRankUp: `Ascenso exitoso.`, code_succRankUp: `Ascenso exitoso.`,
code_noEquipmentToRankUp: `No hay equipo para ascender.`, code_noEquipmentToRankUp: `No hay equipo para ascender.`,
code_succAdded: `Agregado exitosamente.`, code_succAdded: `Agregado exitosamente.`,
@ -48,7 +48,7 @@ dict = {
code_unlockLevelCap: `Desbloquear level cap`, code_unlockLevelCap: `Desbloquear level cap`,
code_count: `Cantidad`, code_count: `Cantidad`,
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, 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.`, code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego.`,
code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`, code_addModsConfirm: `¿Estás seguro de que deseas agregar |COUNT| modificadores a tu cuenta?`,
code_succImport: `Importación exitosa.`, code_succImport: `Importación exitosa.`,
code_succRelog: `Hecho. Ten en cuenta que deberás volver a iniciar sesión para ver los cambios en el juego.`, code_succRelog: `Hecho. Ten en cuenta que deberás volver a iniciar sesión para ver los cambios en el juego.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Financiado`, code_funded: `Financiado`,
code_replays: `Repeticiones`, code_replays: `Repeticiones`,
code_stalker: `Stalker`, 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_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una mejora de utilidad.`, 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_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Potenciadores`, inventory_boosters: `Potenciadores`,
inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`, inventory_flavourItems: `<abbr title="Conjuntos de animaciones, glifos, paletas, etc.">Ítems estéticos</abbr>`,
inventory_shipDecorations: `Decoraciones de nave`, inventory_shipDecorations: `Decoraciones de nave`,
inventory_weaponSkins: `Diseños`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`, inventory_bulkAddFlavourItems: `Añadir items estéticos faltantes`,
inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`, inventory_bulkAddShipDecorations: `Añadir decoraciones de Nave Faltantes`,
inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`, 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_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`, inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`, inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
@ -129,6 +135,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`, inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
inventory_maxPlexus: `Rango máximo de Plexus`, inventory_maxPlexus: `Rango máximo de Plexus`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
quests_list: `Misiones`, quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`, quests_completeAll: `Completar todas las misiones`,
@ -278,7 +285,8 @@ dict = {
worldState_plagueStar: `Estrella Infestada`, worldState_plagueStar: `Estrella Infestada`,
worldState_dogDays: `Canícula`, worldState_dogDays: `Canícula`,
worldState_dogDaysRewards: `Recompensas de Canícula`, worldState_dogDaysRewards: `Recompensas de Canícula`,
worldState_wolfHunt: `Cacería del Lobo (2025)`, worldState_wolfHunt: `Cacería del Lobo`,
worldState_voidCorruption: `Corrupción del Vacío (|VAL|)`,
worldState_orphixVenom: `Veneno de Orphix`, worldState_orphixVenom: `Veneno de Orphix`,
worldState_longShadow: `Sombra Prolongada`, worldState_longShadow: `Sombra Prolongada`,
worldState_hallowedFlame: `Llama Sagrada`, worldState_hallowedFlame: `Llama Sagrada`,
@ -293,8 +301,9 @@ dict = {
worldState_eightClawProgressOverride: `Progreso de Octava Garra`, worldState_eightClawProgressOverride: `Progreso de Octava Garra`,
worldState_thermiaFractures: `Fracturas Thermia`, worldState_thermiaFractures: `Fracturas Thermia`,
worldState_thermiaFracturesProgressOverride: `Progreso de Fracturas Thermia`, worldState_thermiaFracturesProgressOverride: `Progreso de Fracturas Thermia`,
worldState_from_year: `de |VAL|`, worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_pre_year: `antes de |VAL|`, worldState_from_year: `De |VAL|`,
worldState_pre_year: `Antes de |VAL|`,
worldState_week: `Semana |VAL|`, worldState_week: `Semana |VAL|`,
worldState_incompatibleWith: `No compatible con:`, worldState_incompatibleWith: `No compatible con:`,
enabled: `Activado`, enabled: `Activado`,
@ -411,6 +420,8 @@ dict = {
guildView_cheats: `Trucos de Clan`, guildView_cheats: `Trucos de Clan`,
guildView_techProjects: `Investigación`, guildView_techProjects: `Investigación`,
guildView_vaultDecoRecipes: `Planos de Decoración de Dojo`, guildView_vaultDecoRecipes: `Planos de Decoración de Dojo`,
guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`,
guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`,
guildView_alliance: `Alianza`, guildView_alliance: `Alianza`,
guildView_members: `Miembros`, guildView_members: `Miembros`,
guildView_pending: `Pendiente`, guildView_pending: `Pendiente`,
@ -433,6 +444,7 @@ dict = {
guildView_currency_owned: `|COUNT| en la Bóveda.`, guildView_currency_owned: `|COUNT| en la Bóveda.`,
guildView_bulkAddTechProjects: `Añadir proyectos de Investigación Faltantes`, guildView_bulkAddTechProjects: `Añadir proyectos de Investigación Faltantes`,
guildView_bulkAddVaultDecoRecipes: `Añadir planos de Decoración de Dojo Faltantes`, guildView_bulkAddVaultDecoRecipes: `Añadir planos de Decoración de Dojo Faltantes`,
guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `Financiar toda la Investigación`, guildView_bulkFundTechProjects: `Financiar toda la Investigación`,
guildView_bulkCompleteTechProjects: `Completar toda la Investigación`, guildView_bulkCompleteTechProjects: `Completar toda la Investigación`,
guildView_promote: `Promover`, guildView_promote: `Promover`,

View File

@ -33,7 +33,7 @@ dict = {
code_remove: `Retirer`, code_remove: `Retirer`,
code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`, code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`,
code_addTechProjectsConfirm: `Ajouter |COUNT| recherches au clan ?`, code_addTechProjectsConfirm: `Ajouter |COUNT| recherches au clan ?`,
code_addDecoRecipesConfirm: `Ajouter |COUNT| décorations au clan ?`, code_addVaultItemsConfirm: `Ajouter |COUNT| objets au coffre de clan ?`,
code_succRankUp: `Montée de niveau effectuée.`, code_succRankUp: `Montée de niveau effectuée.`,
code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`, code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`,
code_succAdded: `Ajouté.`, code_succAdded: `Ajouté.`,
@ -45,7 +45,7 @@ dict = {
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `Monter de rang`, code_rankUp: `Monter de rang`,
code_rankDown: `Baisser de rang`, code_rankDown: `Baisser de rang`,
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, code_unlockLevelCap: `Débloquer le level cap (9999)`,
code_count: `Quantité`, code_count: `Quantité`,
code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
@ -65,18 +65,22 @@ dict = {
code_completed: `Complétée`, code_completed: `Complétée`,
code_active: `Active`, code_active: `Active`,
code_pigment: `Pigment`, code_pigment: `Pigment`,
code_controller: `[UNTRANSLATED] Controller cursor`, code_controller: `Curseur manette`,
code_mouseLine: `[UNTRANSLATED] Line cursor`, code_mouseLine: `Curseur linéaire`,
code_mouse: `[UNTRANSLATED] Cursor`, code_mouse: `Curseur`,
code_itemColorPalette: `Palette de couleurs |ITEM|`, code_itemColorPalette: `Palette de couleurs |ITEM|`,
code_mature: `Maturer pour le combat`, code_mature: `Maturer pour le combat`,
code_unmature: `Régrésser l'âge génétique`, code_unmature: `Régrésser l'âge génétique`,
code_fund: `Financer`, code_fund: `Financer`,
code_funded: `Complété`, code_funded: `Complété`,
code_replays: `[UNTRANSLATED] Replays`, code_replays: `Rejouée`,
code_stalker: `Stalker`, code_stalker: `Stalker`,
code_cutName: `Coupe |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_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, code_requiredInvigorationUpgrade: `Invigoration offensive et défensive requises.`,
login_description: `Connexion avec les informations de connexion OpenWF.`, login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`, login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`, login_passwordLabel: `Mot de passe`,
@ -110,17 +114,19 @@ dict = {
inventory_kubrowPets: `Bêtes`, inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `Progrès de l'évolution Incarnon`, 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_flavourItems: `<abbr title="Animation Sets, Glyphs, Palettes, etc.">Collectables</abbr>`,
inventory_shipDecorations: `Décorations du vaisseau`, inventory_shipDecorations: `Décorations du vaisseau`,
inventory_weaponSkins: `Aspects`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`, inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`, inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`, inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `Ajouter les collectables manquants ?`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `Ajouter les décorations de vaisseau manquantes`,
inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`, inventory_bulkAddEvolutionProgress: `Ajouter les évolutions Incarnon manquantes`,
inventory_bulkAddWeaponSkins: `Ajouter les skins manquants`,
inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`, inventory_bulkRankUpSuits: `Toutes les Warframes au rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes au rang max`, inventory_bulkRankUpWeapons: `Toutes les armes au rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`, inventory_bulkRankUpSpaceSuits: `Tous les Archwings au rang max`,
@ -129,6 +135,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles au rang max`,
inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`, inventory_bulkRankUpEvolutionProgress: `Toutes les évolutions Incarnon au rang max`,
inventory_maxPlexus: `Plexus au rang max`, inventory_maxPlexus: `Plexus au rang max`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
quests_list: `Quêtes`, quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`, quests_completeAll: `Compléter toutes les quêtes`,
@ -173,8 +180,8 @@ dict = {
invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`, invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`,
detailedView_invigorationOffensiveLabel: `Amélioration offensive`, detailedView_invigorationOffensiveLabel: `Amélioration offensive`,
detailedView_invigorationUtilityLabel: `[UNTRANSLATED] Utility Upgrade`, detailedView_invigorationUtilityLabel: `Amélioration défensive`,
detailedView_invigorationExpiryLabel: `[UNTRANSLATED] Invigoration Expiry (optional)`, detailedView_invigorationExpiryLabel: `Expiration de l'invigoration (optionnel)`,
abilityOverride_label: `Remplacement de pouvoir`, abilityOverride_label: `Remplacement de pouvoir`,
abilityOverride_onSlot: `Sur l'emplacement`, abilityOverride_onSlot: `Sur l'emplacement`,
@ -257,12 +264,12 @@ dict = {
cheats_changeButton: `Changer`, cheats_changeButton: `Changer`,
cheats_markAllAsRead: `Marquer la boîte de réception comme lue`, cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
cheats_finishInvasionsInOneMission: `Compléter les invasions en une mission.`, cheats_finishInvasionsInOneMission: `Compléter les invasions en une mission.`,
cheats_nemesisHenchmenKillsMultiplierGrineer: `[UNTRANSLATED] Rage Progess Multiplier (Grineer)`, cheats_nemesisHenchmenKillsMultiplierGrineer: `Multiplicateur de rage (Grineer)`,
cheats_nemesisHenchmenKillsMultiplierCorpus: `[UNTRANSLATED] Rage Progess Multiplier (Corpus)`, cheats_nemesisHenchmenKillsMultiplierCorpus: `Multiplicateur de rage (Corpus)`,
cheats_nemesisAntivirusGainMultiplier: `[UNTRANSLATED] Antivirus Progress Multiplier`, cheats_nemesisAntivirusGainMultiplier: `Multiplicateur de l'Antivirus`,
cheats_nemesisHintProgressMultiplierGrineer: `[UNTRANSLATED] Hint Progress Multiplier (Grineer)`, cheats_nemesisHintProgressMultiplierGrineer: `Multiplicateur d'indices (Grineer)`,
cheats_nemesisHintProgressMultiplierCorpus: `[UNTRANSLATED] Hint Progress Multiplier (Corpus)`, cheats_nemesisHintProgressMultiplierCorpus: `Multiplicateur d'indices (Corpus)`,
cheats_nemesisExtraWeapon: `[UNTRANSLATED] Extra Nemesis Weapon / Token On Vanquish (0 to disable)`, cheats_nemesisExtraWeapon: `Arme de Nemesis/jeton supplémentaire sur exécution (0 pour désactiver)`,
worldState: `Carte Solaire`, worldState: `Carte Solaire`,
worldState_creditBoost: `Booster de Crédit`, worldState_creditBoost: `Booster de Crédit`,
@ -278,13 +285,14 @@ dict = {
worldState_plagueStar: `Fléau Céleste`, worldState_plagueStar: `Fléau Céleste`,
worldState_dogDays: `Bataille d'Eau`, worldState_dogDays: `Bataille d'Eau`,
worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`, worldState_dogDaysRewards: `Récompenses de la Bataille d'Eau`,
worldState_wolfHunt: `Chasse au Loup (2025)`, worldState_wolfHunt: `Chasse au Loup`,
worldState_voidCorruption: `Corruption du Néant (|VAL|)`,
worldState_orphixVenom: `Venin Orphix`, worldState_orphixVenom: `Venin Orphix`,
worldState_longShadow: `La Propagation des Ombres`, worldState_longShadow: `La Propagation des Ombres`,
worldState_hallowedFlame: `Flamme Hantée`, worldState_hallowedFlame: `Flamme Hantée`,
worldState_hallowedNightmares: `Cauchemars Hantés`, worldState_hallowedNightmares: `Cauchemars Hantés`,
worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`, worldState_hallowedNightmaresRewards: `Récompenses Flamme Hantée Cauchemar`,
worldState_naberusNights: `[UNTRANSLATED] Nights of Naberus`, worldState_naberusNights: `Les Nuits de Naberus`,
worldState_proxyRebellion: `Rébellion Proxy`, worldState_proxyRebellion: `Rébellion Proxy`,
worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`, worldState_proxyRebellionRewards: `Récompenses Rébellion Proxy`,
worldState_bellyOfTheBeast: `Ventre de la Bête`, worldState_bellyOfTheBeast: `Ventre de la Bête`,
@ -293,8 +301,9 @@ dict = {
worldState_eightClawProgressOverride: `Progrès de la Huitième Griffe`, worldState_eightClawProgressOverride: `Progrès de la Huitième Griffe`,
worldState_thermiaFractures: `Crevasses Thermia`, worldState_thermiaFractures: `Crevasses Thermia`,
worldState_thermiaFracturesProgressOverride: `Progrès des Fractures Thermia`, worldState_thermiaFracturesProgressOverride: `Progrès des Fractures Thermia`,
worldState_from_year: `de |VAL|`, worldState_qtccAlerts: `Alertes Quête pour Vaincre le Cancer`,
worldState_pre_year: `pre-|VAL|`, worldState_from_year: `De |VAL|`,
worldState_pre_year: `Pre-|VAL|`,
worldState_week: `Semaine |VAL|`, worldState_week: `Semaine |VAL|`,
worldState_incompatibleWith: `Incompatible avec :`, worldState_incompatibleWith: `Incompatible avec :`,
enabled: `Activé`, enabled: `Activé`,
@ -408,9 +417,11 @@ dict = {
theme_dark: `Thème sombre`, theme_dark: `Thème sombre`,
theme_light: `Thème clair`, theme_light: `Thème clair`,
guildView_cheats: `[UNTRANSLATED] Clan Cheats`, guildView_cheats: `Clan`,
guildView_techProjects: `Recherche`, guildView_techProjects: `Recherche`,
guildView_vaultDecoRecipes: `Schémas de décorations de dojo`, guildView_vaultDecoRecipes: `Schémas de décorations de dojo`,
guildView_vaultMiscItems: `Ressources dans le coffre`,
guildView_vaultShipDecorations: `Décorations dans le coffre`,
guildView_alliance: `Alliance`, guildView_alliance: `Alliance`,
guildView_members: `Members`, guildView_members: `Members`,
guildView_pending: `En Attente`, guildView_pending: `En Attente`,
@ -433,6 +444,7 @@ dict = {
guildView_currency_owned: `|COUNT| dans le coffre.`, guildView_currency_owned: `|COUNT| dans le coffre.`,
guildView_bulkAddTechProjects: `Ajouter les recherches manquantes`, guildView_bulkAddTechProjects: `Ajouter les recherches manquantes`,
guildView_bulkAddVaultDecoRecipes: `Ajouter les schémas de décorations de dojo manquantes`, guildView_bulkAddVaultDecoRecipes: `Ajouter les schémas de décorations de dojo manquantes`,
guildView_bulkAddVaultShipDecorations: `Ajouter dans le coffre les décorations manquantes`,
guildView_bulkFundTechProjects: `Financer toutes les recherches`, guildView_bulkFundTechProjects: `Financer toutes les recherches`,
guildView_bulkCompleteTechProjects: `Compléter toutes les recherches`, guildView_bulkCompleteTechProjects: `Compléter toutes les recherches`,
guildView_promote: `Promouvoir`, guildView_promote: `Promouvoir`,

View File

@ -33,7 +33,7 @@ dict = {
code_remove: `Удалить`, code_remove: `Удалить`,
code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`, code_addItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов на ваш аккаунт?`,
code_addTechProjectsConfirm: `Вы уверены, что хотите добавить |COUNT| исследований в свой клан?`, code_addTechProjectsConfirm: `Вы уверены, что хотите добавить |COUNT| исследований в свой клан?`,
code_addDecoRecipesConfirm: `Вы уверены, что хотите добавить |COUNT| рецептов декораций в свой клан?`, code_addVaultItemsConfirm: `Вы уверены, что хотите добавить |COUNT| предметов в хранилище своего клана?`,
code_succRankUp: `Ранг успешно повышен.`, code_succRankUp: `Ранг успешно повышен.`,
code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`, code_noEquipmentToRankUp: `Нет снаряжения для повышения ранга.`,
code_succAdded: `Успешно добавлено.`, code_succAdded: `Успешно добавлено.`,
@ -48,7 +48,7 @@ dict = {
code_unlockLevelCap: `Разблокировать ограничение уровня`, code_unlockLevelCap: `Разблокировать ограничение уровня`,
code_count: `Количество`, code_count: `Количество`,
code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`, code_focusAllUnlocked: `Все школы Фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ Фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, code_focusUnlocked: `[UNTRANSLATED] Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game.`,
code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`, code_addModsConfirm: `Вы уверены, что хотите добавить |COUNT| модов на ваш аккаунт?`,
code_succImport: `Успешно импортировано.`, code_succImport: `Успешно импортировано.`,
code_succRelog: `Готово. Обратите внимание, что вам нужно будет перезайти, чтобы увидеть изменения в игре.`, code_succRelog: `Готово. Обратите внимание, что вам нужно будет перезайти, чтобы увидеть изменения в игре.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Профинансировано`, code_funded: `Профинансировано`,
code_replays: `Повторов`, code_replays: `Повторов`,
code_stalker: `Сталкер`, code_stalker: `Сталкер`,
code_cutName: `Причёска: |INDEX|`,
code_drifterBeardName: `Борода скитальца: |INDEX|`,
code_drifterFaceName: `Внешность скитальца: |INDEX|`,
code_operatorFaceName: `Внешность оператора: |INDEX|`,
code_succChange: `Успешно изменено.`, code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`, code_requiredInvigorationUpgrade: `Вы должны выбрать как атакующее, так и вспомогательное улучшение.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Бустеры`, inventory_boosters: `Бустеры`,
inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`, inventory_flavourItems: `<abbr title="Наборы анимаций, глифы, палитры и т. д.">Уникальные предметы</abbr>`,
inventory_shipDecorations: `Украшения корабля`, inventory_shipDecorations: `Украшения корабля`,
inventory_weaponSkins: `Скины`,
inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`, inventory_bulkAddSuits: `Добавить отсутствующие Варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`, inventory_bulkAddSpaceSuits: `Добавить отсутствующие Арчвинги`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`, inventory_bulkAddFlavourItems: `Добавить отсутствующие уникальные предметы`,
inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`, inventory_bulkAddShipDecorations: `Добавить отсутствующие украшения корабля`,
inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`, inventory_bulkAddEvolutionProgress: `Добавить отсутствующий прогресс эволюции Инкарнонов`,
inventory_bulkAddWeaponSkins: `Добавить отсутствующие скины`,
inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`, inventory_bulkRankUpSuits: `Макс. ранг всех Варфреймов`,
inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`, inventory_bulkRankUpWeapons: `Макс. ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`, inventory_bulkRankUpSpaceSuits: `Макс. ранг всех Арчвингов`,
@ -129,6 +135,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `Макс. ранг всего оружия Стражей`, inventory_bulkRankUpSentinelWeapons: `Макс. ранг всего оружия Стражей`,
inventory_bulkRankUpEvolutionProgress: `Макс. ранг всех эволюций Инкарнонов`, inventory_bulkRankUpEvolutionProgress: `Макс. ранг всех эволюций Инкарнонов`,
inventory_maxPlexus: `Макс. ранг Плексуса`, inventory_maxPlexus: `Макс. ранг Плексуса`,
inventory_removeIsNew: `Удалить значок восклицательного знака нового снаряжения`,
quests_list: `Квесты`, quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`, quests_completeAll: `Завершить все квесты`,
@ -278,7 +285,8 @@ dict = {
worldState_plagueStar: `Чумная звезда`, worldState_plagueStar: `Чумная звезда`,
worldState_dogDays: `Знойные дни`, worldState_dogDays: `Знойные дни`,
worldState_dogDaysRewards: `Награды Знойных дней`, worldState_dogDaysRewards: `Награды Знойных дней`,
worldState_wolfHunt: `Волчья Охота (2025)`, worldState_wolfHunt: `Волчья Охота`,
worldState_voidCorruption: `Искажение Бездны (|VAL|)`,
worldState_orphixVenom: `Яд Орфикса`, worldState_orphixVenom: `Яд Орфикса`,
worldState_longShadow: `Длинная Тень`, worldState_longShadow: `Длинная Тень`,
worldState_hallowedFlame: `Священное пламя`, worldState_hallowedFlame: `Священное пламя`,
@ -293,8 +301,9 @@ dict = {
worldState_eightClawProgressOverride: `Прогресс Восьми когтей`, worldState_eightClawProgressOverride: `Прогресс Восьми когтей`,
worldState_thermiaFractures: `Разломы Термии`, worldState_thermiaFractures: `Разломы Термии`,
worldState_thermiaFracturesProgressOverride: `Прогресс Разломов Термии`, worldState_thermiaFracturesProgressOverride: `Прогресс Разломов Термии`,
worldState_from_year: `из |VAL|`, worldState_qtccAlerts: `Тревоги Quest to Conquer Cancer`,
worldState_pre_year: `до |VAL|`, worldState_from_year: `Из |VAL|`,
worldState_pre_year: `До |VAL|`,
worldState_week: `Неделя |VAL|`, worldState_week: `Неделя |VAL|`,
worldState_incompatibleWith: `Несовместимо с:`, worldState_incompatibleWith: `Несовместимо с:`,
enabled: `Включено`, enabled: `Включено`,
@ -411,6 +420,8 @@ dict = {
guildView_cheats: `Читы Клана`, guildView_cheats: `Читы Клана`,
guildView_techProjects: `Исследовения`, guildView_techProjects: `Исследовения`,
guildView_vaultDecoRecipes: `Рецепты декораций Додзё`, guildView_vaultDecoRecipes: `Рецепты декораций Додзё`,
guildView_vaultMiscItems: `Ресурсы в Хранилище`,
guildView_vaultShipDecorations: `Укращения в Хранилище`,
guildView_alliance: `Альянс`, guildView_alliance: `Альянс`,
guildView_members: `Товарищи`, guildView_members: `Товарищи`,
guildView_pending: `Ожидание`, guildView_pending: `Ожидание`,
@ -433,6 +444,7 @@ dict = {
guildView_currency_owned: `В хранилище |COUNT|.`, guildView_currency_owned: `В хранилище |COUNT|.`,
guildView_bulkAddTechProjects: `Добавить отсутствующие исследования`, guildView_bulkAddTechProjects: `Добавить отсутствующие исследования`,
guildView_bulkAddVaultDecoRecipes: `Добавить отсутствующие рецепты декораций Дoдзё`, guildView_bulkAddVaultDecoRecipes: `Добавить отсутствующие рецепты декораций Дoдзё`,
guildView_bulkAddVaultShipDecorations: `Добавить отсутствующие укращения в Хранилище`,
guildView_bulkFundTechProjects: `Профинансировать все исследования`, guildView_bulkFundTechProjects: `Профинансировать все исследования`,
guildView_bulkCompleteTechProjects: `Завершить все исследования`, guildView_bulkCompleteTechProjects: `Завершить все исследования`,
guildView_promote: `Повысить`, guildView_promote: `Повысить`,

View File

@ -33,7 +33,7 @@ dict = {
code_remove: `Видалити`, code_remove: `Видалити`,
code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`, code_addItemsConfirm: `Ви впевнені, що хочете додати |COUNT| предметів на ваш обліковий запис?`,
code_addTechProjectsConfirm: `Ви впевнені, що хочете додати |COUNT| досліджень до свого клану?`, code_addTechProjectsConfirm: `Ви впевнені, що хочете додати |COUNT| досліджень до свого клану?`,
code_addDecoRecipesConfirm: `Ви впевнені, що хочете додати |COUNT| рецептів оздоблень до свого клану?`, code_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`,
code_succRankUp: `Рівень успішно підвищено`, code_succRankUp: `Рівень успішно підвищено`,
code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`, code_noEquipmentToRankUp: `Немає спорядження для підвищення рівня.`,
code_succAdded: `Успішно додано.`, code_succAdded: `Успішно додано.`,
@ -48,7 +48,7 @@ dict = {
code_unlockLevelCap: `Розблокувати обмеження рівня`, code_unlockLevelCap: `Розблокувати обмеження рівня`,
code_count: `Кількість`, code_count: `Кількість`,
code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`, code_focusAllUnlocked: `Всі школи Фокусу вже розблоковані.`,
code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження. Відвідування навігації — найпростіший спосіб цього досягти.`, code_focusUnlocked: `Розблоковано |COUNT| нових шкіл Фокусу! Для відображення змін в грі знадобиться оновлення спорядження.`,
code_addModsConfirm: `Ви впевнені, що хочете додати |COUNT| модифікаторів на ваш обліковий запис?`, code_addModsConfirm: `Ви впевнені, що хочете додати |COUNT| модифікаторів на ваш обліковий запис?`,
code_succImport: `Успішно імпортовано.`, code_succImport: `Успішно імпортовано.`,
code_succRelog: `Готово. Зверніть увагу, що вам потрібно буде перезайти, щоб побачити зміни в грі.`, code_succRelog: `Готово. Зверніть увагу, що вам потрібно буде перезайти, щоб побачити зміни в грі.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `Профінансовано`, code_funded: `Профінансовано`,
code_replays: `Повтори`, code_replays: `Повтори`,
code_stalker: `Сталкер`, code_stalker: `Сталкер`,
code_cutName: `Зачіска: |INDEX|`,
code_drifterBeardName: `Борода мандрівника: |INDEX|`,
code_drifterFaceName: `Зовнішність мандрівника: |INDEX|`,
code_operatorFaceName: `Зовнішність оператора: |INDEX|`,
code_succChange: `Успішно змінено.`, code_succChange: `Успішно змінено.`,
code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`, code_requiredInvigorationUpgrade: `Ви повинні вибрати як атакуюче, так і допоміжне вдосконалення.`,
login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`, login_description: `Увійдіть, використовуючи облікові дані OpenWF (ті ж, що й у грі при підключенні до цього серверу).`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `Посилення`, inventory_boosters: `Посилення`,
inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`, inventory_flavourItems: `<abbr title="Набори анімацій, гліфи, палітри і т. д.">Унікальні предмети</abbr>`,
inventory_shipDecorations: `Прикраси судна`, inventory_shipDecorations: `Прикраси судна`,
inventory_weaponSkins: `Вигляди`,
inventory_bulkAddSuits: `Додати відсутні Ворфрейми`, inventory_bulkAddSuits: `Додати відсутні Ворфрейми`,
inventory_bulkAddWeapons: `Додати відсутню зброю`, inventory_bulkAddWeapons: `Додати відсутню зброю`,
inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`, inventory_bulkAddSpaceSuits: `Додати відсутні Арквінґи`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`, inventory_bulkAddFlavourItems: `Додати відсутні унікальні предмети`,
inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`, inventory_bulkAddShipDecorations: `Додати відсутні оздоби корабля`,
inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`, inventory_bulkAddEvolutionProgress: `Додати відсутній прогрес еволюції Інкарнонів`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`, inventory_bulkRankUpSuits: `Макс. рівень всіх Ворфреймів`,
inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`, inventory_bulkRankUpWeapons: `Макс. рівень всієї зброї`,
inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`, inventory_bulkRankUpSpaceSuits: `Макс. рівень всіх Арквінґів`,
@ -129,6 +135,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `Макс. рівень всієї зброї Вартових`, inventory_bulkRankUpSentinelWeapons: `Макс. рівень всієї зброї Вартових`,
inventory_bulkRankUpEvolutionProgress: `Макс. рівень всіх еволюцій Інкарнонів`, inventory_bulkRankUpEvolutionProgress: `Макс. рівень всіх еволюцій Інкарнонів`,
inventory_maxPlexus: `Макс. рівень Плексу`, inventory_maxPlexus: `Макс. рівень Плексу`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
quests_list: `Пригоди`, quests_list: `Пригоди`,
quests_completeAll: `Закінчити всі пригоди`, quests_completeAll: `Закінчити всі пригоди`,
@ -278,7 +285,8 @@ dict = {
worldState_plagueStar: `Морова зірка`, worldState_plagueStar: `Морова зірка`,
worldState_dogDays: `Спекотні дні`, worldState_dogDays: `Спекотні дні`,
worldState_dogDaysRewards: `Нагороди Спекотних днів`, worldState_dogDaysRewards: `Нагороди Спекотних днів`,
worldState_wolfHunt: `Полювання на Вовка (2025)`, worldState_wolfHunt: `Полювання на Вовка`,
worldState_voidCorruption: `Викривлення Порожнечі (|VAL|)`,
worldState_orphixVenom: `Орфіксова отрута`, worldState_orphixVenom: `Орфіксова отрута`,
worldState_longShadow: `Довга тінь`, worldState_longShadow: `Довга тінь`,
worldState_hallowedFlame: `Священне полум'я`, worldState_hallowedFlame: `Священне полум'я`,
@ -293,8 +301,9 @@ dict = {
worldState_eightClawProgressOverride: `Прогрес Восьми кігтів`, worldState_eightClawProgressOverride: `Прогрес Восьми кігтів`,
worldState_thermiaFractures: `Розломи термії`, worldState_thermiaFractures: `Розломи термії`,
worldState_thermiaFracturesProgressOverride: `Прогрес Розломів термії`, worldState_thermiaFracturesProgressOverride: `Прогрес Розломів термії`,
worldState_from_year: `з |VAL|`, worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_pre_year: `до |VAL|`, worldState_from_year: `З |VAL|`,
worldState_pre_year: `До |VAL|`,
worldState_week: `Тиждень |VAL|`, worldState_week: `Тиждень |VAL|`,
worldState_incompatibleWith: `Несумісне з:`, worldState_incompatibleWith: `Несумісне з:`,
enabled: `Увімкнено`, enabled: `Увімкнено`,
@ -411,6 +420,8 @@ dict = {
guildView_cheats: `Кланові чити`, guildView_cheats: `Кланові чити`,
guildView_techProjects: `Дослідження`, guildView_techProjects: `Дослідження`,
guildView_vaultDecoRecipes: `Рецепти оздоблень Доджьо`, guildView_vaultDecoRecipes: `Рецепти оздоблень Доджьо`,
guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`,
guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`,
guildView_alliance: `Альянс`, guildView_alliance: `Альянс`,
guildView_members: `Учасники`, guildView_members: `Учасники`,
guildView_pending: `Очікування`, guildView_pending: `Очікування`,
@ -433,6 +444,7 @@ dict = {
guildView_currency_owned: `В сховищі |COUNT|.`, guildView_currency_owned: `В сховищі |COUNT|.`,
guildView_bulkAddTechProjects: `Додати відсутні дослідження`, guildView_bulkAddTechProjects: `Додати відсутні дослідження`,
guildView_bulkAddVaultDecoRecipes: `Додати відсутні рецепти оздоблень Доджьо`, guildView_bulkAddVaultDecoRecipes: `Додати відсутні рецепти оздоблень Доджьо`,
guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `Фінансувати всі дослідження`, guildView_bulkFundTechProjects: `Фінансувати всі дослідження`,
guildView_bulkCompleteTechProjects: `Завершити всі дослідження`, guildView_bulkCompleteTechProjects: `Завершити всі дослідження`,
guildView_promote: `Підвищити звання`, guildView_promote: `Підвищити звання`,

View File

@ -33,7 +33,7 @@ dict = {
code_remove: `移除`, code_remove: `移除`,
code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`, code_addItemsConfirm: `确定要向您的账户添加 |COUNT| 件物品吗?`,
code_addTechProjectsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| research to your clan?`, 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_addVaultItemsConfirm: `[UNTRANSLATED] Are you sure you want to add |COUNT| items to your clan vault?`,
code_succRankUp: `等级已提升`, code_succRankUp: `等级已提升`,
code_noEquipmentToRankUp: `没有可升级的装备`, code_noEquipmentToRankUp: `没有可升级的装备`,
code_succAdded: `添加成功`, code_succAdded: `添加成功`,
@ -48,7 +48,7 @@ dict = {
code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`, code_unlockLevelCap: `[UNTRANSLATED] Unlock level cap`,
code_count: `数量`, code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁`, code_focusAllUnlocked: `所有专精学派均已解锁`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新.`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效.`,
code_addModsConfirm: `确定要向您的账户添加 |COUNT| 张MOD吗?`, code_addModsConfirm: `确定要向您的账户添加 |COUNT| 张MOD吗?`,
code_succImport: `导入成功`, code_succImport: `导入成功`,
code_succRelog: `完成.需要重新登录游戏才能看到变化.`, code_succRelog: `完成.需要重新登录游戏才能看到变化.`,
@ -75,6 +75,10 @@ dict = {
code_funded: `[UNTRANSLATED] Funded`, code_funded: `[UNTRANSLATED] Funded`,
code_replays: `[UNTRANSLATED] Replays`, code_replays: `[UNTRANSLATED] Replays`,
code_stalker: `追猎者`, code_stalker: `追猎者`,
code_cutName: `[UNTRANSLATED] Cut |INDEX|`,
code_drifterBeardName: `漂泊者胡须 |INDEX|`,
code_drifterFaceName: `漂泊者面部 |INDEX|`,
code_operatorFaceName: `指挥官面部 |INDEX|`,
code_succChange: `更改成功`, code_succChange: `更改成功`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`, code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & utility upgrade.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)`,
@ -112,6 +116,7 @@ dict = {
inventory_boosters: `加成器`, inventory_boosters: `加成器`,
inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`, inventory_flavourItems: `<abbr title="动作表情、浮印、调色板等">装饰物品</abbr>`,
inventory_shipDecorations: `飞船装饰`, inventory_shipDecorations: `飞船装饰`,
inventory_weaponSkins: `外观`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失载具`, inventory_bulkAddSpaceSuits: `添加缺失载具`,
@ -121,6 +126,7 @@ dict = {
inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`, inventory_bulkAddFlavourItems: `[UNTRANSLATED] Add Missing Flavour Items`,
inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`, inventory_bulkAddShipDecorations: `[UNTRANSLATED] Add Missing Ship Decorations`,
inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`, inventory_bulkAddEvolutionProgress: `添加缺失的灵化之源进度`,
inventory_bulkAddWeaponSkins: `[UNTRANSLATED] Add Missing Skins`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有载具升满级`, inventory_bulkRankUpSpaceSuits: `所有载具升满级`,
@ -129,6 +135,7 @@ dict = {
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `所有灵化之源进度最大等级`, inventory_bulkRankUpEvolutionProgress: `所有灵化之源进度最大等级`,
inventory_maxPlexus: `最大深控等级`, inventory_maxPlexus: `最大深控等级`,
inventory_removeIsNew: `[UNTRANSLATED] Remove New Equipment Exclamation Icon`,
quests_list: `系列任务`, quests_list: `系列任务`,
quests_completeAll: `完成所有任务`, quests_completeAll: `完成所有任务`,
@ -278,7 +285,8 @@ dict = {
worldState_plagueStar: `瘟疫之星`, worldState_plagueStar: `瘟疫之星`,
worldState_dogDays: `三伏天`, worldState_dogDays: `三伏天`,
worldState_dogDaysRewards: `三伏天奖励设置`, worldState_dogDaysRewards: `三伏天奖励设置`,
worldState_wolfHunt: `恶狼狩猎 (2025)`, worldState_wolfHunt: `恶狼狩猎`,
worldState_voidCorruption: `虚空堕落 (|VAL|)`,
worldState_orphixVenom: `奥影之毒`, worldState_orphixVenom: `奥影之毒`,
worldState_longShadow: `暗夜长影`, worldState_longShadow: `暗夜长影`,
worldState_hallowedFlame: `万圣之焰`, worldState_hallowedFlame: `万圣之焰`,
@ -293,6 +301,7 @@ dict = {
worldState_eightClawProgressOverride: `大帝金币收集进度(%)`, worldState_eightClawProgressOverride: `大帝金币收集进度(%)`,
worldState_thermiaFractures: `热美亚裂缝`, worldState_thermiaFractures: `热美亚裂缝`,
worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`, worldState_thermiaFracturesProgressOverride: `[UNTRANSLATED] Thermia Fractures Progress`,
worldState_qtccAlerts: `[UNTRANSLATED] Quest to Conquer Cancer Alerts`,
worldState_from_year: `|VAL|`, worldState_from_year: `|VAL|`,
worldState_pre_year: `|VAL|之前`, worldState_pre_year: `|VAL|之前`,
worldState_week: `第|VAL|周`, worldState_week: `第|VAL|周`,
@ -411,6 +420,8 @@ dict = {
guildView_cheats: `[UNTRANSLATED] Clan Cheats`, guildView_cheats: `[UNTRANSLATED] Clan Cheats`,
guildView_techProjects: `研究`, guildView_techProjects: `研究`,
guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`, guildView_vaultDecoRecipes: `[UNTRANSLATED] Dojo Deco Recipes`,
guildView_vaultMiscItems: `[UNTRANSLATED] Resources in Vault`,
guildView_vaultShipDecorations: `[UNTRANSLATED] Decorations in Vault`,
guildView_alliance: `联盟`, guildView_alliance: `联盟`,
guildView_members: `成员`, guildView_members: `成员`,
guildView_pending: `待处理`, guildView_pending: `待处理`,
@ -433,6 +444,7 @@ dict = {
guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`, guildView_currency_owned: `[UNTRANSLATED] |COUNT| in Vault.`,
guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`, guildView_bulkAddTechProjects: `[UNTRANSLATED] Add Missing Research`,
guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`, guildView_bulkAddVaultDecoRecipes: `[UNTRANSLATED] Add Missing Dojo Deco Recipes`,
guildView_bulkAddVaultShipDecorations: `[UNTRANSLATED] Add Missing Decorations in Vault`,
guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`, guildView_bulkFundTechProjects: `[UNTRANSLATED] Fund All Research`,
guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`, guildView_bulkCompleteTechProjects: `[UNTRANSLATED] Complete All Research`,
guildView_promote: `升级`, guildView_promote: `升级`,