Compare commits

...

73 Commits

Author SHA1 Message Date
4a2d863c9c chore(webui): update to Spanish translation (#2588)
Reviewed-on: OpenWF/SpaceNinjaServer#2588
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-08-05 10:18:36 -07:00
9f0cd91105 chore(webui): update German translation (#2587)
I need to double check some other time if it's also called "TennoLive" in German wf, once I'm home again. Should be prob good enough for now...

Reviewed-on: OpenWF/SpaceNinjaServer#2587
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-08-05 09:47:25 -07:00
ebfef52fb1 fix(webui): proper PvPVariant check (#2585)
Closes #2584

Reviewed-on: OpenWF/SpaceNinjaServer#2585
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-08-04 05:18:45 -07:00
dd7bacd22e chore: update PE+ (#2583)
Reviewed-on: OpenWF/SpaceNinjaServer#2583
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-03 15:37:06 -07:00
c00967931e fix(webui): add k-drive (#2581)
Closes #2580

Reviewed-on: OpenWF/SpaceNinjaServer#2581
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-02 04:57:16 -07:00
b15a635e11 fix(webui): exclude zaw strike pvp variants (#2579)
Reviewed-on: OpenWF/SpaceNinjaServer#2579
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-02 04:57:08 -07:00
7e618539fa fix(webui): explicitly specify websocket protocol (#2578)
apparently some browsers (e.g. 2 year old chrome) need this to establish a connection. can't hurt, anyway.

Reviewed-on: OpenWF/SpaceNinjaServer#2578
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-08-01 03:47:14 -07:00
a29398fae6 chore(webui): update Chinese translation (#2577)
Reviewed-on: OpenWF/SpaceNinjaServer#2577
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-31 07:46:01 -07:00
601091f1c0 chore(webui): clarify that eidolon override also takes effect on deimos (#2576)
This is an update in the English translation only.

Reviewed-on: OpenWF/SpaceNinjaServer#2576
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-31 02:28:03 -07:00
f561884f2c feat: worldState.baroTennoConRelay config (#2574)
Closes #2531

Reviewed-on: OpenWF/SpaceNinjaServer#2574
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-31 02:27:38 -07:00
6e1cb0c9f9 chore(webui): update Chinese translation (#2575)
Reviewed-on: OpenWF/SpaceNinjaServer#2575
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-31 02:27:26 -07:00
9286627668 feat: star days rotation (#2573)
Closes #2567

Reviewed-on: OpenWF/SpaceNinjaServer#2573
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 05:44:42 -07:00
f94f2005d3 fix: script error with baroFullyStocked 2025-07-30 13:45:00 +02:00
9901b7af54 chore: rename config.json.example to config-vanilla.json (#2570)
Changing file extensions can be a bit of a chore on stock Windows, so this should simplify matters. Another bonus is that the "vanilla" clarifies the general guideline for how the defaults are configured.

Reviewed-on: OpenWF/SpaceNinjaServer#2570
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 04:01:02 -07:00
2fa846f465 chore(webui): update Chinese translation (#2572)
Reviewed-on: OpenWF/SpaceNinjaServer#2572
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-30 03:51:42 -07:00
541ec3d702 feat: claiming of tennolive relay's secret (#2569)
Reviewed-on: OpenWF/SpaceNinjaServer#2569
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 01:51:07 -07:00
0a28eab65d feat: worldState.tennoLiveRelay config (#2568)
Re #2531

Reviewed-on: OpenWF/SpaceNinjaServer#2568
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 01:50:43 -07:00
8e639a16bd feat: initial protovyre/evolving cosmetics (#2566)
Basic handling of sending the challenge rewards to the inbox upon completion.

Re #2485. Still missing handling for the Protovyre armor pieces which require killing sentients.

Reviewed-on: OpenWF/SpaceNinjaServer#2566
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 01:50:23 -07:00
522924a823 chore: remove empty ModularParts arrays from equipment (#2565)
Reviewed-on: OpenWF/SpaceNinjaServer#2565
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 01:49:55 -07:00
48e3f324e2 chore: log when worldState time is behind real time + make sure client knows fissures are active (#2562)
Reviewed-on: OpenWF/SpaceNinjaServer#2562
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-30 01:49:34 -07:00
8f77c722cb chore(webui): update German translation (#2564)
Reviewed-on: OpenWF/SpaceNinjaServer#2564
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-07-29 07:34:04 -07:00
e7287933b5 chore(webui): update Chinese translation (#2563)
Reviewed-on: OpenWF/SpaceNinjaServer#2563
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-29 07:33:57 -07:00
b21bca7a6d fix: AffiliationChanges disapears from EOM screen when bounty stage is completed (#2560)
Reviewed-on: OpenWF/SpaceNinjaServer#2560
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-07-29 00:31:48 -07:00
d30d450311 chore: add rewards for NewbieJob (#2559)
Closes #2536

Reviewed-on: OpenWF/SpaceNinjaServer#2559
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-07-29 00:31:37 -07:00
b62e326920 feat(webui): ability overrides (#2558)
Closes #851

Reviewed-on: OpenWF/SpaceNinjaServer#2558
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-07-29 00:31:29 -07:00
8b4bc114f6 chore: add logging for bounty medallion rewards (#2557)
Reviewed-on: OpenWF/SpaceNinjaServer#2557
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-07-29 00:31:19 -07:00
564aa06762 fix: correctly apply riven cipher (#2554)
The completeRandomModChallenge endpoint is only supposed to complete the challenge, what a shocker. Because we directly set a unveiled fingerprint, the game was not showing the expected UI.

Reviewed-on: OpenWF/SpaceNinjaServer#2554
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-27 06:29:12 -07:00
2e84f71af8 chore: faithful handling when ki'teer signa was rolled (#2553)
Reviewed-on: OpenWF/SpaceNinjaServer#2553
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-27 06:29:02 -07:00
ddfa98e0b2 chore: update baro.json (#2550)
Co-authored-by: BanLanGen <banlangen@noreply.localhost>
Reviewed-on: OpenWF/SpaceNinjaServer#2550
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-27 06:28:51 -07:00
bb3c3e01b0 chore: add GEAR loadout slot (#2545)
added missing GEAR loadout slot (was causing issues with saving loadout in U29)

Reviewed-on: OpenWF/SpaceNinjaServer#2545
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: azdful <mischzaripov@yandex.ru>
Co-committed-by: azdful <mischzaripov@yandex.ru>
2025-07-25 01:51:13 -07:00
695dcf98e0 chore: handle sale of fusion treasures (#2542)
Closes #2541

Reviewed-on: OpenWF/SpaceNinjaServer#2542
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-24 05:30:55 -07:00
509f7f0d9b feat: selling for Dirac (CrewShipFusionPoints) (#2540)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2540
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: azdful <mischzaripov@yandex.ru>
Co-committed-by: azdful <mischzaripov@yandex.ru>
2025-07-23 11:09:44 -07:00
aada031a80 chore: update mongoose (#2539)
The transform hook signature was changed in the typings, so I just updated them to be explicit about what we expect.

Reviewed-on: OpenWF/SpaceNinjaServer#2539
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-23 07:51:51 -07:00
a2a441ecb0 fix: getUsernameFromEmail returning wrong value (#2538)
Closes #2537

Reviewed-on: OpenWF/SpaceNinjaServer#2538
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-23 07:51:22 -07:00
c0a0463a68 feat: vista suite backdrop and soundscape customisation (#2534)
Closes #2532

Reviewed-on: OpenWF/SpaceNinjaServer#2534
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:46 -07:00
2307a40833 chore(webui): debounce inventory bulk actions (#2533)
Closes #2513

Reviewed-on: OpenWF/SpaceNinjaServer#2533
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:40 -07:00
304af514e2 fix(webui): handle name already being taken (#2530)
Closes #2528

Reviewed-on: OpenWF/SpaceNinjaServer#2530
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:31 -07:00
ddf3cd49b5 chore: handle new T value for orowyrm chest (#2527)
Closes #2526

Reviewed-on: OpenWF/SpaceNinjaServer#2527
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-22 07:34:22 -07:00
41e3f0136f chore(webui): improve auth state management 2025-07-21 20:28:19 +02:00
c0ca9d9398 fix: add try/catch around websocket message event handler (#2529)
Re #2528

Reviewed-on: OpenWF/SpaceNinjaServer#2529
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 07:44:54 -07:00
0f6b55beed chore(webui): update fr (#2525)
Reviewed-on: OpenWF/SpaceNinjaServer#2525
Co-authored-by: Vitruvio <vitruvio@noreply.localhost>
Co-committed-by: Vitruvio <vitruvio@noreply.localhost>
2025-07-21 03:23:12 -07:00
f8550e9afe fix: incorect deimos bounty medallion reward (#2524)
Reviewed-on: OpenWF/SpaceNinjaServer#2524
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-07-21 03:23:05 -07:00
b53c4d9125 feat: reset obstacle course (#2523)
Closes #2520

Reviewed-on: OpenWF/SpaceNinjaServer#2523
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 03:22:59 -07:00
922b65cfab chore: print build date when started via docker (#2517)
Docker updates can be a bit confusing so this should help users know if they're up-to-date.

Reviewed-on: OpenWF/SpaceNinjaServer#2517
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 03:22:46 -07:00
2f642df20a feat: reset decorations (#2516)
Closes #2514

Reviewed-on: OpenWF/SpaceNinjaServer#2516
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-21 03:22:35 -07:00
62314e89c7 fix: refund personal decos when destroying dojo room (#2522)
Closes #2521

Reviewed-on: OpenWF/SpaceNinjaServer#2522
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 10:35:14 -07:00
56aa3e3331 fix: placing decorations in apartment in newer game versions (#2515)
Newer game versions use BootLocation instead of IsApartment

Reviewed-on: OpenWF/SpaceNinjaServer#2515
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 01:29:08 -07:00
c3f486488f chore: npm update (#2512)
There were some low severity vulnerabilites audit was complaining about

Reviewed-on: OpenWF/SpaceNinjaServer#2512
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 00:28:46 -07:00
49c353d895 chore: disable DTLS (#2511)
Reviewed-on: OpenWF/SpaceNinjaServer#2511
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-20 00:28:37 -07:00
90ab560620 chore(webui): don't refresh inventory for sell on the tab that issued it (#2506)
Reviewed-on: OpenWF/SpaceNinjaServer#2506
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-18 15:36:10 -07:00
b0e80fcfa8 chore(webui): update German translation (#2507)
Reviewed-on: OpenWF/SpaceNinjaServer#2507
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-07-17 06:56:23 -07:00
2c62fb3c3c chore: move syncConfigWithDatabase to configService (#2505)
This avoids a cyclic dependency due to configController using this

Reviewed-on: OpenWF/SpaceNinjaServer#2505
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-17 05:04:30 -07:00
5b215733aa chore(webui): update to Spanish translation (#2503)
Reviewed-on: OpenWF/SpaceNinjaServer#2503
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-07-16 23:04:26 -07:00
39866b9a2b fix: hide edit suit invigorations card on detailed view load (#2500)
Fixes #2494

Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2500
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: nyaoouo <nyaoouo@noreply.localhost>
Co-committed-by: nyaoouo <nyaoouo@noreply.localhost>
2025-07-16 08:29:41 -07:00
fad1ee9314 chore(webui): update Chinese translation (#2501)
Reviewed-on: OpenWF/SpaceNinjaServer#2501
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-16 08:29:13 -07:00
64b43fcccf chore(webui): fixing a mess in the translations (#2498)
Re #2494 (only fixes the strings, **NOT** the weapon issue)

- The invigoration stuff now mentions the numbers, percentages of buffs
- Improved some misleading strings (e.g. "Movement Speed", when it was in fact just "Sprint Speed" instead)
- Improved some inconsistencies in some key names (some weren't like other, similar existing ones)
- Got rid of duplicate "None" string & re-used it properly + re-used existing strings to newly added buttons, instead of using unnecessary extra added strings (more consistent to use existing strings, aside that they are shorter, less lines and less work overall for everyone involved)

If I should change anything, lemme know.

Reviewed-on: OpenWF/SpaceNinjaServer#2498
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-07-15 20:59:59 -07:00
e407262cf8 fix: don't send baro message ahead of his visit (#2497)
Closes #2495

Reviewed-on: OpenWF/SpaceNinjaServer#2497
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-15 20:59:49 -07:00
00e57c43df fix: charge correct amount of void traces for upgrading to radiant (#2492)
Closes #2490

Reviewed-on: OpenWF/SpaceNinjaServer#2492
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-15 20:59:39 -07:00
2ab9f39507 chore(webui): update Chinese translation (#2493)
Reviewed-on: OpenWF/SpaceNinjaServer#2493
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-15 02:37:43 -07:00
b60723ef54 feat(webui): edit suit invigorations (#2478)
Co-authored-by: nyaoouo <64143453+nyaoouo@users.noreply.github.com>
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2478
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: nyaoouo <nyaoouo@noreply.localhost>
Co-committed-by: nyaoouo <nyaoouo@noreply.localhost>
2025-07-14 20:33:37 -07:00
b3bf291d10 chore: send event messages for boosters (#2487)
Closes #2464

Reviewed-on: OpenWF/SpaceNinjaServer#2487
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-14 20:24:17 -07:00
db86e2d265 chore(webui): update translations (#2489)
- Updated German translation with all new strings
- Added the rest of the missing Chinese translation contributors (only the ones who did actually translate, that is)

Reviewed-on: OpenWF/SpaceNinjaServer#2489
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-07-14 20:23:56 -07:00
f6cb8414c1 chore(webui): refresh inventory when crafting/buying/gilding kitguns (#2486)
Reviewed-on: OpenWF/SpaceNinjaServer#2486
Reviewed-by: Sainan <63328889+sainan@users.noreply.github.com>
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-07-13 21:08:34 -07:00
ba3df4bdbc fix: don't give mastery xp for SpecialItems except for venari (#2484)
Closes #2482

Reviewed-on: OpenWF/SpaceNinjaServer#2484
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-13 21:08:23 -07:00
8feb3a5b3c feat: give kaithe summon at riding level 9 (#2483)
Closes #2480

Reviewed-on: OpenWF/SpaceNinjaServer#2483
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-13 21:08:14 -07:00
66f3d65d77 fix(webui): none syndicate (#2477)
Closes #2475

Reviewed-on: OpenWF/SpaceNinjaServer#2477
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-12 21:20:08 -07:00
b18f06087b chore(webui): update to Spanish translation (#2481)
Reviewed-on: OpenWF/SpaceNinjaServer#2481
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-07-12 21:19:50 -07:00
987b5b98ff chore(webui): update Chinese translation (#2476)
Reviewed-on: OpenWF/SpaceNinjaServer#2476
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-07-12 00:00:23 -07:00
fbbd9076cf fix: delete galleon of ghouls inbox messages when disabled via webui (#2473)
Reviewed-on: OpenWF/SpaceNinjaServer#2473
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-11 21:16:07 -07:00
838818543c fix: omit plains of eidolon from non-grineer sorties (#2472)
Closes #2470

Reviewed-on: OpenWF/SpaceNinjaServer#2472
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-11 21:15:48 -07:00
a16e2716f1 feat(webui): Change weapon Modular Parts (#2471)
Reviewed-on: OpenWF/SpaceNinjaServer#2471
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-07-11 21:15:16 -07:00
f4c7ce582b chore(webui): handle malformed rivens so they can be deleted at least (#2469)
Closes #2468

Reviewed-on: OpenWF/SpaceNinjaServer#2469
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-11 21:15:04 -07:00
c0187f9446 chore(webui): refresh inventory when pet was consigned (#2467)
Closes #2463

Reviewed-on: OpenWF/SpaceNinjaServer#2467
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-07-11 08:55:04 -07:00
70 changed files with 2226 additions and 944 deletions

View File

@ -21,7 +21,7 @@
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-explicit-any": "off",
"no-loss-of-precision": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-base-to-string": "off",

View File

@ -14,7 +14,7 @@ jobs:
with:
node-version: ">=20.6.0"
- run: npm ci
- run: cp config.json.example config.json
- run: cp config-vanilla.json config.json
- run: npm run verify
- run: npm run lint:ci
- run: npm run prettier

View File

@ -2,3 +2,4 @@ src/routes/api.ts
static/webui/libs/
*.html
*.md
config-vanilla.json

View File

@ -7,5 +7,6 @@ WORKDIR /app
RUN npm i --omit=dev
RUN npm run build
RUN date '+%d %B %Y' > BUILD_DATE
ENTRYPOINT ["/app/docker-entrypoint.sh"]

View File

@ -10,7 +10,7 @@ To get an idea of what functionality you can expect to be missing [have a look t
## config.json
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config-vanilla.json](config-vanilla.json), which has most cheats disabled.
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.

View File

@ -70,8 +70,9 @@
"creditBoost": false,
"affinityBoost": false,
"resourceBoost": false,
"starDays": true,
"tennoLiveRelay": false,
"galleonOfGhouls": 0,
"starDaysOverride": null,
"eidolonOverride": "",
"vallisOverride": "",
"duviriOverride": "",

View File

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

660
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
"dev": "node scripts/dev.js",
"dev:bun": "bun scripts/dev.js",
"verify": "tsgo --noEmit",
"verify:tsc": "tsc --noEmit",
"bun-run": "bun src/index.ts",
"lint": "eslint --ext .ts .",
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
@ -39,7 +40,7 @@
"ncp": "^2.0.0",
"typescript": "^5.5",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.78",
"warframe-public-export-plus": "^0.5.79",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0",

View File

@ -0,0 +1,22 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { RequestHandler } from "express";
export const apartmentController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const personalRooms = await getPersonalRooms(accountId, "Apartment");
const response: IApartmentResponse = {};
if (req.query.backdrop !== undefined) {
response.NewBackdropItem = personalRooms.Apartment.VideoWallBackdrop = req.query.backdrop as string;
}
if (req.query.soundscape !== undefined) {
response.NewSoundscapeItem = personalRooms.Apartment.Soundscape = req.query.soundscape as string;
}
await personalRooms.save();
res.json(response);
};
interface IApartmentResponse {
NewBackdropItem?: string;
NewSoundscapeItem?: string;
}

View File

@ -4,8 +4,7 @@ import { addMiscItems, getInventory, updateCurrency } from "@/src/services/inven
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { createUnveiledRivenFingerprint } from "@/src/helpers/rivenHelper";
import { ExportUpgrades } from "warframe-public-export-plus";
import { IVeiledRivenFingerprint } from "@/src/helpers/rivenHelper";
export const completeRandomModChallengeController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -27,10 +26,11 @@ export const completeRandomModChallengeController: RequestHandler = async (req,
inventoryChanges.MiscItems = miscItemChanges;
}
// Update riven fingerprint to a randomised unveiled state
// Complete the riven challenge
const upgrade = inventory.Upgrades.id(request.ItemId)!;
const meta = ExportUpgrades[upgrade.ItemType];
upgrade.UpgradeFingerprint = JSON.stringify(createUnveiledRivenFingerprint(meta));
const fp = JSON.parse(upgrade.UpgradeFingerprint!) as IVeiledRivenFingerprint;
fp.challenge.Progress = fp.challenge.Required;
upgrade.UpgradeFingerprint = JSON.stringify(fp);
await inventory.save();

View File

@ -88,7 +88,6 @@ export const crewShipFusionController: RequestHandler = async (req, res) => {
}
}
superiorItem.UpgradeFingerprint = JSON.stringify(fingerprint);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inventoryChanges[category] = [superiorItem.toJSON() as any];
await inventory.save();

View File

@ -3,11 +3,13 @@ import {
getGuildForRequestEx,
hasAccessToDojo,
hasGuildPermission,
refundDojoDeco,
removeDojoDeco
} from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { GuildPermission } from "@/src/types/guildTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express";
export const destroyDojoDecoController: RequestHandler = async (req, res) => {
@ -18,9 +20,20 @@ export const destroyDojoDecoController: RequestHandler = async (req, res) => {
res.json({ DojoRequestStatus: -1 });
return;
}
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest;
removeDojoDeco(guild, request.ComponentId, request.DecoId);
const request = JSON.parse(String(req.body)) as IDestroyDojoDecoRequest | IClearObstacleCourseRequest;
if ("DecoType" in request) {
removeDojoDeco(guild, request.ComponentId, request.DecoId);
} else if (request.Act == "cObst") {
const component = guild.DojoComponents.id(request.ComponentId)!;
if (component.Decos) {
for (const deco of component.Decos) {
refundDojoDeco(guild, component, deco);
}
component.Decos.splice(0, component.Decos.length);
}
} else {
logger.error(`unhandled destroyDojoDeco request`, request);
}
await guild.save();
res.json(await getDojoClient(guild, 0, request.ComponentId));
@ -31,3 +44,8 @@ interface IDestroyDojoDecoRequest {
ComponentId: string;
DecoId: string;
}
interface IClearObstacleCourseRequest {
ComponentId: string;
Act: "cObst" | "maybesomethingelsewedontknowabout";
}

View File

@ -2,22 +2,14 @@ import { RequestHandler } from "express";
import { ExportResources } from "warframe-public-export-plus";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addFusionTreasures, addMiscItems, getInventory } from "@/src/services/inventoryService";
import { IFusionTreasure, IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { parseFusionTreasure } from "@/src/helpers/inventoryHelpers";
interface IFusionTreasureRequest {
oldTreasureName: string;
newTreasureName: string;
}
const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
const arr = name.split("_");
return {
ItemType: arr[0],
Sockets: parseInt(arr[1], 16),
ItemCount: count
};
};
export const fusionTreasuresController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);

View File

@ -1,5 +1,6 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
@ -73,4 +74,5 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
InventoryChanges: inventoryChanges,
AffiliationMods: affiliationMods
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -335,6 +335,17 @@ export const getInventoryResponse = async (
for (const uniqueName in ExportFlavour) {
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
}
} else if (config.worldState?.baroTennoConRelay) {
[
"/Lotus/Types/Items/Events/TennoConRelay2022EarlyAccess",
"/Lotus/Types/Items/Events/TennoConRelay2023EarlyAccess",
"/Lotus/Types/Items/Events/TennoConRelay2024EarlyAccess",
"/Lotus/Types/Items/Events/TennoConRelay2025EarlyAccess"
].forEach(uniqueName => {
if (!inventoryResponse.FlavourItems.some(x => x.ItemType == uniqueName)) {
inventoryResponse.FlavourItems.push({ ItemType: uniqueName });
}
});
}
if (config.unlockAllSkins) {

View File

@ -130,7 +130,7 @@ const createLoginResponse = (
resp.Groups = [];
}
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
resp.DTLS = 99;
resp.DTLS = 0; // bit 0 enables DTLS. if enabled, additional bits can be set, e.g. bit 2 to enable logging. on live, the value is 99.
}
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
resp.ClientType = account.ClientType;

View File

@ -1,5 +1,6 @@
import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import {
getInventory,
@ -194,4 +195,5 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
MiscItems: miscItemChanges
}
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -15,6 +15,7 @@ import {
} from "@/src/services/inventoryService";
import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { EquipmentFeatures } from "@/src/types/equipmentTypes";
@ -68,6 +69,7 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
res.json({
InventoryChanges: inventoryChanges
});
sendWsBroadcastTo(accountId, { update_inventory: true });
} else {
throw new Error(`unknown modularWeaponSale op: ${String(req.query.op)}`);
}

View File

@ -16,15 +16,28 @@ export const playerSkillsController: RequestHandler = async (req, res) => {
inventory.PlayerSkills[request.Skill as keyof IPlayerSkills]++;
const inventoryChanges: IInventoryChanges = {};
if (request.Skill == "LPS_COMMAND" && inventory.PlayerSkills.LPS_COMMAND == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
if (request.Skill == "LPS_COMMAND") {
if (inventory.PlayerSkills.LPS_COMMAND == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/Consumable/CrewmateBall",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
}
} else if (request.Skill == "LPS_DRIFT_RIDING") {
if (inventory.PlayerSkills.LPS_DRIFT_RIDING == 9) {
const consumablesChanges = [
{
ItemType: "/Lotus/Types/Restoratives/ErsatzSummon",
ItemCount: 1
}
];
addConsumables(inventory, consumablesChanges);
inventoryChanges.Consumables = consumablesChanges;
}
}
await inventory.save();

View File

@ -11,7 +11,7 @@ export const projectionManagerController: RequestHandler = async (req, res) => {
const [era, category, currentQuality] = parseProjection(request.projectionType);
const upgradeCost = config.dontSubtractVoidTraces
? 0
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
: qualityNumberToCost[request.qualityTag] - qualityNumberToCost[qualityKeywordToNumber[currentQuality]];
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
addMiscItems(inventory, [
{
@ -49,6 +49,7 @@ const qualityKeywordToNumber: Record<VoidProjectionQuality, number> = {
VPQ_GOLD: 2,
VPQ_PLATINUM: 3
};
const qualityNumberToCost = [0, 25, 50, 100];
// e.g. "/Lotus/Types/Game/Projections/T2VoidProjectionProteaPrimeDBronze" -> ["Lith", "W5", "VPQ_BRONZE"]
const parseProjection = (typeName: string): [string, string, VoidProjectionQuality] => {

View File

@ -1,6 +1,7 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { RequestHandler } from "express";
export const releasePetController: RequestHandler = async (req, res) => {
@ -19,6 +20,7 @@ export const releasePetController: RequestHandler = async (req, res) => {
await inventory.save();
res.json({ inventoryChanges }); // Not a mistake; it's "inventoryChanges" here.
sendWsBroadcastTo(accountId, { update_inventory: true });
};
interface IReleasePetRequest {

View File

@ -9,13 +9,16 @@ import {
freeUpSlot,
combineInventoryChanges,
addCrewShipRawSalvage,
addFusionPoints
addFusionPoints,
addCrewShipFusionPoints,
addFusionTreasures
} from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { sendWsBroadcastTo } from "@/src/services/wsService";
import { sendWsBroadcastEx } from "@/src/services/wsService";
import { parseFusionTreasure } from "@/src/helpers/inventoryHelpers";
export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest;
@ -26,6 +29,8 @@ export const sellController: RequestHandler = async (req, res) => {
requiredFields.add("RegularCredits");
} else if (payload.SellCurrency == "SC_FusionPoints") {
requiredFields.add("FusionPoints");
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
requiredFields.add("CrewShipFusionPoints");
} else {
requiredFields.add("MiscItems");
}
@ -79,6 +84,8 @@ export const sellController: RequestHandler = async (req, res) => {
inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") {
addFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_CrewShipFusionPoints") {
addCrewShipFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [
{
@ -290,12 +297,17 @@ export const sellController: RequestHandler = async (req, res) => {
]);
});
}
if (payload.Items.FusionTreasures) {
payload.Items.FusionTreasures.forEach(sellItem => {
addFusionTreasures(inventory, [parseFusionTreasure(sellItem.String, sellItem.Count * -1)]);
});
}
await inventory.save();
res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
});
sendWsBroadcastTo(accountId, { update_inventory: true });
sendWsBroadcastEx({ update_inventory: true }, accountId, parseInt(String(req.query.wsid)));
};
interface ISellRequest {
@ -322,6 +334,7 @@ interface ISellRequest {
CrewMembers?: ISellItem[];
CrewShipWeapons?: ISellItem[];
CrewShipWeaponSkins?: ISellItem[];
FusionTreasures?: ISellItem[];
};
SellPrice: number;
SellCurrency:
@ -330,7 +343,8 @@ interface ISellRequest {
| "SC_FusionPoints"
| "SC_DistillPoints"
| "SC_CrewShipFusionPoints"
| "SC_Resources";
| "SC_Resources"
| "somethingelsewemightnotknowabout";
buildLabel: string;
}

View File

@ -1,20 +1,17 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
import { logger } from "@/src/utils/logger";
import { IShipDecorationsRequest, IResetShipDecorationsRequest } from "@/src/types/personalRoomsTypes";
import { RequestHandler } from "express";
import { handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
import { handleResetShipDecorations, handleSetShipDecorations } from "@/src/services/shipCustomizationsService";
export const shipDecorationsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
try {
if (req.query.reset == "1") {
const request = JSON.parse(req.body as string) as IResetShipDecorationsRequest;
const response = await handleResetShipDecorations(accountId, request);
res.send(response);
} else {
const shipDecorationsRequest = JSON.parse(req.body as string) as IShipDecorationsRequest;
const placedDecoration = await handleSetShipDecorations(accountId, shipDecorationsRequest);
res.send(placedDecoration);
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(`error in shipDecorationsController: ${error.message}`);
res.status(400).json({ error: error.message });
}
}
};

View File

@ -10,6 +10,7 @@ import { logger } from "@/src/utils/logger";
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
const account = await getAccountForRequest(req);
logger.debug(`challenge report:`, challenges);
const inventory = await getInventory(
account._id.toString(),
@ -17,7 +18,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
);
let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) {
affiliationMods = addChallenges(
affiliationMods = await addChallenges(
account,
inventory,
challenges.ChallengeProgress,

View File

@ -0,0 +1,33 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const abilityOverrideController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as IAbilityOverrideRequest;
if (request.category === "Suits") {
const inventory = await getInventory(accountId, request.category);
const item = inventory[request.category].id(request.oid);
if (item) {
if (request.action == "set") {
item.Configs[request.configIndex].AbilityOverride = request.AbilityOverride;
} else {
item.Configs[request.configIndex].AbilityOverride = undefined;
}
await inventory.save();
}
}
res.end();
};
interface IAbilityOverrideRequest {
category: TEquipmentKey;
oid: string;
action: "set" | "remove";
configIndex: number;
AbilityOverride: {
Ability: string;
Index: number;
};
}

View File

@ -0,0 +1,65 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const changeModularPartsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as IUpdateFingerPrintRequest;
const inventory = await getInventory(accountId, request.category);
const item = inventory[request.category].id(request.oid);
if (item) {
item.ModularParts = request.modularParts;
request.modularParts.forEach(part => {
const categoryMap = mapping[part];
if (categoryMap && categoryMap[request.category]) {
item.ItemType = categoryMap[request.category]!;
}
});
await inventory.save();
}
res.end();
};
interface IUpdateFingerPrintRequest {
category: TEquipmentKey;
oid: string;
modularParts: string[];
}
const mapping: Partial<Record<string, Partial<Record<TEquipmentKey, string>>>> = {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun"
},
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun"
},
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary"
},
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary"
},
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart": {
LongGuns: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
Pistols: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA": {
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit"
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB": {
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit"
},
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC": {
MoaPets: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}
};

View File

@ -1,8 +1,8 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { config, syncConfigWithDatabase } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWriterService";
import { sendWsBroadcastExcept } from "@/src/services/wsService";
import { sendWsBroadcastEx } from "@/src/services/wsService";
export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
@ -25,7 +25,8 @@ export const setConfigController: RequestHandler = async (req, res) => {
const [obj, idx] = configIdToIndexable(id);
obj[idx] = value;
}
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
sendWsBroadcastEx({ config_reloaded: true }, undefined, parseInt(String(req.query.wsid)));
syncConfigWithDatabase();
await saveConfig();
res.end();
} else {

View File

@ -11,6 +11,7 @@ import { GuildMember } from "@/src/models/guildModel";
import { Leaderboard } from "@/src/models/leaderboardModel";
import { deleteGuild } from "@/src/services/guildService";
import { Friendship } from "@/src/models/friendModel";
import { sendWsBroadcastTo } from "@/src/services/wsService";
export const deleteAccountController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
@ -36,5 +37,8 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
Ship.deleteMany({ ShipOwnerId: accountId }),
Stats.deleteOne({ accountOwnerId: accountId })
]);
sendWsBroadcastTo(accountId, { logged_out: true });
res.end();
};

View File

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

View File

@ -1,6 +1,7 @@
import { RequestHandler } from "express";
import { getDict, getItemName, getString } from "@/src/services/itemDataService";
import {
ExportAbilities,
ExportArcanes,
ExportAvionics,
ExportBoosters,
@ -57,6 +58,7 @@ interface ItemLists {
mods: ListedItem[];
Boosters: ListedItem[];
VarziaOffers: ListedItem[];
Abilities: ListedItem[];
//circuitGameModes: ListedItem[];
}
@ -94,7 +96,8 @@ const getItemListsController: RequestHandler = (req, response) => {
EvolutionProgress: [],
mods: [],
Boosters: [],
VarziaOffers: []
VarziaOffers: [],
Abilities: []
/*circuitGameModes: [
{
uniqueName: "Survival",
@ -132,6 +135,12 @@ const getItemListsController: RequestHandler = (req, response) => {
name: getString(item.name, lang),
exalted: item.exalted
});
item.abilities.forEach(ability => {
res.Abilities.push({
uniqueName: ability.uniqueName,
name: getString(ability.name || uniqueName, lang)
});
});
}
for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
@ -144,18 +153,21 @@ const getItemListsController: RequestHandler = (req, response) => {
}
for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
if (item.partType) {
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
res.ModularParts.push({
uniqueName,
name: getString(item.name, lang),
partType: item.partType
});
}
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
if (!uniqueName.split("/")[7]?.startsWith("PvPVariant")) {
// not a pvp variant
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
res.ModularParts.push({
uniqueName,
name: getString(item.name, lang),
partType: item.partType
});
}
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
res.miscitems.push({
uniqueName: uniqueName,
name: getString(item.name, lang)
});
}
}
} else if (item.totalDamage !== 0) {
if (
@ -348,6 +360,13 @@ const getItemListsController: RequestHandler = (req, response) => {
});
}
for (const [uniqueName, ability] of Object.entries(ExportAbilities)) {
res.Abilities.push({
uniqueName,
name: getString(ability.name || uniqueName, lang)
});
}
response.json(res);
};

View File

@ -141,7 +141,7 @@ export const getProfileViewingDataGetController: RequestHandler = async (req, re
}
}
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
combinedStats[arrayName].push(entry as any);
}
}

View File

@ -1,6 +1,7 @@
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
import { Types } from "mongoose";
import { TRarity } from "warframe-public-export-plus";
import { IFusionTreasure } from "@/src/types/inventoryTypes/inventoryTypes";
export const version_compare = (a: string, b: string): number => {
const a_digits = a
@ -51,6 +52,15 @@ export const fromMongoDate = (date: IMongoDate): Date => {
return new Date(parseInt(date.$date.$numberLong));
};
export const parseFusionTreasure = (name: string, count: number): IFusionTreasure => {
const arr = name.split("_");
return {
ItemType: arr[0],
Sockets: parseInt(arr[1], 16),
ItemCount: count
};
};
export type TTraitsPool = Record<
"Colors" | "EyeColors" | "FurPatterns" | "BodyTypes" | "Heads" | "Tails",
{ type: string; rarity: TRarity }[]

View File

@ -1,5 +1,5 @@
// First, init config.
import { config, configPath, loadConfig } from "@/src/services/configService";
import { config, configPath, loadConfig, syncConfigWithDatabase } from "@/src/services/configService";
import fs from "fs";
try {
loadConfig();
@ -7,7 +7,7 @@ try {
if (fs.existsSync("config.json")) {
console.log("Failed to load " + configPath + ": " + (e as Error).message);
} else {
console.log("Failed to load " + configPath + ". You can copy config.json.example to create your config file.");
console.log("Failed to load " + configPath + ". You can copy config-vanilla.json to create your config file.");
}
process.exit(1);
}
@ -18,17 +18,23 @@ logger.info("Starting up...");
// Proceed with normal startup: bring up config watcher service, validate config, connect to MongoDB, and finally start listening for HTTP.
import mongoose from "mongoose";
import path from "path";
import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "@/src/services/webService";
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
import { validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "@/src/services/worldStateService";
import { repoDir } from "@/src/helpers/pathHelper";
// Patch JSON.stringify to work flawlessly with Bigints.
JSON.stringify = JSONStringify;
JSON.stringify = JSONStringify; // Patch JSON.stringify to work flawlessly with Bigints.
validateConfig();
fs.readFile(path.join(repoDir, "BUILD_DATE"), "utf-8", (err, data) => {
if (!err) {
logger.info(`Docker image was built on ${data.trim()}`);
}
});
mongoose
.connect(config.mongodbUrl)
.then(() => {

View File

@ -1,16 +1,11 @@
import { NextFunction, Request, Response } from "express";
import { logger } from "@/src/utils/logger";
import { logError } from "@/src/utils/logger";
export const errorHandler = (err: Error, req: Request, res: Response, _next: NextFunction): void => {
if (err.message == "Invalid accountId-nonce pair") {
res.status(400).send("Log-in expired");
} else if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while processing ${req.path} request`;
logger.error(stackArr.join("\n"));
res.status(500).end();
} else {
logger.error(`uncaught error while processing ${req.path} request: ${err.message}`);
logError(err, `processing ${req.path} request`);
res.status(500).end();
}
};

View File

@ -4,8 +4,12 @@ import { typeCountSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IMongoDate, IOid, ITypeCount } from "@/src/types/commonTypes";
export interface IMessageClient
extends Omit<IMessageDatabase, "_id" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"> {
extends Omit<
IMessageDatabase,
"_id" | "globaUpgradeId" | "date" | "startDate" | "endDate" | "ownerId" | "attVisualOnly" | "expiry"
> {
_id?: IOid;
globaUpgradeId?: IOid; // [sic]
date: IMongoDate;
startDate?: IMongoDate;
endDate?: IMongoDate;
@ -14,6 +18,7 @@ export interface IMessageClient
export interface IMessageDatabase extends IMessage {
ownerId: Types.ObjectId;
globaUpgradeId?: Types.ObjectId; // [sic]
date: Date; //created at
attVisualOnly?: boolean;
_id: Types.ObjectId;
@ -101,6 +106,7 @@ const giftSchema = new Schema<IGift>(
const messageSchema = new Schema<IMessageDatabase>(
{
ownerId: Schema.Types.ObjectId,
globaUpgradeId: Schema.Types.ObjectId,
sndr: String,
msg: String,
cinematic: String,
@ -144,7 +150,7 @@ messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
messageSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
const messageDatabase = returnedObject as IMessageDatabase;
const messageClient = returnedObject as IMessageClient;
@ -154,6 +160,10 @@ messageSchema.set("toJSON", {
delete returnedObject.attVisualOnly;
delete returnedObject.expiry;
if (messageDatabase.globaUpgradeId) {
messageClient.globaUpgradeId = toOid(messageDatabase.globaUpgradeId);
}
messageClient.date = toMongoDate(messageDatabase.date);
if (messageDatabase.startDate && messageDatabase.endDate) {

View File

@ -121,7 +121,7 @@ import {
export const typeCountSchema = new Schema<ITypeCount>({ ItemType: String, ItemCount: Number }, { _id: false });
typeCountSchema.set("toJSON", {
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
if (obj.ItemCount > 2147483647) {
obj.ItemCount = 2147483647;
} else if (obj.ItemCount < -2147483648) {
@ -189,7 +189,7 @@ operatorConfigSchema.virtual("ItemId").get(function () {
operatorConfigSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -226,7 +226,7 @@ const ItemConfigSchema = new Schema<IItemConfig>(
);
ItemConfigSchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject.__v;
}
});
@ -261,7 +261,7 @@ RawUpgrades.virtual("LastAdded").get(function () {
RawUpgrades.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -282,7 +282,7 @@ upgradeSchema.virtual("ItemId").get(function () {
upgradeSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -325,7 +325,7 @@ const crewMemberSchema = new Schema<ICrewMemberDatabase>(
crewMemberSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as ICrewMemberDatabase;
const client = obj as ICrewMemberClient;
@ -353,7 +353,7 @@ const FlavourItemSchema = new Schema(
);
FlavourItemSchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -367,7 +367,7 @@ FlavourItemSchema.set("toJSON", {
);
MailboxSchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
const mailboxDatabase = returnedObject as HydratedDocument<IMailboxDatabase, { __v?: number }>;
delete mailboxDatabase.__v;
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
@ -386,7 +386,7 @@ const DuviriInfoSchema = new Schema<IDuviriInfo>(
);
DuviriInfoSchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject.__v;
}
});
@ -416,7 +416,7 @@ const droneSchema = new Schema<IDroneDatabase>(
);
droneSchema.set("toJSON", {
virtuals: true,
transform(_document, obj) {
transform(_document, obj: Record<string, any>) {
const client = obj as IDroneClient;
const db = obj as IDroneDatabase;
@ -457,7 +457,7 @@ const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
personalGoalProgressSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as IPersonalGoalProgressDatabase;
const client = obj as IPersonalGoalProgressClient;
@ -502,7 +502,7 @@ StepSequencersSchema.virtual("ItemId").get(function () {
StepSequencersSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
}
@ -516,7 +516,7 @@ const kubrowPetEggSchema = new Schema<IKubrowPetEggDatabase>(
);
kubrowPetEggSchema.set("toJSON", {
virtuals: true,
transform(_document, obj) {
transform(_document, obj: Record<string, any>) {
const client = obj as IKubrowPetEggClient;
const db = obj as IKubrowPetEggDatabase;
@ -586,7 +586,7 @@ personalTechProjectSchema.virtual("ItemId").get(function () {
personalTechProjectSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
delete ret._id;
delete ret.__v;
@ -687,7 +687,7 @@ const questKeysSchema = new Schema<IQuestKeyDatabase>(
);
questKeysSchema.set("toJSON", {
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
const questKeysDatabase = ret as IQuestKeyDatabase;
if (questKeysDatabase.CompletionDate) {
@ -709,7 +709,7 @@ const invasionProgressSchema = new Schema<IInvasionProgressDatabase>(
);
invasionProgressSchema.set("toJSON", {
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as IInvasionProgressDatabase;
const client = obj as IInvasionProgressClient;
@ -748,7 +748,7 @@ weaponSkinsSchema.virtual("ItemId").get(function () {
weaponSkinsSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
delete ret._id;
delete ret.__v;
}
@ -772,7 +772,7 @@ const periodicMissionCompletionsSchema = new Schema<IPeriodicMissionCompletionDa
);
periodicMissionCompletionsSchema.set("toJSON", {
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
const periodicMissionCompletionDatabase = ret as IPeriodicMissionCompletionDatabase;
(periodicMissionCompletionDatabase as unknown as IPeriodicMissionCompletionResponse).date = toMongoDate(
@ -849,7 +849,7 @@ const endlessXpProgressSchema = new Schema<IEndlessXpProgressDatabase>(
);
endlessXpProgressSchema.set("toJSON", {
transform(_doc, ret) {
transform(_doc, ret: Record<string, any>) {
const db = ret as IEndlessXpProgressDatabase;
const client = ret as IEndlessXpProgressClient;
@ -898,7 +898,7 @@ const crewShipMemberSchema = new Schema<ICrewShipMemberDatabase>(
);
crewShipMemberSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as ICrewShipMemberDatabase;
const client = obj as ICrewShipMemberClient;
if (db.ItemId) {
@ -951,7 +951,7 @@ const dialogueSchema = new Schema<IDialogueDatabase>(
);
dialogueSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret) {
transform(_doc, ret: Record<string, any>) {
const db = ret as IDialogueDatabase;
const client = ret as IDialogueClient;
@ -997,7 +997,7 @@ const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
});
kubrowPetPrintSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as IKubrowPetPrintDatabase;
const client = obj as IKubrowPetPrintClient;
@ -1025,7 +1025,7 @@ const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
);
detailsSchema.set("toJSON", {
transform(_doc, returnedObject) {
transform(_doc, returnedObject: Record<string, any>) {
delete returnedObject.__v;
const db = returnedObject as IKubrowPetDetailsDatabase;
@ -1081,7 +1081,7 @@ EquipmentSchema.virtual("ItemId").get(function () {
EquipmentSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
@ -1132,7 +1132,7 @@ pendingRecipeSchema.virtual("ItemId").get(function () {
pendingRecipeSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.LongGuns;
@ -1170,7 +1170,7 @@ const infestedFoundrySchema = new Schema<IInfestedFoundryDatabase>(
);
infestedFoundrySchema.set("toJSON", {
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
if (ret.AbilityOverrideUnlockCooldown) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
ret.AbilityOverrideUnlockCooldown = toMongoDate(ret.AbilityOverrideUnlockCooldown);
@ -1243,7 +1243,7 @@ const vendorPurchaseHistoryEntrySchema = new Schema<IVendorPurchaseHistoryEntryD
);
vendorPurchaseHistoryEntrySchema.set("toJSON", {
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as IVendorPurchaseHistoryEntryDatabase;
const client = obj as IVendorPurchaseHistoryEntryClient;
client.Expiry = toMongoDate(db.Expiry);
@ -1286,7 +1286,7 @@ const pendingCouponSchema = new Schema<IPendingCouponDatabase>(
);
pendingCouponSchema.set("toJSON", {
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
(ret as IPendingCouponClient).Expiry = toMongoDate((ret as IPendingCouponDatabase).Expiry);
}
});
@ -1353,7 +1353,7 @@ const nemesisSchema = new Schema<INemesisDatabase>(
nemesisSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as INemesisDatabase;
const client = obj as INemesisClient;
@ -1383,7 +1383,7 @@ const lastSortieRewardSchema = new Schema<ILastSortieRewardDatabase>(
lastSortieRewardSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as ILastSortieRewardDatabase;
const client = obj as ILastSortieRewardClient;
@ -1437,6 +1437,8 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PremiumCreditsFree: { type: Number, default: 0 },
//Endo
FusionPoints: { type: Number, default: 0 },
//Dirac
CrewShipFusionPoints: { type: Number, default: 0 },
//Regal Aya
PrimeTokens: { type: Number, default: 0 },
@ -1790,7 +1792,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
);
inventorySchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;

View File

@ -49,7 +49,7 @@ loadoutConfigSchema.virtual("ItemId").get(function () {
loadoutConfigSchema.set("toJSON", {
virtuals: true,
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
delete ret._id;
delete ret.__v;
}
@ -62,6 +62,7 @@ export const loadoutSchema = new Schema<ILoadoutDatabase, loadoutModelType>({
NORMAL_PVP: [loadoutConfigSchema],
LUNARO: [loadoutConfigSchema],
OPERATOR: [loadoutConfigSchema],
GEAR: [loadoutConfigSchema],
KDRIVE: [loadoutConfigSchema],
DATAKNIFE: [loadoutConfigSchema],
MECH: [loadoutConfigSchema],
@ -71,7 +72,7 @@ export const loadoutSchema = new Schema<ILoadoutDatabase, loadoutModelType>({
});
loadoutSchema.set("toJSON", {
transform(_doc, ret, _options) {
transform(_doc, ret: Record<string, any>) {
delete ret._id;
delete ret.__v;
delete ret.loadoutOwnerId;
@ -88,6 +89,7 @@ type loadoutDocumentProps = {
NORMAL_PVP: Types.DocumentArray<ILoadoutConfigDatabase>;
LUNARO: Types.DocumentArray<ILoadoutConfigDatabase>;
OPERATOR: Types.DocumentArray<ILoadoutConfigDatabase>;
GEAR: Types.DocumentArray<ILoadoutConfigDatabase>;
KDRIVE: Types.DocumentArray<ILoadoutConfigDatabase>;
DATAKNIFE: Types.DocumentArray<ILoadoutConfigDatabase>;
MECH: Types.DocumentArray<ILoadoutConfigDatabase>;

View File

@ -32,7 +32,7 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
);
databaseAccountSchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
},

View File

@ -55,7 +55,7 @@ placedDecosSchema.virtual("id").get(function (this: IPlacedDecosDatabase) {
placedDecosSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
}
});
@ -78,7 +78,7 @@ const favouriteLoadoutSchema = new Schema<IFavouriteLoadoutDatabase>(
);
favouriteLoadoutSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
returnedObject.LoadoutId = toOid(returnedObject.LoadoutId);
}
@ -95,7 +95,7 @@ const plantSchema = new Schema<IPlantDatabase>(
plantSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const client = obj as IPlantClient;
const db = obj as IPlantDatabase;
@ -122,7 +122,9 @@ const apartmentSchema = new Schema<IApartmentDatabase>(
{
Rooms: [roomSchema],
FavouriteLoadouts: [favouriteLoadoutSchema],
Gardening: gardeningSchema
Gardening: gardeningSchema,
VideoWallBackdrop: String,
Soundscape: String
},
{ _id: false }
);
@ -156,7 +158,7 @@ const orbiterSchema = new Schema<IOrbiterDatabase>(
);
orbiterSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
transform(_doc, obj: Record<string, any>) {
const db = obj as IOrbiterDatabase;
const client = obj as IOrbiterClient;

View File

@ -22,7 +22,7 @@ shipSchema.virtual("ItemId").get(function () {
shipSchema.set("toJSON", {
virtuals: true,
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
const shipResponse = returnedObject as IShipInventory;
const shipDatabase = returnedObject as IShipDatabase;
delete returnedObject._id;

View File

@ -101,7 +101,7 @@ const statsSchema = new Schema<IStatsDatabase>({
});
statsSchema.set("toJSON", {
transform(_document, returnedObject) {
transform(_document, returnedObject: Record<string, any>) {
delete returnedObject._id;
delete returnedObject.__v;
delete returnedObject.accountOwnerId;

View File

@ -10,6 +10,7 @@ import { addPendingFriendController } from "@/src/controllers/api/addPendingFrie
import { addToAllianceController } from "@/src/controllers/api/addToAllianceController";
import { addToGuildController } from "@/src/controllers/api/addToGuildController";
import { adoptPetController } from "@/src/controllers/api/adoptPetController";
import { apartmentController } from "@/src/controllers/api/apartmentController";
import { arcaneCommonController } from "@/src/controllers/api/arcaneCommonController";
import { archonFusionController } from "@/src/controllers/api/archonFusionController";
import { artifactsController } from "@/src/controllers/api/artifactsController";
@ -168,6 +169,7 @@ const apiRouter = express.Router();
// get
apiRouter.get("/abandonLibraryDailyTask.php", abandonLibraryDailyTaskController);
apiRouter.get("/abortDojoComponentDestruction.php", abortDojoComponentDestructionController);
apiRouter.get("/apartment.php", apartmentController);
apiRouter.get("/cancelGuildAdvertisement.php", cancelGuildAdvertisementController);
apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
apiRouter.get("/changeGuildRank.php", changeGuildRankController);

View File

@ -15,6 +15,7 @@ import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webu
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
import { abilityOverrideController } from "@/src/controllers/custom/abilityOverrideController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController";
import { addCurrencyController } from "@/src/controllers/custom/addCurrencyController";
@ -25,6 +26,8 @@ import { manageQuestsController } from "@/src/controllers/custom/manageQuestsCon
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { setBoosterController } from "@/src/controllers/custom/setBoosterController";
import { updateFingerprintController } from "@/src/controllers/custom/updateFingerprintController";
import { changeModularPartsController } from "@/src/controllers/custom/changeModularPartsController";
import { editSuitInvigorationUpgradeController } from "@/src/controllers/custom/editSuitInvigorationUpgradeController";
import { getConfigController, setConfigController } from "@/src/controllers/custom/configController";
@ -45,6 +48,7 @@ customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.post("/abilityOverride", abilityOverrideController);
customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController);
customRouter.post("/addCurrency", addCurrencyController);
@ -55,6 +59,8 @@ customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController);
customRouter.post("/updateFingerprint", updateFingerprintController);
customRouter.post("/changeModularParts", changeModularPartsController);
customRouter.post("/editSuitInvigorationUpgrade", editSuitInvigorationUpgradeController);
customRouter.post("/getConfig", getConfigController);
customRouter.post("/setConfig", setConfigController);

View File

@ -2,6 +2,7 @@ import fs from "fs";
import path from "path";
import { repoDir } from "@/src/helpers/pathHelper";
import { args } from "@/src/helpers/commandLineArguments";
import { Inbox } from "@/src/models/inboxModel";
export interface IConfig {
mongodbUrl: string;
@ -80,8 +81,10 @@ export interface IConfig {
creditBoost?: boolean;
affinityBoost?: boolean;
resourceBoost?: boolean;
starDays?: boolean;
tennoLiveRelay?: boolean;
baroTennoConRelay?: boolean;
galleonOfGhouls?: number;
starDaysOverride?: boolean;
eidolonOverride?: string;
vallisOverride?: string;
duviriOverride?: string;
@ -113,9 +116,26 @@ export const loadConfig = (): void => {
// Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory.
for (const key of Object.keys(config)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(config as any)[key] = undefined;
}
Object.assign(config, newConfig);
};
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
// Also, for some reason, I can't just do `Inbox.deleteMany(...)`; - it needs this whole circus.
if (!config.worldState?.creditBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666672" }).then(() => {});
}
if (!config.worldState?.affinityBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666673" }).then(() => {});
}
if (!config.worldState?.resourceBoost) {
void Inbox.deleteMany({ globaUpgradeId: "5b23106f283a555109666674" }).then(() => {});
}
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {});
}
};

View File

@ -1,10 +1,9 @@
import chokidar from "chokidar";
import { logger } from "@/src/utils/logger";
import { config, configPath, loadConfig } from "@/src/services/configService";
import { config, configPath, loadConfig, syncConfigWithDatabase } from "@/src/services/configService";
import { saveConfig, shouldReloadConfig } from "@/src/services/configWriterService";
import { getWebPorts, startWebServer, stopWebServer } from "@/src/services/webService";
import { sendWsBroadcast } from "@/src/services/wsService";
import { Inbox } from "@/src/models/inboxModel";
import varzia from "@/static/fixed_responses/worldState/varzia.json";
chokidar.watch(configPath).on("change", () => {
@ -68,10 +67,3 @@ export const validateConfig = (): void => {
void saveConfig();
}
};
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {}); // For some reason, I can't just do `Inbox.deleteMany(...)`; it needs this whole circus.
}
};

View File

@ -13,6 +13,7 @@ import {
IDojoComponentDatabase,
IDojoContributable,
IDojoDecoClient,
IDojoDecoDatabase,
IGuildClient,
IGuildMemberClient,
IGuildMemberDatabase,
@ -309,7 +310,7 @@ export const removeDojoRoom = async (
guild.DojoEnergy -= meta.energy;
}
moveResourcesToVault(guild, component);
component.Decos?.forEach(deco => moveResourcesToVault(guild, deco));
component.Decos?.forEach(deco => refundDojoDeco(guild, component, deco));
if (guild.RoomChanges) {
const index = guild.RoomChanges.findIndex(x => x.componentId.equals(component._id));
@ -344,6 +345,14 @@ export const removeDojoDeco = (
component.Decos!.findIndex(x => x._id.equals(decoId)),
1
)[0];
refundDojoDeco(guild, component, deco);
};
export const refundDojoDeco = (
guild: TGuildDatabaseDocument,
component: IDojoComponentDatabase,
deco: IDojoDecoDatabase
): void => {
const meta = Object.values(ExportDojoRecipes.decos).find(x => x.resultType == deco.Type);
if (meta) {
if (meta.capacityCost) {
@ -369,7 +378,7 @@ export const removeDojoDeco = (
]);
}
}
moveResourcesToVault(guild, deco);
moveResourcesToVault(guild, deco); // Refund resources spent on construction
};
const moveResourcesToVault = (guild: TGuildDatabaseDocument, component: IDojoContributable): void => {

View File

@ -422,6 +422,7 @@ export const importLoadOutPresets = (db: ILoadoutDatabase, client: ILoadOutPrese
db.NORMAL_PVP = client.NORMAL_PVP.map(convertLoadOutConfig);
db.LUNARO = client.LUNARO.map(convertLoadOutConfig);
db.OPERATOR = client.OPERATOR.map(convertLoadOutConfig);
db.GEAR = client.GEAR.map(convertLoadOutConfig);
db.KDRIVE = client.KDRIVE.map(convertLoadOutConfig);
db.DATAKNIFE = client.DATAKNIFE.map(convertLoadOutConfig);
db.MECH = client.MECH.map(convertLoadOutConfig);

View File

@ -35,7 +35,7 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
if (account.LatestEventMessageDate.getTime() < baroActualStart) {
if (Date.now() >= baroActualStart && account.LatestEventMessageDate.getTime() < baroActualStart) {
newEventMessages.push({
sndr: "/Lotus/Language/G1Quests/VoidTraderName",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
@ -55,20 +55,77 @@ export const createNewEventMessages = async (req: Request): Promise<void> => {
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
if (config.worldState?.galleonOfGhouls) {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
const promises = [];
if (config.worldState?.creditBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666672" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666672"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleCreditsName",
msg: "/Lotus/Language/Items/EventDoubleCreditsDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.affinityBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666673" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666673"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleAffinityName",
msg: "/Lotus/Language/Items/EventDoubleAffinityDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.resourceBoost) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, globaUpgradeId: "5b23106f283a555109666674" }))) {
newEventMessages.push({
globaUpgradeId: new Types.ObjectId("5b23106f283a555109666674"),
sndr: "/Lotus/Language/Menu/Mailbox_WarframeSender",
sub: "/Lotus/Language/Items/EventDoubleResourceName",
msg: "/Lotus/Language/Items/EventDoubleResourceDesc",
icon: "/Lotus/Interface/Icons/Npcs/Lotus_d.png",
startDate: new Date(),
CrossPlatform: true
});
}
})()
);
}
if (config.worldState?.galleonOfGhouls) {
promises.push(
(async (): Promise<void> => {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
})()
);
}
await Promise.all(promises);
if (newEventMessages.length === 0) {
return;

View File

@ -25,7 +25,8 @@ import {
INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint,
IDialogueDatabase,
IKubrowPetPrintClient
IKubrowPetPrintClient,
equipmentKeys
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "@/src/types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
@ -1241,6 +1242,15 @@ export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: numb
return add;
};
export const addCrewShipFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
if (inventory.CrewShipFusionPoints + add > 2147483647) {
logger.warn(`capping CrewShipFusionPoints balance at 2147483647`);
add = 2147483647 - inventory.CrewShipFusionPoints;
}
inventory.CrewShipFusionPoints += add;
return add;
};
const standingLimitBinToInventoryKey: Record<
Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">,
keyof IDailyAffiliations
@ -1332,7 +1342,7 @@ export const addStanding = (
// TODO: AffiliationMods support (Nightwave).
export const updateGeneric = async (data: IGenericUpdate, accountId: string): Promise<IUpdateNodeIntrosResponse> => {
const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems");
const inventory = await getInventory(accountId, "NodeIntrosCompleted MiscItems ShipDecorations");
// Make it an array for easier parsing.
if (typeof data.NodeIntrosCompleted === "string") {
@ -1341,7 +1351,15 @@ export const updateGeneric = async (data: IGenericUpdate, accountId: string): Pr
const inventoryChanges: IInventoryChanges = {};
for (const node of data.NodeIntrosCompleted) {
if (node == "KayaFirstVisitPack") {
if (node == "TC2025") {
inventoryChanges.ShipDecorations = [
{
ItemType: "/Lotus/Types/Items/ShipDecos/TauGrineerLancerBobbleHead",
ItemCount: 1
}
];
addShipDecorations(inventory, inventoryChanges.ShipDecorations);
} else if (node == "KayaFirstVisitPack") {
inventoryChanges.MiscItems = [
{
ItemType: "/Lotus/Types/Items/MiscItems/1999FixedStickersPack",
@ -1662,28 +1680,34 @@ export const applyClientEquipmentUpdates = (
item.XP ??= 0;
item.XP += XP;
let xpItemType = item.ItemType;
if (item.ModularParts) {
for (const part of item.ModularParts) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const partType = ExportWeapons[part]?.partType;
if (partType !== undefined && xpEarningParts.indexOf(partType) != -1) {
xpItemType = part;
break;
if (
categoryName != "SpecialItems" ||
item.ItemType == "/Lotus/Powersuits/Khora/Kavat/KhoraKavatPowerSuit" ||
item.ItemType == "/Lotus/Powersuits/Khora/Kavat/KhoraPrimeKavatPowerSuit"
) {
let xpItemType = item.ItemType;
if (item.ModularParts) {
for (const part of item.ModularParts) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const partType = ExportWeapons[part]?.partType;
if (partType !== undefined && xpEarningParts.indexOf(partType) != -1) {
xpItemType = part;
break;
}
}
logger.debug(`adding xp to ${xpItemType} for modular item ${fromOid(ItemId)} (${item.ItemType})`);
}
logger.debug(`adding xp to ${xpItemType} for modular item ${fromOid(ItemId)} (${item.ItemType})`);
}
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == xpItemType);
if (xpinfoIndex !== -1) {
const xpinfo = inventory.XPInfo[xpinfoIndex];
xpinfo.XP += XP;
} else {
inventory.XPInfo.push({
ItemType: xpItemType,
XP: XP
});
const xpinfoIndex = inventory.XPInfo.findIndex(x => x.ItemType == xpItemType);
if (xpinfoIndex !== -1) {
const xpinfo = inventory.XPInfo[xpinfoIndex];
xpinfo.XP += XP;
} else {
inventory.XPInfo.push({
ItemType: xpItemType,
XP: XP
});
}
}
}
@ -1888,25 +1912,87 @@ export const addLoreFragmentScans = (inventory: TInventoryDatabaseDocument, arr:
});
};
export const addChallenges = (
const challengeRewardsInboxMessages: Record<string, IMessageCreationTemplate> = {
SentEvoEphemeraRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Effects/NarmerEvolvingEphemeraB"]
},
SentEvoEphemeraRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingEphemeraUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Effects/NarmerEvolvingEphemeraC"]
},
SentEvoSyandanaRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Scarves/NarmerEvolvingSyandanaBCape"]
},
SentEvoSyandanaRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSyandanaUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Scarves/NarmerEvolvingSyandanaCCape"]
},
SentEvoSekharaRankOne: {
sub: "/Lotus/Language/Inbox/EvolvingSekharaUnlockAName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSekharaUnlockADesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Clan/ZarimanEvolvingSekharaBadgeItemB"]
},
SentEvoSekharaRankTwo: {
sub: "/Lotus/Language/Inbox/EvolvingSekharaUnlockBName",
sndr: "/Lotus/Language/Bosses/Ordis",
msg: "/Lotus/Language/Inbox/EvolvingSekharaUnlockBDesc",
icon: "/Lotus/Interface/Icons/Npcs/Ordis.png",
att: ["/Lotus/Upgrades/Skins/Clan/ZarimanEvolvingSekharaBadgeItemC"]
}
};
export const addChallenges = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
ChallengeProgress: IChallengeProgress[],
SeasonChallengeCompletions: ISeasonChallenge[] | undefined
): IAffiliationMods[] => {
ChallengeProgress.forEach(({ Name, Progress }) => {
const itemIndex = inventory.ChallengeProgress.findIndex(i => i.Name === Name);
if (itemIndex !== -1) {
inventory.ChallengeProgress[itemIndex].Progress = Progress;
): Promise<IAffiliationMods[]> => {
for (const { Name, Progress, Completed } of ChallengeProgress) {
let dbChallenge = inventory.ChallengeProgress.find(x => x.Name == Name);
if (dbChallenge) {
dbChallenge.Progress = Progress;
} else {
inventory.ChallengeProgress.push({ Name, Progress });
dbChallenge = { Name, Progress };
inventory.ChallengeProgress.push(dbChallenge);
}
if (Name.startsWith("Calendar")) {
addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name);
}
});
if ((Completed?.length ?? 0) > (dbChallenge.Completed?.length ?? 0)) {
dbChallenge.Completed ??= [];
for (const completion of Completed!) {
if (dbChallenge.Completed.indexOf(completion) == -1) {
if (completion == "challengeRewards") {
if (Name in challengeRewardsInboxMessages) {
await createMessage(account._id, [challengeRewardsInboxMessages[Name]]);
dbChallenge.Completed.push(completion);
// Would love to somehow let the client know about inbox or inventory changes, but there doesn't seem to anything for updateChallengeProgress.
continue;
}
}
logger.warn(`ignoring unknown challenge completion`, { challenge: Name, completion });
}
}
}
}
const affiliationMods: IAffiliationMods[] = [];
if (SeasonChallengeCompletions) {
@ -2102,6 +2188,21 @@ export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void =>
inventory.LotusCustomization.syancol = {};
}
}
{
let numFixed = 0;
for (const equipmentKey of equipmentKeys) {
for (const item of inventory[equipmentKey]) {
if (item.ModularParts?.length === 0) {
item.ModularParts = undefined;
++numFixed;
}
}
}
if (numFixed != 0) {
logger.debug(`removed ModularParts from ${numFixed} non-modular items`);
}
}
};
export const getDialogue = (inventory: TInventoryDatabaseDocument, dialogueName: string): IDialogueDatabase => {

View File

@ -32,7 +32,7 @@ export const getUsernameFromEmail = async (email: string): Promise<string> => {
name = nameFromEmail + suffix;
} while (await isNameTaken(name));
}
return nameFromEmail;
return name;
};
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {

View File

@ -292,7 +292,7 @@ export const addMissionInventoryUpdates = async (
addRecipes(inventory, value);
break;
case "ChallengeProgress":
addChallenges(account, inventory, value, inventoryUpdates.SeasonChallengeCompletions);
await addChallenges(account, inventory, value, inventoryUpdates.SeasonChallengeCompletions);
break;
case "FusionTreasures":
addFusionTreasures(inventory, value);
@ -972,7 +972,8 @@ export const addMissionRewards = async (
Missions: missions,
RegularCredits: creditDrops,
VoidTearParticipantsCurrWave: voidTearWave,
StrippedItems: strippedItems
StrippedItems: strippedItems,
AffiliationChanges: AffiliationMods
}: IMissionInventoryUpdateRequest,
firstCompletion: boolean
): Promise<AddMissionRewardsReturnType> => {
@ -992,7 +993,6 @@ export const addMissionRewards = async (
);
logger.debug("random mission drops:", MissionRewards);
const inventoryChanges: IInventoryChanges = {};
const AffiliationMods: IAffiliationMods[] = [];
let SyndicateXPItemReward;
let ConquestCompletedMissionsCount;
@ -1283,9 +1283,7 @@ export const addMissionRewards = async (
}
}
}
let medallionAmount = Math.floor(
Math.min(rewardInfo.JobStage, currentJob.xpAmounts.length - 1) / (rewardInfo.Q ? 0.8 : 1)
);
let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1));
if (
["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some(
ending => jobType.endsWith(ending)
@ -1304,6 +1302,9 @@ export const addMissionRewards = async (
ItemCount: medallionAmount
});
SyndicateXPItemReward = medallionAmount;
logger.debug(
`Giving ${medallionAmount} medallions for the ${rewardInfo.JobStage} stage of the ${rewardInfo.JobTier} tier bounty`
);
} else {
if (rewardInfo.JobTier! >= 0) {
addStanding(
@ -1333,6 +1334,9 @@ export const addMissionRewards = async (
}
}
}
if (jobType == "/Lotus/Types/Gameplay/Eidolon/Jobs/NewbieJob") {
addStanding(inventory, "CetusSyndicate", Math.floor(200 / (rewardInfo.Q ? 0.8 : 1)), AffiliationMods);
}
}
if (rewardInfo.challengeMissionId) {
@ -1349,6 +1353,7 @@ export const addMissionRewards = async (
ItemCount: medallionAmount
});
SyndicateXPItemReward = medallionAmount;
logger.debug(`Giving ${medallionAmount} medallions for the ${tier} tier bounty`);
} else {
let standingAmount = (tier + 1) * 1000;
if (tier > 5) standingAmount = 7500; // InfestedLichBounty
@ -1637,7 +1642,10 @@ function getRandomMissionDrops(
rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
];
} else if (RewardInfo.T == 70) {
} else if (
RewardInfo.T == 70 ||
RewardInfo.T == 6 // https://onlyg.it/OpenWF/SpaceNinjaServer/issues/2526
) {
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
@ -1771,6 +1779,11 @@ function getRandomMissionDrops(
}
}
}
if (jobType == "/Lotus/Types/Gameplay/Eidolon/Jobs/NewbieJob") {
rewardManifests = ["/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATableARewards"];
rotations = [3];
if (RewardInfo.Q) rotations.push(3);
}
}
} else if (RewardInfo.challengeMissionId) {
const rewardTables: Record<string, string[]> = {

View File

@ -581,7 +581,7 @@ const handleBoosterPackPurchase = async (
purchaseResponse.InventoryChanges,
await addItem(inventory, specialItemReward.Item)
);
// TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now?
atten.Atten = 0;
} else {
atten.Atten += specialItemReward.PityIncreaseRate!;
}

View File

@ -1,6 +1,8 @@
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService";
import {
IResetShipDecorationsRequest,
IResetShipDecorationsResponse,
ISetPlacedDecoInfoRequest,
ISetShipCustomizationsRequest,
IShipDecorationsRequest,
@ -52,12 +54,7 @@ export const handleSetShipDecorations = async (
): Promise<IShipDecorationsResponse> => {
const personalRooms = await getPersonalRooms(accountId);
const rooms =
placedDecoration.BootLocation == "SHOP"
? personalRooms.TailorShop.Rooms
: placedDecoration.IsApartment
? personalRooms.Apartment.Rooms
: personalRooms.Ship.Rooms;
const rooms = getRoomsForBootLocation(personalRooms, placedDecoration);
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
@ -159,7 +156,6 @@ export const handleSetShipDecorations = async (
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
} else {
@ -192,17 +188,62 @@ export const handleSetShipDecorations = async (
const getRoomsForBootLocation = (
personalRooms: TPersonalRoomsDatabaseDocument,
bootLocation: TBootLocation | undefined
request: { BootLocation?: TBootLocation; IsApartment?: boolean }
): RoomsType[] => {
if (bootLocation == "SHOP") {
if (request.BootLocation == "SHOP") {
return personalRooms.TailorShop.Rooms;
}
if (bootLocation == "APARTMENT") {
if (request.BootLocation == "APARTMENT" || request.IsApartment) {
return personalRooms.Apartment.Rooms;
}
return personalRooms.Ship.Rooms;
};
export const handleResetShipDecorations = async (
accountId: string,
request: IResetShipDecorationsRequest
): Promise<IResetShipDecorationsResponse> => {
const [personalRooms, inventory] = await Promise.all([getPersonalRooms(accountId), getInventory(accountId)]);
const room = getRoomsForBootLocation(personalRooms, request).find(room => room.Name === request.Room);
if (!room) {
throw new Error(`unknown room: ${request.Room}`);
}
for (const deco of room.PlacedDecos) {
const entry = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type);
if (!entry) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
const [itemType, meta] = entry;
if (meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
// refund item
if (!config.unlockAllShipDecorations) {
if (deco.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
}
}
// refund capacity
room.MaxCapacity += meta.capacityCost;
}
// empty room
room.PlacedDecos.splice(0, room.PlacedDecos.length);
await Promise.all([personalRooms.save(), inventory.save()]);
return {
ResetRoom: request.Room,
ClaimedDecos: [], // Not sure what this is for; the client already implies that the decos were returned to inventory.
NewCapacity: room.MaxCapacity
};
};
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {
if (req.GuildId && req.ComponentId) {
const guild = (await Guild.findById(req.GuildId))!;
@ -217,7 +258,7 @@ export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlaced
const personalRooms = await getPersonalRooms(accountId);
const room = getRoomsForBootLocation(personalRooms, req.BootLocation).find(room => room.Name === req.Room);
const room = getRoomsForBootLocation(personalRooms, req).find(room => room.Name === req.Room);
if (!room) {
throw new Error(`unknown room: ${req.Room}`);
}

View File

@ -261,7 +261,8 @@ export const getSortie = (day: number): ISortie => {
if (
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
key in sortieTilesets
key in sortieTilesets &&
(key != "SolNode228" || sortieBossToFaction[boss] == "FC_GRINEER") // PoE does not work for non-infested enemies
) {
nodes.push(key);
}
@ -1037,13 +1038,13 @@ const pushVoidStorms = (arr: IVoidStorm[], hour: number): void => {
};
interface ITimeConstraint {
//name: string;
name: string;
isValidTime: (timeSecs: number) => boolean;
getIdealTimeBefore: (timeSecs: number) => number;
}
const eidolonDayConstraint: ITimeConstraint = {
//name: "eidolon day",
name: "eidolon day",
isValidTime: (timeSecs: number): boolean => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
@ -1061,7 +1062,7 @@ const eidolonDayConstraint: ITimeConstraint = {
};
const eidolonNightConstraint: ITimeConstraint = {
//name: "eidolon night",
name: "eidolon night",
isValidTime: (timeSecs: number): boolean => {
const eidolonEpoch = 1391992660;
const eidolonCycle = Math.trunc((timeSecs - eidolonEpoch) / 9000);
@ -1088,7 +1089,7 @@ const eidolonNightConstraint: ITimeConstraint = {
};
const venusColdConstraint: ITimeConstraint = {
//name: "venus cold",
name: "venus cold",
isValidTime: (timeSecs: number): boolean => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
@ -1114,7 +1115,7 @@ const venusColdConstraint: ITimeConstraint = {
};
const venusWarmConstraint: ITimeConstraint = {
//name: "venus warm",
name: "venus warm",
isValidTime: (timeSecs: number): boolean => {
const vallisEpoch = 1541837628;
const vallisCycle = Math.trunc((timeSecs - vallisEpoch) / 1600);
@ -1156,6 +1157,25 @@ const getIdealTimeSatsifyingConstraints = (constraints: ITimeConstraint[]): numb
return timeSecs;
};
const fullyStockBaro = (vt: IVoidTrader): void => {
for (const armorSet of baro.armorSets) {
if (Array.isArray(armorSet[0])) {
for (const set of armorSet as IVoidTraderOffer[][]) {
for (const item of set) {
vt.Manifest.push(item);
}
}
} else {
for (const item of armorSet as IVoidTraderOffer[]) {
vt.Manifest.push(item);
}
}
}
for (const item of baro.rest) {
vt.Manifest.push(item);
}
};
const getVarziaRotation = (week: number): string => {
const seed = new SRng(week).randomInt(0, 100_000);
const rng = new SRng(seed);
@ -1320,7 +1340,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
});
} else {
constraints.push({
//name: `duviri ${config.worldState.duviriOverride}`,
name: `duviri ${config.worldState.duviriOverride}`,
isValidTime: (timeSecs: number): boolean => {
const moodIndex = Math.trunc(timeSecs / 7200);
return moodIndex % 5 == desiredMood;
@ -1335,11 +1355,20 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}
}
const timeSecs = getIdealTimeSatsifyingConstraints(constraints);
if (constraints.length != 0) {
const delta = Math.trunc(Date.now() / 1000) - timeSecs;
if (delta > 1) {
logger.debug(
`reported time is ${delta} seconds behind real time to satisfy selected constraints (${constraints.map(x => x.name).join(", ")})`
);
}
}
const timeMs = timeSecs * 1000;
const day = Math.trunc((timeMs - EPOCH) / 86400000);
const week = Math.trunc(day / 7);
const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000;
const date = new Date(timeMs);
const worldState: IWorldState = {
BuildLabel: typeof buildLabel == "string" ? buildLabel.split(" ").join("+") : buildConfig.buildLabel,
@ -1366,11 +1395,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
worldState.PVPChallengeInstances = [];
}
if (config.worldState?.starDays) {
if (config.worldState?.tennoLiveRelay) {
worldState.Goals.push({
_id: {
$oid: "687bf9400000000000000000"
},
Activation: {
$date: {
$numberLong: "1752955200000"
}
},
Expiry: {
$date: {
$numberLong: "2000000000000"
}
},
Count: 0,
Goal: 0,
Success: 0,
Personal: true,
Desc: "/Lotus/Language/Locations/RelayStationTennoConB",
ToolTip: "/Lotus/Language/Locations/RelayStationTennoConDescB",
Icon: "/Lotus/Interface/Icons/Categories/IconTennoLive.png",
Tag: "TennoConRelayB",
Node: "TennoConBHUB6"
});
}
if (config.worldState?.baroTennoConRelay) {
worldState.Goals.push({
_id: { $oid: "687bb2f00000000000000000" },
Activation: { $date: { $numberLong: "1752937200000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Count: 0,
Goal: 0,
Success: 0,
Personal: true,
//"Faction": "FC_GRINEER",
Desc: "/Lotus/Language/Locations/RelayStationTennoCon",
ToolTip: "/Lotus/Language/Locations/RelayStationTennoConDesc",
Icon: "/Lotus/Interface/Icons/Categories/IconTennoConSigil.png",
Tag: "TennoConRelay",
Node: "TennoConHUB2"
});
const vt: IVoidTrader = {
_id: { $oid: "687809030379266d790495c6" },
Activation: { $date: { $numberLong: "1752937200000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Character: "Baro'Ki Teel",
Node: "TennoConHUB2",
Manifest: []
};
worldState.VoidTraders.push(vt);
fullyStockBaro(vt);
}
const isFebruary = date.getUTCMonth() == 1;
if (config.worldState?.starDaysOverride ?? isFebruary) {
worldState.Goals.push({
_id: { $oid: "67a4dcce2a198564d62e1647" },
Activation: { $date: { $numberLong: "1738868400000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Activation: {
$date: {
$numberLong: config.worldState?.starDaysOverride
? "1738868400000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1).toString()
}
},
Expiry: {
$date: {
$numberLong: config.worldState?.starDaysOverride
? "2000000000000"
: Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1).toString()
}
},
Count: 0,
Goal: 0,
Success: 0,
@ -1584,24 +1679,8 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
};
worldState.VoidTraders.push(vt);
if (isBeforeNextExpectedWorldStateRefresh(timeMs, baroActualStart)) {
vt.Manifest = [];
if (config.baroFullyStocked) {
for (const armorSet of baro.armorSets) {
if (Array.isArray(armorSet[0])) {
for (const set of armorSet as IVoidTraderOffer[][]) {
for (const item of set) {
vt.Manifest.push(item);
}
}
} else {
for (const item of armorSet as IVoidTraderOffer[]) {
vt.Manifest.push(item);
}
}
}
for (const item of baro.rest) {
vt.Manifest.push(item);
}
fullyStockBaro(vt);
} else {
const rng = new SRng(new SRng(baroIndex).randomInt(0, 100_000));
// TOVERIFY: Constraint for upgrades amount?
@ -1822,7 +1901,10 @@ export const populateFissures = async (worldState: IWorldState): Promise<void> =
_id: toOid(fissure._id),
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: toMongoDate(fissure.Activation),
Activation:
fissure.Activation.getTime() < Date.now() // Activation is in the past?
? { $date: { $numberLong: "1000000000000" } } // Let the client know 'explicitly' to avoid interference from time constraints.
: toMongoDate(fissure.Activation),
Expiry: toMongoDate(fissure.Expiry),
Node: fissure.Node,
MissionType: eMissionType[meta.missionIndex].tag,

View File

@ -5,6 +5,7 @@ import { Account } from "@/src/models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson } from "@/src/types/loginTypes";
import { HydratedDocument } from "mongoose";
import { logError } from "@/src/utils/logger";
let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined;
@ -43,7 +44,7 @@ export const stopWsServers = (promises: Promise<void>[]): void => {
let lastWsid: number = 0;
interface IWsCustomData extends ws {
id?: number;
id: number;
accountId?: string;
}
@ -88,63 +89,67 @@ const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
try {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
account = null;
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
}
}
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
} catch (e) {
logError(e as Error, `processing websocket message`);
}
});
};
@ -181,18 +186,24 @@ export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void
}
};
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
export const sendWsBroadcastEx = (data: IWsMsgToClient, accountId?: string, excludeWsid?: number): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
if (
(!accountId || (client as IWsCustomData).accountId == accountId) &&
(client as IWsCustomData).id != excludeWsid
) {
client.send(msg);
}
}

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Types } from "mongoose";
import { IOid, IMongoDate, IOidWithLegacySupport, ITypeCount } from "@/src/types/commonTypes";
import {
@ -216,6 +215,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
PremiumCredits: number;
PremiumCreditsFree: number;
FusionPoints: number;
CrewShipFusionPoints: number; //Dirac (pre-rework Railjack)
PrimeTokens: number;
SuitBin: ISlots;
WeaponBin: ISlots;

View File

@ -91,12 +91,16 @@ export interface IApartmentClient {
Gardening: IGardeningClient;
Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadout[];
VideoWallBackdrop?: string;
Soundscape?: string;
}
export interface IApartmentDatabase {
Gardening: IGardeningDatabase;
Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadoutDatabase[];
VideoWallBackdrop?: string;
Soundscape?: string;
}
export interface IPlacedDecosDatabase {
@ -150,6 +154,17 @@ export interface IShipDecorationsResponse {
NewRoom?: string;
}
export interface IResetShipDecorationsRequest {
Room: string;
BootLocation?: TBootLocation;
}
export interface IResetShipDecorationsResponse {
ResetRoom: string;
ClaimedDecos: [];
NewCapacity: number;
}
export interface ISetPlacedDecoInfoRequest {
DecoType: string;
DecoId: string;

View File

@ -74,6 +74,7 @@ export type IInventoryChanges = {
InfestedFoundry?: IInfestedFoundryClient;
Drones?: IDroneClient[];
MiscItems?: IMiscItem[];
ShipDecorations?: ITypeCount[];
EmailItems?: ITypeCount[];
CrewShipRawSalvage?: ITypeCount[];
Nemesis?: Partial<INemesisClient>;

View File

@ -79,6 +79,7 @@ export interface ILoadoutDatabase {
NORMAL_PVP: ILoadoutConfigDatabase[];
LUNARO: ILoadoutConfigDatabase[];
OPERATOR: ILoadoutConfigDatabase[];
GEAR: ILoadoutConfigDatabase[];
KDRIVE: ILoadoutConfigDatabase[];
DATAKNIFE: ILoadoutConfigDatabase[];
MECH: ILoadoutConfigDatabase[];

View File

@ -108,3 +108,13 @@ errorLog.on("new", filename => logger.info(`Using error log file: ${filename}`))
combinedLog.on("new", filename => logger.info(`Using combined log file: ${filename}`));
errorLog.on("rotate", filename => logger.info(`Rotated error log file: ${filename}`));
combinedLog.on("rotate", filename => logger.info(`Rotated combined log file: ${filename}`));
export const logError = (err: Error, context: string): void => {
if (err.stack) {
const stackArr = err.stack.split("\n");
stackArr[0] += ` while ${context}`;
logger.error(stackArr.join("\n"));
} else {
logger.error(`uncaught error while ${context}: ${err.message}`);
}
};

View File

@ -304,7 +304,6 @@
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKubrow", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/LisetScarf", "PrimePrice": 600, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerTwitchBItemA", "PrimePrice": 220, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKavatBadgeItem", "PrimePrice": 50, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKavatSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/WraithTurbinesScarf", "PrimePrice": 400, "RegularPrice": 500000 },
@ -363,7 +362,6 @@
{ "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileInarosTomb", "PrimePrice": 325, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/BaroFireWorksCrate", "PrimePrice": 50, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileOrokinExtraction", "PrimePrice": 325, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBait", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitB", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationB", "PrimePrice": 100, "RegularPrice": 100000 },
@ -401,7 +399,39 @@
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationC", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/PedistalPrime", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/BaroEmote", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", "PrimePrice": 2995, "RegularPrice": 1000000 }
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", "PrimePrice": 2995, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageAvaClemCommunityGlyph", "PrimePrice": 20, "RegularPrice": 33333 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TennoconConcert2025Display", "PrimePrice": 90, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/SummerGameFestPoster", "PrimePrice": 90, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/RathuumEventPoster", "PrimePrice": 90, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionCorpus", "PrimePrice": 70, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionEntrati", "PrimePrice": 99, "RegularPrice": 1900 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionScaldra", "PrimePrice": 93, "RegularPrice": 1906 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Factions/GlyphFactionTechrot", "PrimePrice": 98, "RegularPrice": 1901 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Warframes/VorunaActionGlyph", "PrimePrice": 75, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageVoidAngelBaro", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/1999DrippySigil", "PrimePrice": 50, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/Rapier/CrpRapierSkin", "PrimePrice": 375, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Events/OgrisOldSchool", "PrimePrice": 350, "RegularPrice": 325000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusFlamesSigil", "PrimePrice": 55, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageMurmursExpert", "PrimePrice": 375, "RegularPrice": 130000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponRecoilReductionModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponRecoilReductionModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponRecoilReductionModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/TenthAnniversaryLoginSongItem", "PrimePrice": 145, "RegularPrice": 165000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/AbyssofDagathSongItem", "PrimePrice": 150, "RegularPrice": 155000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/ZarimanLoginSongItem", "PrimePrice": 160, "RegularPrice": 180000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/DanteUnboundLoginSongItem", "PrimePrice": 150, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/EmpyreanSongItem", "PrimePrice": 160, "RegularPrice": 155000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/DeimosLoginSongItem", "PrimePrice": 155, "RegularPrice": 160000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/JadeShadowsLoginSongItem", "PrimePrice": 150, "RegularPrice": 170000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/WhispersInTheWallLoginSongItem", "PrimePrice": 165, "RegularPrice": 170000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/CorpusRailjackLoginSongItem", "PrimePrice": 150, "RegularPrice": 165000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/LotusEatersSongItem", "PrimePrice": 165, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/SongItems/KuvaLichLoginSongItem", "PrimePrice": 140, "RegularPrice": 170000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyBaro", "PrimePrice": 100, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyInaros", "PrimePrice": 120, "RegularPrice": 90000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageMurmursExpert", "PrimePrice": 375, "RegularPrice": 130000 }
],
"allIfAny": [
[

View File

@ -388,11 +388,11 @@
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body overflow-auto">
<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_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
<input class="form-control" id="acquire-type-HoverBoards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" />
<input class="form-control" id="acquire-type-HoverBoards-HB_JET" list="datalist-ModularParts-HB_JET" />
<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_ENGINE" list="datalist-ModularParts-HB_ENGINE" />
<input class="form-control" id="acquire-type-Hoverboards-HB_FRONT" list="datalist-ModularParts-HB_FRONT" />
<input class="form-control" id="acquire-type-Hoverboards-HB_JET" list="datalist-ModularParts-HB_JET" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
@ -436,22 +436,22 @@
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="addMissingEquipment(['Suits']);" data-loc="inventory_bulkAddSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="addMissingEquipment(['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEvolutionProgress();" data-loc="inventory_bulkAddEvolutionProgress"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Suits']);" data-loc="inventory_bulkAddSuits"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkAddWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceSuits']);" data-loc="inventory_bulkAddSpaceSuits"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['Sentinels']);" data-loc="inventory_bulkAddSentinels"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="debounce(addMissingEvolutionProgress);" data-loc="inventory_bulkAddEvolutionProgress"></button>
</div>
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Melee', 'LongGuns', 'Pistols']);" data-loc="inventory_bulkRankUpWeapons"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SpaceSuits']);" data-loc="inventory_bulkRankUpSpaceSuits"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['Sentinels']);" data-loc="inventory_bulkRankUpSentinels"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEquipment, ['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="debounce(maxRankAllEvolutions);" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
</div>
</div>
</div>
@ -460,6 +460,13 @@
<h3 id="detailedView-loading" class="mb-0" data-loc="general_loading"></h3>
<h3 id="detailedView-title" class="mb-0"></h3>
<p class="text-body-secondary"></p>
<div id="loadout-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_loadoutLabel"></h5>
<div class="card-body">
<ul class="nav nav-tabs" id="loadoutTabs"></ul>
<div class="tab-content mt-3" id="loadoutTabsContent"></div>
</div>
</div>
<div id="archonShards-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_archonShardsLabel"></h5>
<div class="card-body">
@ -478,6 +485,62 @@
</table>
</div>
</div>
<div id="edit-suit-invigorations-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_suitInvigorationLabel"></h5>
<div class="card-body">
<form onsubmit="submitSuitInvigorationUpgrade(event)">
<div class="mb-3">
<label for="invigoration-offensive" class="form-label" data-loc="invigorations_offensiveLabel"></label>
<select class="form-select" id="dv-invigoration-offensive">
<option value="" data-loc="general_none"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerStrength" data-loc="invigorations_offensive_AbilityStrength"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerRange" data-loc="invigorations_offensive_AbilityRange"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPowerDuration" data-loc="invigorations_offensive_AbilityDuration"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeDamage" data-loc="invigorations_offensive_MeleeDamage"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPrimaryDamage" data-loc="invigorations_offensive_PrimaryDamage"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationSecondaryDamage" data-loc="invigorations_offensive_SecondaryDamage"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationPrimaryCritChance" data-loc="invigorations_offensive_PrimaryCritChance"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationSecondaryCritChance" data-loc="invigorations_offensive_SecondaryCritChance"></option>
<option value="/Lotus/Upgrades/Invigorations/Offensive/OffensiveInvigorationMeleeCritChance" data-loc="invigorations_offensive_MeleeCritChance"></option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-defensive" class="form-label" data-loc="invigorations_defensiveLabel"></label>
<select class="form-select" id="dv-invigoration-defensive">
<option value="" data-loc="general_none"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationPowerEfficiency" data-loc="invigorations_utility_AbilityEfficiency"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationMovementSpeed" data-loc="invigorations_utility_SprintSpeed"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationParkourSpeed" data-loc="invigorations_utility_ParkourVelocity"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationHealth" data-loc="invigorations_utility_HealthMax"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergy" data-loc="invigorations_utility_EnergyMax"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationStatusResistance" data-loc="invigorations_utility_StatusImmune"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationReloadSpeed" data-loc="invigorations_utility_ReloadSpeed"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationHealthRegen" data-loc="invigorations_utility_HealthRegen"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationArmor" data-loc="invigorations_utility_ArmorMax"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationJumps" data-loc="invigorations_utility_Jumps"></option>
<option value="/Lotus/Upgrades/Invigorations/Utility/UtilityInvigorationEnergyRegen" data-loc="invigorations_utility_EnergyRegen"></option>
</select>
</div>
<div class="mb-3">
<label for="invigoration-expiry" class="form-label" data-loc="invigorations_expiryLabel"></label>
<input type="datetime-local" class="form-control" id="dv-invigoration-expiry" />
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary" data-loc="general_setButton"></button>
<button type="button" class="btn btn-danger" onclick="clearSuitInvigorationUpgrades()" data-loc="code_remove"></button>
</div>
</form>
</div>
</div>
<div id="modularParts-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_modularPartsLabel"></h5>
<div class="card-body">
<form id="modularParts-form" class="input-group mb-3" onsubmit="handleModularPartsChange(event)"></form>
</div>
</div>
<div id="valenceBonus-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_valenceBonusLabel"></h5>
<div class="card-body">
@ -863,8 +926,12 @@
<label class="form-check-label" for="worldState.resourceBoost" data-loc="worldState_resourceBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.starDays" />
<label class="form-check-label" for="worldState.starDays" data-loc="worldState_starDays"></label>
<input class="form-check-input" type="checkbox" id="worldState.tennoLiveRelay" />
<label class="form-check-label" for="worldState.tennoLiveRelay" data-loc="worldState_tennoLiveRelay"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.baroTennoConRelay" />
<label class="form-check-label" for="worldState.baroTennoConRelay" data-loc="worldState_baroTennoConRelay"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.varziaFullyStocked" />
@ -879,6 +946,14 @@
<option value="3" data-loc="worldState_we3"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.starDaysOverride" data-loc="worldState_starDays"></label>
<select class="form-control" id="worldState.starDaysOverride" data-default="null">
<option value="null" data-loc="normal"></option>
<option value="true" data-loc="enabled"></option>
<option value="false" data-loc="disabled"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="worldState.eidolonOverride" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride" data-default="">
@ -1017,6 +1092,7 @@
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<datalist id="datalist-Boosters"></datalist>
<datalist id="datalist-Abilities"></datalist>
<datalist id="datalist-circuitGameModes">
<option>Survival</option>
<option>VoidFlood</option>

View File

@ -28,7 +28,8 @@ const sendAuth = isRegister => {
};
function openWebSocket() {
window.ws = new WebSocket("/custom/ws");
const wsProto = location.protocol === "https:" ? "wss://" : "ws://";
window.ws = new WebSocket(wsProto + location.host + "/custom/ws");
window.ws.onopen = () => {
ws_is_open = true;
sendAuth(false);
@ -118,9 +119,16 @@ function doLogin() {
window.registerSubmit = false;
}
async function revalidateAuthz() {
await getWebSocket();
// We have a websocket connection, so authz should be good.
function revalidateAuthz() {
return new Promise(resolve => {
let interval;
interval = setInterval(() => {
if (ws_is_open && !auth_pending) {
clearInterval(interval);
resolve();
}
}, 10);
});
}
function logout() {
@ -282,7 +290,8 @@ function fetchItemList() {
});
const syndicateNone = document.createElement("option");
syndicateNone.textContent = loc("cheats_none");
syndicateNone.value = "";
syndicateNone.textContent = loc("general_none");
document.getElementById("changeSyndicate").innerHTML = "";
document.getElementById("changeSyndicate").appendChild(syndicateNone);
@ -303,8 +312,8 @@ function fetchItemList() {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmourMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmourMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmorMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmorMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": loc("upgrade_WarframeBlastProc").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": loc("upgrade_WarframeBlastProc").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("25"),
@ -333,8 +342,8 @@ function fetchItemList() {
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("22.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("10"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("15"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeRegen").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeRegen").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeHealthRegen").split("|VAL|").join("5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeHealthRegen").split("|VAL|").join("7.5"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": loc("upgrade_WarframeShieldMax").split("|VAL|").join("150"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": loc("upgrade_WarframeShieldMax").split("|VAL|").join("225"),
"/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("30"),
@ -730,7 +739,10 @@ function updateInventory() {
td.appendChild(a);
}
if (["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
if (
["Suits", "LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category) ||
modularWeapons.includes(item.ItemType)
) {
const a = document.createElement("a");
a.href = "/webui/detailedView?productCategory=" + category + "&itemId=" + item.ItemId.$oid;
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M278.5 215.6L23 471c-9.4 9.4-9.4 24.6 0 33.9s24.6 9.4 33.9 0l57-57h68c49.7 0 97.9-14.4 139-41c11.1-7.2 5.5-23-7.8-23c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l81-24.3c2.5-.8 4.8-2.1 6.7-4l22.4-22.4c10.1-10.1 2.9-27.3-11.3-27.3l-32.2 0c-5.1 0-9.2-4.1-9.2-9.2c0-4.1 2.7-7.6 6.5-8.8l112-33.6c4-1.2 7.4-3.9 9.3-7.7C506.4 207.6 512 184.1 512 160c0-41-16.3-80.3-45.3-109.3l-5.5-5.5C432.3 16.3 393 0 352 0s-80.3 16.3-109.3 45.3L139 149C91 197 64 262.1 64 330v55.3L253.6 195.8c6.2-6.2 16.4-6.2 22.6 0c5.4 5.4 6.1 13.6 2.2 19.8z"/></svg>`;
@ -861,15 +873,11 @@ function updateInventory() {
const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
const giveAllQEvolutionProgress = document.querySelector(
'button[onclick*="addMissingEvolutionProgress()"]'
);
if (datalistEvolutionProgress.length === 0) {
formEvolutionProgress.classList.add("disabled");
formEvolutionProgress.querySelector("input").disabled = true;
formEvolutionProgress.querySelector("button").disabled = true;
giveAllQEvolutionProgress.disabled = true;
}
if (data.CrewShipHarnesses?.length) {
@ -1017,13 +1025,19 @@ function updateInventory() {
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
const rivenType = item.ItemType.substr(32);
const fingerprint = JSON.parse(item.UpgradeFingerprint);
if (fingerprint.buffs) {
if ("buffs" in fingerprint) {
// Riven has been revealed?
const tr = document.createElement("tr");
{
const td = document.createElement("td");
td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
td.textContent += " ";
try {
td.textContent += RivenParser.parseRiven(rivenType, fingerprint, 1).name;
} catch (e) {
console.warn("malformed riven", { rivenType, fingerprint });
td.textContent += " [Malformed Riven]";
}
td.innerHTML +=
" <span title='" +
loc("code_buffsNumber") +
@ -1221,6 +1235,124 @@ function updateInventory() {
}
document.getElementById("crystals-list").appendChild(tr);
});
document.getElementById("edit-suit-invigorations-card").classList.remove("d-none");
const { OffensiveUpgrade, DefensiveUpgrade, UpgradesExpiry } =
suitInvigorationUpgradeData(item);
document.getElementById("dv-invigoration-offensive").value = OffensiveUpgrade;
document.getElementById("dv-invigoration-defensive").value = DefensiveUpgrade;
document.getElementById("dv-invigoration-expiry").value = UpgradesExpiry;
{
document.getElementById("loadout-card").classList.remove("d-none");
const maxModConfigNum = Math.min(2 + (item.ModSlotPurchases ?? 0), 5);
const configs = item.Configs ?? [];
const loadoutTabs = document.getElementById("loadoutTabs");
const loadoutTabsContent = document.getElementById("loadoutTabsContent");
loadoutTabs.innerHTML = "";
loadoutTabsContent.innerHTML = "";
for (let i = 0; i <= maxModConfigNum; i++) {
const config = configs[i] ?? {};
{
const li = document.createElement("li");
li.classList.add("nav-item");
const button = document.createElement("button");
button.classList.add("nav-link");
if (i === 0) button.classList.add("active");
button.id = `config${i}-tab`;
button.setAttribute("data-bs-toggle", "tab");
button.setAttribute("data-bs-target", `#config${i}`);
button.innerHTML = config.Name?.trim() || String.fromCharCode(65 + i);
li.appendChild(button);
loadoutTabs.appendChild(li);
}
{
const tabDiv = document.createElement("div");
tabDiv.classList = "tab-pane";
if (i === 0) tabDiv.classList.add("show", "active");
tabDiv.id = `config${i}`;
{
const abilityOverrideForm = document.createElement("form");
abilityOverrideForm.classList = "form-group mt-2";
abilityOverrideForm.setAttribute(
"onsubmit",
`handleAbilityOverride(event, ${i});return false;`
);
const abilityOverrideFormLabel = document.createElement("label");
abilityOverrideFormLabel.setAttribute("data-loc", "abilityOverride_label");
abilityOverrideFormLabel.innerHTML = loc("abilityOverride_label");
abilityOverrideFormLabel.classList = "form-label";
abilityOverrideFormLabel.setAttribute("for", "abilityOverride-ability");
abilityOverrideForm.appendChild(abilityOverrideFormLabel);
const abilityOverrideInputGroup = document.createElement("div");
abilityOverrideInputGroup.classList = "input-group";
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
const abilityOverrideInput = document.createElement("input");
abilityOverrideInput.id = "abilityOverride-ability";
abilityOverrideInput.classList = "form-control";
abilityOverrideInput.setAttribute("list", "datalist-Abilities");
if (config.AbilityOverride) {
const datalist = document.getElementById("datalist-Abilities");
const options = Array.from(datalist.options);
abilityOverrideInput.value = options.find(
option =>
config.AbilityOverride.Ability == option.getAttribute("data-key")
).value;
}
abilityOverrideInputGroup.appendChild(abilityOverrideInput);
const abilityOverrideOnSlot = document.createElement("span");
abilityOverrideOnSlot.classList = "input-group-text";
abilityOverrideOnSlot.setAttribute("data-loc", "abilityOverride_onSlot");
abilityOverrideOnSlot.innerHTML = loc("abilityOverride_onSlot");
abilityOverrideInputGroup.appendChild(abilityOverrideOnSlot);
const abilityOverrideSecondInput = document.createElement("input");
abilityOverrideSecondInput.id = "abilityOverride-ability-index";
abilityOverrideSecondInput.classList = "form-control";
abilityOverrideSecondInput.setAttribute("type", "number");
abilityOverrideSecondInput.setAttribute("min", "0");
abilityOverrideSecondInput.setAttribute("max", "3");
if (config.AbilityOverride)
abilityOverrideSecondInput.value = config.AbilityOverride.Index;
abilityOverrideInputGroup.appendChild(abilityOverrideSecondInput);
const abilityOverrideSetButton = document.createElement("button");
abilityOverrideSetButton.classList = "btn btn-primary";
abilityOverrideSetButton.setAttribute("type", "submit");
abilityOverrideSetButton.setAttribute("value", "set");
abilityOverrideSetButton.setAttribute("data-loc", "general_setButton");
abilityOverrideSetButton.innerHTML = loc("general_setButton");
abilityOverrideInputGroup.appendChild(abilityOverrideSetButton);
const abilityOverrideRemoveButton = document.createElement("button");
abilityOverrideRemoveButton.classList = "btn btn-danger";
abilityOverrideRemoveButton.setAttribute("type", "submit");
abilityOverrideRemoveButton.setAttribute("value", "remove");
abilityOverrideRemoveButton.setAttribute("data-loc", "code_remove");
abilityOverrideRemoveButton.innerHTML = loc("code_remove");
abilityOverrideInputGroup.appendChild(abilityOverrideRemoveButton);
abilityOverrideForm.appendChild(abilityOverrideInputGroup);
tabDiv.appendChild(abilityOverrideForm);
}
loadoutTabsContent.appendChild(tabDiv);
}
}
}
} else if (["LongGuns", "Pistols", "Melee", "SpaceGuns", "SpaceMelee"].includes(category)) {
document.getElementById("valenceBonus-card").classList.remove("d-none");
document.getElementById("valenceBonus-innateDamage").value = "";
@ -1233,6 +1365,35 @@ function updateInventory() {
document.getElementById("valenceBonus-procent").value = Math.round(buffValue * 1000) / 10;
}
}
if (modularWeapons.includes(item.ItemType)) {
document.getElementById("modularParts-card").classList.remove("d-none");
const form = document.getElementById("modularParts-form");
form.innerHTML = "";
const requiredParts = getRequiredParts(category, item.ItemType);
requiredParts.forEach(modularPart => {
const input = document.createElement("input");
input.classList.add("form-control");
input.id = "detailedView-modularPart-" + modularPart;
input.setAttribute("list", "datalist-ModularParts-" + modularPart);
const datalist = document.getElementById("datalist-ModularParts-" + modularPart);
const options = Array.from(datalist.options);
input.value =
options.find(option => item.ModularParts.includes(option.getAttribute("data-key")))
?.value || "";
form.appendChild(input);
});
const changeButton = document.createElement("button");
changeButton.classList.add("btn");
changeButton.classList.add("btn-primary");
changeButton.type = "submit";
changeButton.setAttribute("data-loc", "cheats_changeButton");
changeButton.innerHTML = loc("cheats_changeButton");
form.appendChild(changeButton);
}
} else {
single.loadRoute("/webui/inventory");
}
@ -1332,47 +1493,41 @@ function doAcquireEquipment(category) {
});
}
function doAcquireModularEquipment(category, WeaponType) {
let requiredParts;
let Parts = [];
function getRequiredParts(category, WeaponType) {
switch (category) {
case "HoverBoards":
WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit";
requiredParts = ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
break;
case "Hoverboards":
return ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
case "OperatorAmps":
requiredParts = ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
break;
return ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
case "Melee":
requiredParts = ["BLADE", "HILT", "HILT_WEIGHT"];
break;
return ["BLADE", "HILT", "HILT_WEIGHT"];
case "LongGuns":
requiredParts = ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
break;
return ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
case "Pistols":
requiredParts = ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
break;
return ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
case "MoaPets":
if (WeaponType == "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
requiredParts = ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"];
} else {
requiredParts = ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"];
}
break;
case "KubrowPets":
if (
[
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit"
].includes(WeaponType)
) {
requiredParts = ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"];
} else {
requiredParts = ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"];
}
break;
return WeaponType === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit"
? ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"]
: ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"];
case "KubrowPets": {
return WeaponType.endsWith("InfestedCatbrowPetPowerSuit")
? ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"]
: ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"];
}
}
}
function doAcquireModularEquipment(category, WeaponType) {
if (category === "Hoverboards") WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit";
const requiredParts = getRequiredParts(category, WeaponType);
let Parts = [];
requiredParts.forEach(part => {
const partName = getKey(document.getElementById("acquire-type-" + category + "-" + part));
if (partName) {
@ -1489,19 +1644,22 @@ function doAcquireEvolution() {
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
}
$("input[list]").on("input", function () {
$(document).on("input", "input[list]", function () {
$(this).removeClass("is-invalid");
});
function dispatchAddItemsRequestsBatch(requests) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/addItems?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
resolve();
});
});
});
}
@ -1522,7 +1680,7 @@ function addMissingEquipment(categories) {
});
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
dispatchAddItemsRequestsBatch(requests);
return dispatchAddItemsRequestsBatch(requests);
}
}
@ -1538,7 +1696,7 @@ function addMissingEvolutionProgress() {
requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
});
if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
setEvolutionProgress(requests);
return setEvolutionProgress(requests);
}
}
@ -1706,7 +1864,7 @@ function disposeOfGear(category, oid) {
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz,
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
contentType: "text/plain",
data: JSON.stringify(data)
});
@ -1728,7 +1886,7 @@ function disposeOfItems(category, type, count) {
];
revalidateAuthz().then(() => {
$.post({
url: "/api/sell.php?" + window.authz,
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
contentType: "text/plain",
data: JSON.stringify(data)
});
@ -1765,14 +1923,17 @@ function maturePet(oid, revert) {
}
function setEvolutionProgress(requests) {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
return new Promise(resolve => {
revalidateAuthz().then(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
resolve();
});
});
});
}
@ -1938,7 +2099,13 @@ for (const id of uiConfigs) {
if (elm.tagName == "SELECT") {
elm.onchange = function () {
let value = this.value;
if (!isNaN(parseInt(value))) {
if (value == "true") {
value = true;
} else if (value == "false") {
value = false;
} else if (value == "null") {
value = null;
} else if (!isNaN(parseInt(value))) {
value = parseInt(value);
}
$.post({
@ -2040,6 +2207,10 @@ single.getRoute("/webui/cheats").on("beforeload", function () {
})
.fail(res => {
if (res.responseText == "Log-in expired") {
if (ws_is_open && !auth_pending) {
console.warn("Credentials invalidated but the server didn't let us know");
sendAuth();
}
revalidateAuthz().then(() => {
if (single.getCurrentPath() == "/webui/cheats") {
single.loadRoute("/webui/cheats");
@ -2162,7 +2333,7 @@ function doRemoveUnrankedMods() {
req.done(inventory => {
window.itemListPromise.then(itemMap => {
$.post({
url: "/api/sell.php?" + window.authz,
url: "/api/sell.php?" + window.authz + "&wsid=" + wsid,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
@ -2195,7 +2366,11 @@ single.getRoute("#detailedView-route").on("beforeload", function () {
document.getElementById("detailedView-loading").classList.remove("d-none");
document.getElementById("detailedView-title").textContent = "";
document.querySelector("#detailedView-route .text-body-secondary").textContent = "";
document.getElementById("loadout-card").classList.add("d-none");
document.getElementById("archonShards-card").classList.add("d-none");
document.getElementById("edit-suit-invigorations-card").classList.add("d-none");
document.getElementById("modularParts-card").classList.add("d-none");
document.getElementById("modularParts-form").innerHTML = "";
document.getElementById("valenceBonus-card").classList.add("d-none");
if (window.didInitialInventoryUpdate) {
updateInventory();
@ -2353,22 +2528,10 @@ function handleModularSelection(category) {
modularFieldsZanuka.style.display = "none";
}
} else if (inventoryCategory === "KubrowPets") {
if (
[
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit"
].includes(key)
) {
if (key.endsWith("InfestedCatbrowPetPowerSuit")) {
modularFieldsCatbrow.style.display = "";
modularFieldsKubrow.style.display = "none";
} else if (
[
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
].includes(key)
) {
} else if (key.endsWith("PredatorKubrowPetPowerSuit")) {
modularFieldsCatbrow.style.display = "none";
modularFieldsKubrow.style.display = "";
} else {
@ -2469,9 +2632,17 @@ function formatDatetime(fmt, date) {
const calls_in_flight = new Set();
async function debounce(func, ...args) {
calls_in_flight.add(func);
await func(...args);
calls_in_flight.delete(func);
if (!func.name) {
throw new Error(`cannot debounce anonymous functions`);
}
const callid = JSON.stringify({ func: func.name, args });
if (!calls_in_flight.has(callid)) {
calls_in_flight.add(callid);
await func(...args);
calls_in_flight.delete(callid);
} else {
console.log("debouncing", callid);
}
}
async function doMaxPlexus() {
@ -2807,3 +2978,133 @@ async function markAllAsRead() {
}
toast(loc(any ? "code_succRelog" : "code_nothingToDo"));
}
function handleModularPartsChange(event) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const form = document.getElementById("modularParts-form");
const inputs = form.querySelectorAll("input");
const modularParts = [];
inputs.forEach(input => {
const key = getKey(input);
if (!key) {
input.classList.add("is-invalid");
} else {
modularParts.push(key);
}
});
if (inputs.length == modularParts.length) {
revalidateAuthz().then(() => {
$.post({
url: "/custom/changeModularParts?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
modularParts
})
}).then(function () {
toast(loc("code_succChange"));
updateInventory();
});
});
}
}
function suitInvigorationUpgradeData(suitData) {
let expiryDate = "";
if (suitData.UpgradesExpiry) {
if (suitData.UpgradesExpiry.$date) {
expiryDate = new Date(parseInt(suitData.UpgradesExpiry.$date.$numberLong));
} else if (typeof suitData.UpgradesExpiry === "number") {
expiryDate = new Date(suitData.UpgradesExpiry);
} else if (suitData.UpgradesExpiry instanceof Date) {
expiryDate = suitData.UpgradesExpiry;
}
if (expiryDate && !isNaN(expiryDate.getTime())) {
const year = expiryDate.getFullYear();
const month = String(expiryDate.getMonth() + 1).padStart(2, "0");
const day = String(expiryDate.getDate()).padStart(2, "0");
const hours = String(expiryDate.getHours()).padStart(2, "0");
const minutes = String(expiryDate.getMinutes()).padStart(2, "0");
expiryDate = `${year}-${month}-${day}T${hours}:${minutes}`;
} else {
expiryDate = "";
}
}
return {
oid: suitData.ItemId.$oid,
OffensiveUpgrade: suitData.OffensiveUpgrade || "",
DefensiveUpgrade: suitData.DefensiveUpgrade || "",
UpgradesExpiry: expiryDate
};
}
function submitSuitInvigorationUpgrade(event) {
event.preventDefault();
const oid = new URLSearchParams(window.location.search).get("itemId");
const offensiveUpgrade = document.getElementById("dv-invigoration-offensive").value;
const defensiveUpgrade = document.getElementById("dv-invigoration-defensive").value;
const expiry = document.getElementById("dv-invigoration-expiry").value;
if (!offensiveUpgrade || !defensiveUpgrade) {
alert(loc("code_requiredInvigorationUpgrade"));
return;
}
const data = {
OffensiveUpgrade: offensiveUpgrade,
DefensiveUpgrade: defensiveUpgrade
};
if (expiry) {
data.UpgradesExpiry = new Date(expiry).getTime();
}
editSuitInvigorationUpgrade(oid, data);
}
function clearSuitInvigorationUpgrades() {
editSuitInvigorationUpgrade(new URLSearchParams(window.location.search).get("itemId"), null);
}
async function editSuitInvigorationUpgrade(oid, data) {
/* data?: {
DefensiveUpgrade: string;
OffensiveUpgrade: string;
UpgradesExpiry?: number;
}*/
$.post({
url: "/custom/editSuitInvigorationUpgrade?" + window.authz,
contentType: "application/json",
data: JSON.stringify({ oid, data })
}).done(function () {
updateInventory();
});
}
function handleAbilityOverride(event, configIndex) {
event.preventDefault();
const urlParams = new URLSearchParams(window.location.search);
const action = event.submitter.value;
const Ability = getKey(document.getElementById("abilityOverride-ability"));
const Index = document.getElementById("abilityOverride-ability-index").value;
revalidateAuthz().then(() => {
$.post({
url: "/custom/abilityOverride?" + window.authz,
contentType: "application/json",
data: JSON.stringify({
category: urlParams.get("productCategory"),
oid: urlParams.get("itemId"),
configIndex,
action,
AbilityOverride: {
Ability,
Index
}
})
}).done(function () {
updateInventory();
});
});
}

View File

@ -1,13 +1,14 @@
// German translation by Animan8000
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_inventoryUpdateNote: `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_addButton: `Hinzufügen`,
general_setButton: `[UNTRANSLATED] Set`,
general_setButton: `Festlegen`,
general_none: `Keines`,
general_bulkActions: `Massenaktionen`,
general_loading: `[UNTRANSLATED] Loading...`,
general_loading: `Lädt...`,
code_loginFail: `[UNTRANSLATED] Login failed. Double-check the email and password.`,
code_regFail: `[UNTRANSLATED] Registration failed. Account already exists?`,
code_loginFail: `Anmeldung fehlgeschlagen. Bitte überprüfe deine Angaben.`,
code_regFail: `Registrierung fehlgeschlagen. Account existiert bereits?`,
code_changeNameConfirm: `In welchen Namen möchtest du deinen Account umbenennen?`,
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`,
@ -45,8 +46,8 @@ dict = {
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_addModsConfirm: `Bist du sicher, dass du |COUNT| Mods zu deinem Account hinzufügen möchtest?`,
code_succImport: `Erfolgreich importiert.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_succRelog: `Fertig. Bitte beachte, dass du dich neu einloggen musst, um Änderungen im Spiel zu sehen.`,
code_nothingToDo: `Fertig. Es gab nichts zu tun.`,
code_gild: `Veredeln`,
code_moa: `Moa`,
code_zanuka: `Jagdhund`,
@ -61,6 +62,8 @@ dict = {
code_pigment: `Pigment`,
code_mature: `Für den Kampf auswachsen lassen`,
code_unmature: `Genetisches Altern zurücksetzen`,
code_succChange: `Erfolgreich geändert.`,
code_requiredInvigorationUpgrade: `Du musst sowohl ein offensives & defensives 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_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`,
@ -90,7 +93,7 @@ dict = {
inventory_moaPets: `Moas`,
inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `Incarnon-Entwicklungsfortschritte`,
inventory_Boosters: `[UNTRANSLATED] Boosters`,
inventory_Boosters: `Booster`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
@ -105,7 +108,7 @@ dict = {
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `Alle Incarnon-Entwicklungsfortschritte auf Max. Rang`,
inventory_maxPlexus: `[UNTRANSLATED] Max Rank Plexus`,
inventory_maxPlexus: `Plexus auf Max. Rang`,
quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`,
@ -122,14 +125,46 @@ dict = {
detailedView_archonShardsDescription: `Du kannst diese unbegrenzten Slots nutzen, um eine Vielzahl von Verbesserungen anzuwenden.`,
detailedView_archonShardsDescription2: `Hinweis: Jede Archon-Scherbe benötigt beim Laden etwas Zeit, um angewendet zu werden.`,
detailedView_valenceBonusLabel: `Valenz-Bonus`,
detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`,
detailedView_valenceBonusDescription: `Du kannst den Valenz-Bonus deiner Waffe festlegen oder entfernen.`,
detailedView_modularPartsLabel: `Modulare Teile ändern`,
detailedView_suitInvigorationLabel: `Warframe-Kräftigung`,
detailedView_loadoutLabel: `Loadouts`,
invigorations_offensive_AbilityStrength: `+200% Fähigkeitsstärke`,
invigorations_offensive_AbilityRange: `+100% Fähigkeitsreichweite`,
invigorations_offensive_AbilityDuration: `+100% Fähigkeitsdauer`,
invigorations_offensive_MeleeDamage: `+250% Nahkampfschaden`,
invigorations_offensive_PrimaryDamage: `+250% Primärwaffen Schaden`,
invigorations_offensive_SecondaryDamage: `+250% Sekundärwaffen Schaden`,
invigorations_offensive_PrimaryCritChance: `+200% Primärwaffen Krit. Chance`,
invigorations_offensive_SecondaryCritChance: `+200% Sekundärwaffen Krit. Chance`,
invigorations_offensive_MeleeCritChance: `+200% Nahkampfwaffen Krit. Chance`,
invigorations_utility_AbilityEfficiency: `+75% Fähigkeitseffizienz`,
invigorations_utility_SprintSpeed: `+75% Sprintgeschwindigkeit`,
invigorations_utility_ParkourVelocity: `+75% Parkourgeschwindigkeit`,
invigorations_utility_HealthMax: `+1000 Gesundheit`,
invigorations_utility_EnergyMax: `+200% Max. Energie`,
invigorations_utility_StatusImmune: `Immun gegen Statuseffekte`,
invigorations_utility_ReloadSpeed: `+75% Nachladegeschwindigkeit`,
invigorations_utility_HealthRegen: `+25 Gesundheitsregeneration pro Sekunde`,
invigorations_utility_ArmorMax: `+1000 Rüstung`,
invigorations_utility_Jumps: `+5 Sprung-Zurücksetzungen`,
invigorations_utility_EnergyRegen: `+2 Energieregeneration pro Sekunde`,
invigorations_offensiveLabel: `Offensives Upgrade`,
invigorations_defensiveLabel: `Defensives Upgrade`,
invigorations_expiryLabel: `Upgrades Ablaufdatum (optional)`,
abilityOverride_label: `Fähigkeitsüberschreibung`,
abilityOverride_onSlot: `auf Slot`,
mods_addRiven: `Riven hinzufügen`,
mods_fingerprint: `Fingerabdruck`,
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
mods_rivens: `Rivens`,
mods_mods: `Mods`,
mods_addMax: `[UNTRANSLATED] Add Maxed`,
mods_addMax: `Max. hinzufügen`,
mods_addMissingUnrankedMods: `Fehlende Mods ohne Rang hinzufügen`,
mods_removeUnranked: `Mods ohne Rang entfernen`,
mods_addMissingMaxRankMods: `Fehlende Mods mit Max. Rang hinzufügen`,
@ -139,17 +174,17 @@ dict = {
cheats_skipAllDialogue: `Alle Dialoge überspringen`,
cheats_unlockAllScans: `Alle Scans freischalten`,
cheats_unlockAllMissions: `Alle Missionen freischalten`,
cheats_unlockAllMissions_ok: `[UNTRANSLATED] Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
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_infinitePlatinum: `Unendlich Platinum`,
cheats_infiniteEndo: `Unendlich Endo`,
cheats_infiniteRegalAya: `Unendlich Reines Aya`,
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
cheats_claimingBlueprintRefundsIngredients: `Fertige Blaupausen erstatten Ressourcen zurück`,
cheats_dontSubtractPurchaseCreditCost: `[UNTRANSLATED] Don't Subtract Purchase Credit Cost`,
cheats_dontSubtractPurchasePlatinumCost: `[UNTRANSLATED] Don't Subtract Purchase Platinum Cost`,
cheats_dontSubtractPurchaseItemCost: `[UNTRANSLATED] Don't Subtract Purchase Item Cost`,
cheats_dontSubtractPurchaseStandingCost: `[UNTRANSLATED] Don't Subtract Purchase Standing Cost`,
cheats_dontSubtractPurchaseCreditCost: `Credits beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchasePlatinumCost: `Platinum beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchaseItemCost: `Gegenstände beim Kauf nicht verbrauchen`,
cheats_dontSubtractPurchaseStandingCost: `Ansehen beim Kauf nicht verbrauchen`,
cheats_dontSubtractVoidTraces: `Void-Spuren nicht verbrauchen`,
cheats_dontSubtractConsumables: `Verbrauchsgegenstände (Ausrüstung) nicht verbrauchen`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
@ -169,11 +204,11 @@ dict = {
cheats_noVendorPurchaseLimits: `Keine Kaufbeschränkungen bei Händlern`,
cheats_noDeathMarks: `Keine Todesmarkierungen`,
cheats_noKimCooldowns: `Keine Wartezeit bei KIM`,
cheats_fullyStockedVendors: `[UNTRANSLATED] Fully Stocked Vendors`,
cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
cheats_fullyStockedVendors: `Händler haben volles Inventar`,
cheats_baroAlwaysAvailable: `Baro immer im Relais verfügbar`,
cheats_baroFullyStocked: `Baro hat volles Inventar`,
cheats_syndicateMissionsRepeatable: `Syndikat-Missionen wiederholbar`,
cheats_unlockAllProfitTakerStages: `[UNTRANSLATED] Unlock All Profit Taker Stages`,
cheats_unlockAllProfitTakerStages: `Alle Profiteintreiber-Phasen freischalten`,
cheats_instantFinishRivenChallenge: `Riven-Mod Herausforderung sofort abschließen`,
cheats_instantResourceExtractorDrones: `Sofortige Ressourcen-Extraktor-Drohnen`,
cheats_noResourceExtractorDronesDamage: `Kein Schaden für Ressourcen-Extraktor-Drohnen`,
@ -184,129 +219,131 @@ dict = {
cheats_noDojoResearchCosts: `Keine Dojo-Forschungskosten`,
cheats_noDojoResearchTime: `Keine Dojo-Forschungszeit`,
cheats_fastClanAscension: `Schneller Clan-Aufstieg`,
cheats_missionsCanGiveAllRelics: `[UNTRANSLATED] Missions Can Give All Relics`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
cheats_unlockAllSimarisResearchEntries: `[UNTRANSLATED] Unlock All Simaris Research Entries`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_missionsCanGiveAllRelics: `Alle Relikte als Missionsbelohnung möglich`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Überragende Relikte geben immer Bronze-Belohnung`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Makellose Relikte geben immer Silber-Belohnung`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Strahlende Relikte geben immer Gold-Belohnung`,
cheats_unlockAllSimarisResearchEntries: `Alle Simaris-Forschungseinträge freischalten`,
cheats_disableDailyTribute: `Täglicher Tribut deaktivieren`,
cheats_spoofMasteryRank: `Gefälschter Meisterschaftsrang (-1 zum deaktivieren)`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
cheats_nightwaveStandingMultiplier: `[UNTRANSLATED] Nightwave Standing Multiplier`,
cheats_save: `[UNTRANSLATED] Save`,
cheats_relicRewardItemCountMultiplier: `Belohnungsmultiplikator für Relikte`,
cheats_nightwaveStandingMultiplier: `Nightwave Ansehen Multiplikator`,
cheats_save: `Speichern`,
cheats_account: `Account`,
cheats_unlockAllFocusSchools: `Alle Fokus-Schulen freischalten`,
cheats_helminthUnlockAll: `Helminth vollständig aufleveln`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_addMissingSubsumedAbilities: `Fehlende konsumierte Fähigkeiten hinzufügen`,
cheats_intrinsicsUnlockAll: `Alle Inhärenzen auf Max. Rang`,
cheats_changeSupportedSyndicate: `Unterstütztes Syndikat`,
cheats_changeButton: `Ändern`,
cheats_none: `Keines`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
cheats_markAllAsRead: `Posteingang als gelesen markieren`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
worldState_varziaFullyStocked: `[UNTRANSLATED] Varzia Fully Stocked`,
worldState_varziaOverride: `[UNTRANSLATED] Varzia Rotation Override`,
worldState: `Weltstatus`,
worldState_creditBoost: `Event Booster: Credit`,
worldState_affinityBoost: `Event Booster: Erfahrung`,
worldState_resourceBoost: `Event Booster: Ressourcen`,
worldState_tennoLiveRelay: `TennoLive Relais`,
worldState_baroTennoConRelay: `Baros TennoCon Relais`,
worldState_starDays: `Sternen-Tage`,
worldState_galleonOfGhouls: `Galeone der Ghule`,
enabled: `Aktiviert`,
disabled: `Deaktiviert`,
worldState_we1: `Wochenende 1`,
worldState_we2: `Wochenende 2`,
worldState_we3: `Wochenende 3`,
worldState_eidolonOverride: `Eidolon-Überschreibung`,
worldState_day: `Tag`,
worldState_night: `Nacht`,
worldState_vallisOverride: `Orbis-Tal-Überschreibung`,
worldState_warm: `Warm`,
worldState_cold: `Kalt`,
worldState_duviriOverride: `Duviri-Überschreibung`,
worldState_joy: `Freude`,
worldState_anger: `Wut`,
worldState_envy: `Neid`,
worldState_sorrow: `Trauer`,
worldState_fear: `Angst`,
worldState_nightwaveOverride: `Nightwave-Überschreibung`,
worldState_RadioLegionIntermission13Syndicate: `Noras Mix - Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Noras Mix - Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Noras Mix - Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `Noras Mix - Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `Noras Mix - Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `Noras Mix - Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `Noras Mix - Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `Noras Mix - Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `Noras Mix - Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `Noras Wahl`,
worldState_RadioLegionIntermission3Syndicate: `Sendepause III`,
worldState_RadioLegion3Syndicate: `Der Glasmacher`,
worldState_RadioLegionIntermission2Syndicate: `Sendepause II`,
worldState_RadioLegion2Syndicate: `Der Botschafter`,
worldState_RadioLegionIntermissionSyndicate: `Sendepause I`,
worldState_RadioLegionSyndicate: `Der Wolf von Saturn Six`,
worldState_fissures: `Void-Risse`,
normal: `Normal`,
worldState_allAtOnceNormal: `Alle gleichzeitig, Normal`,
worldState_allAtOnceSteelPath: `Alle gleichzeitig, Stählerne Pfad`,
worldState_theCircuitOverride: `Der Rundkurs-Überschreibung`,
worldState_darvoStockMultiplier: `Darvo-Vorratsmultiplikator`,
worldState_varziaFullyStocked: `Varzia hat volles Inventar`,
worldState_varziaOverride: `Varzia-Angebotsüberschreibung`,
import_importNote: `Du kannst hier eine vollständige oder teilweise Inventarantwort (Client-Darstellung) einfügen. Alle Felder, die vom Importer unterstützt werden, <b>werden in deinem Account überschrieben</b>.`,
import_submit: `Absenden`,
import_samples: `[UNTRANSLATED] Samples:`,
import_samples_maxFocus: `[UNTRANSLATED] All Focus Schools Maxed Out`,
import_samples: `Beispiele:`,
import_samples_maxFocus: `Alle Fokus-Schulen maximiert`,
upgrade_Equilibrium: `[UNTRANSLATED] +|VAL|% Energy from Health pickups, +|VAL|% Health from Energy pickups`,
upgrade_MeleeCritDamage: `[UNTRANSLATED] +|VAL|% Melee Critical Damage`,
upgrade_PrimaryStatusChance: `[UNTRANSLATED] +|VAL|% Primary Status Chance`,
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
upgrade_WarframeCorrosiveStack: `[UNTRANSLATED] Increase max stacks of Corrosion Status by +|VAL|`,
upgrade_WarframeCritDamageBoost: `[UNTRANSLATED] +|VAL|% Melee Critical Damage (Doubles over 500 Energy)`,
upgrade_WarframeElectricDamage: `[UNTRANSLATED] +|VAL1|% Primary Electricity Damage (+|VAL2|% per additional Shard)`,
upgrade_WarframeElectricDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Electricity Status`,
upgrade_WarframeEnergyMax: `[UNTRANSLATED] +|VAL| Energy Max`,
upgrade_WarframeGlobeEffectEnergy: `[UNTRANSLATED] +|VAL|% Energy Orb Effectiveness`,
upgrade_WarframeGlobeEffectHealth: `[UNTRANSLATED] +|VAL|% Health Orb Effectiveness`,
upgrade_WarframeHealthMax: `[UNTRANSLATED] +|VAL| Health`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,
upgrade_WarframeToxinHeal: `[UNTRANSLATED] +|VAL| Health on damaging enemies with Toxin Status`,
upgrade_WeaponCritBoostFromHeat: `[UNTRANSLATED] +|VAL1|% Secondary Critical Chance per Heat-affected enemy killed (Max |VAL2|%)`,
upgrade_AvatarAbilityRange: `[UNTRANSLATED] +7.5% Ability Range`,
upgrade_AvatarAbilityEfficiency: `[UNTRANSLATED] +5% Ability Efficiency`,
upgrade_AvatarEnergyRegen: `[UNTRANSLATED] +0.5 Energy Regen/s`,
upgrade_AvatarEnemyRadar: `[UNTRANSLATED] +5m Enemy Radar`,
upgrade_AvatarLootRadar: `[UNTRANSLATED] +7m Loot Radar`,
upgrade_WeaponAmmoMax: `[UNTRANSLATED] +15% Ammo Max`,
upgrade_EnemyArmorReductionAura: `[UNTRANSLATED] -3% Enemy Armor`,
upgrade_OnExecutionAmmo: `[UNTRANSLATED] +100% Primary and Secondary Magazine Refill on Mercy`,
upgrade_OnExecutionHealthDrop: `[UNTRANSLATED] +100% Health Orb Chance on Mercy`,
upgrade_OnExecutionEnergyDrop: `[UNTRANSLATED] +50% Energy Orb Chance on Mercy`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
upgrade_DamageReductionOnHack: `[UNTRANSLATED] +75% Damage Reduction while Hacking`,
upgrade_OnExecutionReviveCompanion: `[UNTRANSLATED] Mercy Kills reduce Companion Recovery by 15s`,
upgrade_OnExecutionParkourSpeed: `[UNTRANSLATED] +60% Parkour Speed after a Mercy for 15s`,
upgrade_AvatarTimeLimitIncrease: `[UNTRANSLATED] +8s to Hacking`,
upgrade_ElectrifyOnHack: `[UNTRANSLATED] Shock enemies within 20m while Hacking`,
upgrade_OnExecutionTerrify: `[UNTRANSLATED] +50% chance for enemies within 15m to cower in fear for 8 seconds on Mercy`,
upgrade_OnHackLockers: `[UNTRANSLATED] Unlock 5 lockers within 20m after Hacking`,
upgrade_OnExecutionBlind: `[UNTRANSLATED] Blind enemies within 18m on Mercy`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] Next ability cast gains +50% Ability Strength on Mercy`,
upgrade_OnHackSprintSpeed: `[UNTRANSLATED] +75% Sprint Speed for 15s after Hacking`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`,
upgrade_Equilibrium: `+|VAL|% Energie bei Gesundheitskugeln, +|VAL|% Gesundheit bei Energiekugeln`,
upgrade_MeleeCritDamage: `+|VAL|% Krit. Nahkampfschaden`,
upgrade_PrimaryStatusChance: `+|VAL|% Primärwaffen Statuschance`,
upgrade_SecondaryCritChance: `+|VAL|% Sekundärwaffen Krit. Chance`,
upgrade_WarframeAbilityDuration: `+|VAL|% Fähigkeitsdauer`,
upgrade_WarframeAbilityStrength: `+|VAL|% Fähigkeitsstärke`,
upgrade_WarframeArmorMax: `+|VAL| Rüstung`,
upgrade_WarframeBlastProc: `+|VAL| Schilde beim Töten eines Gegners mit Explosionsschaden`,
upgrade_WarframeCastingSpeed: `+|VAL|% Aktivierungsgeschwindigkeit`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Korrosions-Status betroffen sind`,
upgrade_WarframeCorrosiveStack: `Erhöhe maximale Stapelanzahl von Korrosions-Status um +|VAL|`,
upgrade_WarframeCritDamageBoost: `+|VAL|% Krit. Nahkampfschaden (verdoppelt bei über 500 Energie)`,
upgrade_WarframeElectricDamage: `+|VAL1|% Primärwaffen Elektrizitätsschaden (+|VAL2|% pro zusätzlicher Scherbe)`,
upgrade_WarframeElectricDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Elektrizitäts-Status betroffen sind`,
upgrade_WarframeEnergyMax: `+|VAL| Max. Energie`,
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% Wirksamkeit bei Energiekugeln`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% Wirksamkeit bei Gesundheitskugeln`,
upgrade_WarframeHealthMax: `+|VAL| Gesundheit`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Gesundheit beim Töten eines Gegners mit Explosionsschaden (Max. |VAL2| Gesundheit)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Parkourgeschwindigkeit`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Fähigkeitsschaden auf Gegner, die von Strahlungs-Status betroffen sind`,
upgrade_WarframeHealthRegen: `+|VAL| Gesundheitsregeneration pro Sekunde`,
upgrade_WarframeShieldMax: `+|VAL| Schildkapazität`,
upgrade_WarframeStartingEnergy: `+|VAL|% Max. Energie beim Spawnen`,
upgrade_WarframeToxinDamage: `+|VAL|% Schaden durch Gift-Statuseffekte`,
upgrade_WarframeToxinHeal: `+|VAL| Gesundheit beim Schaden an Gegnern mit Gift-Status`,
upgrade_WeaponCritBoostFromHeat: `+|VAL1|% Sekundärwaffen Krit. Chance pro getötetem Gegner mit Hitze-Statuseffekt (Max. |VAL2|%)`,
upgrade_AvatarAbilityRange: `+7.5% Fähigkeitsreichweite`,
upgrade_AvatarAbilityEfficiency: `+5% Fähigkeitseffizienz`,
upgrade_AvatarEnergyRegen: `+0.5 Energieregeneration pro Sekunde`,
upgrade_AvatarEnemyRadar: `+5m Feindradar`,
upgrade_AvatarLootRadar: `+7m Beuteradar`,
upgrade_WeaponAmmoMax: `+15% Max. Munition`,
upgrade_EnemyArmorReductionAura: `-3% Rüstung bei Feinden`,
upgrade_OnExecutionAmmo: `+100% Magazinfüllung für Primär- und Sekundärwaffen bei Gnadenstoß`,
upgrade_OnExecutionHealthDrop: `+100% Gesundheitskugel Chance bei Gnadenstoß`,
upgrade_OnExecutionEnergyDrop: `+50% Energiekugel Chance bei Gnadenstoß`,
upgrade_OnFailHackReset: `+50% Chance, das Hacken bei Fehlschlag zu wiederholen`,
upgrade_DamageReductionOnHack: `+75% Schadensreduktion beim Hacken`,
upgrade_OnExecutionReviveCompanion: `Gnadenstoß-Kills verkürzen die Erholungszeit des Begleiters um 15s`,
upgrade_OnExecutionParkourSpeed: `+60% Parkourgeschwindigkeit für 15s nach Gnadenstoß`,
upgrade_AvatarTimeLimitIncrease: `+8s extra Zeit beim Hacken`,
upgrade_ElectrifyOnHack: `Setze beim Hacken Gegner innerhalb von 20m unter Strom`,
upgrade_OnExecutionTerrify: `+50% Chance bei Gnadenstoß, dass Feinde innerhalb von 15m vor Furcht für 8s kauern`,
upgrade_OnHackLockers: `Schließe nach dem Hacken 5 Spinde innerhalb von 20m auf`,
upgrade_OnExecutionBlind: `Blende bei einem Gnadenstoß Gegner innerhalb von 18m`,
upgrade_OnExecutionDrainPower: `Nächste Fähigkeit erhält +50% Fähigkeitsstärke nach Gnadenstoß`,
upgrade_OnHackSprintSpeed: `+75% Sprintgeschwindigkeit für 15s nach dem Hacken`,
upgrade_SwiftExecute: `+50% Gnadenstoßgeschwindigkeit`,
upgrade_OnHackInvis: `+15s Unsichtbarkeit nach dem Hacken`,
damageType_Electricity: `Elektrizität`,
damageType_Fire: `Hitze`,
@ -316,8 +353,8 @@ dict = {
damageType_Poison: `Gift`,
damageType_Radiation: `Strahlung`,
theme_dark: `[UNTRANSLATED] Dark Theme`,
theme_light: `[UNTRANSLATED] Light Theme`,
theme_dark: `Dunkles Design`,
theme_light: `Helles Design`,
prettier_sucks_ass: ``
};

View File

@ -2,6 +2,7 @@ dict = {
general_inventoryUpdateNote: `Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Add`,
general_setButton: `Set`,
general_none: `None`,
general_bulkActions: `Bulk Actions`,
general_loading: `Loading...`,
@ -60,6 +61,8 @@ dict = {
code_pigment: `Pigment`,
code_mature: `Mature for combat`,
code_unmature: `Regress genetic aging`,
code_succChange: `Successfully changed.`,
code_requiredInvigorationUpgrade: `You must select both an offensive & defensive upgrade.`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`,
login_passwordLabel: `Password`,
@ -122,6 +125,38 @@ dict = {
detailedView_archonShardsDescription2: `Note that each archon shard takes some time to be applied when loading in.`,
detailedView_valenceBonusLabel: `Valence Bonus`,
detailedView_valenceBonusDescription: `You can set or remove the Valence Bonus from your weapon.`,
detailedView_modularPartsLabel: `Change Modular Parts`,
detailedView_suitInvigorationLabel: `Warframe Invigoration`,
detailedView_loadoutLabel: `Loadouts`,
invigorations_offensive_AbilityStrength: `+200% Ability Strength`,
invigorations_offensive_AbilityRange: `+100% Ability Range`,
invigorations_offensive_AbilityDuration: `+100% Ability Duration`,
invigorations_offensive_MeleeDamage: `+250% Melee Damage`,
invigorations_offensive_PrimaryDamage: `+250% Primary Damage`,
invigorations_offensive_SecondaryDamage: `+250% Secondary Damage`,
invigorations_offensive_PrimaryCritChance: `+200% Primary Critical Chance`,
invigorations_offensive_SecondaryCritChance: `+200% Secondary Critical Chance`,
invigorations_offensive_MeleeCritChance: `+200% Melee Critical Chance`,
invigorations_utility_AbilityEfficiency: `+75% Ability Efficiency`,
invigorations_utility_SprintSpeed: `+75% Sprint Speed`,
invigorations_utility_ParkourVelocity: `+75% Parkour Velocity`,
invigorations_utility_HealthMax: `+1000 Health`,
invigorations_utility_EnergyMax: `+200% Energy Max`,
invigorations_utility_StatusImmune: `Immune to Status Effects`,
invigorations_utility_ReloadSpeed: `+75% Reload Speed`,
invigorations_utility_HealthRegen: `+25 Health Regen/s`,
invigorations_utility_ArmorMax: `+1000 Armor`,
invigorations_utility_Jumps: `+5 Jump Resets`,
invigorations_utility_EnergyRegen: `+2 Energy Regen/s`,
invigorations_offensiveLabel: `Offensive Upgrade`,
invigorations_defensiveLabel: `Defensive Upgrade`,
invigorations_expiryLabel: `Upgrades Expiry (optional)`,
abilityOverride_label: `Ability Override`,
abilityOverride_onSlot: `on slot`,
mods_addRiven: `Add Riven`,
mods_fingerprint: `Fingerprint`,
@ -200,22 +235,24 @@ dict = {
cheats_intrinsicsUnlockAll: `Max Rank All Intrinsics`,
cheats_changeSupportedSyndicate: `Supported syndicate`,
cheats_changeButton: `Change`,
cheats_none: `None`,
cheats_markAllAsRead: `Mark Inbox As Read`,
worldState: `World State`,
worldState_creditBoost: `Credit Boost`,
worldState_affinityBoost: `Affinity Boost`,
worldState_resourceBoost: `Resource Boost`,
worldState_tennoLiveRelay: `TennoLive Relay`,
worldState_baroTennoConRelay: `Baro's TennoCon Relay`,
worldState_starDays: `Star Days`,
worldState_galleonOfGhouls: `Galleon of Ghouls`,
enabled: `Enabled`,
disabled: `Disabled`,
worldState_we1: `Weekend 1`,
worldState_we2: `Weekend 2`,
worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `Eidolon Override`,
worldState_day: `Day`,
worldState_night: `Night`,
worldState_eidolonOverride: `Eidolon/Deimos Override`,
worldState_day: `Day/Fass`,
worldState_night: `Night/Vome`,
worldState_vallisOverride: `Orb Vallis Override`,
worldState_warm: `Warm`,
worldState_cold: `Cold`,
@ -262,7 +299,7 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `+|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `+|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `+|VAL| Armor`,
upgrade_WarframeArmorMax: `+|VAL| Armor`,
upgrade_WarframeBlastProc: `+|VAL| Shields on kill with Blast Damage`,
upgrade_WarframeCastingSpeed: `+|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% Ability Damage on enemies affected by Corrosion Status`,
@ -277,7 +314,7 @@ dict = {
upgrade_WarframeHPBoostFromImpact: `+|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `+|VAL| Health Regen/s`,
upgrade_WarframeHealthRegen: `+|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `+|VAL| Shield`,
upgrade_WarframeStartingEnergy: `+|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `+|VAL|% Toxin Status Effect Damage`,

View File

@ -3,6 +3,7 @@ dict = {
general_inventoryUpdateNote: `Para ver los cambios en el juego, necesitas volver a sincronizar tu inventario, por ejemplo, usando el comando /sync del bootstrapper, visitando un dojo o repetidor, o volviendo a iniciar sesión.`,
general_addButton: `Agregar`,
general_setButton: `Establecer`,
general_none: `Ninguno`,
general_bulkActions: `Acciones masivas`,
general_loading: `Cargando...`,
@ -61,6 +62,8 @@ dict = {
code_pigment: `Pigmento`,
code_mature: `Listo para el combate`,
code_unmature: `Regresar el envejecimiento genético`,
code_succChange: `Cambiado correctamente`,
code_requiredInvigorationUpgrade: `Debes seleccionar una mejora ofensiva y una defensiva.`,
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`,
@ -123,6 +126,38 @@ dict = {
detailedView_archonShardsDescription2: `Ten en cuenta que cada fragmento de archón tarda un poco en aplicarse al cargar`,
detailedView_valenceBonusLabel: `Bônus de Valência`,
detailedView_valenceBonusDescription: `Puedes establecer o quitar el bono de valencia de tu arma.`,
detailedView_modularPartsLabel: `Cambiar partes modulares`,
detailedView_suitInvigorationLabel: `Vigorización de Warframe`,
detailedView_loadoutLabel: `Equipamientos`,
invigorations_offensive_AbilityStrength: `+200% Fuerza de Habilidad`,
invigorations_offensive_AbilityRange: `+100% Alcance de Habilidad`,
invigorations_offensive_AbilityDuration: `+100% Duración de Habilidad`,
invigorations_offensive_MeleeDamage: `+250% Daño Cuerpo a Cuerpo`,
invigorations_offensive_PrimaryDamage: `+250% Daño de Arma Principal`,
invigorations_offensive_SecondaryDamage: `+250% Daño de Arma Secundaria`,
invigorations_offensive_PrimaryCritChance: `+200% Probabilidad Crítica de Arma Principal`,
invigorations_offensive_SecondaryCritChance: `+200% Probabilidad Crítica de Arma Secundaria`,
invigorations_offensive_MeleeCritChance: `+200% Probabilidad Crítica Cuerpo a Cuerpo`,
invigorations_utility_AbilityEfficiency: `+75% Eficiencia de Habilidad`,
invigorations_utility_SprintSpeed: `+75% Velocidad de Sprint`,
invigorations_utility_ParkourVelocity: `+75% Velocidad de Parkour`,
invigorations_utility_HealthMax: `+1000 Salud Máx.`,
invigorations_utility_EnergyMax: `+200% Energía Máx.`,
invigorations_utility_StatusImmune: `Inmune a Efectos de Estado`,
invigorations_utility_ReloadSpeed: `+75% Velocidad de Recarga`,
invigorations_utility_HealthRegen: `+25 Regeneración de Salud/s`,
invigorations_utility_ArmorMax: `+1000 Armadura Máx.`,
invigorations_utility_Jumps: `+5 Restablecimientos de Salto`,
invigorations_utility_EnergyRegen: `+2 Regeneración de Energía/s`,
invigorations_offensiveLabel: `Mejora Ofensiva`,
invigorations_defensiveLabel: `Mejora Defensiva`,
invigorations_expiryLabel: `Caducidad de Mejoras (opcional)`,
abilityOverride_label: `Anulación de Habilidad`,
abilityOverride_onSlot: `en el espacio`,
mods_addRiven: `Agregar Agrietado`,
mods_fingerprint: `Huella digital`,
@ -201,15 +236,17 @@ dict = {
cheats_intrinsicsUnlockAll: `Maximizar todos los intrínsecos`,
cheats_changeSupportedSyndicate: `Sindicatos disponibles`,
cheats_changeButton: `Cambiar`,
cheats_none: `Ninguno`,
cheats_markAllAsRead: `Marcar bandeja de entrada como leída`,
worldState: `Estado del mundo`,
worldState_creditBoost: `Potenciador de Créditos`,
worldState_affinityBoost: `Potenciador de Afinidad`,
worldState_resourceBoost: `Potenciador de Recursos`,
worldState_tennoLiveRelay: `Repetidor de TennoLive`,
worldState_baroTennoConRelay: `Repetidor de Baro de la TennoCon`,
worldState_starDays: `Días estelares`,
worldState_galleonOfGhouls: `Galeón de Gules`,
enabled: `Activado`,
disabled: `Desactivado`,
worldState_we1: `Semana 1`,
worldState_we2: `Semana 2`,
@ -263,7 +300,7 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|% de probabilidad crítica en armas secundarias`,
upgrade_WarframeAbilityDuration: `+|VAL|% de duración de habilidades`,
upgrade_WarframeAbilityStrength: `+|VAL|% de fuerza de habilidades`,
upgrade_WarframeArmourMax: `+|VAL| de armadura`,
upgrade_WarframeArmorMax: `+|VAL| de armadura`,
upgrade_WarframeBlastProc: `+|VAL| de escudos al matar con daño de explosión`,
upgrade_WarframeCastingSpeed: `+|VAL|% de velocidad de lanzamiento de habilidades`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado corrosivo`,
@ -278,7 +315,7 @@ dict = {
upgrade_WarframeHPBoostFromImpact: `+|VAL1| de salud al eliminar con daño explosivo (máx. |VAL2| de salud)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de velocidad de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de daño de habilidades a enemigos con estado radiactivo`,
upgrade_WarframeRegen: `+|VAL| de regeneración de salud por segundo`,
upgrade_WarframeHealthRegen: `+|VAL| de regeneración de salud por segundo`,
upgrade_WarframeShieldMax: `+|VAL| de escudo`,
upgrade_WarframeStartingEnergy: `+|VAL|% de energía al reaparecer`,
upgrade_WarframeToxinDamage: `+|VAL|% de daño por efecto de estado tóxico`,

View File

@ -1,10 +1,11 @@
// French translation by Vitruvio
dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_inventoryUpdateNote: `Note : Pour voir les changements en jeu, l'inventaire doit être actualisé. Cela se fait en tapant /sync dans le tchat, en visitant un dojo/relais ou en se reconnectant.`,
general_addButton: `Ajouter`,
general_setButton: `[UNTRANSLATED] Set`,
general_setButton: `Définir`,
general_none: `Aucun`,
general_bulkActions: `Action groupée`,
general_loading: `[UNTRANSLATED] Loading...`,
general_loading: `Chargement...`,
code_loginFail: `Connexion échouée. Vérifiez le mot de passe.`,
code_regFail: `Enregistrement impossible. Compte existant?`,
@ -45,8 +46,8 @@ dict = {
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
code_addModsConfirm: `Ajouter |COUNT| mods à l'inventaire ?`,
code_succImport: `Importé.`,
code_succRelog: `[UNTRANSLATED] Done. Please note that you'll need to relog to see a difference in-game.`,
code_nothingToDo: `[UNTRANSLATED] Done. There was nothing to do.`,
code_succRelog: `Succès. Un redémarrage du jeu est nécessaire.`,
code_nothingToDo: `Succès.`,
code_gild: `Polir`,
code_moa: `Moa`,
code_zanuka: `Molosse`,
@ -61,6 +62,8 @@ dict = {
code_pigment: `Pigment`,
code_mature: `Maturer pour le combat`,
code_unmature: `Régrésser l'âge génétique`,
code_succChange: `Changement effectué.`,
code_requiredInvigorationUpgrade: `Augmentation offensive et défensive requises.`,
login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`,
@ -122,14 +125,46 @@ dict = {
detailedView_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations`,
detailedView_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`,
detailedView_valenceBonusLabel: `Bonus de Valence`,
detailedView_valenceBonusDescription: `[UNTRANSLATED] You can set or remove the Valence Bonus from your weapon.`,
detailedView_valenceBonusDescription: `Définir le Bonus Valence de l'arme.`,
detailedView_modularPartsLabel: `Changer l'équipement modulaire`,
detailedView_suitInvigorationLabel: `Invigoration de Warframe`,
detailedView_loadoutLabel: `Équipements`,
invigorations_offensive_AbilityStrength: `+200% de puissance de pouvoir`,
invigorations_offensive_AbilityRange: `+100% de portée de pouvoir`,
invigorations_offensive_AbilityDuration: `+100% de durée de pouvoir`,
invigorations_offensive_MeleeDamage: `+250% de dégâts de mêlée`,
invigorations_offensive_PrimaryDamage: `+250% de dégâts d'arme primaire`,
invigorations_offensive_SecondaryDamage: `+250% de dégâts d'arme secondaire`,
invigorations_offensive_PrimaryCritChance: `+200% de chances critique sur arme primaire`,
invigorations_offensive_SecondaryCritChance: `+200% de chances critique sur arme secondaire`,
invigorations_offensive_MeleeCritChance: `+200% de chances critique en mêlée`,
invigorations_utility_AbilityEfficiency: `+75% d'efficacité de pouvoir`,
invigorations_utility_SprintSpeed: `+75% de vitesse de course`,
invigorations_utility_ParkourVelocity: `+75% de vélocité de parkour`,
invigorations_utility_HealthMax: `+1000 de vie`,
invigorations_utility_EnergyMax: `+200% d'énergie max`,
invigorations_utility_StatusImmune: `Immunisé contre les effets de statut`,
invigorations_utility_ReloadSpeed: `+75% de vitesse de rechargement`,
invigorations_utility_HealthRegen: `+25 de vie régénérés/s`,
invigorations_utility_ArmorMax: `+1000 d'armure`,
invigorations_utility_Jumps: `+5 réinitialisations de saut`,
invigorations_utility_EnergyRegen: `+2 d'énergie régénérés/s`,
invigorations_offensiveLabel: `Amélioration offensive`,
invigorations_defensiveLabel: `Amélioration défensive`,
invigorations_expiryLabel: `Expiration de l'invigoration (optionnel)`,
abilityOverride_label: `[UNTRANSLATED] Ability Override`,
abilityOverride_onSlot: `[UNTRANSLATED] on slot`,
mods_addRiven: `Ajouter un riven`,
mods_fingerprint: `Empreinte`,
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
mods_rivens: `Rivens`,
mods_mods: `Mods`,
mods_addMax: `[UNTRANSLATED] Add Maxed`,
mods_addMax: `Ajouter les mods niveau max`,
mods_addMissingUnrankedMods: `Ajouter les mods sans rang manquants`,
mods_removeUnranked: `Retirer les mods sans rang`,
mods_addMissingMaxRankMods: `Ajouter les mods niveau max manquants`,
@ -139,7 +174,7 @@ dict = {
cheats_skipAllDialogue: `Passer les dialogues`,
cheats_unlockAllScans: `Débloquer tous les scans`,
cheats_unlockAllMissions: `Débloquer toutes les missions`,
cheats_unlockAllMissions_ok: `[UNTRANSLATED] Success. Please note that you'll need to enter a dojo/relay or relog for the client to refresh the star chart.`,
cheats_unlockAllMissions_ok: `Succès. Une actualisation de l'inventaire est nécessaire.`,
cheats_infiniteCredits: `Crédits infinis`,
cheats_infinitePlatinum: `Platinum infini`,
cheats_infiniteEndo: `Endo infini`,
@ -170,8 +205,8 @@ dict = {
cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_fullyStockedVendors: `Les vendeurs ont un stock à 100%`,
cheats_baroAlwaysAvailable: `[UNTRANSLATED] Baro Always Available`,
cheats_baroFullyStocked: `[UNTRANSLATED] Baro Fully Stocked`,
cheats_baroAlwaysAvailable: `Baro toujours présent`,
cheats_baroFullyStocked: `Stock de Baro au max`,
cheats_syndicateMissionsRepeatable: `Mission syndicat répétables`,
cheats_unlockAllProfitTakerStages: `Débloquer toutes les étapes du Preneur de Profit`,
cheats_instantFinishRivenChallenge: `Débloquer le challenge Riven instantanément`,
@ -185,76 +220,78 @@ dict = {
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_missionsCanGiveAllRelics: `Les missions donnent toutes les reliques`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `[UNTRANSLATED] Exceptional Relics Always Give Bronze Reward`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `[UNTRANSLATED] Flawless Relics Always Give Silver Reward`,
cheats_radiantRelicsAlwaysGiveGoldReward: `[UNTRANSLATED] Radiant Relics Always Give Gold Reward`,
cheats_exceptionalRelicsAlwaysGiveBronzeReward: `Les reliques exceptionnelles donnent toujours une récompense en bronze`,
cheats_flawlessRelicsAlwaysGiveSilverReward: `Les reliques parfaites donnent toujours une récompense en argent`,
cheats_radiantRelicsAlwaysGiveGoldReward: `Les reliques éclatantes donnent toujours une récompense en or`,
cheats_unlockAllSimarisResearchEntries: `Débloquer toute les recherches chez Simaris`,
cheats_disableDailyTribute: `[UNTRANSLATED] Disable Daily Tribute`,
cheats_disableDailyTribute: `Désactiver la récompense quotidienne de connexion`,
cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_relicRewardItemCountMultiplier: `[UNTRANSLATED] Relic Reward Item Count Multiplier`,
cheats_relicRewardItemCountMultiplier: `Multiplicateur de récompenses de relique`,
cheats_nightwaveStandingMultiplier: `Multiplicateur de réputation d'Ondes Nocturnes`,
cheats_save: `Sauvegarder`,
cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_addMissingSubsumedAbilities: `[UNTRANSLATED] Add Missing Subsumed Abilities`,
cheats_addMissingSubsumedAbilities: `Ajouter les capacités subsumées manquantes`,
cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`,
cheats_none: `Aucun`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
cheats_markAllAsRead: `Marquer la boîte de réception comme lue`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
worldState_duviriOverride: `[UNTRANSLATED] Duviri Override`,
worldState_joy: `[UNTRANSLATED] Joy`,
worldState_anger: `[UNTRANSLATED] Anger`,
worldState_envy: `[UNTRANSLATED] Envy`,
worldState_sorrow: `[UNTRANSLATED] Sorrow`,
worldState_fear: `[UNTRANSLATED] Fear`,
worldState_nightwaveOverride: `[UNTRANSLATED] Nightwave Override`,
worldState_RadioLegionIntermission13Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `[UNTRANSLATED] Nora's Mix Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `[UNTRANSLATED] Nora's Choice`,
worldState_RadioLegionIntermission3Syndicate: `[UNTRANSLATED] Intermission III`,
worldState_RadioLegion3Syndicate: `[UNTRANSLATED] Glassmaker`,
worldState_RadioLegionIntermission2Syndicate: `[UNTRANSLATED] Intermission II`,
worldState_RadioLegion2Syndicate: `[UNTRANSLATED] The Emissary`,
worldState_RadioLegionIntermissionSyndicate: `[UNTRANSLATED] Intermission I`,
worldState_RadioLegionSyndicate: `[UNTRANSLATED] The Wolf of Saturn Six`,
worldState_fissures: `[UNTRANSLATED] Fissures`,
normal: `[UNTRANSLATED] Normal`,
worldState_allAtOnceNormal: `[UNTRANSLATED] All At Once, Normal`,
worldState_allAtOnceSteelPath: `[UNTRANSLATED] All At Once, Steel Path`,
worldState_theCircuitOverride: `[UNTRANSLATED] The Circuit Override`,
worldState_darvoStockMultiplier: `[UNTRANSLATED] Darvo Stock Multiplier`,
worldState_varziaFullyStocked: `[UNTRANSLATED] Varzia Fully Stocked`,
worldState_varziaOverride: `[UNTRANSLATED] Varzia Rotation Override`,
worldState: `Carte Solaire`,
worldState_creditBoost: `Booster de Crédit`,
worldState_affinityBoost: `Booster d'Affinité`,
worldState_resourceBoost: `Booster de Ressource`,
worldState_tennoLiveRelay: `[UNTRANSLATED] TennoLive Relay`,
worldState_baroTennoConRelay: `[UNTRANSLATED] Baro's TennoCon Relay`,
worldState_starDays: `Jours Stellaires`,
worldState_galleonOfGhouls: `Galion des Goules`,
enabled: `[UNTRANSLATED] Enabled`,
disabled: `Désactivé`,
worldState_we1: `Weekend 1`,
worldState_we2: `Weekend 2`,
worldState_we3: `Weekend 3`,
worldState_eidolonOverride: `Météo Plaines d'Eidolon`,
worldState_day: `Jour`,
worldState_night: `Nuit`,
worldState_vallisOverride: `Météo Vallée Orbis`,
worldState_warm: `Chaud`,
worldState_cold: `Froid`,
worldState_duviriOverride: `Spirale Duviri`,
worldState_joy: `Joie`,
worldState_anger: `Colère`,
worldState_envy: `Envie `,
worldState_sorrow: `hagrin`,
worldState_fear: `Peur`,
worldState_nightwaveOverride: `Saison d'Ondes Nocturnes`,
worldState_RadioLegionIntermission13Syndicate: `Mix de Nora Vol. 9`,
worldState_RadioLegionIntermission12Syndicate: `Mix de Nora Vol. 8`,
worldState_RadioLegionIntermission11Syndicate: `Mix de Nora Vol. 7`,
worldState_RadioLegionIntermission10Syndicate: `Mix de Nora Vol. 6`,
worldState_RadioLegionIntermission9Syndicate: `Mix de Nora Vol. 5`,
worldState_RadioLegionIntermission8Syndicate: `Mix de Nora Vol. 4`,
worldState_RadioLegionIntermission7Syndicate: `Mix de Nora Vol. 3`,
worldState_RadioLegionIntermission6Syndicate: `Mix de Nora Vol. 2`,
worldState_RadioLegionIntermission5Syndicate: `Mix de Nora Vol. 1`,
worldState_RadioLegionIntermission4Syndicate: `La Sélection de Nora`,
worldState_RadioLegionIntermission3Syndicate: `Intermission III`,
worldState_RadioLegion3Syndicate: `Les Mystères du Verre`,
worldState_RadioLegionIntermission2Syndicate: `Intermission II`,
worldState_RadioLegion2Syndicate: `L'Émissaire`,
worldState_RadioLegionIntermissionSyndicate: `Intermission I`,
worldState_RadioLegionSyndicate: `Le Loup de Saturne Six`,
worldState_fissures: `Fissures`,
normal: `Normal`,
worldState_allAtOnceNormal: `Toutes, Normal`,
worldState_allAtOnceSteelPath: `Toutes, Route de l'Acier`,
worldState_theCircuitOverride: `Remplacement du Circuit`,
worldState_darvoStockMultiplier: `Multiplicateur du stock de Darvo`,
worldState_varziaFullyStocked: `Stock de Varzia au max`,
worldState_varziaOverride: `Rotation de Varzia`,
import_importNote: `Import manuel. Toutes les modifcations supportées par l'inventaire <b>écraseront celles présentes dans la base de données</b>.`,
import_submit: `Soumettre`,
import_samples: `Echantillons :`,
import_samples: `Échantillons :`,
import_samples_maxFocus: `Toutes les écoles de focus au rang max`,
upgrade_Equilibrium: `Ramasser de la santé donne +|VAL|% d'énergie supplémentaire. Ramasser de l'énergie donne +|VAL|% de santé supplémentaire.`,
@ -263,7 +300,7 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|% de chance critique sur arme secondaire`,
upgrade_WarframeAbilityDuration: `+|VAL|% de durée de pouvoir`,
upgrade_WarframeAbilityStrength: `+|VAL|% de puissance de pouvoir`,
upgrade_WarframeArmourMax: `+|VAL| d'armure`,
upgrade_WarframeArmorMax: `+|VAL| d'armure`,
upgrade_WarframeBlastProc: `+|VAL| de boucliers sur élimination avec des dégats d'explosion`,
upgrade_WarframeCastingSpeed: `+|VAL|% de vitesse de lancement de pouvoir`,
upgrade_WarframeCorrosiveDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut corrosif`,
@ -275,10 +312,10 @@ dict = {
upgrade_WarframeGlobeEffectEnergy: `+|VAL|% d'efficacité d'orbe d'énergie`,
upgrade_WarframeGlobeEffectHealth: `+|VAL|% d'efficacité d'orbe de santé`,
upgrade_WarframeHealthMax: `+|VAL| de santé`,
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeHPBoostFromImpact: `+|VAL1| de vie sur élimination avec des dégâts d'explostion (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `+|VAL|% de vélocité de parkour`,
upgrade_WarframeRadiationDamageBoost: `+|VAL|% de dégâts de pouvoir sur les ennemis affectés par du statut radiation`,
upgrade_WarframeRegen: `+|VAL| régénération de santé/s`,
upgrade_WarframeHealthRegen: `+|VAL| régénération de santé/s`,
upgrade_WarframeShieldMax: `+|VAL| de boucliers`,
upgrade_WarframeStartingEnergy: `+|VAL|% d'énergie sur apparition`,
upgrade_WarframeToxinDamage: `+|VAL|% de dégâts sur le statut poison`,
@ -294,7 +331,7 @@ dict = {
upgrade_OnExecutionAmmo: `100% de rechargement des armes primaires et secondaires sur une une miséricorde`,
upgrade_OnExecutionHealthDrop: `100% de chance de drop une orbe de santé sur une miséricorde`,
upgrade_OnExecutionEnergyDrop: `50% de chance de drop une orbe d'énergie sur une miséricorde`,
upgrade_OnFailHackReset: `[UNTRANSLATED] +50% Hacking Retry Chance`,
upgrade_OnFailHackReset: `+50% de chance de refaire un piratage`,
upgrade_DamageReductionOnHack: `75% de réduction de dégâts pendant un piratage`,
upgrade_OnExecutionReviveCompanion: `Les miséricordes réduisent le temps de récupération du compagnon de 15s`,
upgrade_OnExecutionParkourSpeed: `+60% de vitesse de parkour pendant 15s après une miséricorde`,
@ -303,10 +340,10 @@ dict = {
upgrade_OnExecutionTerrify: `Les ennemis dans un rayon de 15m ont 50% de chance de s'enfuir après une miséricorde`,
upgrade_OnHackLockers: `5 casiers s'ouvrent dans un rayon de 20m après un piratage`,
upgrade_OnExecutionBlind: `Les ennemis sont aveuglés dans un rayon de 18 après une miséricorde`,
upgrade_OnExecutionDrainPower: `[UNTRANSLATED] Next ability cast gains +50% Ability Strength on Mercy`,
upgrade_OnExecutionDrainPower: `Le prochain pouvoir activé gagne +50% de puissance de pouvoir après une miséricorde`,
upgrade_OnHackSprintSpeed: `+75% de vitesse de course pendant 15s après un piratage`,
upgrade_SwiftExecute: `[UNTRANSLATED] +50% Mercy Kill Speed`,
upgrade_OnHackInvis: `[UNTRANSLATED] Invisible for 15 seconds after Hacking`,
upgrade_SwiftExecute: `+50% de vitesse de d'éxecution en miséricorde`,
upgrade_OnHackInvis: `Invisible pendant 15s après un piratage`,
damageType_Electricity: `Électrique`,
damageType_Fire: `Feu`,
@ -316,8 +353,8 @@ dict = {
damageType_Poison: `Poison`,
damageType_Radiation: `Radiations`,
theme_dark: `[UNTRANSLATED] Dark Theme`,
theme_light: `[UNTRANSLATED] Light Theme`,
theme_dark: `Thème sombre`,
theme_light: `Thème clair`,
prettier_sucks_ass: ``
};

View File

@ -3,6 +3,7 @@ dict = {
general_inventoryUpdateNote: `[UNTRANSLATED] Note: To see changes in-game, you need to resync your inventory, e.g. using the bootstrapper's /sync command, visiting a dojo/relay, or relogging.`,
general_addButton: `Добавить`,
general_setButton: `Установить`,
general_none: `Отсутствует`,
general_bulkActions: `Массовые действия`,
general_loading: `[UNTRANSLATED] Loading...`,
@ -61,6 +62,8 @@ dict = {
code_pigment: `Пигмент`,
code_mature: `Подготовить к сражениям`,
code_unmature: `Регрессия генетического старения`,
code_succChange: `Успешно изменено.`,
code_requiredInvigorationUpgrade: `[UNTRANSLATED] You must select both an offensive & defensive upgrade.`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`,
@ -123,6 +126,38 @@ dict = {
detailedView_archonShardsDescription2: `Обратите внимание: каждый фрагмент архонта применяется с задержкой при загрузке.`,
detailedView_valenceBonusLabel: `Бонус Валентности`,
detailedView_valenceBonusDescription: `Вы можете установить или убрать бонус валентности с вашего оружия.`,
detailedView_modularPartsLabel: `Изменить Модульные Части`,
detailedView_suitInvigorationLabel: `[UNTRANSLATED] Warframe Invigoration`,
detailedView_loadoutLabel: `Конфигурации`,
invigorations_offensive_AbilityStrength: `[UNTRANSLATED] +200% Ability Strength`,
invigorations_offensive_AbilityRange: `[UNTRANSLATED] +100% Ability Range`,
invigorations_offensive_AbilityDuration: `[UNTRANSLATED] +100% Ability Duration`,
invigorations_offensive_MeleeDamage: `[UNTRANSLATED] +250% Melee Damage`,
invigorations_offensive_PrimaryDamage: `[UNTRANSLATED] +250% Primary Damage`,
invigorations_offensive_SecondaryDamage: `[UNTRANSLATED] +250% Secondary Damage`,
invigorations_offensive_PrimaryCritChance: `[UNTRANSLATED] +200% Primary Critical Chance`,
invigorations_offensive_SecondaryCritChance: `[UNTRANSLATED] +200% Secondary Critical Chance`,
invigorations_offensive_MeleeCritChance: `[UNTRANSLATED] +200% Melee Critical Chance`,
invigorations_utility_AbilityEfficiency: `[UNTRANSLATED] +75% Ability Efficiency`,
invigorations_utility_SprintSpeed: `[UNTRANSLATED] +75% Sprint Speed`,
invigorations_utility_ParkourVelocity: `[UNTRANSLATED] +75% Parkour Velocity`,
invigorations_utility_HealthMax: `[UNTRANSLATED] +1000 Health`,
invigorations_utility_EnergyMax: `[UNTRANSLATED] +200% Energy Max`,
invigorations_utility_StatusImmune: `[UNTRANSLATED] Immune to Status Effects`,
invigorations_utility_ReloadSpeed: `[UNTRANSLATED] +75% Reload Speed`,
invigorations_utility_HealthRegen: `[UNTRANSLATED] +25 Health Regen/s`,
invigorations_utility_ArmorMax: `[UNTRANSLATED] +1000 Armor`,
invigorations_utility_Jumps: `[UNTRANSLATED] +5 Jump Resets`,
invigorations_utility_EnergyRegen: `[UNTRANSLATED] +2 Energy Regen/s`,
invigorations_offensiveLabel: `[UNTRANSLATED] Offensive Upgrade`,
invigorations_defensiveLabel: `[UNTRANSLATED] Defensive Upgrade`,
invigorations_expiryLabel: `[UNTRANSLATED] Upgrades Expiry (optional)`,
abilityOverride_label: `Переопределение способности`,
abilityOverride_onSlot: `в ячейке`,
mods_addRiven: `Добавить Мод Разлома`,
mods_fingerprint: `Отпечаток`,
@ -201,22 +236,24 @@ dict = {
cheats_intrinsicsUnlockAll: `Полностью улучшить Модуляры`,
cheats_changeSupportedSyndicate: `Поддерживаемый синдикат`,
cheats_changeButton: `Изменить`,
cheats_none: `Отсутствует`,
cheats_markAllAsRead: `[UNTRANSLATED] Mark Inbox As Read`,
worldState: `[UNTRANSLATED] World State`,
worldState_creditBoost: `[UNTRANSLATED] Credit Boost`,
worldState_affinityBoost: `[UNTRANSLATED] Affinity Boost`,
worldState_resourceBoost: `[UNTRANSLATED] Resource Boost`,
worldState_tennoLiveRelay: `[UNTRANSLATED] TennoLive Relay`,
worldState_baroTennoConRelay: `[UNTRANSLATED] Baro's TennoCon Relay`,
worldState_starDays: `[UNTRANSLATED] Star Days`,
worldState_galleonOfGhouls: `[UNTRANSLATED] Galleon of Ghouls`,
enabled: `[UNTRANSLATED] Enabled`,
disabled: `[UNTRANSLATED] Disabled`,
worldState_we1: `[UNTRANSLATED] Weekend 1`,
worldState_we2: `[UNTRANSLATED] Weekend 2`,
worldState_we3: `[UNTRANSLATED] Weekend 3`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon Override`,
worldState_day: `[UNTRANSLATED] Day`,
worldState_night: `[UNTRANSLATED] Night`,
worldState_eidolonOverride: `[UNTRANSLATED] Eidolon/Deimos Override`,
worldState_day: `[UNTRANSLATED] Day/Fass`,
worldState_night: `[UNTRANSLATED] Night/Vome`,
worldState_vallisOverride: `[UNTRANSLATED] Orb Vallis Override`,
worldState_warm: `[UNTRANSLATED] Warm`,
worldState_cold: `[UNTRANSLATED] Cold`,
@ -263,7 +300,7 @@ dict = {
upgrade_SecondaryCritChance: `[UNTRANSLATED] +|VAL|% Secondary Critical Chance`,
upgrade_WarframeAbilityDuration: `[UNTRANSLATED] +|VAL|% Ability Duration`,
upgrade_WarframeAbilityStrength: `[UNTRANSLATED] +|VAL|% Ability Strength`,
upgrade_WarframeArmourMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeArmorMax: `[UNTRANSLATED] +|VAL| Armor`,
upgrade_WarframeBlastProc: `[UNTRANSLATED] +|VAL| Shields on kill with Blast Damage`,
upgrade_WarframeCastingSpeed: `[UNTRANSLATED] +|VAL|% Casting Speed`,
upgrade_WarframeCorrosiveDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Corrosion Status`,
@ -278,7 +315,7 @@ dict = {
upgrade_WarframeHPBoostFromImpact: `[UNTRANSLATED] +|VAL1| Health on kill with Blast Damage (Max |VAL2| Health)`,
upgrade_WarframeParkourVelocity: `[UNTRANSLATED] +|VAL|% Parkour Velocity`,
upgrade_WarframeRadiationDamageBoost: `[UNTRANSLATED] +|VAL|% Ability Damage on enemies affected by Radiation Status`,
upgrade_WarframeRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeHealthRegen: `[UNTRANSLATED] +|VAL| Health Regen/s`,
upgrade_WarframeShieldMax: `[UNTRANSLATED] +|VAL| Shield`,
upgrade_WarframeStartingEnergy: `[UNTRANSLATED] +|VAL|% Energy on Spawn`,
upgrade_WarframeToxinDamage: `[UNTRANSLATED] +|VAL|% Toxin Status Effect Damage`,

View File

@ -1,8 +1,9 @@
// Chinese translation by meb154, bishan178 & Corvus
// Chinese translation by meb154, bishan178, nyaoouo, qianlishun, CrazyZhang & Corvus
dict = {
general_inventoryUpdateNote: `注意:要在游戏中查看更改,您需要重新同步库存,例如使用引导程序的 /sync 命令、访问道场/中继站或重新登录客户端.`,
general_addButton: `添加`,
general_setButton: `设置`,
general_none: ``,
general_bulkActions: `批量操作`,
general_loading: `加载中...`,
@ -61,6 +62,8 @@ dict = {
code_pigment: `颜料`,
code_mature: `成长并战备`,
code_unmature: `逆转衰老基因`,
code_succChange: `更改成功.`,
code_requiredInvigorationUpgrade: `您必须同时选择一个进攻型和一个功能型活化属性.`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同).`,
login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`,
@ -123,6 +126,38 @@ dict = {
detailedView_archonShardsDescription2: `请注意,在加载时,每个执政官源力石都需要一定的时间来生效。`,
detailedView_valenceBonusLabel: `效价加成`,
detailedView_valenceBonusDescription: `您可以设置或移除武器上的效价加成.`,
detailedView_modularPartsLabel: `更换部件`,
detailedView_suitInvigorationLabel: `编辑战甲活化属性`,
detailedView_loadoutLabel: `配置`,
invigorations_offensive_AbilityStrength: `+200%技能强度`,
invigorations_offensive_AbilityRange: `+100%技能范围`,
invigorations_offensive_AbilityDuration: `+100%技能持续时间`,
invigorations_offensive_MeleeDamage: `+250%近战武器伤害`,
invigorations_offensive_PrimaryDamage: `+250%主要武器伤害`,
invigorations_offensive_SecondaryDamage: `+250%次要武器伤害`,
invigorations_offensive_PrimaryCritChance: `+200%主要武器暴击几率`,
invigorations_offensive_SecondaryCritChance: `+200%次要武器暴击几率`,
invigorations_offensive_MeleeCritChance: `+200%近战武器暴击几率`,
invigorations_utility_AbilityEfficiency: `+75%技能效率`,
invigorations_utility_SprintSpeed: `+75%冲刺速度`,
invigorations_utility_ParkourVelocity: `+75%跑酷速度`,
invigorations_utility_HealthMax: `+1000生命值`,
invigorations_utility_EnergyMax: `+200%最大能量`,
invigorations_utility_StatusImmune: `异常状态免疫`,
invigorations_utility_ReloadSpeed: `+75%装填速度`,
invigorations_utility_HealthRegen: `+25/秒生命再生`,
invigorations_utility_ArmorMax: `+1000护甲值`,
invigorations_utility_Jumps: `+5跳跃次数`,
invigorations_utility_EnergyRegen: `+2/秒能量再生`,
invigorations_offensiveLabel: `进攻型属性`,
invigorations_defensiveLabel: `功能型属性`,
invigorations_expiryLabel: `活化时效(可选)`,
abilityOverride_label: `技能替换`,
abilityOverride_onSlot: `槽位`,
mods_addRiven: `添加裂罅MOD`,
mods_fingerprint: `印记`,
@ -201,15 +236,17 @@ dict = {
cheats_intrinsicsUnlockAll: `所有内源之力最大等级`,
cheats_changeSupportedSyndicate: `支持的集团`,
cheats_changeButton: `更改`,
cheats_none: ``,
cheats_markAllAsRead: `收件箱全部标记为已读`,
worldState: `世界状态配置`,
worldState_creditBoost: `现金加成`,
worldState_affinityBoost: `经验加成`,
worldState_resourceBoost: `资源加成`,
worldState_tennoLiveRelay: `TennoLive中继站`,
worldState_baroTennoConRelay: `Baro的TennoCon中继站`,
worldState_starDays: `活动:星日`,
worldState_galleonOfGhouls: `战术警报:尸鬼的帆船战舰`,
enabled: `启用`,
disabled: `关闭/取消配置`,
worldState_we1: `活动阶段:第一周`,
worldState_we2: `活动阶段:第二周`,
@ -263,7 +300,7 @@ dict = {
upgrade_SecondaryCritChance: `+|VAL|%次要武器暴击几率`,
upgrade_WarframeAbilityDuration: `+|VAL|%技能持续时间`,
upgrade_WarframeAbilityStrength: `+|VAL|%技能强度`,
upgrade_WarframeArmourMax: `+|VAL|护甲`,
upgrade_WarframeArmorMax: `+|VAL|护甲`,
upgrade_WarframeBlastProc: `使用爆炸伤害击杀敌人时恢复+|VAL|护盾`,
upgrade_WarframeCastingSpeed: `+|VAL|%施放速度`,
upgrade_WarframeCorrosiveDamageBoost: `对受腐蚀异常影响的敌人+|VAL|%额外技能伤害`,
@ -278,7 +315,7 @@ dict = {
upgrade_WarframeHPBoostFromImpact: `通过爆炸伤害击杀的每个敌人可增加|VAL1|生命上限(最大|VAL2|生命上限)`,
upgrade_WarframeParkourVelocity: `+|VAL|%跑酷速度`,
upgrade_WarframeRadiationDamageBoost: `对受辐射状态影响的敌人+|VAL|%技能伤害`,
upgrade_WarframeRegen: `+|VAL|/秒生命再生`,
upgrade_WarframeHealthRegen: `+|VAL|/秒生命再生`,
upgrade_WarframeShieldMax: `+|VAL|护盾值`,
upgrade_WarframeStartingEnergy: `进任务时+|VAL|%初始能量`,
upgrade_WarframeToxinDamage: `毒素伤害额外+|VAL|%伤害`,