Compare commits

..

87 Commits

Author SHA1 Message Date
db0e0d80dd chore: remove PropertyTextHash from auto-generated vendors 2025-04-27 07:20:04 +02:00
5cda2e2d08 chore: improve unlockAllScans's handling of existing scans (#1875)
Reviewed-on: OpenWF/SpaceNinjaServer#1875
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 19:28:03 -07:00
e23d865044 fix: use a list of "known good" syndicate missions (#1874)
Closes #1870

Reviewed-on: OpenWF/SpaceNinjaServer#1874
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 19:27:50 -07:00
c7658b5b20 chore: use parallelForeach in removePigmentsFromGuildMembers 2025-04-27 04:22:18 +02:00
9993500eca chore(webui): update to Spanish translation (#1881)
Reviewed-on: OpenWF/SpaceNinjaServer#1881
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-26 18:53:46 -07:00
267357871b feat: handle HenchmenKilled & HintProgress incrementing (#1877)
Closes #1807

Reviewed-on: OpenWF/SpaceNinjaServer#1877
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 15:25:47 -07:00
cf5ed0442d fix: don't assume rewardInfo.node is in ExportRegions (#1879)
Fixes #1878

Reviewed-on: OpenWF/SpaceNinjaServer#1879
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 14:23:00 -07:00
de36e2ee8d fix: close connection for dating saveDialogue request (#1873)
Missing fix for #1852

Reviewed-on: OpenWF/SpaceNinjaServer#1873
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 12:14:42 -07:00
ca1b6c31b6 fix: give rewards for completing a capture mission (#1872)
Reviewed-on: OpenWF/SpaceNinjaServer#1872
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:57:03 -07:00
d66c474bfc fix: some issues with sortie generation (#1871)
Now using sortieTilesets as a source of truth for allowed mission nodes as it's based only on real sorties, also added disallowed mission types for FC_OROKIN (Corrupted Vor) that otherwise cause a script error.

Closes #1865

Reviewed-on: OpenWF/SpaceNinjaServer#1871
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:41 -07:00
781f01520f feat: save lotus customization (#1864)
Closes #768

Reviewed-on: OpenWF/SpaceNinjaServer#1864
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:22 -07:00
ac37702468 feat(webui): add missing max rank mods (#1863)
Closes #916

Reviewed-on: OpenWF/SpaceNinjaServer#1863
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:16 -07:00
75c011e3cb fix: don't set IsNew flag for starting gear (#1859)
Reviewed-on: OpenWF/SpaceNinjaServer#1859
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:56:06 -07:00
4d4f885c8e feat: dontSubtractConsumables cheat (#1857)
Closes #1838

Reviewed-on: OpenWF/SpaceNinjaServer#1857
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:55:45 -07:00
66d1a65e63 fix: handle credits & platinum prices from vendors (#1856)
Fixes #1837

Reviewed-on: OpenWF/SpaceNinjaServer#1856
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:55:03 -07:00
48eefd8db1 fix: don't give droptable rewards for non-assassination sortie missions (#1855)
Closes #1835

Reviewed-on: OpenWF/SpaceNinjaServer#1855
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:54 -07:00
4a6a5ea9cc feat: handle WeaponSkins picked up in missions (#1854)
For sigils.

Closes #1839

Reviewed-on: OpenWF/SpaceNinjaServer#1854
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:38 -07:00
95c0ad7892 fix: handle saveDialogue request without Data or Gift (#1853)
Needed to just set booleans when starting dating.

Closes #1852

Reviewed-on: OpenWF/SpaceNinjaServer#1853
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:25 -07:00
a90d3a5156 feat: gardening (#1849)
Reviewed-on: OpenWF/SpaceNinjaServer#1849
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:54:06 -07:00
d0c9409a2d fix: exclude pvp variants from daily special parts (#1846)
Fixes #1836

Reviewed-on: OpenWF/SpaceNinjaServer#1846
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-26 11:53:41 -07:00
bbde7b2141 chore: don't change remote/origin url 2025-04-26 08:12:54 +02:00
5271123090 chore(webui): update to Spanish translation (#1862)
Reviewed-on: OpenWF/SpaceNinjaServer#1862
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-25 22:13:28 -07:00
f3e56480e5 fix(webui): error for inventory without EvolutionProgress (#1861)
Reviewed-on: OpenWF/SpaceNinjaServer#1861
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 21:26:58 -07:00
6f46ace40c fix(webui): revalidate authz for rename & delete account actions (#1860)
Reviewed-on: OpenWF/SpaceNinjaServer#1860
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 21:26:52 -07:00
883426e429 fix: align guild advertisment vendor rotation to monday 0 UTC (#1858)
Reviewed-on: OpenWF/SpaceNinjaServer#1858
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 21:26:16 -07:00
13432bf034 fix: future-proof oid string generation (#1847)
This ensures they are still 24 bytes long even past the year 2106. :^)

Reviewed-on: OpenWF/SpaceNinjaServer#1847
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 18:55:09 -07:00
a1267e5f64 chore: add temple vendor manifest (#1851)
Closes #1850

Reviewed-on: OpenWF/SpaceNinjaServer#1851
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 18:54:52 -07:00
2058207b6a fix: nemesis fp from client could be of type number 2025-04-26 03:15:58 +02:00
c7c416c100 chore: simplify arguments defaulted to undefined 2025-04-26 00:40:57 +02:00
90e97d7888 chore: auto-generate guild advertisment vendor (#1845)
With this, preprocessing is simplified to just refreshing expiry dates. No real change to auto-generation logic.

Reviewed-on: OpenWF/SpaceNinjaServer#1845
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 15:12:45 -07:00
3f6734ac1c feat(webui): EvolutionProgress support (#1818)
Closes #1815

Reviewed-on: OpenWF/SpaceNinjaServer#1818
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-25 12:00:38 -07:00
6f64690b91 fix: refresh duviri seed after non-quit completion of a duviri game mode (#1834)
Reviewed-on: OpenWF/SpaceNinjaServer#1834
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:56:40 -07:00
fb5a7320bb chore: update to allScans cheat (#1844)
Reviewed-on: OpenWF/SpaceNinjaServer#1844
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-25 11:56:27 -07:00
143b358a03 chore: always update rewardSeed in missionInventoryUpdate (#1833)
This should be slightly more faithful. Also logging a warning in case we have a mismatch as that shouldn't happen.

Reviewed-on: OpenWF/SpaceNinjaServer#1833
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:54:11 -07:00
0b75757277 chore: improve distribution of rewardSeed (#1831)
This was previously not ideal due to float imprecision, but now it's 64 bits and there's enough entropy for all of them.

Reviewed-on: OpenWF/SpaceNinjaServer#1831
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:53:54 -07:00
fd7f4c9e92 feat: calendar progress (#1830)
Closes #1775

Reviewed-on: OpenWF/SpaceNinjaServer#1830
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:53:34 -07:00
fa6fac494b fix: some problems with 1999 calendar rotation (#1829)
- First day was incorrect for summer & autumn
- Only 1 reward was shown, now is a choice of 2
- Only 1 upgrade was shown, now is a choice of 3
- First 2 challenges in the season are now guaranteed to be "easy"

Reviewed-on: OpenWF/SpaceNinjaServer#1829
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:53:04 -07:00
6b3f524574 feat: sortie mission credit rewards (#1828)
Closes #1820

Reviewed-on: OpenWF/SpaceNinjaServer#1828
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:52:42 -07:00
506365f97e feat: auto-generate debt token vendor manifest (#1827)
Yet another pretty big change to how these things are generated, but getting closer to where we wanna be now.

Reviewed-on: OpenWF/SpaceNinjaServer#1827
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:52:31 -07:00
70646160c3 fix: give no rewards if there are no qualifications (#1826)
Fixes #1823

Reviewed-on: OpenWF/SpaceNinjaServer#1826
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:52:16 -07:00
3ffa4a7fd3 fix: exclude some more nodes from syndicate missions (#1825)
Closes #1819

Reviewed-on: OpenWF/SpaceNinjaServer#1825
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:51:54 -07:00
826a09a473 fix: provide a response to setShipFavouriteLoadout (#1824)
Seems to be the same format as the request, so just mirror it back. This is so the client knows we acknowledged the change as it won't resync the ship until the next login.

Closes #1822

Reviewed-on: OpenWF/SpaceNinjaServer#1824
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-25 11:51:43 -07:00
100aefcee4 fix: give corresponding weapon when crafting Hound (#1816)
Reviewed-on: OpenWF/SpaceNinjaServer#1816
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-24 11:24:53 -07:00
409c089d11 feat: handle account already owning a nightwave skin item (#1814)
Closes #1811

Reviewed-on: OpenWF/SpaceNinjaServer#1814
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-24 11:24:38 -07:00
8c32dc2670 fix: add MoaPets into sellController (#1813)
Reviewed-on: OpenWF/SpaceNinjaServer#1813
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-24 11:24:25 -07:00
a67f99b665 chore: don't use sequential values as RNG seeds directly (#1812)
This should help get a slightly better distribution

Reviewed-on: OpenWF/SpaceNinjaServer#1812
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-24 11:24:11 -07:00
756a01d270 fix: pass Emblem field on in getGuildClient 2025-04-24 05:36:16 +02:00
efc7467a99 chore: remove unused MoaPets array from getItemLists 2025-04-24 02:00:27 +02:00
99e1a66da8 chore: improve typings in getItemLists 2025-04-24 00:46:33 +02:00
370f8c1008 fix: getItemLists fixup 2025-04-24 00:42:33 +02:00
f039998d71 chore: update PE+ to 0.5.58 2025-04-24 00:33:20 +02:00
eb594af9d8 chore: improve archwing mission detection (#1794)
SettlementNode10 was not being excluded

Reviewed-on: OpenWF/SpaceNinjaServer#1794
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:40:45 -07:00
bb8596fa87 fix(webui): use proper 'size' abbreviations for vallis & deimos fish (#1804)
Closes #1763

Reviewed-on: OpenWF/SpaceNinjaServer#1804
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:38:04 -07:00
a85539a686 feat: set IsNew flag on new sentinels (#1802)
Reviewed-on: OpenWF/SpaceNinjaServer#1802
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:37:52 -07:00
ada6a4bad0 fix: occupy correct slot for arch-guns (#1801)
Reviewed-on: OpenWF/SpaceNinjaServer#1801
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:37:43 -07:00
948104a9a6 fix: "logged in elsewhere" when logging in on account created via webui (#1800)
Reviewed-on: OpenWF/SpaceNinjaServer#1800
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:37:31 -07:00
7a8b12b372 chore: cap FusionPoints balance at 2147483647 (#1797)
Same idea as with typeCountSchema. The game needs to be able to store these safely in an i32 on the C++ side.

Reviewed-on: OpenWF/SpaceNinjaServer#1797
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:37:10 -07:00
26d644a982 feat: handle scale for the dojo decos that need it (#1795)
Closes #1785

Reviewed-on: OpenWF/SpaceNinjaServer#1795
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:36:57 -07:00
d6750cd84b chore: provide tileset for sortie missions (#1793)
Closes #1788

Reviewed-on: OpenWF/SpaceNinjaServer#1793
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:36:32 -07:00
f3601ec43e chore: allow MT_CAPTURE for sorties (#1792)
e.g. `SolNode1` is a capture mission but it should still be a valid node for sorties. Not that the mission will actually be a capture.

Reviewed-on: OpenWF/SpaceNinjaServer#1792
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:36:19 -07:00
15aaa28a4f feat: conquest progression & rewards (#1791)
Closes #1570

Co-authored-by: Jānis <janisslsm@noreply.localhost>
Reviewed-on: OpenWF/SpaceNinjaServer#1791
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:35:57 -07:00
ce5b0fc9e2 fix: limit MT_LANDSCAPE sortie missions to PoE (#1790)
Closes #1789

Reviewed-on: OpenWF/SpaceNinjaServer#1790
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-23 11:35:29 -07:00
64290b72c0 chore(webui): update to Spanish translation (#1809)
Reviewed-on: OpenWF/SpaceNinjaServer#1809
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-23 11:20:40 -07:00
570c6fe0d1 chore(webui): update German translation (#1806)
Reviewed-on: OpenWF/SpaceNinjaServer#1806
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-23 11:20:25 -07:00
146dbd1b89 chore(webui): update to Spanish translation (#1803)
Reviewed-on: OpenWF/SpaceNinjaServer#1803
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-22 18:52:46 -07:00
e17d43dcb6 chore: fix slotNames duplication (#1798)
Reviewed-on: OpenWF/SpaceNinjaServer#1798
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 15:20:50 -07:00
daacbf6f7b fix(webui): add exalted array for KubrowPets ItemLists (#1782)
Closes #1770

Reviewed-on: OpenWF/SpaceNinjaServer#1782
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-22 10:00:58 -07:00
32bb6d4ccb feat: syndicate mission rotation (#1781)
Closes #1530

Reviewed-on: OpenWF/SpaceNinjaServer#1781
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 10:00:49 -07:00
23dafb53d1 fix: skipTutorial sets ReceivedStartingGear before giving the gear (#1780)
This was raising a warning when creating a new account.

Reviewed-on: OpenWF/SpaceNinjaServer#1780
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 10:00:38 -07:00
3aa853f953 feat(webui): register (#1779)
Closes #740

Reviewed-on: OpenWF/SpaceNinjaServer#1779
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 10:00:26 -07:00
409f41d3bf feat(webui): remove unranked mods (#1778)
Reviewed-on: OpenWF/SpaceNinjaServer#1778
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 10:00:10 -07:00
c4b8a71c5a chore(webui): provide "max rank" option when only exalted needs it (#1776)
Reviewed-on: OpenWF/SpaceNinjaServer#1776
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 10:00:04 -07:00
3b20a109f6 fix: handle saveDialogue without YearIteration having been supplied (#1774)
This is needed for The Hex rank up dialogues, which are independent of the year iterations.

Fixes #1773

Reviewed-on: OpenWF/SpaceNinjaServer#1774
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 09:59:54 -07:00
6d93ae9f2d fix: be less strict with required avatar type for personal synthesis (#1768)
Fixes #1766

Reviewed-on: OpenWF/SpaceNinjaServer#1768
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 09:59:41 -07:00
ad2f143f15 feat: cleanup some problems in inventories at daily reset (#1767)
Reviewed-on: OpenWF/SpaceNinjaServer#1767
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-22 09:59:30 -07:00
03590c7360 chore(webui): update German translation (#1786)
Reviewed-on: OpenWF/SpaceNinjaServer#1786
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-04-22 09:44:47 -07:00
e3fca682d6 Update static/webui/translations/fr.js (#1784)
Reviewed-on: OpenWF/SpaceNinjaServer#1784
Co-authored-by: Vitruvio <vitruvio@noreply.localhost>
Co-committed-by: Vitruvio <vitruvio@noreply.localhost>
2025-04-22 09:44:07 -07:00
c94bc3ef90 chore(webui): update Russian translation (#1783)
Reviewed-on: OpenWF/SpaceNinjaServer#1783
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-22 00:00:52 -07:00
731be0d5e3 fix: exclude MT_ARENA from sortie node options (#1769)
Fixes #1764

Reviewed-on: OpenWF/SpaceNinjaServer#1769
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 22:06:39 -07:00
a49edefbd1 fix(webui): don't halve required R30 XP for MoaPets & KubrowPets (#1771)
Reviewed-on: OpenWF/SpaceNinjaServer#1771
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 15:21:40 -07:00
e3a34399e5 chore(webui): update to Spanish translation (#1772)
Reviewed-on: OpenWF/SpaceNinjaServer#1772
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-04-21 11:55:58 -07:00
ec6729db4d feat: setHubNpcCustomizations (#1762)
Closes #1757

Reviewed-on: OpenWF/SpaceNinjaServer#1762
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 10:44:12 -07:00
72b28f1d75 feat: send hex quest email when The Lotus Eaters and The Duviri Paradox are complete (#1761)
Close #1759

Reviewed-on: OpenWF/SpaceNinjaServer#1761
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 10:44:01 -07:00
bdf0ac722b feat: give lotus eaters quest at completion of whispers in the walls (#1760)
Closes #1758

Reviewed-on: OpenWF/SpaceNinjaServer#1760
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 10:43:26 -07:00
98aebba677 fix: EOM endo rewards showing as doubled in the client (#1756)
Closes #1754

Reviewed-on: OpenWF/SpaceNinjaServer#1756
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 10:43:10 -07:00
9912a623b1 fix: complete all quests not working (#1755)
Fixes #1742

Reviewed-on: OpenWF/SpaceNinjaServer#1755
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-04-21 10:42:56 -07:00
98975edca1 feat(webui): KubrowPets support (#1752)
also using `/api/modularWeaponCrafting.php` instead of  `/custom/addModularEquipment` for modular equipment

Reviewed-on: OpenWF/SpaceNinjaServer#1752
Reviewed-by: Sainan <sainan@calamity.inc>
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-04-21 10:42:48 -07:00
80 changed files with 3405 additions and 1197 deletions

View File

@ -1,7 +1,6 @@
@echo off @echo off
echo Updating SpaceNinjaServer... echo Updating SpaceNinjaServer...
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
git fetch --prune git fetch --prune
git stash git stash
git reset --hard origin/main git reset --hard origin/main

View File

@ -19,6 +19,7 @@
"infiniteEndo": false, "infiniteEndo": false,
"infiniteRegalAya": false, "infiniteRegalAya": false,
"infiniteHelminthMaterials": false, "infiniteHelminthMaterials": false,
"dontSubtractConsumables": false,
"unlockAllShipFeatures": false, "unlockAllShipFeatures": false,
"unlockAllShipDecorations": false, "unlockAllShipDecorations": false,
"unlockAllFlavourItems": false, "unlockAllFlavourItems": false,

8
package-lock.json generated
View File

@ -18,7 +18,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.56", "warframe-public-export-plus": "^0.5.58",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"
@ -3789,9 +3789,9 @@
} }
}, },
"node_modules/warframe-public-export-plus": { "node_modules/warframe-public-export-plus": {
"version": "0.5.56", "version": "0.5.58",
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.56.tgz", "resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.58.tgz",
"integrity": "sha512-px+J7tUm6fkSzwKkvL73ySQReDq9oM1UrHSLM3vbYGBvELM892iBgPYG45okIhScCSdwmmXTiWZTf4x/I4qiNQ==" "integrity": "sha512-2G3tKcoblUl7S3Rkk5k/qH+VGZBUmU2QjtIrEO/Bt6UlgO83s648elkNdDKOLBKXnxIsa194nVwz+ci1K86sXg=="
}, },
"node_modules/warframe-riven-info": { "node_modules/warframe-riven-info": {
"version": "0.1.2", "version": "0.1.2",

View File

@ -25,7 +25,7 @@
"morgan": "^1.10.0", "morgan": "^1.10.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",
"typescript": "^5.5", "typescript": "^5.5",
"warframe-public-export-plus": "^0.5.56", "warframe-public-export-plus": "^0.5.58",
"warframe-riven-info": "^0.1.2", "warframe-riven-info": "^0.1.2",
"winston": "^3.17.0", "winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0" "winston-daily-rotate-file": "^5.0.0"

View File

@ -26,7 +26,7 @@ app.use((req, _res, next) => {
app.use(bodyParser.raw()); app.use(bodyParser.raw());
app.use(express.json({ limit: "4mb" })); app.use(express.json({ limit: "4mb" }));
app.use(bodyParser.text()); app.use(bodyParser.text({ limit: "4mb" }));
app.use(requestLogger); app.use(requestLogger);
app.use("/api", apiRouter); app.use("/api", apiRouter);

View File

@ -2,15 +2,18 @@ const millisecondsPerSecond = 1000;
const secondsPerMinute = 60; const secondsPerMinute = 60;
const minutesPerHour = 60; const minutesPerHour = 60;
const hoursPerDay = 24; const hoursPerDay = 24;
const daysPerWeek = 7;
const unixSecond = millisecondsPerSecond; const unixSecond = millisecondsPerSecond;
const unixMinute = secondsPerMinute * millisecondsPerSecond; const unixMinute = secondsPerMinute * millisecondsPerSecond;
const unixHour = unixMinute * minutesPerHour; const unixHour = unixMinute * minutesPerHour;
const unixDay = hoursPerDay * unixHour; const unixDay = hoursPerDay * unixHour;
const unixWeek = daysPerWeek * unixDay;
export const unixTimesInMs = { export const unixTimesInMs = {
second: unixSecond, second: unixSecond,
minute: unixMinute, minute: unixMinute,
hour: unixHour, hour: unixHour,
day: unixDay day: unixDay,
week: unixWeek
}; };

View File

@ -1,4 +1,4 @@
import { getInventory } from "@/src/services/inventoryService"; import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
@ -17,7 +17,7 @@ export const claimLibraryDailyTaskRewardController: RequestHandler = async (req,
} }
syndicate.Standing += rewardStanding; syndicate.Standing += rewardStanding;
inventory.FusionPoints += 80 * rewardQuantity; addFusionPoints(inventory, 80 * rewardQuantity);
await inventory.save(); await inventory.save();
res.json({ res.json({

View File

@ -0,0 +1,41 @@
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getWorldState } from "@/src/services/worldStateService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
// GET request; query parameters: CompletedEventIdx=0&Iteration=4&Version=19&Season=CST_SUMMER
export const completeCalendarEventController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
let inventoryChanges: IInventoryChanges = {};
let dayIndex = 0;
for (const day of currentSeason.Days) {
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
if (day.events.length != 0) {
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
if (selection.type == "CET_REWARD") {
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
.InventoryChanges;
} else if (selection.type == "CET_UPGRADE") {
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
} else if (selection.type != "CET_PLOT") {
throw new Error(`unexpected selection type: ${selection.type}`);
}
}
break;
}
++dayIndex;
}
}
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,
CalendarProgress: inventory.CalendarProgress
});
};

View File

@ -2,7 +2,7 @@ import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers"; import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { Guild } from "@/src/models/guildModel"; import { Guild } from "@/src/models/guildModel";
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService"; import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
import { getInventory } from "@/src/services/inventoryService"; import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { Types } from "mongoose"; import { Types } from "mongoose";
@ -36,7 +36,7 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
// Either way, endo is given to the contributor. // Either way, endo is given to the contributor.
const inventory = await getInventory(accountId, "FusionPoints"); const inventory = await getInventory(accountId, "FusionPoints");
inventory.FusionPoints += guild.CeremonyEndo!; addFusionPoints(inventory, guild.CeremonyEndo!);
await inventory.save(); await inventory.save();
res.json({ res.json({

View File

@ -62,14 +62,7 @@ export const crewShipIdentifySalvageController: RequestHandler = async (req, res
} satisfies IInnateDamageFingerprint) } satisfies IInnateDamageFingerprint)
}; };
} }
addEquipment( addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, defaultOverwrites, inventoryChanges);
inventory,
"CrewShipSalvagedWeapons",
payload.ItemType,
undefined,
inventoryChanges,
defaultOverwrites
);
} }
inventoryChanges.CrewShipRawSalvage = [ inventoryChanges.CrewShipRawSalvage = [

View File

@ -21,10 +21,12 @@ export const entratiLabConquestModeController: RequestHandler = async (req, res)
inventory.EntratiVaultCountResetDate = new Date(weekEnd); inventory.EntratiVaultCountResetDate = new Date(weekEnd);
if (inventory.EntratiLabConquestUnlocked) { if (inventory.EntratiLabConquestUnlocked) {
inventory.EntratiLabConquestUnlocked = 0; inventory.EntratiLabConquestUnlocked = 0;
inventory.EntratiLabConquestCacheScoreMission = 0;
inventory.EntratiLabConquestActiveFrameVariants = []; inventory.EntratiLabConquestActiveFrameVariants = [];
} }
if (inventory.EchoesHexConquestUnlocked) { if (inventory.EchoesHexConquestUnlocked) {
inventory.EchoesHexConquestUnlocked = 0; inventory.EchoesHexConquestUnlocked = 0;
inventory.EchoesHexConquestCacheScoreMission = 0;
inventory.EchoesHexConquestActiveFrameVariants = []; inventory.EchoesHexConquestActiveFrameVariants = [];
inventory.EchoesHexConquestActiveStickers = []; inventory.EchoesHexConquestActiveStickers = [];
} }

View File

@ -104,13 +104,14 @@ export const focusController: RequestHandler = async (req, res) => {
} }
case FocusOperation.SentTrainingAmplifier: { case FocusOperation.SentTrainingAmplifier: {
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest; const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
const parts: string[] = [ const inventory = await getInventory(accountId);
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
ModularParts: [
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis", "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel" "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
]; ]
const inventory = await getInventory(accountId); });
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
occupySlot(inventory, InventorySlot.AMPS, false); occupySlot(inventory, InventorySlot.AMPS, false);
await inventory.save(); await inventory.save();
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]); res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);

View File

@ -0,0 +1,84 @@
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
import { toStoreItem } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { IMongoDate } from "@/src/types/commonTypes";
import { IMissionReward } from "@/src/types/missionTypes";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IGardeningClient } from "@/src/types/shipTypes";
import { RequestHandler } from "express";
import { dict_en, ExportResources } from "warframe-public-export-plus";
export const gardeningController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGardeningRequest>(String(req.body));
if (data.Mode != "HarvestAll") {
throw new Error(`unexpected gardening mode: ${data.Mode}`);
}
const accountId = await getAccountIdForRequest(req);
const [inventory, personalRooms] = await Promise.all([
getInventory(accountId, "MiscItems"),
getPersonalRooms(accountId, "Apartment")
]);
// Harvest plants
const inventoryChanges: IInventoryChanges = {};
const rewards: Record<string, IMissionReward[][]> = {};
for (const planter of personalRooms.Apartment.Gardening.Planters) {
rewards[planter.Name] = [];
for (const plant of planter.Plants) {
const itemType =
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
plant.PlantType.substring(plant.PlantType.length - 1);
const itemCount = Math.random() < 0.775 ? 2 : 4;
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
rewards[planter.Name].push([
{
StoreItem: toStoreItem(itemType),
TypeName: itemType,
ItemCount: itemCount,
DailyCooldown: false,
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
ProductCategory: "MiscItems"
}
]);
}
}
// Refresh garden
personalRooms.Apartment.Gardening = createGarden();
await Promise.all([inventory.save(), personalRooms.save()]);
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
const plant = planter.Plants[planter.Plants.length - 1];
res.json({
GardenTagName: planter.Name,
PlantType: plant.PlantType,
PlotIndex: plant.PlotIndex,
EndTime: toMongoDate(plant.EndTime),
InventoryChanges: inventoryChanges,
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
Rewards: rewards
} satisfies IGardeningResponse);
};
interface IGardeningRequest {
Mode: string;
}
interface IGardeningResponse {
GardenTagName: string;
PlantType: string;
PlotIndex: number;
EndTime: IMongoDate;
InventoryChanges: IInventoryChanges;
Gardening: IGardeningClient;
Rewards: Record<string, IMissionReward[][]>;
}

View File

@ -2,17 +2,24 @@ import { RequestHandler } from "express";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json"; import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getPersonalRooms } from "@/src/services/personalRoomsService"; import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
import { getShip } from "@/src/services/shipService"; import { getShip } from "@/src/services/shipService";
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toOid } from "@/src/helpers/inventoryHelpers";
import { IGetShipResponse } from "@/src/types/shipTypes"; import { IGetShipResponse } from "@/src/types/shipTypes";
import { IPersonalRooms } from "@/src/types/personalRoomsTypes"; import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { getLoadout } from "@/src/services/loadoutService"; import { getLoadout } from "@/src/services/loadoutService";
export const getShipController: RequestHandler = async (req, res) => { export const getShipController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const personalRoomsDb = await getPersonalRooms(accountId); const personalRoomsDb = await getPersonalRooms(accountId);
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
personalRoomsDb.Apartment.Gardening = createGarden();
await personalRoomsDb.save();
}
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
const loadout = await getLoadout(accountId); const loadout = await getLoadout(accountId);
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem"); const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");

View File

@ -441,16 +441,9 @@ const finishComponentRepair = (
const inventoryChanges = { const inventoryChanges = {
...(category == "CrewShipWeaponSkins" ...(category == "CrewShipWeaponSkins"
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint) ? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
: addEquipment( : addEquipment(inventory, category, salvageItem.ItemType, {
inventory,
category,
salvageItem.ItemType,
undefined,
{},
{
UpgradeFingerprint: salvageItem.UpgradeFingerprint UpgradeFingerprint: salvageItem.UpgradeFingerprint
} })),
)),
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false) ...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
}; };

View File

@ -14,7 +14,12 @@ import {
ExportVirtuals ExportVirtuals
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService"; import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService"; import {
addMiscItems,
allDailyAffiliationKeys,
cleanupInventory,
createLibraryDailyTask
} from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers"; import { catBreadHash } from "@/src/helpers/stringHelpers";
@ -79,6 +84,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
} }
} }
cleanupInventory(inventory);
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000); inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
await inventory.save(); await inventory.save();
} }

View File

@ -21,7 +21,11 @@ export const loginController: RequestHandler = async (request, response) => {
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress; const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") { if (
!account &&
((config.autoCreateAccount && loginRequest.ClientType != "webui") ||
loginRequest.ClientType == "webui-register")
) {
try { try {
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@")); const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja"; let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
@ -37,7 +41,7 @@ export const loginController: RequestHandler = async (request, response) => {
password: loginRequest.password, password: loginRequest.password,
DisplayName: name, DisplayName: name,
CountryCode: loginRequest.lang.toUpperCase(), CountryCode: loginRequest.lang.toUpperCase(),
ClientType: loginRequest.ClientType, ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
CrossPlatformAllowed: true, CrossPlatformAllowed: true,
ForceLogoutVersion: 0, ForceLogoutVersion: 0,
ConsentNeeded: false, ConsentNeeded: false,
@ -59,6 +63,11 @@ export const loginController: RequestHandler = async (request, response) => {
return; return;
} }
if (loginRequest.ClientType == "webui-register") {
response.status(400).json({ error: "account already exists" });
return;
}
if (!isCorrectPassword(loginRequest.password, account.password)) { if (!isCorrectPassword(loginRequest.password, account.password)) {
response.status(400).json({ error: "incorrect login data" }); response.status(400).json({ error: "incorrect login data" });
return; return;

View File

@ -3,9 +3,10 @@ import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes"; import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService"; import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
import { getInventory } from "@/src/services/inventoryService"; import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
import { getInventoryResponse } from "./inventoryController"; import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger"; import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
/* /*
**** INPUT **** **** INPUT ****
@ -62,6 +63,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
missionReport.MissionStatus !== "GS_SUCCESS" && missionReport.MissionStatus !== "GS_SUCCESS" &&
!(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId) !(missionReport.RewardInfo?.jobId || missionReport.RewardInfo?.challengeMissionId)
) { ) {
inventory.RewardSeed = generateRewardSeed();
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true);
res.json({ res.json({
@ -71,9 +73,16 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
return; return;
} }
const { MissionRewards, inventoryChanges, credits, AffiliationMods, SyndicateXPItemReward } = const {
await addMissionRewards(inventory, missionReport, firstCompletion); MissionRewards,
inventoryChanges,
credits,
AffiliationMods,
SyndicateXPItemReward,
ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion);
inventory.RewardSeed = generateRewardSeed();
await inventory.save(); await inventory.save();
const inventoryResponse = await getInventoryResponse(inventory, true); const inventoryResponse = await getInventoryResponse(inventory, true);
@ -84,10 +93,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
MissionRewards, MissionRewards,
...credits, ...credits,
...inventoryUpdates, ...inventoryUpdates,
FusionPoints: inventoryChanges?.FusionPoints, //FusionPoints: inventoryChanges?.FusionPoints, // This in combination with InventoryJson or InventoryChanges seems to just double the number of endo shown, so unsure when this is needed.
SyndicateXPItemReward, SyndicateXPItemReward,
AffiliationMods AffiliationMods,
}); ConquestCompletedMissionsCount
} satisfies IMissionInventoryUpdateResponse);
}; };
/* /*

View File

@ -17,12 +17,13 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper"; import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { getRandomInt } from "@/src/services/rngService"; import { getRandomInt } from "@/src/services/rngService";
import { ExportSentinels, IDefaultUpgrade } from "warframe-public-export-plus"; import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
import { Status } from "@/src/types/inventoryTypes/inventoryTypes"; import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
interface IModularCraftRequest { interface IModularCraftRequest {
WeaponType: string; WeaponType: string;
Parts: string[]; Parts: string[];
isWebUi?: boolean;
} }
export const modularWeaponCraftingController: RequestHandler = async (req, res) => { export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
@ -35,7 +36,9 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
let defaultUpgrades: IDefaultUpgrade[] | undefined; let defaultUpgrades: IDefaultUpgrade[] | undefined;
const defaultOverwrites: Partial<IEquipmentDatabase> = {}; const defaultOverwrites: Partial<IEquipmentDatabase> = {
ModularParts: data.Parts
};
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
if (category == "KubrowPets") { if (category == "KubrowPets") {
const traits = { const traits = {
@ -137,22 +140,39 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
} else { } else {
defaultUpgrades = getDefaultUpgrades(data.Parts); defaultUpgrades = getDefaultUpgrades(data.Parts);
} }
if (category == "MoaPets") {
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
if (weapon) {
const category = ExportWeapons[weapon].productCategory;
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
}
}
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades); defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites); addEquipment(inventory, category, data.WeaponType, defaultOverwrites, inventoryChanges);
combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false)); combineInventoryChanges(
inventoryChanges,
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
);
if (defaultUpgrades) { if (defaultUpgrades) {
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 })); inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
} }
// Remove credits & parts // Remove credits & parts
const miscItemChanges = []; const miscItemChanges = [];
let currencyChanges = {};
if (!data.isWebUi) {
for (const part of data.Parts) { for (const part of data.Parts) {
miscItemChanges.push({ miscItemChanges.push({
ItemType: part, ItemType: part,
ItemCount: -1 ItemCount: -1
}); });
} }
const currencyChanges = updateCurrency( currencyChanges = updateCurrency(
inventory, inventory,
category == "Hoverboards" || category == "Hoverboards" ||
category == "MoaPets" || category == "MoaPets" ||
@ -164,8 +184,9 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
false false
); );
addMiscItems(inventory, miscItemChanges); addMiscItems(inventory, miscItemChanges);
await inventory.save(); }
await inventory.save();
// Tell client what we did // Tell client what we did
res.json({ res.json({
InventoryChanges: { InventoryChanges: {

View File

@ -21,7 +21,11 @@ import { IInventoryChanges } from "@/src/types/purchaseTypes";
export const modularWeaponSaleController: RequestHandler = async (req, res) => { export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const partTypeToParts: Record<string, string[]> = {}; const partTypeToParts: Record<string, string[]> = {};
for (const [uniqueName, data] of Object.entries(ExportWeapons)) { for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
if (data.partType && data.premiumPrice) { if (
data.partType &&
data.premiumPrice &&
!data.excludeFromCodex // exclude pvp variants
) {
partTypeToParts[data.partType] ??= []; partTypeToParts[data.partType] ??= [];
partTypeToParts[data.partType].push(uniqueName); partTypeToParts[data.partType].push(uniqueName);
} }
@ -41,24 +45,18 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts); const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
const configs = applyDefaultUpgrades(inventory, defaultUpgrades); const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
const inventoryChanges: IInventoryChanges = { const inventoryChanges: IInventoryChanges = {
...addEquipment( ...addEquipment(inventory, category, weaponInfo.ItemType, {
inventory,
category,
weaponInfo.ItemType,
weaponInfo.ModularParts,
{},
{
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED, Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
ItemName: payload.ItemName, ItemName: payload.ItemName,
Configs: configs, Configs: configs,
ModularParts: weaponInfo.ModularParts,
Polarity: [ Polarity: [
{ {
Slot: payload.PolarizeSlot, Slot: payload.PolarizeSlot,
Value: payload.PolarizeValue Value: payload.PolarizeValue
} }
] ]
} }),
),
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true), ...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
...updateCurrency(inventory, weaponInfo.PremiumPrice, true) ...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
}; };

View File

@ -37,6 +37,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
const deco = component.Decos.find(x => x._id.equals(request.MoveId))!; const deco = component.Decos.find(x => x._id.equals(request.MoveId))!;
deco.Pos = request.Pos; deco.Pos = request.Pos;
deco.Rot = request.Rot; deco.Rot = request.Rot;
deco.Scale = request.Scale;
} else { } else {
const deco = const deco =
component.Decos[ component.Decos[
@ -45,6 +46,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
Type: request.Type, Type: request.Type,
Pos: request.Pos, Pos: request.Pos,
Rot: request.Rot, Rot: request.Rot,
Scale: request.Scale,
Name: request.Name, Name: request.Name,
Sockets: request.Sockets Sockets: request.Sockets
}) - 1 }) - 1
@ -113,9 +115,9 @@ interface IPlaceDecoInComponentRequest {
Type: string; Type: string;
Pos: number[]; Pos: number[];
Rot: number[]; Rot: number[];
Scale?: number;
Name?: string; Name?: string;
Sockets?: number; Sockets?: number;
Scale?: number; // only provided alongside MoveId and seems to always be 1
MoveId?: string; MoveId?: string;
ShipDeco?: boolean; ShipDeco?: boolean;
VaultDeco?: boolean; VaultDeco?: boolean;

View File

@ -8,7 +8,11 @@ export const releasePetController: RequestHandler = async (req, res) => {
const inventory = await getInventory(accountId, "RegularCredits KubrowPets"); const inventory = await getInventory(accountId, "RegularCredits KubrowPets");
const payload = getJSONfromString<IReleasePetRequest>(String(req.body)); const payload = getJSONfromString<IReleasePetRequest>(String(req.body));
const inventoryChanges = updateCurrency(inventory, 25000, false); const inventoryChanges = updateCurrency(
inventory,
payload.recipeName == "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe" ? 25000 : 0,
false
);
inventoryChanges.RemovedIdItems = [{ ItemId: { $oid: payload.petId } }]; inventoryChanges.RemovedIdItems = [{ ItemId: { $oid: payload.petId } }];
inventory.KubrowPets.pull({ _id: payload.petId }); inventory.KubrowPets.pull({ _id: payload.petId });
@ -18,6 +22,6 @@ export const releasePetController: RequestHandler = async (req, res) => {
}; };
interface IReleasePetRequest { interface IReleasePetRequest {
recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe"; recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe" | "webui";
petId: string; petId: string;
} }

View File

@ -4,7 +4,6 @@ import { addEmailItem, getInventory, updateCurrency } from "@/src/services/inven
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes"; import { ICompletedDialogue, IDialogueDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import { RequestHandler } from "express"; import { RequestHandler } from "express";
export const saveDialogueController: RequestHandler = async (req, res) => { export const saveDialogueController: RequestHandler = async (req, res) => {
@ -12,27 +11,21 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
const request = JSON.parse(String(req.body)) as SaveDialogueRequest; const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
if ("YearIteration" in request) { if ("YearIteration" in request) {
const inventory = await getInventory(accountId, "DialogueHistory"); const inventory = await getInventory(accountId, "DialogueHistory");
if (inventory.DialogueHistory) { inventory.DialogueHistory ??= {};
inventory.DialogueHistory.YearIteration = request.YearIteration; inventory.DialogueHistory.YearIteration = request.YearIteration;
} else {
inventory.DialogueHistory = { YearIteration: request.YearIteration };
}
await inventory.save(); await inventory.save();
res.end(); res.end();
} else { } else {
const inventory = await getInventory(accountId); const inventory = await getInventory(accountId);
if (!inventory.DialogueHistory) {
throw new Error("bad inventory state");
}
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
const tomorrowAt0Utc = config.noKimCooldowns const tomorrowAt0Utc = config.noKimCooldowns
? Date.now() ? Date.now()
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000; : (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
inventory.DialogueHistory ??= {};
inventory.DialogueHistory.Dialogues ??= []; inventory.DialogueHistory.Dialogues ??= [];
const dialogue = getDialogue(inventory, request.DialogueName); const dialogue = getDialogue(inventory, request.DialogueName);
dialogue.Rank = request.Rank; dialogue.Rank = request.Rank;
dialogue.Chemistry = request.Chemistry; dialogue.Chemistry = request.Chemistry;
if (request.Data) {
dialogue.QueuedDialogues = request.QueuedDialogues; dialogue.QueuedDialogues = request.QueuedDialogues;
for (const bool of request.Booleans) { for (const bool of request.Booleans) {
dialogue.Booleans.push(bool); dialogue.Booleans.push(bool);
@ -50,8 +43,6 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
dialogue.Booleans.splice(index, 1); dialogue.Booleans.splice(index, 1);
} }
} }
dialogue.Completed.push(request.Data);
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
for (const info of request.OtherDialogueInfos) { for (const info of request.OtherDialogueInfos) {
const otherDialogue = getDialogue(inventory, info.Dialogue); const otherDialogue = getDialogue(inventory, info.Dialogue);
if (info.Tag != "") { if (info.Tag != "") {
@ -59,6 +50,9 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
} }
otherDialogue.Chemistry += info.Value; // unsure otherDialogue.Chemistry += info.Value; // unsure
} }
if (request.Data) {
dialogue.Completed.push(request.Data);
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
await inventory.save(); await inventory.save();
res.json({ res.json({
InventoryChanges: inventoryChanges, InventoryChanges: inventoryChanges,
@ -79,7 +73,7 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } } AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
}); });
} else { } else {
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`); res.end();
} }
} }
}; };

View File

@ -8,7 +8,8 @@ import {
addConsumables, addConsumables,
freeUpSlot, freeUpSlot,
combineInventoryChanges, combineInventoryChanges,
addCrewShipRawSalvage addCrewShipRawSalvage,
addFusionPoints
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes"; import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus"; import { ExportDojoRecipes } from "warframe-public-export-plus";
@ -44,7 +45,7 @@ export const sellController: RequestHandler = async (req, res) => {
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) { if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
requiredFields.add(InventorySlot.SPACEWEAPONS); requiredFields.add(InventorySlot.SPACEWEAPONS);
} }
if (payload.Items.Sentinels || payload.Items.SentinelWeapons) { if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
requiredFields.add(InventorySlot.SENTINELS); requiredFields.add(InventorySlot.SENTINELS);
} }
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
@ -69,7 +70,7 @@ export const sellController: RequestHandler = async (req, res) => {
if (payload.SellCurrency == "SC_RegularCredits") { if (payload.SellCurrency == "SC_RegularCredits") {
inventory.RegularCredits += payload.SellPrice; inventory.RegularCredits += payload.SellPrice;
} else if (payload.SellCurrency == "SC_FusionPoints") { } else if (payload.SellCurrency == "SC_FusionPoints") {
inventory.FusionPoints += payload.SellPrice; addFusionPoints(inventory, payload.SellPrice);
} else if (payload.SellCurrency == "SC_PrimeBucks") { } else if (payload.SellCurrency == "SC_PrimeBucks") {
addMiscItems(inventory, [ addMiscItems(inventory, [
{ {
@ -147,6 +148,12 @@ export const sellController: RequestHandler = async (req, res) => {
freeUpSlot(inventory, InventorySlot.SENTINELS); freeUpSlot(inventory, InventorySlot.SENTINELS);
}); });
} }
if (payload.Items.MoaPets) {
payload.Items.MoaPets.forEach(sellItem => {
inventory.MoaPets.pull({ _id: sellItem.String });
freeUpSlot(inventory, InventorySlot.SENTINELS);
});
}
if (payload.Items.OperatorAmps) { if (payload.Items.OperatorAmps) {
payload.Items.OperatorAmps.forEach(sellItem => { payload.Items.OperatorAmps.forEach(sellItem => {
inventory.OperatorAmps.pull({ _id: sellItem.String }); inventory.OperatorAmps.pull({ _id: sellItem.String });
@ -280,6 +287,7 @@ interface ISellRequest {
SpaceMelee?: ISellItem[]; SpaceMelee?: ISellItem[];
Sentinels?: ISellItem[]; Sentinels?: ISellItem[];
SentinelWeapons?: ISellItem[]; SentinelWeapons?: ISellItem[];
MoaPets?: ISellItem[];
OperatorAmps?: ISellItem[]; OperatorAmps?: ISellItem[];
Hoverboards?: ISellItem[]; Hoverboards?: ISellItem[];
Drones?: ISellItem[]; Drones?: ISellItem[];

View File

@ -0,0 +1,21 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { IHubNpcCustomization } from "@/src/types/inventoryTypes/inventoryTypes";
import { RequestHandler } from "express";
export const setHubNpcCustomizationsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "HubNpcCustomizations");
const upload = getJSONfromString<IHubNpcCustomization>(String(req.body));
inventory.HubNpcCustomizations ??= [];
const cust = inventory.HubNpcCustomizations.find(x => x.Tag == upload.Tag);
if (cust) {
cust.Colors = upload.Colors;
cust.Pattern = upload.Pattern;
} else {
inventory.HubNpcCustomizations.push(upload);
}
await inventory.save();
res.end();
};

View File

@ -20,7 +20,7 @@ export const setShipFavouriteLoadoutController: RequestHandler = async (req, res
throw new Error(`unexpected BootLocation: ${body.BootLocation}`); throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
} }
await personalRooms.save(); await personalRooms.save();
res.json({}); res.json(body);
}; };
interface ISetShipFavouriteLoadoutRequest { interface ISetShipFavouriteLoadoutRequest {

View File

@ -6,6 +6,9 @@ import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService"; import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes"; import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { isStoreItem, toStoreItem } from "@/src/services/itemDataService"; import { isStoreItem, toStoreItem } from "@/src/services/itemDataService";
import { logger } from "@/src/utils/logger";
const nightwaveCredsItemType = ExportNightwave.rewards[ExportNightwave.rewards.length - 1].uniqueName;
export const syndicateSacrificeController: RequestHandler = async (request, response) => { export const syndicateSacrificeController: RequestHandler = async (request, response) => {
const accountId = await getAccountIdForRequest(request); const accountId = await getAccountIdForRequest(request);
@ -74,10 +77,14 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
if (!isStoreItem(rewardType)) { if (!isStoreItem(rewardType)) {
rewardType = toStoreItem(rewardType); rewardType = toStoreItem(rewardType);
} }
combineInventoryChanges( const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount))
res.InventoryChanges, .InventoryChanges;
(await handleStoreItemAcquisition(rewardType, inventory, reward.itemCount)).InventoryChanges if (Object.keys(rewardInventoryChanges).length == 0) {
); logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
}
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
} }
} }

View File

@ -1,12 +1,16 @@
import { RequestHandler } from "express"; import { RequestHandler } from "express";
import { getAccountIdForRequest } from "@/src/services/loginService"; import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory } from "@/src/services/inventoryService"; import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
export const addCurrencyController: RequestHandler = async (req, res) => { export const addCurrencyController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req); const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const request = req.body as IAddCurrencyRequest; const request = req.body as IAddCurrencyRequest;
const inventory = await getInventory(accountId, request.currency);
if (request.currency == "FusionPoints") {
addFusionPoints(inventory, request.delta);
} else {
inventory[request.currency] += request.delta; inventory[request.currency] += request.delta;
}
await inventory.save(); await inventory.save();
res.end(); res.end();
}; };

View File

@ -0,0 +1,44 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "Upgrades");
const maxOwnedRanks: Record<string, number> = {};
for (const upgrade of inventory.Upgrades) {
const fingerprint = JSON.parse(upgrade.UpgradeFingerprint ?? "{}") as { lvl?: number };
if (fingerprint.lvl) {
maxOwnedRanks[upgrade.ItemType] ??= 0;
if (fingerprint.lvl > maxOwnedRanks[upgrade.ItemType]) {
maxOwnedRanks[upgrade.ItemType] = fingerprint.lvl;
}
}
}
for (const [uniqueName, data] of Object.entries(ExportUpgrades)) {
if (data.fusionLimit != 0 && data.type != "PARAZON" && maxOwnedRanks[uniqueName] != data.fusionLimit) {
inventory.Upgrades.push({
ItemType: uniqueName,
UpgradeFingerprint: JSON.stringify({ lvl: data.fusionLimit })
});
}
}
for (const [uniqueName, data] of Object.entries(ExportArcanes)) {
if (
data.name != "/Lotus/Language/Items/GenericCosmeticEnhancerName" &&
maxOwnedRanks[uniqueName] != data.fusionLimit
) {
inventory.Upgrades.push({
ItemType: uniqueName,
UpgradeFingerprint: JSON.stringify({ lvl: data.fusionLimit })
});
}
}
await inventory.save();
res.end();
};

View File

@ -1,98 +0,0 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import {
getInventory,
addEquipment,
occupySlot,
productCategoryToInventoryBin,
applyDefaultUpgrades
} from "@/src/services/inventoryService";
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
import { getDefaultUpgrades } from "@/src/services/itemDataService";
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { ExportWeapons } from "warframe-public-export-plus";
import { RequestHandler } from "express";
export const addModularEquipmentController: RequestHandler = async (req, res) => {
const requiredFields = new Set();
const accountId = await getAccountIdForRequest(req);
const request = req.body as IAddModularEquipmentRequest;
const category = modularWeaponTypes[request.ItemType];
const inventoryBin = productCategoryToInventoryBin(category)!;
requiredFields.add(category);
requiredFields.add(inventoryBin);
request.ModularParts.forEach(part => {
if (ExportWeapons[part].gunType) {
if (category == "LongGuns") {
request.ItemType = {
GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
GT_BEAM: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
}[ExportWeapons[part].gunType];
} else {
request.ItemType = {
GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
GT_BEAM: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
}[ExportWeapons[part].gunType];
}
} else if (request.ItemType == "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
if (part.includes("ZanukaPetPartHead")) {
request.ItemType = {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}[part]!;
}
}
});
const defaultUpgrades = getDefaultUpgrades(request.ModularParts);
if (defaultUpgrades) {
requiredFields.add("RawUpgrades");
}
const defaultWeaponsMap: Record<string, string[]> = {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": [
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponIP"
],
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": [
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponIS"
],
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": [
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponPS"
]
};
const defaultWeapons = defaultWeaponsMap[request.ItemType] as string[] | undefined;
if (defaultWeapons) {
for (const defaultWeapon of defaultWeapons) {
const category = ExportWeapons[defaultWeapon].productCategory;
requiredFields.add(category);
requiredFields.add(productCategoryToInventoryBin(category));
}
}
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
if (defaultWeapons) {
for (const defaultWeapon of defaultWeapons) {
const category = ExportWeapons[defaultWeapon].productCategory;
addEquipment(inventory, category, defaultWeapon);
occupySlot(inventory, productCategoryToInventoryBin(category)!, true);
}
}
const defaultOverwrites: Partial<IEquipmentDatabase> = {
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
};
addEquipment(inventory, category, request.ItemType, request.ModularParts, undefined, defaultOverwrites);
occupySlot(inventory, inventoryBin, true);
await inventory.save();
res.end();
};
interface IAddModularEquipmentRequest {
ItemType: string;
ModularParts: string[];
}

View File

@ -20,6 +20,7 @@ import {
TRelicQuality TRelicQuality
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json"; import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
interface ListedItem { interface ListedItem {
uniqueName: string; uniqueName: string;
@ -29,6 +30,30 @@ interface ListedItem {
badReason?: "starter" | "frivolous" | "notraw"; badReason?: "starter" | "frivolous" | "notraw";
partType?: string; partType?: string;
chainLength?: number; chainLength?: number;
parazon?: boolean;
}
interface ItemLists {
archonCrystalUpgrades: Record<string, string>;
uniqueLevelCaps: Record<string, number>;
Suits: ListedItem[];
LongGuns: ListedItem[];
Melee: ListedItem[];
ModularParts: ListedItem[];
Pistols: ListedItem[];
Sentinels: ListedItem[];
SentinelWeapons: ListedItem[];
SpaceGuns: ListedItem[];
SpaceMelee: ListedItem[];
SpaceSuits: ListedItem[];
MechSuits: ListedItem[];
miscitems: ListedItem[];
Syndicates: ListedItem[];
OperatorAmps: ListedItem[];
QuestKeys: ListedItem[];
KubrowPets: ListedItem[];
EvolutionProgress: ListedItem[];
mods: ListedItem[];
} }
const relicQualitySuffixes: Record<TRelicQuality, string> = { const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -40,22 +65,28 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
const getItemListsController: RequestHandler = (req, response) => { const getItemListsController: RequestHandler = (req, response) => {
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en"); const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
const res: Record<string, ListedItem[]> = {}; const res: ItemLists = {
res.Suits = []; archonCrystalUpgrades,
res.LongGuns = []; uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
res.Melee = []; Suits: [],
res.ModularParts = []; LongGuns: [],
res.Pistols = []; Melee: [],
res.Sentinels = []; ModularParts: [],
res.SentinelWeapons = []; Pistols: [],
res.SpaceGuns = []; Sentinels: [],
res.SpaceMelee = []; SentinelWeapons: [],
res.SpaceSuits = []; SpaceGuns: [],
res.MechSuits = []; SpaceMelee: [],
res.miscitems = []; SpaceSuits: [],
res.Syndicates = []; MechSuits: [],
res.OperatorAmps = []; miscitems: [],
res.QuestKeys = []; Syndicates: [],
OperatorAmps: [],
QuestKeys: [],
KubrowPets: [],
EvolutionProgress: [],
mods: []
};
for (const [uniqueName, item] of Object.entries(ExportWarframes)) { for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({ res[item.productCategory].push({
uniqueName, uniqueName,
@ -64,20 +95,23 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
for (const [uniqueName, item] of Object.entries(ExportSentinels)) { for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
if (item.productCategory == "Sentinels") { if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
res[item.productCategory].push({ res[item.productCategory].push({
uniqueName, uniqueName,
name: getString(item.name, lang) name: getString(item.name, lang),
exalted: item.exalted
}); });
} }
} }
for (const [uniqueName, item] of Object.entries(ExportWeapons)) { for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
if (item.partType) { if (item.partType) {
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
res.ModularParts.push({ res.ModularParts.push({
uniqueName, uniqueName,
name: getString(item.name, lang), name: getString(item.name, lang),
partType: item.partType partType: item.partType
}); });
}
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") { if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
res.miscitems.push({ res.miscitems.push({
uniqueName: uniqueName, uniqueName: uniqueName,
@ -110,6 +144,7 @@ const getItemListsController: RequestHandler = (req, response) => {
let name = getString(item.name, lang); let name = getString(item.name, lang);
if ("dissectionParts" in item) { if ("dissectionParts" in item) {
name = getString("/Lotus/Language/Fish/FishDisplayName", lang).split("|FISH_NAME|").join(name); name = getString("/Lotus/Language/Fish/FishDisplayName", lang).split("|FISH_NAME|").join(name);
if (item.syndicateTag == "CetusSyndicate") {
if (uniqueName.indexOf("Large") != -1) { if (uniqueName.indexOf("Large") != -1) {
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeLargeAbbrev", lang)); name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeLargeAbbrev", lang));
} else if (uniqueName.indexOf("Medium") != -1) { } else if (uniqueName.indexOf("Medium") != -1) {
@ -117,6 +152,21 @@ const getItemListsController: RequestHandler = (req, response) => {
} else { } else {
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeSmallAbbrev", lang)); name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeSmallAbbrev", lang));
} }
} else {
if (uniqueName.indexOf("Large") != -1) {
name = name
.split("|FISH_SIZE|")
.join(getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryElderAbbrev", lang));
} else if (uniqueName.indexOf("Medium") != -1) {
name = name
.split("|FISH_SIZE|")
.join(getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryMatureAbbrev", lang));
} else {
name = name
.split("|FISH_SIZE|")
.join(getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryYoungAbbrev", lang));
}
}
} }
if ( if (
name && name &&
@ -179,7 +229,6 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
res.mods = [];
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) { for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
const mod: ListedItem = { const mod: ListedItem = {
uniqueName, uniqueName,
@ -193,6 +242,9 @@ const getItemListsController: RequestHandler = (req, response) => {
} else if (upgrade.upgradeEntries) { } else if (upgrade.upgradeEntries) {
mod.badReason = "notraw"; mod.badReason = "notraw";
} }
if (upgrade.type == "PARAZON") {
mod.parazon = true;
}
res.mods.push(mod); res.mods.push(mod);
} }
for (const [uniqueName, upgrade] of Object.entries(ExportAvionics)) { for (const [uniqueName, upgrade] of Object.entries(ExportAvionics)) {
@ -234,12 +286,14 @@ const getItemListsController: RequestHandler = (req, response) => {
}); });
} }
} }
for (const uniqueName of allIncarnons) {
response.json({ res.EvolutionProgress.push({
archonCrystalUpgrades, uniqueName,
uniqueLevelCaps: ExportMisc.uniqueLevelCaps, name: getString(getItemName(uniqueName) || "", lang)
...res
}); });
}
response.json(res);
}; };
export { getItemListsController }; export { getItemListsController };

View File

@ -35,11 +35,9 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
switch (operation) { switch (operation) {
case "completeAll": { case "completeAll": {
if (allQuestKeys.includes(questItemType)) {
for (const questKey of inventory.QuestKeys) { for (const questKey of inventory.QuestKeys) {
await completeQuest(inventory, questKey.ItemType); await completeQuest(inventory, questKey.ItemType);
} }
}
break; break;
} }
case "resetAll": { case "resetAll": {

View File

@ -0,0 +1,33 @@
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const setEvolutionProgressController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const payload = req.body as ISetEvolutionProgressRequest;
inventory.EvolutionProgress ??= [];
payload.forEach(element => {
const entry = inventory.EvolutionProgress!.find(entry => entry.ItemType === element.ItemType);
if (entry) {
entry.Progress = 0;
entry.Rank = element.Rank;
} else {
inventory.EvolutionProgress!.push({
Progress: 0,
Rank: element.Rank,
ItemType: element.ItemType
});
}
});
await inventory.save();
res.end();
};
type ISetEvolutionProgressRequest = {
ItemType: string;
Rank: number;
}[];

View File

@ -27,7 +27,15 @@ const viewController: RequestHandler = async (req, res) => {
for (const type of Object.keys(ExportEnemies.avatars)) { for (const type of Object.keys(ExportEnemies.avatars)) {
if (!scans.has(type)) scans.add(type); if (!scans.has(type)) scans.add(type);
} }
responseJson.Scans ??= [];
// Take any existing scans and also set them to 9999
if (responseJson.Scans) {
for (const scan of responseJson.Scans) {
scans.add(scan.type);
}
}
responseJson.Scans = [];
for (const type of scans) { for (const type of scans) {
responseJson.Scans.push({ type: type, scans: 9999 }); responseJson.Scans.push({ type: type, scans: 9999 });
} }

View File

@ -1,5 +1,6 @@
import { IMongoDate, IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { TRarity } from "warframe-public-export-plus";
export const toOid = (objectId: Types.ObjectId): IOid => { export const toOid = (objectId: Types.ObjectId): IOid => {
return { $oid: objectId.toString() } satisfies IOid; return { $oid: objectId.toString() } satisfies IOid;
@ -8,3 +9,144 @@ export const toOid = (objectId: Types.ObjectId): IOid => {
export const toMongoDate = (date: Date): IMongoDate => { export const toMongoDate = (date: Date): IMongoDate => {
return { $date: { $numberLong: date.getTime().toString() } }; return { $date: { $numberLong: date.getTime().toString() } };
}; };
export const kubrowWeights: Record<TRarity, number> = {
COMMON: 6,
UNCOMMON: 4,
RARE: 2,
LEGENDARY: 1
};
export const kubrowFurPatternsWeights: Record<TRarity, number> = {
COMMON: 6,
UNCOMMON: 5,
RARE: 2,
LEGENDARY: 1
};
export const catbrowDetails = {
Colors: [
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
],
EyeColors: [
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
],
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
BodyTypes: [
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
],
Heads: [
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
],
Tails: [
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
]
};
export const kubrowDetails = {
Colors: [
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
],
EyeColors: [
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
],
FurPatterns: [
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
],
BodyTypes: [
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
],
Heads: [],
Tails: []
};

View File

@ -6,6 +6,7 @@ import { logger } from "../utils/logger";
import { IOid } from "../types/commonTypes"; import { IOid } from "../types/commonTypes";
import { Types } from "mongoose"; import { Types } from "mongoose";
import { addMods } from "../services/inventoryService"; import { addMods } from "../services/inventoryService";
import { isArchwingMission } from "../services/worldStateService";
export const getInfNodes = (faction: string, rank: number): IInfNode[] => { export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
const infNodes = []; const infNodes = [];
@ -22,7 +23,7 @@ export const getInfNodes = (faction: string, rank: number): IInfNode[] => {
value.missionIndex != 42 && // not face off value.missionIndex != 42 && // not face off
value.name.indexOf("1999NodeI") == -1 && // not stage defence value.name.indexOf("1999NodeI") == -1 && // not stage defence
value.name.indexOf("1999NodeJ") == -1 && // not lich bounty value.name.indexOf("1999NodeJ") == -1 && // not lich bounty
value.name.indexOf("Archwing") == -1 !isArchwingMission(value)
) { ) {
//console.log(dict_en[value.name]); //console.log(dict_en[value.name]);
infNodes.push({ Node: key, Influence: 1 }); infNodes.push({ Node: key, Influence: 1 });

View File

@ -23,6 +23,7 @@ const dojoDecoSchema = new Schema<IDojoDecoDatabase>({
Type: String, Type: String,
Pos: [Number], Pos: [Number],
Rot: [Number], Rot: [Number],
Scale: Number,
Name: String, Name: String,
Sockets: Number, Sockets: Number,
RegularCredits: Number, RegularCredits: Number,

View File

@ -95,7 +95,9 @@ import {
ISortieRewardAttenuation, ISortieRewardAttenuation,
IInvasionProgressDatabase, IInvasionProgressDatabase,
IInvasionProgressClient, IInvasionProgressClient,
IAccolades IAccolades,
IHubNpcCustomization,
ILotusCustomization
} from "../../types/inventoryTypes/inventoryTypes"; } from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes"; import { IOid } from "../../types/commonTypes";
import { import {
@ -389,7 +391,7 @@ MailboxSchema.set("toJSON", {
const DuviriInfoSchema = new Schema<IDuviriInfo>( const DuviriInfoSchema = new Schema<IDuviriInfo>(
{ {
Seed: Number, Seed: BigInt,
NumCompletions: { type: Number, default: 0 } NumCompletions: { type: Number, default: 0 }
}, },
{ {
@ -779,6 +781,10 @@ const loreFragmentScansSchema = new Schema<ILoreFragmentScan>(
{ _id: false } { _id: false }
); );
const lotusCustomizationSchema = new Schema<ILotusCustomization>().add(ItemConfigSchema).add({
Persona: String
});
const evolutionProgressSchema = new Schema<IEvolutionProgress>( const evolutionProgressSchema = new Schema<IEvolutionProgress>(
{ {
Progress: Number, Progress: Number,
@ -908,7 +914,7 @@ dialogueSchema.set("toJSON", {
const dialogueHistorySchema = new Schema<IDialogueHistoryDatabase>( const dialogueHistorySchema = new Schema<IDialogueHistoryDatabase>(
{ {
YearIteration: { type: Number, required: true }, YearIteration: Number,
Resets: Number, Resets: Number,
Dialogues: { type: [dialogueSchema], required: false } Dialogues: { type: [dialogueSchema], required: false }
}, },
@ -1124,15 +1130,15 @@ const CustomMarkersSchema = new Schema<ICustomMarkers>(
const calenderProgressSchema = new Schema<ICalendarProgress>( const calenderProgressSchema = new Schema<ICalendarProgress>(
{ {
Version: { type: Number, default: 19 }, Version: { type: Number, default: 19 },
Iteration: { type: Number, default: 2 }, Iteration: { type: Number, required: true },
YearProgress: { YearProgress: {
Upgrades: { type: [] } Upgrades: { type: [String], default: [] }
}, },
SeasonProgress: { SeasonProgress: {
SeasonType: String, SeasonType: { type: String, required: true },
LastCompletedDayIdx: { type: Number, default: -1 }, LastCompletedDayIdx: { type: Number, default: 0 },
LastCompletedChallengeDayIdx: { type: Number, default: -1 }, LastCompletedChallengeDayIdx: { type: Number, default: 0 },
ActivatedChallenges: [] ActivatedChallenges: { type: [String], default: [] }
} }
}, },
{ _id: false } { _id: false }
@ -1327,6 +1333,15 @@ const lockedWeaponGroupSchema = new Schema<ILockedWeaponGroupDatabase>(
{ _id: false } { _id: false }
); );
const hubNpcCustomizationSchema = new Schema<IHubNpcCustomization>(
{
Colors: colorSchema,
Pattern: String,
Tag: String
},
{ _id: false }
);
const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>( const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
{ {
accountOwnerId: Schema.Types.ObjectId, accountOwnerId: Schema.Types.ObjectId,
@ -1618,7 +1633,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Purchase this new permanent skin from the Lotus customization options in Personal Quarters located in your Orbiter. //Purchase this new permanent skin from the Lotus customization options in Personal Quarters located in your Orbiter.
//https://warframe.fandom.com/wiki/Lotus#The_New_War //https://warframe.fandom.com/wiki/Lotus#The_New_War
LotusCustomization: Schema.Types.Mixed, LotusCustomization: { type: lotusCustomizationSchema, default: undefined },
//Progress+Rank+ItemType(ZarimanPumpShotgun) //Progress+Rank+ItemType(ZarimanPumpShotgun)
//https://warframe.fandom.com/wiki/Incarnon //https://warframe.fandom.com/wiki/Incarnon
@ -1680,7 +1695,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
// G3 + Zanuka // G3 + Zanuka
BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined }, BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined } LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined },
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined }
}, },
{ timestamps: { createdAt: "Created", updatedAt: false } } { timestamps: { createdAt: "Created", updatedAt: false } }
); );

View File

@ -1,14 +1,17 @@
import { toOid } from "@/src/helpers/inventoryHelpers"; import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
import { colorSchema } from "@/src/models/inventoryModels/inventoryModel"; import { colorSchema } from "@/src/models/inventoryModels/inventoryModel";
import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes"; import { IOrbiter, IPersonalRoomsDatabase, PersonalRoomsModelType } from "@/src/types/personalRoomsTypes";
import { import {
IFavouriteLoadoutDatabase, IFavouriteLoadoutDatabase,
IGardening, IGardeningDatabase,
IPlacedDecosDatabase, IPlacedDecosDatabase,
IPictureFrameInfo, IPictureFrameInfo,
IRoom, IRoom,
ITailorShopDatabase, ITailorShopDatabase,
IApartmentDatabase IApartmentDatabase,
IPlanterDatabase,
IPlantDatabase,
IPlantClient
} from "@/src/types/shipTypes"; } from "@/src/types/shipTypes";
import { Schema, model } from "mongoose"; import { Schema, model } from "mongoose";
@ -77,15 +80,45 @@ favouriteLoadoutSchema.set("toJSON", {
} }
}); });
const gardeningSchema = new Schema<IGardening>({ const plantSchema = new Schema<IPlantDatabase>(
Planters: [Schema.Types.Mixed] //TODO: add when implementing gardening {
PlantType: String,
EndTime: Date,
PlotIndex: Number
},
{ _id: false }
);
plantSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const client = obj as IPlantClient;
const db = obj as IPlantDatabase;
client.EndTime = toMongoDate(db.EndTime);
}
}); });
const planterSchema = new Schema<IPlanterDatabase>(
{
Name: { type: String, required: true },
Plants: { type: [plantSchema], default: [] }
},
{ _id: false }
);
const gardeningSchema = new Schema<IGardeningDatabase>(
{
Planters: { type: [planterSchema], default: [] }
},
{ _id: false }
);
const apartmentSchema = new Schema<IApartmentDatabase>( const apartmentSchema = new Schema<IApartmentDatabase>(
{ {
Rooms: [roomSchema], Rooms: [roomSchema],
FavouriteLoadouts: [favouriteLoadoutSchema], FavouriteLoadouts: [favouriteLoadoutSchema],
Gardening: gardeningSchema // TODO: ensure this is correct Gardening: gardeningSchema
}, },
{ _id: false } { _id: false }
); );
@ -98,7 +131,9 @@ const apartmentDefault: IApartmentDatabase = {
{ Name: "DuviriHallway", MaxCapacity: 1600 } { Name: "DuviriHallway", MaxCapacity: 1600 }
], ],
FavouriteLoadouts: [], FavouriteLoadouts: [],
Gardening: {} Gardening: {
Planters: []
}
}; };
const orbiterSchema = new Schema<IOrbiter>( const orbiterSchema = new Schema<IOrbiter>(

View File

@ -19,6 +19,7 @@ import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompl
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController"; import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController"; import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController"; import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
import { completeCalendarEventController } from "@/src/controllers/api/completeCalendarEventController";
import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController"; import { completeRandomModChallengeController } from "@/src/controllers/api/completeRandomModChallengeController";
import { confirmAllianceInvitationController } from "@/src/controllers/api/confirmAllianceInvitationController"; import { confirmAllianceInvitationController } from "@/src/controllers/api/confirmAllianceInvitationController";
import { confirmGuildInvitationGetController, confirmGuildInvitationPostController } from "@/src/controllers/api/confirmGuildInvitationController"; import { confirmGuildInvitationGetController, confirmGuildInvitationPostController } from "@/src/controllers/api/confirmGuildInvitationController";
@ -47,6 +48,7 @@ import { findSessionsController } from "@/src/controllers/api/findSessionsContro
import { fishmongerController } from "@/src/controllers/api/fishmongerController"; import { fishmongerController } from "@/src/controllers/api/fishmongerController";
import { focusController } from "@/src/controllers/api/focusController"; import { focusController } from "@/src/controllers/api/focusController";
import { fusionTreasuresController } from "@/src/controllers/api/fusionTreasuresController"; import { fusionTreasuresController } from "@/src/controllers/api/fusionTreasuresController";
import { gardeningController } from "@/src/controllers/api/gardeningController";
import { genericUpdateController } from "@/src/controllers/api/genericUpdateController"; import { genericUpdateController } from "@/src/controllers/api/genericUpdateController";
import { getAllianceController } from "@/src/controllers/api/getAllianceController"; import { getAllianceController } from "@/src/controllers/api/getAllianceController";
import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDailyDealStockLevelsController"; import { getDailyDealStockLevelsController } from "@/src/controllers/api/getDailyDealStockLevelsController";
@ -117,6 +119,7 @@ import { setDojoComponentMessageController } from "@/src/controllers/api/setDojo
import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController"; import { setDojoComponentSettingsController } from "@/src/controllers/api/setDojoComponentSettingsController";
import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController"; import { setEquippedInstrumentController } from "@/src/controllers/api/setEquippedInstrumentController";
import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController"; import { setGuildMotdController } from "@/src/controllers/api/setGuildMotdController";
import { setHubNpcCustomizationsController } from "@/src/controllers/api/setHubNpcCustomizationsController";
import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController"; import { setPlacedDecoInfoController } from "@/src/controllers/api/setPlacedDecoInfoController";
import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController"; import { setShipCustomizationsController } from "@/src/controllers/api/setShipCustomizationsController";
import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController"; import { setShipFavouriteLoadoutController } from "@/src/controllers/api/setShipFavouriteLoadoutController";
@ -157,6 +160,7 @@ apiRouter.get("/changeDojoRoot.php", changeDojoRootController);
apiRouter.get("/changeGuildRank.php", changeGuildRankController); apiRouter.get("/changeGuildRank.php", changeGuildRankController);
apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController); apiRouter.get("/checkDailyMissionBonus.php", checkDailyMissionBonusController);
apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController); apiRouter.get("/claimLibraryDailyTaskReward.php", claimLibraryDailyTaskRewardController);
apiRouter.get("/completeCalendarEvent.php", completeCalendarEventController);
apiRouter.get("/confirmAllianceInvitation.php", confirmAllianceInvitationController); apiRouter.get("/confirmAllianceInvitation.php", confirmAllianceInvitationController);
apiRouter.get("/confirmGuildInvitation.php", confirmGuildInvitationGetController); apiRouter.get("/confirmGuildInvitation.php", confirmGuildInvitationGetController);
apiRouter.get("/credits.php", creditsController); apiRouter.get("/credits.php", creditsController);
@ -237,6 +241,7 @@ apiRouter.post("/findSessions.php", findSessionsController);
apiRouter.post("/fishmonger.php", fishmongerController); apiRouter.post("/fishmonger.php", fishmongerController);
apiRouter.post("/focus.php", focusController); apiRouter.post("/focus.php", focusController);
apiRouter.post("/fusionTreasures.php", fusionTreasuresController); apiRouter.post("/fusionTreasures.php", fusionTreasuresController);
apiRouter.post("/gardening.php", gardeningController);
apiRouter.post("/genericUpdate.php", genericUpdateController); apiRouter.post("/genericUpdate.php", genericUpdateController);
apiRouter.post("/getAlliance.php", getAllianceController); apiRouter.post("/getAlliance.php", getAllianceController);
apiRouter.post("/getFriends.php", getFriendsController); apiRouter.post("/getFriends.php", getFriendsController);
@ -285,6 +290,7 @@ apiRouter.post("/setDojoComponentMessage.php", setDojoComponentMessageController
apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController); apiRouter.post("/setDojoComponentSettings.php", setDojoComponentSettingsController);
apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController); apiRouter.post("/setEquippedInstrument.php", setEquippedInstrumentController);
apiRouter.post("/setGuildMotd.php", setGuildMotdController); apiRouter.post("/setGuildMotd.php", setGuildMotdController);
apiRouter.post("/setHubNpcCustomizations.php", setHubNpcCustomizationsController);
apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController); apiRouter.post("/setPlacedDecoInfo.php", setPlacedDecoInfoController);
apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController); apiRouter.post("/setShipCustomizations.php", setShipCustomizationsController);
apiRouter.post("/setShipFavouriteLoadout.php", setShipFavouriteLoadoutController); apiRouter.post("/setShipFavouriteLoadout.php", setShipFavouriteLoadoutController);

View File

@ -10,18 +10,19 @@ import { getAccountInfoController } from "@/src/controllers/custom/getAccountInf
import { renameAccountController } from "@/src/controllers/custom/renameAccountController"; import { renameAccountController } from "@/src/controllers/custom/renameAccountController";
import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController"; import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController";
import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController"; import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController";
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
import { createAccountController } from "@/src/controllers/custom/createAccountController"; import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController"; import { createMessageController } from "@/src/controllers/custom/createMessageController";
import { addCurrencyController } from "@/src/controllers/custom/addCurrencyController"; import { addCurrencyController } from "@/src/controllers/custom/addCurrencyController";
import { addItemsController } from "@/src/controllers/custom/addItemsController"; import { addItemsController } from "@/src/controllers/custom/addItemsController";
import { addModularEquipmentController } from "@/src/controllers/custom/addModularEquipmentController";
import { addXpController } from "@/src/controllers/custom/addXpController"; import { addXpController } from "@/src/controllers/custom/addXpController";
import { importController } from "@/src/controllers/custom/importController"; import { importController } from "@/src/controllers/custom/importController";
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController"; import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController"; import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
const customRouter = express.Router(); const customRouter = express.Router();
@ -35,15 +36,16 @@ customRouter.get("/getAccountInfo", getAccountInfoController);
customRouter.get("/renameAccount", renameAccountController); customRouter.get("/renameAccount", renameAccountController);
customRouter.get("/ircDropped", ircDroppedController); customRouter.get("/ircDropped", ircDroppedController);
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController); customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.post("/createAccount", createAccountController); customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController); customRouter.post("/createMessage", createMessageController);
customRouter.post("/addCurrency", addCurrencyController); customRouter.post("/addCurrency", addCurrencyController);
customRouter.post("/addItems", addItemsController); customRouter.post("/addItems", addItemsController);
customRouter.post("/addModularEquipment", addModularEquipmentController);
customRouter.post("/addXp", addXpController); customRouter.post("/addXp", addXpController);
customRouter.post("/import", importController); customRouter.post("/import", importController);
customRouter.post("/manageQuests", manageQuestsController); customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.get("/config", getConfigDataController); customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController); customRouter.post("/config", updateConfigDataController);

View File

@ -24,6 +24,7 @@ interface IConfig {
infiniteEndo?: boolean; infiniteEndo?: boolean;
infiniteRegalAya?: boolean; infiniteRegalAya?: boolean;
infiniteHelminthMaterials?: boolean; infiniteHelminthMaterials?: boolean;
dontSubtractConsumables?: boolean;
unlockAllShipFeatures?: boolean; unlockAllShipFeatures?: boolean;
unlockAllShipDecorations?: boolean; unlockAllShipDecorations?: boolean;
unlockAllFlavourItems?: boolean; unlockAllFlavourItems?: boolean;

View File

@ -105,6 +105,7 @@ export const getGuildClient = async (guild: TGuildDatabaseDocument, accountId: s
Members: members, Members: members,
Ranks: guild.Ranks, Ranks: guild.Ranks,
Tier: guild.Tier, Tier: guild.Tier,
Emblem: guild.Emblem,
Vault: getGuildVault(guild), Vault: getGuildVault(guild),
ActiveDojoColorResearch: guild.ActiveDojoColorResearch, ActiveDojoColorResearch: guild.ActiveDojoColorResearch,
Class: guild.Class, Class: guild.Class,
@ -133,7 +134,7 @@ export const getGuildVault = (guild: TGuildDatabaseDocument): IGuildVault => {
export const getDojoClient = async ( export const getDojoClient = async (
guild: TGuildDatabaseDocument, guild: TGuildDatabaseDocument,
status: number, status: number,
componentId: Types.ObjectId | string | undefined = undefined componentId?: Types.ObjectId | string
): Promise<IDojoClient> => { ): Promise<IDojoClient> => {
const dojo: IDojoClient = { const dojo: IDojoClient = {
_id: { $oid: guild._id.toString() }, _id: { $oid: guild._id.toString() },
@ -222,6 +223,7 @@ export const getDojoClient = async (
Type: deco.Type, Type: deco.Type,
Pos: deco.Pos, Pos: deco.Pos,
Rot: deco.Rot, Rot: deco.Rot,
Scale: deco.Scale,
Name: deco.Name, Name: deco.Name,
Sockets: deco.Sockets, Sockets: deco.Sockets,
PictureFrameInfo: deco.PictureFrameInfo PictureFrameInfo: deco.PictureFrameInfo
@ -503,7 +505,7 @@ export const hasGuildPermissionEx = (
export const removePigmentsFromGuildMembers = async (guildId: string | Types.ObjectId): Promise<void> => { export const removePigmentsFromGuildMembers = async (guildId: string | Types.ObjectId): Promise<void> => {
const members = await GuildMember.find({ guildId, status: 0 }, "accountId"); const members = await GuildMember.find({ guildId, status: 0 }, "accountId");
for (const member of members) { await parallelForeach(members, async member => {
const inventory = await getInventory(member.accountId.toString(), "MiscItems"); const inventory = await getInventory(member.accountId.toString(), "MiscItems");
const index = inventory.MiscItems.findIndex( const index = inventory.MiscItems.findIndex(
x => x.ItemType == "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment" x => x.ItemType == "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment"
@ -512,7 +514,7 @@ export const removePigmentsFromGuildMembers = async (guildId: string | Types.Obj
inventory.MiscItems.splice(index, 1); inventory.MiscItems.splice(index, 1);
await inventory.save(); await inventory.save();
} }
} });
}; };
export const processGuildTechProjectContributionsUpdate = async ( export const processGuildTechProjectContributionsUpdate = async (
@ -552,7 +554,7 @@ export const setGuildTechLogState = (
guild: TGuildDatabaseDocument, guild: TGuildDatabaseDocument,
type: string, type: string,
state: number, state: number,
dateTime: Date | undefined = undefined dateTime?: Date
): boolean => { ): boolean => {
guild.TechChanges ??= []; guild.TechChanges ??= [];
const entry = guild.TechChanges.find(x => x.details == type); const entry = guild.TechChanges.find(x => x.details == type);

View File

@ -37,6 +37,7 @@ import {
} from "../types/inventoryTypes/inventoryTypes"; } from "../types/inventoryTypes/inventoryTypes";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel"; import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { ILoadoutConfigDatabase, ILoadoutDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase, ILoadoutDatabase } from "../types/saveLoadoutTypes";
import { slotNames } from "../types/purchaseTypes";
const convertDate = (value: IMongoDate): Date => { const convertDate = (value: IMongoDate): Date => {
return new Date(parseInt(value.$date.$numberLong)); return new Date(parseInt(value.$date.$numberLong));
@ -168,6 +169,7 @@ const convertPendingRecipe = (client: IPendingRecipeClient): IPendingRecipeDatab
const convertNemesis = (client: INemesisClient): INemesisDatabase => { const convertNemesis = (client: INemesisClient): INemesisDatabase => {
return { return {
...client, ...client,
fp: BigInt(client.fp),
d: convertDate(client.d) d: convertDate(client.d)
}; };
}; };
@ -212,20 +214,7 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
replaceArray<IOperatorConfigDatabase>(db[key], client[key].map(convertOperatorConfig)); replaceArray<IOperatorConfigDatabase>(db[key], client[key].map(convertOperatorConfig));
} }
} }
for (const key of [ for (const key of slotNames) {
"SuitBin",
"WeaponBin",
"SentinelBin",
"SpaceSuitBin",
"SpaceWeaponBin",
"PvpBonusLoadoutBin",
"PveBonusLoadoutBin",
"RandomModBin",
"MechBin",
"CrewMemberBin",
"OperatorAmpBin",
"CrewShipSalvageBin"
] as const) {
if (client[key] !== undefined) { if (client[key] !== undefined) {
replaceSlots(db[key], client[key]); replaceSlots(db[key], client[key]);
} }

View File

@ -18,12 +18,15 @@ import {
IKubrowPetEggDatabase, IKubrowPetEggDatabase,
IKubrowPetEggClient, IKubrowPetEggClient,
ILibraryDailyTaskInfo, ILibraryDailyTaskInfo,
ICalendarProgress,
IDroneClient, IDroneClient,
IUpgradeClient, IUpgradeClient,
TPartialStartingGear, TPartialStartingGear,
ILoreFragmentScan, ILoreFragmentScan,
ICrewMemberClient ICrewMemberClient,
Status,
IKubrowPetDetailsDatabase,
ITraits,
ICalendarProgress
} from "@/src/types/inventoryTypes/inventoryTypes"; } from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate"; import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes"; import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -58,16 +61,24 @@ import {
ExportWeapons, ExportWeapons,
IDefaultUpgrade, IDefaultUpgrade,
IPowersuit, IPowersuit,
ISentinel,
TStandingLimitBin TStandingLimitBin
} from "warframe-public-export-plus"; } from "warframe-public-export-plus";
import { createShip } from "./shipService"; import { createShip } from "./shipService";
import { toOid } from "../helpers/inventoryHelpers"; import {
catbrowDetails,
kubrowDetails,
kubrowFurPatternsWeights,
kubrowWeights,
toOid
} from "../helpers/inventoryHelpers";
import { addQuestKey, completeQuest } from "@/src/services/questService"; import { addQuestKey, completeQuest } from "@/src/services/questService";
import { handleBundleAcqusition } from "./purchaseService"; import { handleBundleAcqusition } from "./purchaseService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json"; import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { getRandomElement, getRandomInt, SRng } from "./rngService"; import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from "./rngService";
import { createMessage } from "./inboxService"; import { createMessage } from "./inboxService";
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper"; import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
import { getWorldState } from "./worldStateService";
export const createInventory = async ( export const createInventory = async (
accountOwnerId: Types.ObjectId, accountOwnerId: Types.ObjectId,
@ -77,13 +88,10 @@ export const createInventory = async (
const inventory = new Inventory({ const inventory = new Inventory({
accountOwnerId: accountOwnerId, accountOwnerId: accountOwnerId,
LoadOutPresets: defaultItemReferences.loadOutPresetId, LoadOutPresets: defaultItemReferences.loadOutPresetId,
Ships: [defaultItemReferences.ship], Ships: [defaultItemReferences.ship]
PlayedParkourTutorial: config.skipTutorial,
ReceivedStartingGear: config.skipTutorial
}); });
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask(); inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
inventory.CalendarProgress = createCalendar();
inventory.RewardSeed = generateRewardSeed(); inventory.RewardSeed = generateRewardSeed();
inventory.DuviriInfo = { inventory.DuviriInfo = {
Seed: generateRewardSeed(), Seed: generateRewardSeed(),
@ -92,6 +100,7 @@ export const createInventory = async (
await addItem(inventory, "/Lotus/Types/Friendly/PlayerControllable/Weapons/DuviriDualSwords"); await addItem(inventory, "/Lotus/Types/Friendly/PlayerControllable/Weapons/DuviriDualSwords");
if (config.skipTutorial) { if (config.skipTutorial) {
inventory.PlayedParkourTutorial = true;
await addStartingGear(inventory); await addStartingGear(inventory);
await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"); await completeQuest(inventory, "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain");
@ -111,10 +120,15 @@ export const createInventory = async (
} }
}; };
export const generateRewardSeed = (): number => { export const generateRewardSeed = (): bigint => {
const min = -Number.MAX_SAFE_INTEGER; const hiDword = getRandomInt(0, 0x7fffffff);
const max = Number.MAX_SAFE_INTEGER; const loDword = getRandomInt(0, 0xffffffff);
return Math.floor(Math.random() * (max - min + 1)) + min; let seed = (BigInt(hiDword) << 32n) | BigInt(loDword);
if (Math.random() < 0.5) {
seed *= -1n;
seed -= 1n;
}
return seed;
}; };
//TODO: RawUpgrades might need to return a LastAdded //TODO: RawUpgrades might need to return a LastAdded
@ -129,7 +143,7 @@ const awakeningRewards = [
export const addStartingGear = async ( export const addStartingGear = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
startingGear: TPartialStartingGear | undefined = undefined startingGear?: TPartialStartingGear
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const { LongGuns, Pistols, Suits, Melee } = startingGear || { const { LongGuns, Pistols, Suits, Melee } = startingGear || {
LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }], LongGuns: [{ ItemType: "/Lotus/Weapons/Tenno/Rifle/Rifle" }],
@ -140,23 +154,22 @@ export const addStartingGear = async (
//TODO: properly merge weapon bin changes it is currently static here //TODO: properly merge weapon bin changes it is currently static here
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, undefined, inventoryChanges); addEquipment(inventory, "LongGuns", LongGuns[0].ItemType, { IsNew: false }, inventoryChanges);
addEquipment(inventory, "Pistols", Pistols[0].ItemType, undefined, inventoryChanges); addEquipment(inventory, "Pistols", Pistols[0].ItemType, { IsNew: false }, inventoryChanges);
addEquipment(inventory, "Melee", Melee[0].ItemType, undefined, inventoryChanges); addEquipment(inventory, "Melee", Melee[0].ItemType, { IsNew: false }, inventoryChanges);
await addPowerSuit(inventory, Suits[0].ItemType, inventoryChanges); await addPowerSuit(inventory, Suits[0].ItemType, { IsNew: false }, inventoryChanges);
addEquipment( addEquipment(
inventory, inventory,
"DataKnives", "DataKnives",
"/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon", "/Lotus/Weapons/Tenno/HackingDevices/TnHackingDevice/TnHackingDeviceWeapon",
undefined, { XP: 450_000, IsNew: false },
inventoryChanges, inventoryChanges
{ XP: 450_000 }
); );
addEquipment( addEquipment(
inventory, inventory,
"Scoops", "Scoops",
"/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest", "/Lotus/Weapons/Tenno/Speedball/SpeedballWeaponTest",
undefined, { IsNew: false },
inventoryChanges inventoryChanges
); );
@ -199,6 +212,15 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
for (const key in delta) { for (const key in delta) {
if (!(key in InventoryChanges)) { if (!(key in InventoryChanges)) {
InventoryChanges[key] = delta[key]; InventoryChanges[key] = delta[key];
} else if (key == "MiscItems") {
for (const deltaItem of delta[key]!) {
const existing = InventoryChanges[key]!.find(x => x.ItemType == deltaItem.ItemType);
if (existing) {
existing.ItemCount += deltaItem.ItemCount;
} else {
InventoryChanges[key]!.push(deltaItem);
}
}
} else if (Array.isArray(delta[key])) { } else if (Array.isArray(delta[key])) {
const left = InventoryChanges[key] as object[]; const left = InventoryChanges[key] as object[];
const right: object[] = delta[key]; const right: object[] = delta[key];
@ -231,7 +253,7 @@ export const combineInventoryChanges = (InventoryChanges: IInventoryChanges, del
export const getInventory = async ( export const getInventory = async (
accountOwnerId: string, accountOwnerId: string,
projection: string | undefined = undefined projection?: string
): Promise<TInventoryDatabaseDocument> => { ): Promise<TInventoryDatabaseDocument> => {
const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId }, projection); const inventory = await Inventory.findOne({ accountOwnerId: accountOwnerId }, projection);
@ -508,14 +530,7 @@ export const addItem = async (
] ]
}); });
} }
const inventoryChanges = addEquipment( const inventoryChanges = addEquipment(inventory, weapon.productCategory, typeName, defaultOverwrites);
inventory,
weapon.productCategory,
typeName,
[],
{},
defaultOverwrites
);
if (weapon.additionalItems) { if (weapon.additionalItems) {
for (const item of weapon.additionalItems) { for (const item of weapon.additionalItems) {
combineInventoryChanges(inventoryChanges, await addItem(inventory, item, 1)); combineInventoryChanges(inventoryChanges, await addItem(inventory, item, 1));
@ -523,7 +538,11 @@ export const addItem = async (
} }
return { return {
...inventoryChanges, ...inventoryChanges,
...occupySlot(inventory, InventorySlot.WEAPONS, premiumPurchase) ...occupySlot(
inventory,
productCategoryToInventoryBin(weapon.productCategory) ?? InventorySlot.WEAPONS,
premiumPurchase
)
}; };
} else { } else {
// Modular weapon parts // Modular weapon parts
@ -571,7 +590,7 @@ export const addItem = async (
} }
if (typeName in ExportFusionBundles) { if (typeName in ExportFusionBundles) {
const fusionPointsTotal = ExportFusionBundles[typeName].fusionPoints * quantity; const fusionPointsTotal = ExportFusionBundles[typeName].fusionPoints * quantity;
inventory.FusionPoints += fusionPointsTotal; addFusionPoints(inventory, fusionPointsTotal);
return { return {
FusionPoints: fusionPointsTotal FusionPoints: fusionPointsTotal
}; };
@ -612,12 +631,9 @@ export const addItem = async (
switch (typeName.substr(1).split("/")[2]) { switch (typeName.substr(1).split("/")[2]) {
default: { default: {
return { return {
...(await addPowerSuit( ...(await addPowerSuit(inventory, typeName, {
inventory, Features: premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined
typeName, })),
{},
premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined
)),
...occupySlot(inventory, InventorySlot.SUITS, premiumPurchase) ...occupySlot(inventory, InventorySlot.SUITS, premiumPurchase)
}; };
} }
@ -714,6 +730,11 @@ export const addItem = async (
return { return {
MiscItems: miscItemChanges MiscItems: miscItemChanges
}; };
} else if (
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
typeName.substr(1).split("/")[3] == "KubrowPet"
) {
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) { } else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
if (!seed) { if (!seed) {
throw new Error(`Expected crew member to have a seed`); throw new Error(`Expected crew member to have a seed`);
@ -807,7 +828,8 @@ const addSentinel = (
const features = premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined; const features = premiumPurchase ? EquipmentFeatures.DOUBLE_CAPACITY : undefined;
const sentinelIndex = const sentinelIndex =
inventory.Sentinels.push({ ItemType: sentinelName, Configs: configs, XP: 0, Features: features }) - 1; inventory.Sentinels.push({ ItemType: sentinelName, Configs: configs, XP: 0, Features: features, IsNew: true }) -
1;
inventoryChanges.Sentinels ??= []; inventoryChanges.Sentinels ??= [];
inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>()); inventoryChanges.Sentinels.push(inventory.Sentinels[sentinelIndex].toJSON<IEquipmentClient>());
@ -831,8 +853,8 @@ const addSentinelWeapon = (
export const addPowerSuit = async ( export const addPowerSuit = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
powersuitName: string, powersuitName: string,
inventoryChanges: IInventoryChanges = {}, defaultOverwrites?: Partial<IEquipmentDatabase>,
features: number | undefined = undefined inventoryChanges: IInventoryChanges = {}
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const powersuit = ExportWarframes[powersuitName] as IPowersuit | undefined; const powersuit = ExportWarframes[powersuitName] as IPowersuit | undefined;
const exalted = powersuit?.exalted ?? []; const exalted = powersuit?.exalted ?? [];
@ -846,15 +868,20 @@ export const addPowerSuit = async (
} }
} }
} }
const suitIndex = const suit: Omit<IEquipmentDatabase, "_id"> = Object.assign(
inventory.Suits.push({ {
ItemType: powersuitName, ItemType: powersuitName,
Configs: [], Configs: [],
UpgradeVer: 101, UpgradeVer: 101,
XP: 0, XP: 0,
Features: features,
IsNew: true IsNew: true
}) - 1; },
defaultOverwrites
);
if (!suit.IsNew) {
suit.IsNew = undefined;
}
const suitIndex = inventory.Suits.push(suit) - 1;
inventoryChanges.Suits ??= []; inventoryChanges.Suits ??= [];
inventoryChanges.Suits.push(inventory.Suits[suitIndex].toJSON<IEquipmentClient>()); inventoryChanges.Suits.push(inventory.Suits[suitIndex].toJSON<IEquipmentClient>());
return inventoryChanges; return inventoryChanges;
@ -864,7 +891,7 @@ export const addMechSuit = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
mechsuitName: string, mechsuitName: string,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {},
features: number | undefined = undefined features?: number
): Promise<IInventoryChanges> => { ): Promise<IInventoryChanges> => {
const powersuit = ExportWarframes[mechsuitName] as IPowersuit | undefined; const powersuit = ExportWarframes[mechsuitName] as IPowersuit | undefined;
const exalted = powersuit?.exalted ?? []; const exalted = powersuit?.exalted ?? [];
@ -916,7 +943,7 @@ export const addSpaceSuit = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
spacesuitName: string, spacesuitName: string,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {},
features: number | undefined = undefined features?: number
): IInventoryChanges => { ): IInventoryChanges => {
const suitIndex = const suitIndex =
inventory.SpaceSuits.push({ inventory.SpaceSuits.push({
@ -932,6 +959,89 @@ export const addSpaceSuit = (
return inventoryChanges; return inventoryChanges;
}; };
export const addKubrowPet = (
inventory: TInventoryDatabaseDocument,
kubrowPetName: string,
details: IKubrowPetDetailsDatabase | undefined,
premiumPurchase: boolean,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
combineInventoryChanges(inventoryChanges, occupySlot(inventory, InventorySlot.SENTINELS, premiumPurchase));
const kubrowPet = ExportSentinels[kubrowPetName] as ISentinel | undefined;
const exalted = kubrowPet?.exalted ?? [];
for (const specialItem of exalted) {
addSpecialItem(inventory, specialItem, inventoryChanges);
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
if (!details) {
let traits: ITraits;
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
traits = {
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
SecondaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryVampire",
TertiaryColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryVampire",
AccentColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsVampire",
EyeColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA",
FurPattern: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternVampire",
Personality: kubrowPetName,
BodyType: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetVampireBodyType",
Head: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadVampire",
Tail: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailVampire"
};
} else {
const isCatbrow = [
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit"
].includes(kubrowPetName);
const traitsPool = isCatbrow ? catbrowDetails : kubrowDetails;
traits = {
BaseColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
SecondaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
TertiaryColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
AccentColor: getRandomWeightedReward(traitsPool.Colors, kubrowWeights)!.type,
EyeColor: getRandomWeightedReward(traitsPool.EyeColors, kubrowWeights)!.type,
FurPattern: getRandomWeightedReward(traitsPool.FurPatterns, kubrowFurPatternsWeights)!.type,
Personality: kubrowPetName,
BodyType: getRandomWeightedReward(traitsPool.BodyTypes, kubrowWeights)!.type,
Head: isCatbrow ? getRandomWeightedReward(traitsPool.Heads, kubrowWeights)!.type : undefined,
Tail: isCatbrow ? getRandomWeightedReward(traitsPool.Tails, kubrowWeights)!.type : undefined
};
}
details = {
Name: "",
IsPuppy: false,
HasCollar: true,
PrintsRemaining: 2,
Status: Status.StatusStasis,
HatchDate: new Date(Math.trunc(Date.now() / 86400000) * 86400000),
IsMale: !!getRandomInt(0, 1),
Size: getRandomInt(70, 100) / 100,
DominantTraits: traits,
RecessiveTraits: traits
};
}
const kubrowPetIndex =
inventory.KubrowPets.push({
ItemType: kubrowPetName,
Configs: configs,
XP: 0,
Details: details,
IsNew: true
}) - 1;
inventoryChanges.KubrowPets ??= [];
inventoryChanges.KubrowPets.push(inventory.KubrowPets[kubrowPetIndex].toJSON<IEquipmentClient>());
return inventoryChanges;
};
export const updateSlots = ( export const updateSlots = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
slotName: SlotNames, slotName: SlotNames,
@ -972,6 +1082,15 @@ export const updateCurrency = (
return currencyChanges; return currencyChanges;
}; };
export const addFusionPoints = (inventory: TInventoryDatabaseDocument, add: number): number => {
if (inventory.FusionPoints + add > 2147483647) {
logger.warn(`capping FusionPoints balance at 2147483647`);
add = 2147483647 - inventory.FusionPoints;
}
inventory.FusionPoints += add;
return add;
};
const standingLimitBinToInventoryKey: Record< const standingLimitBinToInventoryKey: Record<
Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">, Exclude<TStandingLimitBin, "STANDING_LIMIT_BIN_NONE">,
keyof IDailyAffiliations keyof IDailyAffiliations
@ -1083,20 +1202,21 @@ export const addEquipment = (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
category: TEquipmentKey, category: TEquipmentKey,
type: string, type: string,
modularParts: string[] | undefined = undefined, defaultOverwrites?: Partial<IEquipmentDatabase>,
inventoryChanges: IInventoryChanges = {}, inventoryChanges: IInventoryChanges = {}
defaultOverwrites: Partial<IEquipmentDatabase> | undefined = undefined
): IInventoryChanges => { ): IInventoryChanges => {
const equipment = Object.assign( const equipment: Omit<IEquipmentDatabase, "_id"> = Object.assign(
{ {
ItemType: type, ItemType: type,
Configs: [], Configs: [],
XP: 0, XP: 0,
ModularParts: modularParts, IsNew: category != "CrewShipWeapons" && category != "CrewShipSalvagedWeapons"
IsNew: category != "CrewShipWeapons" && category != "CrewShipSalvagedWeapons" ? true : undefined
}, },
defaultOverwrites defaultOverwrites
); );
if (!equipment.IsNew) {
equipment.IsNew = undefined;
}
const index = inventory[category].push(equipment) - 1; const index = inventory[category].push(equipment) - 1;
inventoryChanges[category] ??= []; inventoryChanges[category] ??= [];
@ -1125,12 +1245,16 @@ export const addSkin = (
typeName: string, typeName: string,
inventoryChanges: IInventoryChanges = {} inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => { ): IInventoryChanges => {
if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
logger.debug(`refusing to add WeaponSkin ${typeName} because account already owns it`);
} else {
const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1; const index = inventory.WeaponSkins.push({ ItemType: typeName, IsNew: true }) - 1;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
inventoryChanges.WeaponSkins ??= []; inventoryChanges.WeaponSkins ??= [];
(inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push( (inventoryChanges.WeaponSkins as IWeaponSkinClient[]).push(
inventory.WeaponSkins[index].toJSON<IWeaponSkinClient>() inventory.WeaponSkins[index].toJSON<IWeaponSkinClient>()
); );
}
return inventoryChanges; return inventoryChanges;
}; };
@ -1348,6 +1472,22 @@ export const addGearExpByCategory = (
}); });
}; };
export const addMiscItem = (
inventory: TInventoryDatabaseDocument,
type: string,
count: number,
inventoryChanges: IInventoryChanges
): void => {
const miscItemChanges: IMiscItem[] = [
{
ItemType: type,
ItemCount: count
}
];
addMiscItems(inventory, miscItemChanges);
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
};
export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray: IMiscItem[]): void => { export const addMiscItems = (inventory: TInventoryDatabaseDocument, itemsArray: IMiscItem[]): void => {
const { MiscItems } = inventory; const { MiscItems } = inventory;
@ -1641,20 +1781,6 @@ export const createLibraryDailyTask = (): ILibraryDailyTaskInfo => {
}; };
}; };
const createCalendar = (): ICalendarProgress => {
return {
Version: 19,
Iteration: 2,
YearProgress: { Upgrades: [] },
SeasonProgress: {
SeasonType: "CST_SPRING",
LastCompletedDayIdx: -1,
LastCompletedChallengeDayIdx: -1,
ActivatedChallenges: []
}
};
};
export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void => { export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void => {
inventory.Affiliations.push({ inventory.Affiliations.push({
Title: 1, Title: 1,
@ -1671,3 +1797,57 @@ export const setupKahlSyndicate = (inventory: TInventoryDatabaseDocument): void
Tag: "KahlSyndicate" Tag: "KahlSyndicate"
}); });
}; };
export const cleanupInventory = (inventory: TInventoryDatabaseDocument): void => {
let index = inventory.MiscItems.findIndex(x => x.ItemType == "");
if (index != -1) {
inventory.MiscItems.splice(index, 1);
}
index = inventory.Affiliations.findIndex(x => x.Tag == "KahlSyndicate");
if (index != -1 && !inventory.Affiliations[index].WeeklyMissions) {
logger.debug(`KahlSyndicate seems broken, removing it and setting up again`);
inventory.Affiliations.splice(index, 1);
setupKahlSyndicate(inventory);
}
const LibrarySyndicate = inventory.Affiliations.find(x => x.Tag == "LibrarySyndicate");
if (LibrarySyndicate && LibrarySyndicate.FreeFavorsEarned) {
logger.debug(`removing FreeFavorsEarned from LibrarySyndicate`);
LibrarySyndicate.FreeFavorsEarned = undefined;
}
};
export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICalendarProgress => {
const currentSeason = getWorldState().KnownCalendarSeasons[0];
if (!inventory.CalendarProgress) {
inventory.CalendarProgress = {
Version: 19,
Iteration: currentSeason.YearIteration,
YearProgress: {
Upgrades: []
},
SeasonProgress: {
SeasonType: currentSeason.Season,
LastCompletedDayIdx: 0,
LastCompletedChallengeDayIdx: 0,
ActivatedChallenges: []
}
};
}
const yearRolledOver = inventory.CalendarProgress.Iteration != currentSeason.YearIteration;
if (yearRolledOver) {
inventory.CalendarProgress.Iteration = currentSeason.YearIteration;
inventory.CalendarProgress.YearProgress.Upgrades = [];
}
if (yearRolledOver || inventory.CalendarProgress.SeasonProgress.SeasonType != currentSeason.Season) {
inventory.CalendarProgress.SeasonProgress.SeasonType = currentSeason.Season;
inventory.CalendarProgress.SeasonProgress.LastCompletedDayIdx = -1;
inventory.CalendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = -1;
inventory.CalendarProgress.SeasonProgress.ActivatedChallenges = [];
}
return inventory.CalendarProgress;
};

View File

@ -19,6 +19,7 @@ import {
addCrewShipRawSalvage, addCrewShipRawSalvage,
addEmailItem, addEmailItem,
addFocusXpIncreases, addFocusXpIncreases,
addFusionPoints,
addFusionTreasures, addFusionTreasures,
addGearExpByCategory, addGearExpByCategory,
addItem, addItem,
@ -29,9 +30,11 @@ import {
addMods, addMods,
addRecipes, addRecipes,
addShipDecorations, addShipDecorations,
addSkin,
addStanding, addStanding,
combineInventoryChanges, combineInventoryChanges,
generateRewardSeed, generateRewardSeed,
getCalendarProgress,
updateCurrency, updateCurrency,
updateSyndicate updateSyndicate
} from "@/src/services/inventoryService"; } from "@/src/services/inventoryService";
@ -43,7 +46,7 @@ import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/invento
import { getEntriesUnsafe } from "@/src/utils/ts-utils"; import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { handleStoreItemAcquisition } from "./purchaseService"; import { handleStoreItemAcquisition } from "./purchaseService";
import { IMissionReward } from "../types/missionTypes"; import { IMissionCredits, IMissionReward } from "../types/missionTypes";
import { crackRelic } from "@/src/helpers/relicHelper"; import { crackRelic } from "@/src/helpers/relicHelper";
import { createMessage } from "./inboxService"; import { createMessage } from "./inboxService";
import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json"; import kuriaMessage50 from "@/static/fixed_responses/kuriaMessages/fiftyPercent.json";
@ -55,6 +58,7 @@ import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes"; import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService"; import { getLiteSortie, getWorldState, idToWeek } from "./worldStateService";
import { config } from "./configService"; import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => { const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[] => {
// For Spy missions, e.g. 3 vaults cracked = A, B, C // For Spy missions, e.g. 3 vaults cracked = A, B, C
@ -66,18 +70,22 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
return rotations; return rotations;
} }
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const missionIndex: number | undefined = ExportRegions[rewardInfo.node]?.missionIndex;
// For Rescue missions // For Rescue missions
if (rewardInfo.node in ExportRegions && ExportRegions[rewardInfo.node].missionIndex == 3 && rewardInfo.rewardTier) { if (missionIndex == 3 && rewardInfo.rewardTier) {
return [rewardInfo.rewardTier]; return [rewardInfo.rewardTier];
} }
// Aborting a railjack mission should not give any rewards (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741) const rotationCount = rewardInfo.rewardQualifications?.length || 0;
if (rewardInfo.rewardQualifications === undefined) {
return [];
}
const rotationCount = rewardInfo.rewardQualifications.length || 0; // Empty or absent rewardQualifications should not give rewards when:
if (rotationCount === 0) return [0]; // - Completing only 1 zone of (E)SO (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
// - Aborting a railjack mission (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1741)
if (rotationCount == 0 && missionIndex != 30 && missionIndex != 32) {
return [0];
}
const rotationPattern = const rotationPattern =
tierOverride === undefined tierOverride === undefined
@ -183,6 +191,12 @@ export const addMissionInventoryUpdates = async (
if (inventoryUpdates.RewardInfo.NemesisAbandonedRewards) { if (inventoryUpdates.RewardInfo.NemesisAbandonedRewards) {
inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards; inventory.NemesisAbandonedRewards = inventoryUpdates.RewardInfo.NemesisAbandonedRewards;
} }
if (inventoryUpdates.RewardInfo.NemesisHenchmenKills && inventory.Nemesis) {
inventory.Nemesis.HenchmenKilled += inventoryUpdates.RewardInfo.NemesisHenchmenKills;
}
if (inventoryUpdates.RewardInfo.NemesisHintProgress && inventory.Nemesis) {
inventory.Nemesis.HintProgress += inventoryUpdates.RewardInfo.NemesisHintProgress;
}
if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) { if (inventoryUpdates.MissionStatus == "GS_SUCCESS" && inventoryUpdates.RewardInfo.jobId) {
// e.g. for Profit-Taker Phase 1: // e.g. for Profit-Taker Phase 1:
// JobTier: -6, // JobTier: -6,
@ -264,7 +278,14 @@ export const addMissionInventoryUpdates = async (
addMiscItems(inventory, value); addMiscItems(inventory, value);
break; break;
case "Consumables": case "Consumables":
if (config.dontSubtractConsumables) {
addConsumables(
inventory,
value.filter(x => x.ItemCount > 0)
);
} else {
addConsumables(inventory, value); addConsumables(inventory, value);
}
break; break;
case "Recipes": case "Recipes":
addRecipes(inventory, value); addRecipes(inventory, value);
@ -286,14 +307,14 @@ export const addMissionInventoryUpdates = async (
addShipDecorations(inventory, value); addShipDecorations(inventory, value);
break; break;
case "FusionBundles": { case "FusionBundles": {
let fusionPoints = 0; let fusionPointsDelta = 0;
for (const fusionBundle of value) { for (const fusionBundle of value) {
const fusionPointsTotal = fusionPointsDelta += addFusionPoints(
ExportFusionBundles[fusionBundle.ItemType].fusionPoints * fusionBundle.ItemCount; inventory,
inventory.FusionPoints += fusionPointsTotal; ExportFusionBundles[fusionBundle.ItemType].fusionPoints * fusionBundle.ItemCount
fusionPoints += fusionPointsTotal; );
} }
inventoryChanges.FusionPoints = fusionPoints; inventoryChanges.FusionPoints = fusionPointsDelta;
break; break;
} }
case "EmailItems": { case "EmailItems": {
@ -331,10 +352,10 @@ export const addMissionInventoryUpdates = async (
case "LibraryScans": case "LibraryScans":
value.forEach(scan => { value.forEach(scan => {
let synthesisIgnored = true; let synthesisIgnored = true;
if ( if (inventory.LibraryPersonalTarget) {
inventory.LibraryPersonalTarget && const taskAvatar = libraryPersonalTargetToAvatar[inventory.LibraryPersonalTarget];
libraryPersonalTargetToAvatar[inventory.LibraryPersonalTarget] == scan.EnemyType const taskAvatars = libraryDailyTasks.find(x => x.indexOf(taskAvatar) != -1)!;
) { if (taskAvatars.indexOf(scan.EnemyType) != -1) {
let progress = inventory.LibraryPersonalProgress.find( let progress = inventory.LibraryPersonalProgress.find(
x => x.TargetType == inventory.LibraryPersonalTarget x => x.TargetType == inventory.LibraryPersonalTarget
); );
@ -361,6 +382,7 @@ export const addMissionInventoryUpdates = async (
logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`); logger.debug(`synthesis of ${scan.EnemyType} added to personal target progress`);
synthesisIgnored = false; synthesisIgnored = false;
} }
}
if ( if (
inventory.LibraryActiveDailyTaskInfo && inventory.LibraryActiveDailyTaskInfo &&
inventory.LibraryActiveDailyTaskInfo.EnemyTypes.find(x => x == scan.EnemyType) inventory.LibraryActiveDailyTaskInfo.EnemyTypes.find(x => x == scan.EnemyType)
@ -409,6 +431,11 @@ export const addMissionInventoryUpdates = async (
upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress upgrade.UpgradeFingerprint = clientUpgrade.UpgradeFingerprint; // primitive way to copy over the riven challenge progress
}); });
break; break;
case "WeaponSkins":
for (const item of value) {
addSkin(inventory, item.ItemType);
}
break;
case "Boosters": case "Boosters":
value.forEach(booster => { value.forEach(booster => {
addBooster(booster.ItemType, booster.ExpiryDate, inventory); addBooster(booster.ItemType, booster.ExpiryDate, inventory);
@ -559,6 +586,22 @@ export const addMissionInventoryUpdates = async (
} }
break; break;
} }
case "CalendarProgress": {
const calendarProgress = getCalendarProgress(inventory);
for (const progress of value) {
const challengeName = progress.challenge.substring(progress.challenge.lastIndexOf("/") + 1);
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx++;
calendarProgress.SeasonProgress.ActivatedChallenges.push(challengeName);
}
break;
}
case "duviriCaveOffers": {
// Duviri cave offers (generated with the duviri seed) change after completing one of its game modes (not when aborting).
if (inventoryUpdates.MissionStatus != "GS_QUIT") {
inventory.DuviriInfo.Seed = generateRewardSeed();
}
break;
}
default: default:
// Equipment XP updates // Equipment XP updates
if (equipmentKeys.includes(key as TEquipmentKey)) { if (equipmentKeys.includes(key as TEquipmentKey)) {
@ -584,8 +627,140 @@ interface AddMissionRewardsReturnType {
credits?: IMissionCredits; credits?: IMissionCredits;
AffiliationMods?: IAffiliationMods[]; AffiliationMods?: IAffiliationMods[];
SyndicateXPItemReward?: number; SyndicateXPItemReward?: number;
ConquestCompletedMissionsCount?: number;
} }
interface IConquestReward {
at: number;
pool: IRngResult[];
}
const labConquestRewards: IConquestReward[] = [
{
at: 5,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/EntratiLabConquestRewards/EntratiLabConquestSilverRewards"
][0] as IRngResult[]
},
{
at: 10,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/EntratiLabConquestRewards/EntratiLabConquestSilverRewards"
][0] as IRngResult[]
},
{
at: 15,
pool: [
{
type: "/Lotus/StoreItems/Types/Gameplay/EntratiLab/Resources/EntratiLanthornBundle",
itemCount: 3,
probability: 1
}
]
},
{
at: 20,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/EntratiLabConquestRewards/EntratiLabConquestGoldRewards"
][0] as IRngResult[]
},
{
at: 28,
pool: [
{
type: "/Lotus/StoreItems/Types/Items/MiscItems/DistillPoints",
itemCount: 20,
probability: 1
}
]
},
{
at: 31,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/EntratiLabConquestRewards/EntratiLabConquestGoldRewards"
][0] as IRngResult[]
},
{
at: 34,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/EntratiLabConquestRewards/EntratiLabConquestArcaneRewards"
][0] as IRngResult[]
},
{
at: 37,
pool: [
{
type: "/Lotus/StoreItems/Types/Items/MiscItems/DistillPoints",
itemCount: 50,
probability: 1
}
]
}
];
const hexConquestRewards: IConquestReward[] = [
{
at: 5,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/1999ConquestRewards/1999ConquestSilverRewards"
][0] as IRngResult[]
},
{
at: 10,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/1999ConquestRewards/1999ConquestSilverRewards"
][0] as IRngResult[]
},
{
at: 15,
pool: [
{
type: "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedea",
itemCount: 1,
probability: 1
}
]
},
{
at: 20,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/1999ConquestRewards/1999ConquestGoldRewards"
][0] as IRngResult[]
},
{
at: 28,
pool: [
{
type: "/Lotus/StoreItems/Types/Items/MiscItems/1999ConquestBucks",
itemCount: 6,
probability: 1
}
]
},
{
at: 31,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/1999ConquestRewards/1999ConquestGoldRewards"
][0] as IRngResult[]
},
{
at: 34,
pool: ExportRewards[
"/Lotus/Types/Game/MissionDecks/1999ConquestRewards/1999ConquestArcaneRewards"
][0] as IRngResult[]
},
{
at: 37,
pool: [
{
type: "/Lotus/StoreItems/Types/Items/MiscItems/1999ConquestBucks",
itemCount: 9,
probability: 1
}
]
}
];
//TODO: return type of partial missioninventoryupdate response //TODO: return type of partial missioninventoryupdate response
export const addMissionRewards = async ( export const addMissionRewards = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
@ -607,17 +782,13 @@ export const addMissionRewards = async (
return { MissionRewards: [] }; return { MissionRewards: [] };
} }
if (rewardInfo.rewardSeed) {
// We're using a reward seed, so give the client a new one in the response. On live, missionInventoryUpdate seems to always provide a fresh one in the response.
inventory.RewardSeed = generateRewardSeed();
}
//TODO: check double reward merging //TODO: check double reward merging
const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion); const MissionRewards: IMissionReward[] = getRandomMissionDrops(inventory, rewardInfo, wagerTier, firstCompletion);
logger.debug("random mission drops:", MissionRewards); logger.debug("random mission drops:", MissionRewards);
const inventoryChanges: IInventoryChanges = {}; const inventoryChanges: IInventoryChanges = {};
const AffiliationMods: IAffiliationMods[] = []; const AffiliationMods: IAffiliationMods[] = [];
let SyndicateXPItemReward; let SyndicateXPItemReward;
let ConquestCompletedMissionsCount;
let missionCompletionCredits = 0; let missionCompletionCredits = 0;
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display //inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
@ -656,7 +827,8 @@ export const addMissionRewards = async (
missions.Tag != "SolNode761" && // the index missions.Tag != "SolNode761" && // the index
missions.Tag != "SolNode762" && // the index missions.Tag != "SolNode762" && // the index
missions.Tag != "SolNode763" && // the index missions.Tag != "SolNode763" && // the index
missions.Tag != "CrewBattleNode556" // free flight missions.Tag != "CrewBattleNode556" && // free flight
getRotations(rewardInfo).length > 0 // (E)SO should not give credits for only completing zone 1, in which case it has no rewardQualifications (https://onlyg.it/OpenWF/SpaceNinjaServer/issues/1823)
) { ) {
const levelCreditReward = getLevelCreditRewards(node); const levelCreditReward = getLevelCreditRewards(node);
missionCompletionCredits += levelCreditReward; missionCompletionCredits += levelCreditReward;
@ -668,6 +840,14 @@ export const addMissionRewards = async (
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo); missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo);
} }
if (rewardInfo.sortieTag == "Mission1") {
missionCompletionCredits += 20_000;
} else if (rewardInfo.sortieTag == "Mission2") {
missionCompletionCredits += 30_000;
} else if (rewardInfo.sortieTag == "Final") {
missionCompletionCredits += 50_000;
}
if (missions.Tag == "PlutoToErisJunction") { if (missions.Tag == "PlutoToErisJunction") {
await createMessage(inventory.accountOwnerId, [ await createMessage(inventory.accountOwnerId, [
{ {
@ -689,6 +869,62 @@ export const addMissionRewards = async (
}); });
} }
if (rewardInfo.ConquestCompleted !== undefined) {
let score = 1;
if (rewardInfo.ConquestHardModeActive === 1) score += 3;
if (rewardInfo.ConquestPersonalModifiersActive !== undefined)
score += rewardInfo.ConquestPersonalModifiersActive;
if (rewardInfo.ConquestEquipmentSuggestionsFulfilled !== undefined)
score += rewardInfo.ConquestEquipmentSuggestionsFulfilled;
score *= rewardInfo.ConquestCompleted + 1;
if (rewardInfo.ConquestCompleted == 2 && rewardInfo.ConquestHardModeActive === 1) score += 1;
logger.debug(`completed conquest mission ${rewardInfo.ConquestCompleted + 1} for a score of ${score}`);
const conquestType = rewardInfo.ConquestType;
const conquestNode =
conquestType == "HexConquest" ? "EchoesHexConquestHardModeUnlocked" : "EntratiLabConquestHardModeUnlocked";
if (score >= 25 && inventory.NodeIntrosCompleted.indexOf(conquestNode) == -1)
inventory.NodeIntrosCompleted.push(conquestNode);
if (conquestType == "HexConquest") {
inventory.EchoesHexConquestCacheScoreMission ??= 0;
if (score > inventory.EchoesHexConquestCacheScoreMission) {
for (const reward of hexConquestRewards) {
if (score >= reward.at && inventory.EchoesHexConquestCacheScoreMission < reward.at) {
const rolled = getRandomReward(reward.pool)!;
logger.debug(`rolled hex conquest reward for reaching ${reward.at} points`, rolled);
MissionRewards.push({
StoreItem: rolled.type,
ItemCount: rolled.itemCount
});
}
}
inventory.EchoesHexConquestCacheScoreMission = score;
}
} else {
inventory.EntratiLabConquestCacheScoreMission ??= 0;
if (score > inventory.EntratiLabConquestCacheScoreMission) {
for (const reward of labConquestRewards) {
if (score >= reward.at && inventory.EntratiLabConquestCacheScoreMission < reward.at) {
const rolled = getRandomReward(reward.pool)!;
logger.debug(`rolled lab conquest reward for reaching ${reward.at} points`, rolled);
MissionRewards.push({
StoreItem: rolled.type,
ItemCount: rolled.itemCount
});
}
}
inventory.EntratiLabConquestCacheScoreMission = score;
}
}
ConquestCompletedMissionsCount = rewardInfo.ConquestCompleted == 2 ? 0 : rewardInfo.ConquestCompleted + 1;
}
for (const reward of MissionRewards) { for (const reward of MissionRewards) {
const inventoryChange = await handleStoreItemAcquisition( const inventoryChange = await handleStoreItemAcquisition(
reward.StoreItem, reward.StoreItem,
@ -880,15 +1116,15 @@ export const addMissionRewards = async (
} }
} }
return { inventoryChanges, MissionRewards, credits, AffiliationMods, SyndicateXPItemReward }; return {
inventoryChanges,
MissionRewards,
credits,
AffiliationMods,
SyndicateXPItemReward,
ConquestCompletedMissionsCount
};
}; };
interface IMissionCredits {
MissionCredits: number[];
CreditBonus: number[];
TotalCredits: number[];
DailyMissionBonus?: boolean;
}
//creditBonus is not entirely accurate. //creditBonus is not entirely accurate.
//TODO: consider ActiveBoosters //TODO: consider ActiveBoosters
@ -1066,6 +1302,9 @@ function getRandomMissionDrops(
// Invasion assassination has Phorid has the boss who should drop Nyx parts // Invasion assassination has Phorid has the boss who should drop Nyx parts
// TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic // TODO: Check that the invasion faction is indeed FC_INFESTATION once the Invasions in worldState are more dynamic
rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"]; rewardManifests = ["/Lotus/Types/Game/MissionDecks/BossMissionRewards/NyxRewards"];
} else if (RewardInfo.sortieId && region.missionIndex != 0) {
// Sortie mission types differ from the underlying node and hence also don't give rewards from the underlying nodes. Assassinations are an exception to this.
rewardManifests = [];
} else { } else {
rewardManifests = region.rewardManifests; rewardManifests = region.rewardManifests;
} }
@ -1214,6 +1453,11 @@ function getRandomMissionDrops(
if (rewardManifests.length != 0) { if (rewardManifests.length != 0) {
logger.debug(`generating random mission rewards`, { rewardManifests, rotations }); logger.debug(`generating random mission rewards`, { rewardManifests, rotations });
} }
if (RewardInfo.rewardSeed) {
if (RewardInfo.rewardSeed != inventory.RewardSeed) {
logger.warn(`RewardSeed mismatch:`, { client: RewardInfo.rewardSeed, database: inventory.RewardSeed });
}
}
const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn); const rng = new SRng(BigInt(RewardInfo.rewardSeed ?? generateRewardSeed()) ^ 0xffffffffffffffffn);
rewardManifests.forEach(name => { rewardManifests.forEach(name => {
const table = ExportRewards[name]; const table = ExportRewards[name];

View File

@ -1,9 +1,14 @@
import { PersonalRooms } from "@/src/models/personalRoomsModel"; import { PersonalRooms } from "@/src/models/personalRoomsModel";
import { addItem, getInventory } from "@/src/services/inventoryService"; import { addItem, getInventory } from "@/src/services/inventoryService";
import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes"; import { TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
import { IGardeningDatabase } from "../types/shipTypes";
import { getRandomElement } from "./rngService";
export const getPersonalRooms = async (accountId: string): Promise<TPersonalRoomsDatabaseDocument> => { export const getPersonalRooms = async (
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId }); accountId: string,
projection?: string
): Promise<TPersonalRoomsDatabaseDocument> => {
const personalRooms = await PersonalRooms.findOne({ personalRoomsOwnerId: accountId }, projection);
if (!personalRooms) { if (!personalRooms) {
throw new Error(`personal rooms not found for account ${accountId}`); throw new Error(`personal rooms not found for account ${accountId}`);
@ -25,3 +30,64 @@ export const updateShipFeature = async (accountId: string, shipFeature: string):
await addItem(inventory, shipFeature, -1); await addItem(inventory, shipFeature, -1);
await inventory.save(); await inventory.save();
}; };
export const createGarden = (): IGardeningDatabase => {
const plantTypes = [
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantA",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantB",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantC",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantD",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantE",
"/Lotus/Types/Items/Plants/MiscItems/DuvxDuviriGrowingPlantF"
];
const endTime = new Date((Math.trunc(Date.now() / 1000) + 79200) * 1000); // Plants will take 22 hours to grow
return {
Planters: [
{
Name: "Garden0",
Plants: [
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 1
}
]
},
{
Name: "Garden1",
Plants: [
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 1
}
]
},
{
Name: "Garden2",
Plants: [
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 0
},
{
PlantType: getRandomElement(plantTypes),
EndTime: endTime,
PlotIndex: 1
}
]
}
]
};
};

View File

@ -66,6 +66,18 @@ export const handlePurchase = async (
if (!offer) { if (!offer) {
throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`); throw new Error(`unknown vendor offer: ${ItemId ? ItemId : purchaseRequest.PurchaseParams.StoreItem}`);
} }
if (offer.RegularPrice) {
combineInventoryChanges(
prePurchaseInventoryChanges,
updateCurrency(inventory, offer.RegularPrice[0], false)
);
}
if (offer.PremiumPrice) {
combineInventoryChanges(
prePurchaseInventoryChanges,
updateCurrency(inventory, offer.PremiumPrice[0], true)
);
}
if (offer.ItemPrices) { if (offer.ItemPrices) {
handleItemPrices( handleItemPrices(
inventory, inventory,
@ -170,6 +182,9 @@ export const handlePurchase = async (
purchaseResponse.InventoryChanges, purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.RegularPrice, false) updateCurrency(inventory, offer.RegularPrice, false)
); );
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`vendor purchase should not have an expected price`);
}
const invItem: IMiscItem = { const invItem: IMiscItem = {
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks", ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
@ -223,12 +238,18 @@ export const handlePurchase = async (
const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!]; const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!];
const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem); const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem);
if (offer) { if (offer) {
if (offer.credits) { if (typeof offer.credits == "number") {
combineInventoryChanges( combineInventoryChanges(
purchaseResponse.InventoryChanges, purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.credits, false) updateCurrency(inventory, offer.credits, false)
); );
} }
if (typeof offer.platinum == "number") {
combineInventoryChanges(
purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.platinum, true)
);
}
if (offer.itemPrices) { if (offer.itemPrices) {
handleItemPrices( handleItemPrices(
inventory, inventory,
@ -239,6 +260,9 @@ export const handlePurchase = async (
} }
} }
} }
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`vendor purchase should not have an expected price`);
}
break; break;
case 18: { case 18: {
if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) { if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) {

View File

@ -191,6 +191,25 @@ const getQuestCompletionItems = (questKey: string): ITypeCount[] | undefined =>
return items; return items;
}; };
// Checks that `questKey` is in `requirements`, and if so, that all other quests in `requirements` are also already completed.
const doesQuestCompletionFinishSet = (
inventory: TInventoryDatabaseDocument,
questKey: string,
requirements: string[]
): boolean => {
let holds = false;
for (const requirement of requirements) {
if (questKey == requirement) {
holds = true;
} else {
if (!inventory.QuestKeys.find(x => x.ItemType == requirement)?.Completed) {
return false;
}
}
}
return holds;
};
const handleQuestCompletion = async ( const handleQuestCompletion = async (
inventory: TInventoryDatabaseDocument, inventory: TInventoryDatabaseDocument,
questKey: string, questKey: string,
@ -218,12 +237,10 @@ const handleQuestCompletion = async (
// Whispers in the Walls is unlocked once The New + Heart of Deimos are completed. // Whispers in the Walls is unlocked once The New + Heart of Deimos are completed.
if ( if (
(questKey == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain" && doesQuestCompletionFinishSet(inventory, questKey, [
inventory.QuestKeys.find( "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain",
x => x.ItemType == "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain" "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain"
)?.Completed) || ])
(questKey == "/Lotus/Types/Keys/InfestedMicroplanetQuest/InfestedMicroplanetQuestKeyChain" &&
inventory.QuestKeys.find(x => x.ItemType == "/Lotus/Types/Keys/NewWarQuest/NewWarQuestKeyChain")?.Completed)
) { ) {
await createMessage(inventory.accountOwnerId, [ await createMessage(inventory.accountOwnerId, [
{ {
@ -237,6 +254,25 @@ const handleQuestCompletion = async (
]); ]);
} }
// The Hex (Quest) is unlocked once The Lotus Eaters + The Duviri Paradox are completed.
if (
doesQuestCompletionFinishSet(inventory, questKey, [
"/Lotus/Types/Keys/1999PrologueQuest/1999PrologueQuestKeyChain",
"/Lotus/Types/Keys/DuviriQuest/DuviriQuestKeyChain"
])
) {
await createMessage(inventory.accountOwnerId, [
{
sndr: "/Lotus/Language/NewWar/P3M1ChooseMara",
msg: "/Lotus/Language/1999Quest/1999QuestInboxBody",
att: ["/Lotus/Types/Keys/1999Quest/1999QuestKeyChain"],
sub: "/Lotus/Language/1999Quest/1999QuestInboxSubject",
icon: "/Lotus/Interface/Icons/Npcs/Operator.png",
highPriority: true
}
]);
}
const questCompletionItems = getQuestCompletionItems(questKey); const questCompletionItems = getQuestCompletionItems(questKey);
logger.debug(`quest completion items`, questCompletionItems); logger.debug(`quest completion items`, questCompletionItems);
if (questCompletionItems) { if (questCompletionItems) {

View File

@ -97,9 +97,17 @@ export class CRng {
} }
randomInt(min: number, max: number): number { randomInt(min: number, max: number): number {
min = Math.ceil(min); const diff = max - min;
max = Math.floor(max); if (diff != 0) {
return Math.floor(this.random() * (max - min + 1)) + min; if (diff < 0) {
throw new Error(`max must be greater than min`);
}
if (diff > 0x3fffffff) {
throw new Error(`insufficient entropy`);
}
min += Math.floor(this.random() * (diff + 1));
}
return min;
} }
randomElement<T>(arr: T[]): T { randomElement<T>(arr: T[]): T {

View File

@ -161,6 +161,11 @@ export const handleInventoryItemConfigChange = async (
} }
break; break;
} }
case "LotusCustomization": {
logger.debug(`saved LotusCustomization`, equipmentChanges.LotusCustomization);
inventory.LotusCustomization = equipmentChanges.LotusCustomization;
break;
}
default: { default: {
if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") { if (equipmentKeys.includes(equipmentName as TEquipmentKey) && equipmentName != "ValidNewLoadoutId") {
logger.debug(`general Item config saved of type ${equipmentName}`, { logger.debug(`general Item config saved of type ${equipmentName}`, {

View File

@ -1,13 +1,8 @@
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { CRng, mixSeeds } from "@/src/services/rngService"; import { CRng, mixSeeds } from "@/src/services/rngService";
import { IMongoDate } from "@/src/types/commonTypes"; import { IMongoDate } from "@/src/types/commonTypes";
import { import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
IItemManifestPreprocessed, import { ExportVendors, IRange } from "warframe-public-export-plus";
IRawVendorManifest,
IVendorInfo,
IVendorManifestPreprocessed
} from "@/src/types/vendorTypes";
import { ExportVendors } from "warframe-public-export-plus";
import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json"; import ArchimedeanVendorManifest from "@/static/fixed_responses/getVendorInfo/ArchimedeanVendorManifest.json";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json"; import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
@ -23,7 +18,6 @@ import DeimosProspectorVendorManifest from "@/static/fixed_responses/getVendorIn
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json"; import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json"; import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json"; import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
import GuildAdvertisementVendorManifest from "@/static/fixed_responses/getVendorInfo/GuildAdvertisementVendorManifest.json";
import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json"; import HubsIronwakeDondaVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsIronwakeDondaVendorManifest.json";
import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json"; import HubsRailjackCrewMemberVendorManifest from "@/static/fixed_responses/getVendorInfo/HubsRailjackCrewMemberVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json"; import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
@ -32,14 +26,14 @@ import OstronFishmongerVendorManifest from "@/static/fixed_responses/getVendorIn
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json"; import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json"; import OstronProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronProspectorVendorManifest.json";
import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json"; import RadioLegionIntermission12VendorManifest from "@/static/fixed_responses/getVendorInfo/RadioLegionIntermission12VendorManifest.json";
import SolarisDebtTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorManifest.json";
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json"; import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json"; import SolarisFishmongerVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisFishmongerVendorManifest.json";
import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json"; import SolarisProspectorVendorManifest from "@/static/fixed_responses/getVendorInfo/SolarisProspectorVendorManifest.json";
import Temple1999VendorManifest from "@/static/fixed_responses/getVendorInfo/Temple1999VendorManifest.json";
import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json"; import TeshinHardModeVendorManifest from "@/static/fixed_responses/getVendorInfo/TeshinHardModeVendorManifest.json";
import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json"; import ZarimanCommisionsManifestArchimedean from "@/static/fixed_responses/getVendorInfo/ZarimanCommisionsManifestArchimedean.json";
const rawVendorManifests: IRawVendorManifest[] = [ const rawVendorManifests: IVendorManifest[] = [
ArchimedeanVendorManifest, ArchimedeanVendorManifest,
DeimosEntratiFragmentVendorProductsManifest, DeimosEntratiFragmentVendorProductsManifest,
DeimosFishmongerVendorManifest, DeimosFishmongerVendorManifest,
@ -54,7 +48,6 @@ const rawVendorManifests: IRawVendorManifest[] = [
DuviriAcrithisVendorManifest, DuviriAcrithisVendorManifest,
EntratiLabsEntratiLabsCommisionsManifest, EntratiLabsEntratiLabsCommisionsManifest,
EntratiLabsEntratiLabVendorManifest, EntratiLabsEntratiLabVendorManifest,
GuildAdvertisementVendorManifest, // uses preprocessing
HubsIronwakeDondaVendorManifest, // uses preprocessing HubsIronwakeDondaVendorManifest, // uses preprocessing
HubsRailjackCrewMemberVendorManifest, HubsRailjackCrewMemberVendorManifest,
MaskSalesmanManifest, MaskSalesmanManifest,
@ -63,16 +56,16 @@ const rawVendorManifests: IRawVendorManifest[] = [
OstronPetVendorManifest, OstronPetVendorManifest,
OstronProspectorVendorManifest, OstronProspectorVendorManifest,
RadioLegionIntermission12VendorManifest, RadioLegionIntermission12VendorManifest,
SolarisDebtTokenVendorManifest,
SolarisDebtTokenVendorRepossessionsManifest, SolarisDebtTokenVendorRepossessionsManifest,
SolarisFishmongerVendorManifest, SolarisFishmongerVendorManifest,
SolarisProspectorVendorManifest, SolarisProspectorVendorManifest,
Temple1999VendorManifest,
TeshinHardModeVendorManifest, // uses preprocessing TeshinHardModeVendorManifest, // uses preprocessing
ZarimanCommisionsManifestArchimedean ZarimanCommisionsManifestArchimedean
]; ];
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> { interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleStart: number; cycleOffset?: number;
cycleDuration: number; cycleDuration: number;
} }
@ -80,30 +73,39 @@ const generatableVendors: IGeneratableVendorInfo[] = [
{ {
_id: { $oid: "67dadc30e4b6e0e5979c8d84" }, _id: { $oid: "67dadc30e4b6e0e5979c8d84" },
TypeName: "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest", TypeName: "/Lotus/Types/Game/VendorManifests/TheHex/InfestedLichWeaponVendorManifest",
PropertyTextHash: "77093DD05A8561A022DEC9A4B9BB4A56",
RandomSeedType: "VRST_WEAPON", RandomSeedType: "VRST_WEAPON",
RequiredGoalTag: "", RequiredGoalTag: "",
WeaponUpgradeValueAttenuationExponent: 2.25, WeaponUpgradeValueAttenuationExponent: 2.25,
cycleStart: 1740960000_000, cycleOffset: 1740960000_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
}, },
{ {
_id: { $oid: "60ad3b6ec96976e97d227e19" }, _id: { $oid: "60ad3b6ec96976e97d227e19" },
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/PerrinSequenceWeaponVendorManifest", TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/PerrinSequenceWeaponVendorManifest",
PropertyTextHash: "34F8CF1DFF745F0D67433A5EF0A03E70",
RandomSeedType: "VRST_WEAPON", RandomSeedType: "VRST_WEAPON",
WeaponUpgradeValueAttenuationExponent: 2.25, WeaponUpgradeValueAttenuationExponent: 2.25,
cycleStart: 1744934400_000, cycleOffset: 1744934400_000,
cycleDuration: 4 * unixTimesInMs.day cycleDuration: 4 * unixTimesInMs.day
},
{
_id: { $oid: "5be4a159b144f3cdf1c22efa" },
TypeName: "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.hour
},
{
_id: { $oid: "61ba123467e5d37975aeeb03" },
TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
RandomSeedType: "VRST_FLAVOUR_TEXT",
cycleDuration: unixTimesInMs.week
} }
// { // {
// _id: { $oid: "5dbb4c41e966f7886c3ce939" }, // _id: { $oid: "5dbb4c41e966f7886c3ce939" },
// TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest", // TypeName: "/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest"
// PropertyTextHash: "62B64A8065B7C0FA345895D4BC234621"
// } // }
]; ];
export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPreprocessed | undefined => { export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo.TypeName == typeName) { if (vendorManifest.VendorInfo.TypeName == typeName) {
return preprocessVendorManifest(vendorManifest); return preprocessVendorManifest(vendorManifest);
@ -117,7 +119,7 @@ export const getVendorManifestByTypeName = (typeName: string): IVendorManifestPr
return undefined; return undefined;
}; };
export const getVendorManifestByOid = (oid: string): IVendorManifestPreprocessed | undefined => { export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) { for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo._id.$oid == oid) { if (vendorManifest.VendorInfo._id.$oid == oid) {
return preprocessVendorManifest(vendorManifest); return preprocessVendorManifest(vendorManifest);
@ -131,29 +133,20 @@ export const getVendorManifestByOid = (oid: string): IVendorManifestPreprocessed
return undefined; return undefined;
}; };
const preprocessVendorManifest = (originalManifest: IRawVendorManifest): IVendorManifestPreprocessed => { const preprocessVendorManifest = (originalManifest: IVendorManifest): IVendorManifest => {
if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) { if (Date.now() >= parseInt(originalManifest.VendorInfo.Expiry.$date.$numberLong)) {
const manifest = structuredClone(originalManifest); const manifest = structuredClone(originalManifest);
const info = manifest.VendorInfo; const info = manifest.VendorInfo;
refreshExpiry(info.Expiry); refreshExpiry(info.Expiry);
for (const offer of info.ItemManifest) { for (const offer of info.ItemManifest) {
const iteration = refreshExpiry(offer.Expiry); refreshExpiry(offer.Expiry);
if (offer.ItemPrices) {
for (const price of offer.ItemPrices) {
if (typeof price.ItemType != "string") {
const itemSeed = parseInt(offer.Id.$oid.substring(16), 16);
const rng = new CRng(mixSeeds(itemSeed, iteration));
price.ItemType = rng.randomElement(price.ItemType);
} }
return manifest;
} }
} return originalManifest;
}
return manifest as IVendorManifestPreprocessed;
}
return originalManifest as IVendorManifestPreprocessed;
}; };
const refreshExpiry = (expiry: IMongoDate): number => { const refreshExpiry = (expiry: IMongoDate): void => {
const period = parseInt(expiry.$date.$numberLong); const period = parseInt(expiry.$date.$numberLong);
if (Date.now() >= period) { if (Date.now() >= period) {
const epoch = 1734307200_000; // Monday (for weekly schedules) const epoch = 1734307200_000; // Monday (for weekly schedules)
@ -161,65 +154,136 @@ const refreshExpiry = (expiry: IMongoDate): number => {
const start = epoch + iteration * period; const start = epoch + iteration * period;
const end = start + period; const end = start + period;
expiry.$date.$numberLong = end.toString(); expiry.$date.$numberLong = end.toString();
return iteration;
} }
return 0;
}; };
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifestPreprocessed => { const toRange = (value: IRange | number): IRange => {
const EPOCH = vendorInfo.cycleStart; if (typeof value == "number") {
return { minValue: value, maxValue: value };
}
return value;
};
const vendorInfoCache: Record<string, IVendorInfo> = {};
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
if (!(vendorInfo.TypeName in vendorInfoCache)) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
vendorInfoCache[vendorInfo.TypeName] = {
...clientVendorInfo,
ItemManifest: [],
Expiry: { $date: { $numberLong: "0" } }
};
}
const processed = vendorInfoCache[vendorInfo.TypeName];
if (Date.now() >= parseInt(processed.Expiry.$date.$numberLong)) {
// Remove expired offers
for (let i = 0; i != processed.ItemManifest.length; ) {
if (Date.now() >= parseInt(processed.ItemManifest[i].Expiry.$date.$numberLong)) {
processed.ItemManifest.splice(i, 1);
} else {
++i;
}
}
// Add new offers
const vendorSeed = parseInt(vendorInfo._id.$oid.substring(16), 16);
const cycleOffset = vendorInfo.cycleOffset ?? 1734307200_000;
const cycleDuration = vendorInfo.cycleDuration;
const cycleIndex = Math.trunc((Date.now() - cycleOffset) / cycleDuration);
const rng = new CRng(mixSeeds(vendorSeed, cycleIndex));
const manifest = ExportVendors[vendorInfo.TypeName]; const manifest = ExportVendors[vendorInfo.TypeName];
const offersToAdd = [];
if (manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue) {
const numItemsTarget = rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue);
while (processed.ItemManifest.length + offersToAdd.length < numItemsTarget) {
// TODO: Consider per-bin item limits
// TODO: Consider item probability weightings
offersToAdd.push(rng.randomElement(manifest.items));
}
} else {
let binThisCycle; let binThisCycle;
if (manifest.isOneBinPerCycle) { if (manifest.isOneBinPerCycle) {
const cycleDuration = vendorInfo.cycleDuration; // manifest.items[0].durationHours! * 3600_000;
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now. binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
} }
const items: IItemManifestPreprocessed[] = []; for (const rawItem of manifest.items) {
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER; if (!manifest.isOneBinPerCycle || rawItem.bin == binThisCycle) {
for (let i = 0; i != manifest.items.length; ++i) { offersToAdd.push(rawItem);
const rawItem = manifest.items[i];
if (manifest.isOneBinPerCycle && rawItem.bin != binThisCycle) {
continue;
} }
const cycleDuration = vendorInfo.cycleDuration; // rawItem.durationHours! * 3600_000;
const cycleIndex = Math.trunc((Date.now() - EPOCH) / cycleDuration);
const cycleStart = EPOCH + cycleIndex * cycleDuration;
const cycleEnd = cycleStart + cycleDuration;
if (soonestOfferExpiry > cycleEnd) {
soonestOfferExpiry = cycleEnd;
} }
const rng = new CRng(cycleIndex);
rng.churnSeed(i); // For most vendors, the offers seem to roughly be in reverse order from the manifest. Coda weapons are an odd exception.
/*for (let j = -1; j != rawItem.duplicates; ++j)*/ { if (!manifest.isOneBinPerCycle) {
const item: IItemManifestPreprocessed = { offersToAdd.reverse();
}
}
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
for (const rawItem of offersToAdd) {
const durationHoursRange = toRange(rawItem.durationHours);
const expiry =
cycleStart +
rng.randomInt(durationHoursRange.minValue, durationHoursRange.maxValue) * unixTimesInMs.hour;
const item: IItemManifest = {
StoreItem: rawItem.storeItem, StoreItem: rawItem.storeItem,
ItemPrices: rawItem.itemPrices!.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })), ItemPrices: rawItem.itemPrices?.map(itemPrice => ({ ...itemPrice, ProductCategory: "MiscItems" })),
Bin: "BIN_" + rawItem.bin, Bin: "BIN_" + rawItem.bin,
QuantityMultiplier: 1, QuantityMultiplier: 1,
Expiry: { $date: { $numberLong: cycleEnd.toString() } }, Expiry: { $date: { $numberLong: expiry.toString() } },
AllowMultipurchase: false, AllowMultipurchase: false,
Id: { Id: {
$oid: $oid:
i.toString(16).padStart(8, "0") + ((cycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") +
vendorInfo._id.$oid.substring(8, 16) + vendorInfo._id.$oid.substring(8, 16) +
rng.randomInt(0, 0xffffffff).toString(16).padStart(8, "0") rng.randomInt(0, 0xffff).toString(16).padStart(4, "0") +
rng.randomInt(0, 0xffff).toString(16).padStart(4, "0")
} }
}; };
if (rawItem.numRandomItemPrices) {
item.ItemPrices = [];
for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
let itemPrice: { type: string; count: IRange };
do {
itemPrice = rng.randomElement(manifest.randomItemPricesPerBin![rawItem.bin]);
} while (item.ItemPrices.find(x => x.ItemType == itemPrice.type));
item.ItemPrices.push({
ItemType: itemPrice.type,
ItemCount: rng.randomInt(itemPrice.count.minValue, itemPrice.count.maxValue),
ProductCategory: "MiscItems"
});
}
}
if (rawItem.credits) {
const value =
typeof rawItem.credits == "number"
? rawItem.credits
: rng.randomInt(
rawItem.credits.minValue / rawItem.credits.step,
rawItem.credits.maxValue / rawItem.credits.step
) * rawItem.credits.step;
item.RegularPrice = [value, value];
}
if (vendorInfo.RandomSeedType) { if (vendorInfo.RandomSeedType) {
item.LocTagRandSeed = item.LocTagRandSeed = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
(BigInt(rng.randomInt(0, 0xffffffff)) << 32n) | BigInt(rng.randomInt(0, 0xffffffff)); if (vendorInfo.RandomSeedType == "VRST_WEAPON") {
} const highDword = (rng.randomInt(0, 0xffff) << 16) | rng.randomInt(0, 0xffff);
items.push(item); item.LocTagRandSeed = (BigInt(highDword) << 32n) | BigInt(item.LocTagRandSeed);
} }
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars processed.ItemManifest.push(item);
const { cycleStart, cycleDuration, ...clientVendorInfo } = vendorInfo; }
// Update vendor expiry
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
for (const offer of processed.ItemManifest) {
const offerExpiry = parseInt(offer.Expiry.$date.$numberLong);
if (soonestOfferExpiry > offerExpiry) {
soonestOfferExpiry = offerExpiry;
}
}
processed.Expiry.$date.$numberLong = soonestOfferExpiry.toString();
}
return { return {
VendorInfo: { VendorInfo: processed
...clientVendorInfo,
ItemManifest: items,
Expiry: { $date: { $numberLong: soonestOfferExpiry.toString() } }
}
}; };
}; };

View File

@ -1,15 +1,18 @@
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json"; import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
import { buildConfig } from "@/src/services/buildConfigService"; import { buildConfig } from "@/src/services/buildConfigService";
import { unixTimesInMs } from "@/src/constants/timeConstants"; import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService"; import { config } from "@/src/services/configService";
import { CRng } from "@/src/services/rngService"; import { CRng } from "@/src/services/rngService";
import { eMissionType, ExportNightwave, ExportRegions } from "warframe-public-export-plus"; import { eMissionType, ExportNightwave, ExportRegions, IRegion } from "warframe-public-export-plus";
import { import {
ICalendarDay, ICalendarDay,
ICalendarEvent,
ICalendarSeason, ICalendarSeason,
ILiteSortie, ILiteSortie,
ISeasonChallenge, ISeasonChallenge,
ISortie, ISortieMission,
IWorldState IWorldState
} from "../types/worldStateTypes"; } from "../types/worldStateTypes";
@ -48,21 +51,27 @@ const sortieBossToFaction: Record<string, string> = {
SORTIE_BOSS_PHORID: "FC_INFESTATION", SORTIE_BOSS_PHORID: "FC_INFESTATION",
SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION", SORTIE_BOSS_LEPHANTIS: "FC_INFESTATION",
SORTIE_BOSS_INFALAD: "FC_INFESTATION", SORTIE_BOSS_INFALAD: "FC_INFESTATION",
SORTIE_BOSS_CORRUPTED_VOR: "FC_CORRUPTED" SORTIE_BOSS_CORRUPTED_VOR: "FC_OROKIN"
}; };
const sortieFactionToSystemIndexes: Record<string, number[]> = { const sortieFactionToSystemIndexes: Record<string, number[]> = {
FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18], FC_GRINEER: [0, 2, 3, 5, 6, 9, 11, 18],
FC_CORPUS: [1, 4, 7, 8, 12, 15], FC_CORPUS: [1, 4, 7, 8, 12, 15],
FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15], FC_INFESTATION: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 15],
FC_CORRUPTED: [14] FC_OROKIN: [14]
}; };
const sortieFactionToFactionIndexes: Record<string, number[]> = { const sortieFactionToFactionIndexes: Record<string, number[]> = {
FC_GRINEER: [0], FC_GRINEER: [0],
FC_CORPUS: [1], FC_CORPUS: [1],
FC_INFESTATION: [0, 1, 2], FC_INFESTATION: [0, 1, 2],
FC_CORRUPTED: [3] FC_OROKIN: [3]
};
const sortieFactionToSpecialMissionTileset: Record<string, string> = {
FC_GRINEER: "GrineerGalleonTileset",
FC_CORPUS: "CorpusShipTileset",
FC_INFESTATION: "CorpusShipTileset"
}; };
const sortieBossNode: Record<string, string> = { const sortieBossNode: Record<string, string> = {
@ -174,7 +183,36 @@ const getSortieTime = (day: number): number => {
return dayStart + (isDst ? 16 : 17) * 3600000; return dayStart + (isDst ? 16 : 17) * 3600000;
}; };
const pushSortieIfRelevant = (out: ISortie[], day: number): void => { const pushSyndicateMissions = (
worldState: IWorldState,
day: number,
seed: number,
idSuffix: string,
syndicateTag: string
): void => {
const nodeOptions: string[] = [...syndicateMissions];
const rng = new CRng(seed);
const nodes: string[] = [];
for (let i = 0; i != 6; ++i) {
const index = rng.randomInt(0, nodeOptions.length - 1);
nodes.push(nodeOptions[index]);
nodeOptions.splice(index, 1);
}
const dayStart = getSortieTime(day);
const dayEnd = getSortieTime(day + 1);
worldState.SyndicateMissions.push({
_id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + idSuffix },
Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } },
Tag: syndicateTag,
Seed: seed,
Nodes: nodes
});
};
const pushSortieIfRelevant = (worldState: IWorldState, day: number): void => {
const dayStart = getSortieTime(day); const dayStart = getSortieTime(day);
if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) { if (!isBeforeNextExpectedWorldStateRefresh(dayStart)) {
return; return;
@ -184,7 +222,8 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
return; return;
} }
const rng = new CRng(day); const seed = new CRng(day).randomInt(0, 0xffff);
const rng = new CRng(seed);
const boss = rng.randomElement(sortieBosses); const boss = rng.randomElement(sortieBosses);
@ -228,12 +267,7 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
if ( if (
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) && sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) && sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
value.name.indexOf("Archwing") == -1 && key in sortieTilesets
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
value.missionIndex != 5 && // Exclude MT_CAPTURE
value.missionIndex != 21 && // Exclude MT_PURIFY
value.missionIndex != 23 && // Exclude MT_JUNCTION
value.missionIndex <= 28
) { ) {
if (!availableMissionIndexes.includes(value.missionIndex)) { if (!availableMissionIndexes.includes(value.missionIndex)) {
availableMissionIndexes.push(value.missionIndex); availableMissionIndexes.push(value.missionIndex);
@ -242,32 +276,57 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
} }
} }
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = []; const specialMissionTypes = [1, 3, 5, 9];
if (!(sortieBossToFaction[boss] in sortieFactionToSpecialMissionTileset)) {
for (const missionType of specialMissionTypes) {
const i = availableMissionIndexes.indexOf(missionType);
if (i != -1) {
availableMissionIndexes.splice(i, 1);
}
}
}
const selectedNodes: ISortieMission[] = [];
const missionTypes = new Set(); const missionTypes = new Set();
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const randomIndex = rng.randomInt(0, nodes.length - 1); let randomIndex;
const node = nodes[randomIndex]; let node;
let missionIndex = ExportRegions[node].missionIndex; let missionIndex;
do {
randomIndex = rng.randomInt(0, nodes.length - 1);
node = nodes[randomIndex];
if ( missionIndex = ExportRegions[node].missionIndex;
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions if (missionIndex != 28) {
missionIndex != 28 &&
rng.randomInt(0, 2) == 2
) {
missionIndex = rng.randomElement(availableMissionIndexes); missionIndex = rng.randomElement(availableMissionIndexes);
} }
} while (
specialMissionTypes.indexOf(missionIndex) != -1 &&
sortieTilesets[node as keyof typeof sortieTilesets] !=
sortieFactionToSpecialMissionTileset[sortieBossToFaction[boss]]
);
if (i == 2 && rng.randomInt(0, 2) == 2) { if (i == 2 && rng.randomInt(0, 2) == 2) {
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY"); const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
const modifierType = rng.randomElement(filteredModifiers); const modifierType = rng.randomElement(filteredModifiers);
if (boss == "SORTIE_BOSS_PHORID") { if (boss == "SORTIE_BOSS_PHORID") {
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node }); selectedNodes.push({
missionType: "MT_ASSASSINATION",
modifierType,
node,
tileset: sortieTilesets[node as keyof typeof sortieTilesets]
});
nodes.splice(randomIndex, 1); nodes.splice(randomIndex, 1);
continue; continue;
} else if (sortieBossNode[boss]) { } else if (sortieBossNode[boss]) {
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] }); selectedNodes.push({
missionType: "MT_ASSASSINATION",
modifierType,
node: sortieBossNode[boss],
tileset: sortieTilesets[sortieBossNode[boss] as keyof typeof sortieTilesets]
});
continue; continue;
} }
} }
@ -286,20 +345,32 @@ const pushSortieIfRelevant = (out: ISortie[], day: number): void => {
const modifierType = rng.randomElement(filteredModifiers); const modifierType = rng.randomElement(filteredModifiers);
selectedNodes.push({ missionType, modifierType, node }); selectedNodes.push({
missionType,
modifierType,
node,
tileset: sortieTilesets[node as keyof typeof sortieTilesets]
});
nodes.splice(randomIndex, 1); nodes.splice(randomIndex, 1);
missionTypes.add(missionType); missionTypes.add(missionType);
} }
out.push({ worldState.Sorties.push({
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" }, _id: { $oid: ((dayStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "d4d932c97c0a3acd" },
Activation: { $date: { $numberLong: dayStart.toString() } }, Activation: { $date: { $numberLong: dayStart.toString() } },
Expiry: { $date: { $numberLong: dayEnd.toString() } }, Expiry: { $date: { $numberLong: dayEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
Seed: day, Seed: seed,
Boss: boss, Boss: boss,
Variants: selectedNodes Variants: selectedNodes
}); });
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48049", "ArbitersSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804a", "CephalonSudaSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4804e", "NewLokaSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48050", "PerrinSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa4805e", "RedVeilSyndicate");
pushSyndicateMissions(worldState, day, rng.randomInt(0, 0xffff), "ba6f84724fa48061", "SteelMeridianSyndicate");
}; };
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x => const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
@ -309,7 +380,7 @@ const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => { const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
const dayStart = EPOCH + day * 86400000; const dayStart = EPOCH + day * 86400000;
const dayEnd = EPOCH + (day + 3) * 86400000; const dayEnd = EPOCH + (day + 3) * 86400000;
const rng = new CRng(day); const rng = new CRng(new CRng(day).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") }, _id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
Daily: true, Daily: true,
@ -329,7 +400,7 @@ const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge =>
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new CRng(challengeId); const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
@ -346,7 +417,7 @@ const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChalleng
const weekStart = EPOCH + week * 604800000; const weekStart = EPOCH + week * 604800000;
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
const challengeId = week * 7 + id; const challengeId = week * 7 + id;
const rng = new CRng(challengeId); const rng = new CRng(new CRng(challengeId).randomInt(0, 0xffff));
return { return {
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") }, _id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
@ -398,8 +469,8 @@ const birthdays: number[] = [
const getCalendarSeason = (week: number): ICalendarSeason => { const getCalendarSeason = (week: number): ICalendarSeason => {
const seasonIndex = week % 4; const seasonIndex = week % 4;
const seasonDay1 = seasonIndex * 90 + 1; const seasonDay1 = [1, 91, 182, 274][seasonIndex];
const seasonDay91 = seasonIndex * 90 + 91; const seasonDay91 = seasonDay1 + 90;
const eventDays: ICalendarDay[] = []; const eventDays: ICalendarDay[] = [];
for (const day of birthdays) { for (const day of birthdays) {
if (day < seasonDay1) { if (day < seasonDay1) {
@ -411,7 +482,7 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
//logger.debug(`birthday on day ${day}`); //logger.debug(`birthday on day ${day}`);
eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0 eventDays.push({ day, events: [] }); // This is how CET_PLOT looks in worldState as of around 38.5.0
} }
const rng = new CRng(week); const rng = new CRng(new CRng(week).randomInt(0, 0xffff));
const challenges = [ const challenges = [
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy", "/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesEasy",
"/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium", "/Lotus/Types/Challenges/Calendar1999/CalendarKillEnemiesMedium",
@ -458,8 +529,12 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
challengeDay = rng.randomInt(chunkDay1, chunkDay13); challengeDay = rng.randomInt(chunkDay1, chunkDay13);
} while (birthdays.indexOf(challengeDay) != -1); } while (birthdays.indexOf(challengeDay) != -1);
const challengeIndex = rng.randomInt(0, challenges.length - 1); let challengeIndex;
const challenge = challenges[challengeIndex]; let challenge;
do {
challengeIndex = rng.randomInt(0, challenges.length - 1);
challenge = challenges[challengeIndex];
} while (i < 2 && !challenge.endsWith("Easy")); // First 2 challenges should be easy
challenges.splice(challengeIndex, 1); challenges.splice(challengeIndex, 1);
//logger.debug(`challenge on day ${challengeDay}`); //logger.debug(`challenge on day ${challengeDay}`);
@ -506,69 +581,100 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
"/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalViolet" "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalViolet"
]; ];
for (let i = 0; i != rewardRanges.length - 1; ++i) { for (let i = 0; i != rewardRanges.length - 1; ++i) {
const events: ICalendarEvent[] = [];
for (let j = 0; j != 2; ++j) {
const rewardIndex = rng.randomInt(0, rewards.length - 1); const rewardIndex = rng.randomInt(0, rewards.length - 1);
const reward = rewards[rewardIndex]; events.push({ type: "CET_REWARD", reward: rewards[rewardIndex] });
rewards.splice(rewardIndex, 1); rewards.splice(rewardIndex, 1);
}
//logger.debug(`trying to fit a reward between day ${rewardRanges[i]} and ${rewardRanges[i + 1]}`); //logger.debug(`trying to fit rewards between day ${rewardRanges[i]} and ${rewardRanges[i + 1]}`);
let day: number; let day: number;
do { do {
day = rng.randomInt(rewardRanges[i] + 1, rewardRanges[i + 1] - 1); day = rng.randomInt(rewardRanges[i] + 1, rewardRanges[i + 1] - 1);
} while (eventDays.find(x => x.day == day)); } while (eventDays.find(x => x.day == day));
eventDays.push({ day, events: [{ type: "CET_REWARD", reward }] }); eventDays.push({ day, events });
} }
const upgrades = [ const upgradesByHexMember = [
"/Lotus/Upgrades/Calendar/MeleeCritChance", [
"/Lotus/Upgrades/Calendar/MeleeAttackSpeed", "/Lotus/Upgrades/Calendar/AttackAndMovementSpeedOnCritMelee",
"/Lotus/Upgrades/Calendar/EnergyOrbToAbilityRange", "/Lotus/Upgrades/Calendar/ElectricalDamageOnBulletJump",
"/Lotus/Upgrades/Calendar/ElectricDamagePerDistance",
"/Lotus/Upgrades/Calendar/ElectricStatusDamageAndChance",
"/Lotus/Upgrades/Calendar/OvershieldCap",
"/Lotus/Upgrades/Calendar/SpeedBuffsWhenAirborne"
],
[
"/Lotus/Upgrades/Calendar/AbilityStrength", "/Lotus/Upgrades/Calendar/AbilityStrength",
"/Lotus/Upgrades/Calendar/EnergyOrbToAbilityRange",
"/Lotus/Upgrades/Calendar/MagnetStatusPull",
"/Lotus/Upgrades/Calendar/MagnitizeWithinRangeEveryXCasts",
"/Lotus/Upgrades/Calendar/PowerStrengthAndEfficiencyPerEnergySpent",
"/Lotus/Upgrades/Calendar/SharedFreeAbilityEveryXCasts"
],
[
"/Lotus/Upgrades/Calendar/EnergyWavesOnCombo",
"/Lotus/Upgrades/Calendar/FinisherChancePerComboMultiplier",
"/Lotus/Upgrades/Calendar/MeleeAttackSpeed",
"/Lotus/Upgrades/Calendar/MeleeCritChance",
"/Lotus/Upgrades/Calendar/MeleeSlideFowardMomentumOnEnemyHit",
"/Lotus/Upgrades/Calendar/RadialJavelinOnHeavy"
],
[
"/Lotus/Upgrades/Calendar/Armor", "/Lotus/Upgrades/Calendar/Armor",
"/Lotus/Upgrades/Calendar/RadiationProcOnTakeDamage", "/Lotus/Upgrades/Calendar/CloneActiveCompanionForEnergySpent",
"/Lotus/Upgrades/Calendar/CompanionDamage", "/Lotus/Upgrades/Calendar/CompanionDamage",
"/Lotus/Upgrades/Calendar/CompanionsBuffNearbyPlayer",
"/Lotus/Upgrades/Calendar/CompanionsRadiationChance",
"/Lotus/Upgrades/Calendar/RadiationProcOnTakeDamage",
"/Lotus/Upgrades/Calendar/ReviveEnemyAsSpectreOnKill"
],
[
"/Lotus/Upgrades/Calendar/EnergyOrbsGrantShield",
"/Lotus/Upgrades/Calendar/EnergyRestoration",
"/Lotus/Upgrades/Calendar/ExplodingHealthOrbs",
"/Lotus/Upgrades/Calendar/GenerateOmniOrbsOnWeakKill",
"/Lotus/Upgrades/Calendar/HealingEffects",
"/Lotus/Upgrades/Calendar/OrbsDuplicateOnPickup"
],
[
"/Lotus/Upgrades/Calendar/BlastEveryXShots",
"/Lotus/Upgrades/Calendar/GasChanceToPrimaryAndSecondary", "/Lotus/Upgrades/Calendar/GasChanceToPrimaryAndSecondary",
"/Lotus/Upgrades/Calendar/GuidingMissilesChance",
"/Lotus/Upgrades/Calendar/MagazineCapacity", "/Lotus/Upgrades/Calendar/MagazineCapacity",
"/Lotus/Upgrades/Calendar/PunchToPrimary", "/Lotus/Upgrades/Calendar/PunchToPrimary",
"/Lotus/Upgrades/Calendar/HealingEffects",
"/Lotus/Upgrades/Calendar/EnergyRestoration",
"/Lotus/Upgrades/Calendar/OvershieldCap",
"/Lotus/Upgrades/Calendar/ElectricStatusDamageAndChance",
"/Lotus/Upgrades/Calendar/FinisherChancePerComboMultiplier",
"/Lotus/Upgrades/Calendar/MagnetStatusPull",
"/Lotus/Upgrades/Calendar/CompanionsBuffNearbyPlayer",
"/Lotus/Upgrades/Calendar/StatusChancePerAmmoSpent",
"/Lotus/Upgrades/Calendar/OrbsDuplicateOnPickup",
"/Lotus/Upgrades/Calendar/AttackAndMovementSpeedOnCritMelee",
"/Lotus/Upgrades/Calendar/RadialJavelinOnHeavy",
"/Lotus/Upgrades/Calendar/MagnitizeWithinRangeEveryXCasts",
"/Lotus/Upgrades/Calendar/CompanionsRadiationChance",
"/Lotus/Upgrades/Calendar/BlastEveryXShots",
"/Lotus/Upgrades/Calendar/GenerateOmniOrbsOnWeakKill",
"/Lotus/Upgrades/Calendar/ElectricDamagePerDistance",
"/Lotus/Upgrades/Calendar/MeleeSlideFowardMomentumOnEnemyHit",
"/Lotus/Upgrades/Calendar/SharedFreeAbilityEveryXCasts",
"/Lotus/Upgrades/Calendar/ReviveEnemyAsSpectreOnKill",
"/Lotus/Upgrades/Calendar/RefundBulletOnStatusProc", "/Lotus/Upgrades/Calendar/RefundBulletOnStatusProc",
"/Lotus/Upgrades/Calendar/ExplodingHealthOrbs", "/Lotus/Upgrades/Calendar/StatusChancePerAmmoSpent"
"/Lotus/Upgrades/Calendar/SpeedBuffsWhenAirborne", ]
"/Lotus/Upgrades/Calendar/EnergyWavesOnCombo",
"/Lotus/Upgrades/Calendar/PowerStrengthAndEfficiencyPerEnergySpent",
"/Lotus/Upgrades/Calendar/CloneActiveCompanionForEnergySpent",
"/Lotus/Upgrades/Calendar/GuidingMissilesChance",
"/Lotus/Upgrades/Calendar/EnergyOrbsGrantShield",
"/Lotus/Upgrades/Calendar/ElectricalDamageOnBulletJump"
]; ];
for (let i = 0; i != upgradeRanges.length - 1; ++i) { for (let i = 0; i != upgradeRanges.length - 1; ++i) {
const upgradeIndex = rng.randomInt(0, upgrades.length - 1); // Pick 3 unique hex members
const upgrade = upgrades[upgradeIndex]; const hexMembersPickedForThisDay: number[] = [];
upgrades.splice(upgradeIndex, 1); for (let j = 0; j != 3; ++j) {
let hexMemberIndex: number;
do {
hexMemberIndex = rng.randomInt(0, upgradesByHexMember.length - 1);
} while (hexMembersPickedForThisDay.indexOf(hexMemberIndex) != -1);
hexMembersPickedForThisDay.push(hexMemberIndex);
}
hexMembersPickedForThisDay.sort(); // Always present them in the same order
//logger.debug(`trying to fit an upgrade between day ${upgradeRanges[i]} and ${upgradeRanges[i + 1]}`); // For each hex member, pick an upgrade that was not yet picked this season.
const events: ICalendarEvent[] = [];
for (const hexMemberIndex of hexMembersPickedForThisDay) {
const upgrades = upgradesByHexMember[hexMemberIndex];
const upgradeIndex = rng.randomInt(0, upgrades.length - 1);
events.push({ type: "CET_UPGRADE", upgrade: upgrades[upgradeIndex] });
upgrades.splice(upgradeIndex, 1);
}
//logger.debug(`trying to fit upgrades between day ${upgradeRanges[i]} and ${upgradeRanges[i + 1]}`);
let day: number; let day: number;
do { do {
day = rng.randomInt(upgradeRanges[i] + 1, upgradeRanges[i + 1] - 1); day = rng.randomInt(upgradeRanges[i] + 1, upgradeRanges[i + 1] - 1);
} while (eventDays.find(x => x.day == day)); } while (eventDays.find(x => x.day == day));
eventDays.push({ day, events: [{ type: "CET_UPGRADE", upgrade }] }); eventDays.push({ day, events });
} }
eventDays.sort((a, b) => a.day - b.day); eventDays.sort((a, b) => a.day - b.day);
@ -579,7 +685,7 @@ const getCalendarSeason = (week: number): ICalendarSeason => {
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Days: eventDays, Days: eventDays,
Season: ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][seasonIndex], Season: (["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"] as const)[seasonIndex],
YearIteration: Math.trunc(week / 4), YearIteration: Math.trunc(week / 4),
Version: 19, Version: 19,
UpgradeAvaliabilityRequirements: ["/Lotus/Upgrades/Calendar/1999UpgradeApplicationRequirement"] UpgradeAvaliabilityRequirements: ["/Lotus/Upgrades/Calendar/1999UpgradeApplicationRequirement"]
@ -645,7 +751,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
// Elite Sanctuary Onslaught cycling every week // Elite Sanctuary Onslaught cycling every week
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = new CRng(week).randomInt(0, 0xffff);
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation // Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
let bountyCycle = Math.trunc(Date.now() / 9000000); let bountyCycle = Math.trunc(Date.now() / 9000000);
@ -654,7 +760,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const bountyCycleStart = bountyCycle * 9000000; const bountyCycleStart = bountyCycle * 9000000;
bountyCycleEnd = bountyCycleStart + 9000000; bountyCycleEnd = bountyCycleStart + 9000000;
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" }, _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000029" },
Activation: { $date: { $numberLong: bountyCycleStart.toString() } }, Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
Tag: "ZarimanSyndicate", Tag: "ZarimanSyndicate",
@ -662,7 +768,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Nodes: [] Nodes: []
}); });
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" }, _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000004" },
Activation: { $date: { $numberLong: bountyCycleStart.toString() } }, Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
Tag: "EntratiLabSyndicate", Tag: "EntratiLabSyndicate",
@ -670,7 +776,7 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Nodes: [] Nodes: []
}); });
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" }, _id: { $oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000006" },
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "HexSyndicate", Tag: "HexSyndicate",
@ -684,14 +790,18 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
// TODO: xpAmounts need to be calculated based on the jobType somehow? // TODO: xpAmounts need to be calculated based on the jobType somehow?
const seed = new CRng(bountyCycle).randomInt(0, 0xffff);
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000008" }, _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
},
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "CetusSyndicate", Tag: "CetusSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -755,13 +865,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000025" }, _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
},
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "SolarisSyndicate", Tag: "SolarisSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -825,13 +937,15 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
} }
{ {
const rng = new CRng(bountyCycle); const rng = new CRng(seed);
worldState.SyndicateMissions.push({ worldState.SyndicateMissions.push({
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000002" }, _id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
},
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } }, Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } }, Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
Tag: "EntratiSyndicate", Tag: "EntratiSyndicate",
Seed: bountyCycle, Seed: seed,
Nodes: [], Nodes: [],
Jobs: [ Jobs: [
{ {
@ -952,9 +1066,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
}); });
} }
// Sortie cycling every day // Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
pushSortieIfRelevant(worldState.Sorties, day - 1); pushSortieIfRelevant(worldState, day - 1);
pushSortieIfRelevant(worldState.Sorties, day); pushSortieIfRelevant(worldState, day);
// Archon Hunt cycling every week // Archon Hunt cycling every week
worldState.LiteSorties.push(getLiteSortie(week)); worldState.LiteSorties.push(getLiteSortie(week));
@ -1047,14 +1161,15 @@ export const getLiteSortie = (week: number): ILiteSortie => {
value.systemIndex === systemIndex && value.systemIndex === systemIndex &&
value.factionIndex !== undefined && value.factionIndex !== undefined &&
value.factionIndex < 2 && value.factionIndex < 2 &&
value.name.indexOf("Archwing") == -1 && !isArchwingMission(value) &&
value.missionIndex != 0 // Exclude MT_ASSASSINATION value.missionIndex != 0 // Exclude MT_ASSASSINATION
) { ) {
nodes.push(key); nodes.push(key);
} }
} }
const rng = new CRng(week); const seed = new CRng(week).randomInt(0, 0xffff);
const rng = new CRng(seed);
const firstNodeIndex = rng.randomInt(0, nodes.length - 1); const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
const firstNode = nodes[firstNodeIndex]; const firstNode = nodes[firstNodeIndex];
nodes.splice(firstNodeIndex, 1); nodes.splice(firstNodeIndex, 1);
@ -1063,12 +1178,12 @@ export const getLiteSortie = (week: number): ILiteSortie => {
const weekEnd = weekStart + 604800000; const weekEnd = weekStart + 604800000;
return { return {
_id: { _id: {
$oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c" $oid: ((weekStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "5e23a244740a190c"
}, },
Activation: { $date: { $numberLong: weekStart.toString() } }, Activation: { $date: { $numberLong: weekStart.toString() } },
Expiry: { $date: { $numberLong: weekEnd.toString() } }, Expiry: { $date: { $numberLong: weekEnd.toString() } },
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards", Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
Seed: week, Seed: seed,
Boss: boss, Boss: boss,
Missions: [ Missions: [
{ {
@ -1098,3 +1213,14 @@ export const getLiteSortie = (week: number): ILiteSortie => {
] ]
}; };
}; };
export const isArchwingMission = (node: IRegion): boolean => {
if (node.name.indexOf("Archwing") != -1) {
return true;
}
// SettlementNode10
if (node.missionIndex == 25) {
return true;
}
return false;
};

View File

@ -11,6 +11,7 @@ export interface IGuildClient {
Members: IGuildMemberClient[]; Members: IGuildMemberClient[];
Ranks: IGuildRank[]; Ranks: IGuildRank[];
Tier: number; Tier: number;
Emblem?: boolean;
Vault: IGuildVault; Vault: IGuildVault;
ActiveDojoColorResearch: string; ActiveDojoColorResearch: string;
Class: number; Class: number;
@ -206,6 +207,7 @@ export interface IDojoDecoClient {
Type: string; Type: string;
Pos: number[]; Pos: number[];
Rot: number[]; Rot: number[];
Scale?: number;
Name?: string; // for teleporters Name?: string; // for teleporters
Sockets?: number; Sockets?: number;
RegularCredits?: number; RegularCredits?: number;

View File

@ -134,7 +134,7 @@ export const equipmentKeys = [
export type TEquipmentKey = (typeof equipmentKeys)[number]; export type TEquipmentKey = (typeof equipmentKeys)[number];
export interface IDuviriInfo { export interface IDuviriInfo {
Seed: number; Seed: bigint;
NumCompletions: number; NumCompletions: number;
} }
@ -202,7 +202,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
Mailbox?: IMailboxClient; Mailbox?: IMailboxClient;
SubscribedToEmails: number; SubscribedToEmails: number;
Created: IMongoDate; Created: IMongoDate;
RewardSeed: number | bigint; RewardSeed: bigint;
RegularCredits: number; RegularCredits: number;
PremiumCredits: number; PremiumCredits: number;
PremiumCreditsFree: number; PremiumCreditsFree: number;
@ -328,7 +328,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
BlessingCooldown?: IMongoDate; BlessingCooldown?: IMongoDate;
CrewShipRawSalvage: ITypeCount[]; CrewShipRawSalvage: ITypeCount[];
CrewMembers: ICrewMemberClient[]; CrewMembers: ICrewMemberClient[];
LotusCustomization: ILotusCustomization; LotusCustomization?: ILotusCustomization;
UseAdultOperatorLoadout?: boolean; UseAdultOperatorLoadout?: boolean;
NemesisAbandonedRewards: string[]; NemesisAbandonedRewards: string[];
LastInventorySync: IOid; LastInventorySync: IOid;
@ -353,7 +353,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
DeathSquadable: boolean; DeathSquadable: boolean;
EndlessXP?: IEndlessXpProgress[]; EndlessXP?: IEndlessXpProgress[];
DialogueHistory?: IDialogueHistoryClient; DialogueHistory?: IDialogueHistoryClient;
CalendarProgress: ICalendarProgress; CalendarProgress?: ICalendarProgress;
SongChallenges?: ISongChallenge[]; SongChallenges?: ISongChallenge[];
EntratiVaultCountLastPeriod?: number; EntratiVaultCountLastPeriod?: number;
EntratiVaultCountResetDate?: IMongoDate; EntratiVaultCountResetDate?: IMongoDate;
@ -368,6 +368,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
EchoesHexConquestActiveStickers?: string[]; EchoesHexConquestActiveStickers?: string[];
BrandedSuits?: IOid[]; BrandedSuits?: IOid[];
LockedWeaponGroup?: ILockedWeaponGroupClient; LockedWeaponGroup?: ILockedWeaponGroupClient;
HubNpcCustomizations?: IHubNpcCustomization[];
} }
export interface IAffiliation { export interface IAffiliation {
@ -844,7 +845,7 @@ export interface IMission extends IMissionDatabase {
} }
export interface INemesisBaseClient { export interface INemesisBaseClient {
fp: bigint; fp: bigint | number;
manifest: string; manifest: string;
KillingSuit: string; KillingSuit: string;
killingDamageType: number; killingDamageType: number;
@ -862,7 +863,8 @@ export interface INemesisBaseClient {
Weakened: boolean; Weakened: boolean;
} }
export interface INemesisBaseDatabase extends Omit<INemesisBaseClient, "d"> { export interface INemesisBaseDatabase extends Omit<INemesisBaseClient, "fp" | "d"> {
fp: bigint;
d: Date; d: Date;
} }
@ -876,7 +878,8 @@ export interface INemesisClient extends INemesisBaseClient {
LastEnc: number; LastEnc: number;
} }
export interface INemesisDatabase extends Omit<INemesisClient, "d"> { export interface INemesisDatabase extends Omit<INemesisClient, "fp" | "d"> {
fp: bigint;
d: Date; d: Date;
} }
@ -1129,13 +1132,13 @@ export interface IEndlessXpProgress {
} }
export interface IDialogueHistoryClient { export interface IDialogueHistoryClient {
YearIteration: number; YearIteration?: number;
Resets?: number; // added in 38.5.0 Resets?: number; // added in 38.5.0
Dialogues?: IDialogueClient[]; Dialogues?: IDialogueClient[];
} }
export interface IDialogueHistoryDatabase { export interface IDialogueHistoryDatabase {
YearIteration: number; YearIteration?: number;
Resets?: number; Resets?: number;
Dialogues?: IDialogueDatabase[]; Dialogues?: IDialogueDatabase[];
} }
@ -1192,17 +1195,18 @@ export interface IMarker {
z: number; z: number;
showInHud: boolean; showInHud: boolean;
} }
export interface ISeasonProgress { export interface ISeasonProgress {
SeasonType: "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL"; SeasonType: "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
LastCompletedDayIdx: number; LastCompletedDayIdx: number;
LastCompletedChallengeDayIdx: number; LastCompletedChallengeDayIdx: number;
ActivatedChallenges: unknown[]; ActivatedChallenges: string[];
} }
export interface ICalendarProgress { export interface ICalendarProgress {
Version: number; Version: number;
Iteration: number; Iteration: number;
YearProgress: { Upgrades: unknown[] }; YearProgress: { Upgrades: string[] };
SeasonProgress: ISeasonProgress; SeasonProgress: ISeasonProgress;
} }
@ -1228,3 +1232,9 @@ export interface ILockedWeaponGroupDatabase {
} }
export type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">; export type TPartialStartingGear = Pick<IInventoryClient, "LongGuns" | "Suits" | "Pistols" | "Melee">;
export interface IHubNpcCustomization {
Colors?: IColor;
Pattern: string;
Tag: string;
}

View File

@ -1,3 +1,5 @@
import { IAffiliationMods, IInventoryChanges } from "./purchaseTypes";
export const inventoryFields = ["RawUpgrades", "MiscItems", "Consumables", "Recipes"] as const; export const inventoryFields = ["RawUpgrades", "MiscItems", "Consumables", "Recipes"] as const;
export type IInventoryFieldType = (typeof inventoryFields)[number]; export type IInventoryFieldType = (typeof inventoryFields)[number];
@ -6,8 +8,27 @@ export interface IMissionReward {
TypeName?: string; TypeName?: string;
UpgradeLevel?: number; UpgradeLevel?: number;
ItemCount: number; ItemCount: number;
DailyCooldown?: boolean;
Rarity?: number;
TweetText?: string; TweetText?: string;
ProductCategory?: string; ProductCategory?: string;
FromEnemyCache?: boolean; FromEnemyCache?: boolean;
IsStrippedItem?: boolean; IsStrippedItem?: boolean;
} }
export interface IMissionCredits {
MissionCredits: number[];
CreditBonus: number[];
TotalCredits: number[];
DailyMissionBonus?: boolean;
}
export interface IMissionInventoryUpdateResponse extends Partial<IMissionCredits> {
ConquestCompletedMissionsCount?: number;
InventoryJson?: string;
MissionRewards?: IMissionReward[];
InventoryChanges?: IInventoryChanges;
FusionPoints?: number;
SyndicateXPItemReward?: number;
AffiliationMods?: IAffiliationMods[];
}

View File

@ -1,12 +1,12 @@
import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { import {
IApartment,
IRoom, IRoom,
IPlacedDecosDatabase, IPlacedDecosDatabase,
ITailorShop, ITailorShop,
ITailorShopDatabase, ITailorShopDatabase,
TBootLocation, TBootLocation,
IApartmentDatabase IApartmentDatabase,
IApartmentClient
} from "@/src/types/shipTypes"; } from "@/src/types/shipTypes";
import { Document, Model, Types } from "mongoose"; import { Document, Model, Types } from "mongoose";
@ -21,10 +21,10 @@ export interface IOrbiter {
BootLocation?: TBootLocation; BootLocation?: TBootLocation;
} }
export interface IPersonalRooms { export interface IPersonalRoomsClient {
ShipInteriorColors: IColor; ShipInteriorColors: IColor;
Ship: IOrbiter; Ship: IOrbiter;
Apartment: IApartment; Apartment: IApartmentClient;
TailorShop: ITailorShop; TailorShop: ITailorShop;
} }

View File

@ -39,6 +39,7 @@ export type IInventoryChanges = {
RegularCredits?: number; RegularCredits?: number;
PremiumCredits?: number; PremiumCredits?: number;
PremiumCreditsFree?: number; PremiumCreditsFree?: number;
FusionPoints?: number;
PrimeTokens?: number; PrimeTokens?: number;
InfestedFoundry?: IInfestedFoundryClient; InfestedFoundry?: IInfestedFoundryClient;
Drones?: IDroneClient[]; Drones?: IDroneClient[];
@ -102,6 +103,7 @@ export const slotNames = [
"WeaponBin", "WeaponBin",
"MechBin", "MechBin",
"PveBonusLoadoutBin", "PveBonusLoadoutBin",
"PvpBonusLoadoutBin",
"SentinelBin", "SentinelBin",
"SpaceSuitBin", "SpaceSuitBin",
"SpaceWeaponBin", "SpaceWeaponBin",

View File

@ -20,7 +20,8 @@ import {
IDiscoveredMarker, IDiscoveredMarker,
ILockedWeaponGroupClient, ILockedWeaponGroupClient,
ILoadOutPresets, ILoadOutPresets,
IInvasionProgressClient IInvasionProgressClient,
IWeaponSkinClient
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
import { IGroup } from "./loginTypes"; import { IGroup } from "./loginTypes";
@ -44,6 +45,7 @@ export type IMissionInventoryUpdateRequest = {
SyndicateId?: string; SyndicateId?: string;
SortieId?: string; SortieId?: string;
CalendarProgress?: { challenge: string }[];
SeasonChallengeCompletions?: ISeasonChallenge[]; SeasonChallengeCompletions?: ISeasonChallenge[];
AffiliationChanges?: IAffiliationChange[]; AffiliationChanges?: IAffiliationChange[];
crossPlaySetting?: string; crossPlaySetting?: string;
@ -100,6 +102,7 @@ export type IMissionInventoryUpdateRequest = {
}[]; }[];
CollectibleScans?: ICollectibleEntry[]; CollectibleScans?: ICollectibleEntry[];
Upgrades?: IUpgradeClient[]; // riven challenge progress Upgrades?: IUpgradeClient[]; // riven challenge progress
WeaponSkins?: IWeaponSkinClient[];
StrippedItems?: { StrippedItems?: {
DropTable: string; DropTable: string;
DROP_MOD?: number[]; DROP_MOD?: number[];
@ -125,6 +128,16 @@ export type IMissionInventoryUpdateRequest = {
wagerTier?: number; // the index wagerTier?: number; // the index
creditsFee?: number; // the index creditsFee?: number; // the index
InvasionProgress?: IInvasionProgressClient[]; InvasionProgress?: IInvasionProgressClient[];
ConquestMissionsCompleted?: number;
duviriSuitSelection?: string;
duviriPistolSelection?: string;
duviriLongGunSelection?: string;
duviriMeleeSelection?: string;
duviriCaveOffers?: {
Seed: number | bigint;
Warframes: string[];
Weapons: string[];
};
} & { } & {
[K in TEquipmentKey]?: IEquipmentClient[]; [K in TEquipmentKey]?: IEquipmentClient[];
}; };
@ -134,7 +147,7 @@ export interface IRewardInfo {
invasionId?: string; invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS"; invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
sortieId?: string; sortieId?: string;
sortieTag?: string; sortieTag?: "Mission1" | "Mission2" | "Final";
sortiePrereqs?: string[]; sortiePrereqs?: string[];
VaultsCracked?: number; // for Spy missions VaultsCracked?: number; // for Spy missions
rewardTier?: number; rewardTier?: number;
@ -145,12 +158,19 @@ export interface IRewardInfo {
lostTargetWave?: number; lostTargetWave?: number;
defenseTargetCount?: number; defenseTargetCount?: number;
NemesisAbandonedRewards?: string[]; NemesisAbandonedRewards?: string[];
NemesisHenchmenKills?: number;
NemesisHintProgress?: number;
EOM_AFK?: number; EOM_AFK?: number;
rewardQualifications?: string; // did a Survival for 5 minutes and this was "1" rewardQualifications?: string; // did a Survival for 5 minutes and this was "1"
PurgatoryRewardQualifications?: string; PurgatoryRewardQualifications?: string;
rewardSeed?: number | bigint; rewardSeed?: number | bigint;
periodicMissionTag?: string; periodicMissionTag?: string;
ConquestType?: string;
ConquestCompleted?: number;
ConquestEquipmentSuggestionsFulfilled?: number;
ConquestPersonalModifiersActive?: number;
ConquestStickersActive?: number;
ConquestHardModeActive?: number;
// for bounties, only EOM_AFK and node are given from above, plus: // for bounties, only EOM_AFK and node are given from above, plus:
JobTier?: number; JobTier?: number;
jobId?: string; jobId?: string;

View File

@ -6,7 +6,8 @@ import {
ICrewShipMembersClient, ICrewShipMembersClient,
ICrewShipWeapon, ICrewShipWeapon,
IFlavourItem, IFlavourItem,
ILoadoutConfigClient ILoadoutConfigClient,
ILotusCustomization
} from "./inventoryTypes/inventoryTypes"; } from "./inventoryTypes/inventoryTypes";
export interface ISaveLoadoutRequest { export interface ISaveLoadoutRequest {
@ -43,6 +44,7 @@ export interface ISaveLoadoutRequest {
EquippedEmotes: string[]; EquippedEmotes: string[];
UseAdultOperatorLoadout: boolean; UseAdultOperatorLoadout: boolean;
WeaponSkins: IItemEntry; WeaponSkins: IItemEntry;
LotusCustomization: ILotusCustomization;
} }
export type ISaveLoadoutRequestNoUpgradeVer = Omit<ISaveLoadoutRequest, "UpgradeVer">; export type ISaveLoadoutRequestNoUpgradeVer = Omit<ISaveLoadoutRequest, "UpgradeVer">;

View File

@ -1,12 +1,12 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IOid } from "@/src/types/commonTypes"; import { IMongoDate, IOid } from "@/src/types/commonTypes";
import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes"; import { IColor } from "@/src/types/inventoryTypes/commonInventoryTypes";
import { ILoadoutClient } from "./saveLoadoutTypes"; import { ILoadoutClient } from "./saveLoadoutTypes";
export interface IGetShipResponse { export interface IGetShipResponse {
ShipOwnerId: string; ShipOwnerId: string;
Ship: IShip; Ship: IShip;
Apartment: IApartment; Apartment: IApartmentClient;
TailorShop: ITailorShop; TailorShop: ITailorShop;
LoadOutInventory: { LoadOutPresets: ILoadoutClient }; LoadOutInventory: { LoadOutPresets: ILoadoutClient };
} }
@ -51,28 +51,42 @@ export interface IRoom {
PlacedDecos?: IPlacedDecosDatabase[]; PlacedDecos?: IPlacedDecosDatabase[];
} }
export interface IPlants { export interface IPlantClient {
PlantType: string; PlantType: string;
EndTime: IOid; EndTime: IMongoDate;
PlotIndex: number; PlotIndex: number;
} }
export interface IPlanters {
export interface IPlantDatabase extends Omit<IPlantClient, "EndTime"> {
EndTime: Date;
}
export interface IPlanterClient {
Name: string; Name: string;
Plants: IPlants[]; Plants: IPlantClient[];
} }
export interface IGardening { export interface IPlanterDatabase {
Planters?: IPlanters[]; Name: string;
Plants: IPlantDatabase[];
} }
export interface IApartment { export interface IGardeningClient {
Gardening: IGardening; Planters: IPlanterClient[];
}
export interface IGardeningDatabase {
Planters: IPlanterDatabase[];
}
export interface IApartmentClient {
Gardening: IGardeningClient;
Rooms: IRoom[]; Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadout[]; FavouriteLoadouts: IFavouriteLoadout[];
} }
export interface IApartmentDatabase { export interface IApartmentDatabase {
Gardening: IGardening; Gardening: IGardeningDatabase;
Rooms: IRoom[]; Rooms: IRoom[];
FavouriteLoadouts: IFavouriteLoadoutDatabase[]; FavouriteLoadouts: IFavouriteLoadoutDatabase[];
} }

View File

@ -1,18 +1,16 @@
import { IMongoDate, IOid } from "./commonTypes"; import { IMongoDate, IOid } from "./commonTypes";
export interface IItemPrice { export interface IItemPrice {
ItemType: string | string[]; // If string[], preprocessing will use RNG to pick one for the current period. ItemType: string;
ItemCount: number; ItemCount: number;
ProductCategory: string; ProductCategory: string;
} }
export interface IItemPricePreprocessed extends Omit<IItemPrice, "ItemType"> {
ItemType: string;
}
export interface IItemManifest { export interface IItemManifest {
StoreItem: string; StoreItem: string;
ItemPrices?: IItemPrice[]; ItemPrices?: IItemPrice[];
RegularPrice?: number[];
PremiumPrice?: number[];
Bin: string; Bin: string;
QuantityMultiplier: number; QuantityMultiplier: number;
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing. Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
@ -23,10 +21,6 @@ export interface IItemManifest {
Id: IOid; Id: IOid;
} }
export interface IItemManifestPreprocessed extends Omit<IItemManifest, "ItemPrices"> {
ItemPrices?: IItemPricePreprocessed[];
}
export interface IVendorInfo { export interface IVendorInfo {
_id: IOid; _id: IOid;
TypeName: string; TypeName: string;
@ -38,14 +32,6 @@ export interface IVendorInfo {
Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing. Expiry: IMongoDate; // Either a date in the distant future or a period in milliseconds for preprocessing.
} }
export interface IVendorInfoPreprocessed extends Omit<IVendorInfo, "ItemManifest"> { export interface IVendorManifest {
ItemManifest: IItemManifestPreprocessed[];
}
export interface IRawVendorManifest {
VendorInfo: IVendorInfo; VendorInfo: IVendorInfo;
} }
export interface IVendorManifestPreprocessed {
VendorInfo: IVendorInfoPreprocessed;
}

View File

@ -97,6 +97,13 @@ export interface ISortie {
}[]; }[];
} }
export interface ISortieMission {
missionType: string;
modifierType: string;
node: string;
tileset: string;
}
export interface ILiteSortie { export interface ILiteSortie {
_id: IOid; _id: IOid;
Activation: IMongoDate; Activation: IMongoDate;
@ -126,7 +133,7 @@ export interface ISeasonChallenge {
export interface ICalendarSeason { export interface ICalendarSeason {
Activation: IMongoDate; Activation: IMongoDate;
Expiry: IMongoDate; Expiry: IMongoDate;
Season: string; // "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL" Season: "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL";
Days: ICalendarDay[]; Days: ICalendarDay[];
YearIteration: number; YearIteration: number;
Version: number; Version: number;

View File

@ -0,0 +1,49 @@
[
"/Lotus/Weapons/ClanTech/Bio/BioWeapon",
"/Lotus/Weapons/ClanTech/Energy/EnergyRifle",
"/Lotus/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun",
"/Lotus/Weapons/Corpus/Pistols/CrpHandRL/CorpusHandRocketLauncher",
"/Lotus/Weapons/Grineer/LongGuns/GrineerSawbladeGun/SawBladeGun",
"/Lotus/Weapons/Grineer/Melee/GrineerTylAxeAndBoar/RegorAxeShield",
"/Lotus/Weapons/Grineer/Pistols/HeatGun/GrnHeatGun",
"/Lotus/Weapons/Infested/Pistols/InfVomitGun/InfVomitGunWep",
"/Lotus/Weapons/Syndicates/CephalonSuda/Pistols/CSDroidArray",
"/Lotus/Weapons/Tenno/Bows/HuntingBow",
"/Lotus/Weapons/Tenno/Bows/StalkerBow",
"/Lotus/Weapons/Tenno/LongGuns/TnoLeverAction/TnoLeverActionRifle",
"/Lotus/Weapons/Tenno/Melee/Axe/DualInfestedAxesWeapon",
"/Lotus/Weapons/Tenno/Melee/Dagger/CeramicDagger",
"/Lotus/Weapons/Tenno/Melee/Fist/Fist",
"/Lotus/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer",
"/Lotus/Weapons/Tenno/Melee/LongSword/LongSword",
"/Lotus/Weapons/Tenno/Melee/Maces/PaladinMace/PaladinMaceWeapon",
"/Lotus/Weapons/Tenno/Melee/Scythe/StalkerScytheWeapon",
"/Lotus/Weapons/Tenno/Melee/Scythe/ParisScythe/ParisScythe",
"/Lotus/Weapons/Tenno/Melee/Staff/Staff",
"/Lotus/Weapons/Tenno/Melee/Swords/CutlassAndPoignard/TennoCutlass",
"/Lotus/Weapons/Tenno/Melee/Swords/TennoSai/TennoSais",
"/Lotus/Weapons/Tenno/Pistol/AutoPistol",
"/Lotus/Weapons/Tenno/Pistol/BurstPistol",
"/Lotus/Weapons/Tenno/Pistol/HandShotGun",
"/Lotus/Weapons/Tenno/Pistol/HeavyPistol",
"/Lotus/Weapons/Tenno/Pistol/Pistol",
"/Lotus/Weapons/Tenno/Pistol/RevolverPistol",
"/Lotus/Weapons/Tenno/Pistols/ConclaveLeverPistol/ConclaveLeverPistol",
"/Lotus/Weapons/Tenno/Rifle/BoltoRifle",
"/Lotus/Weapons/Tenno/Rifle/BurstRifle",
"/Lotus/Weapons/Tenno/Rifle/HeavyRifle",
"/Lotus/Weapons/Tenno/Rifle/Rifle",
"/Lotus/Weapons/Tenno/Rifle/SemiAutoRifle",
"/Lotus/Weapons/Tenno/Rifle/TennoAR",
"/Lotus/Weapons/Tenno/Shotgun/FullAutoShotgun",
"/Lotus/Weapons/Tenno/Shotgun/Shotgun",
"/Lotus/Weapons/Tenno/ThrowingWeapons/Kunai",
"/Lotus/Weapons/Tenno/ThrowingWeapons/StalkerKunai",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
"/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
]

View File

@ -1098,5 +1098,6 @@
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampLarge",
"/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall", "/Lotus/Types/LevelObjects/InfestedPumpkinCocoonLampSmall",
"/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem", "/Lotus/Types/LevelObjects/InfestedPumpkinExplosiveTotem",
"/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar" "/Lotus/Types/Enemies/Orokin/OrokinMoaBipedAvatar",
"/Lotus/Types/Enemies/Grineer/AIWeek/Avatars/BeastMasterAvatar"
] ]

View File

@ -1,101 +0,0 @@
{
"VendorInfo": {
"_id": { "$oid": "61ba123467e5d37975aeeb03" },
"TypeName": "/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementMoon",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_4",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 79554843,
"Id": { "$oid": "67bbb592e1534511d6c1c1e2" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementMountain",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 7,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2413820225,
"Id": { "$oid": "67bbb592e1534511d6c1c1e3" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementStorm",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 3,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3262300883,
"Id": { "$oid": "67bbb592e1534511d6c1c1e4" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementShadow",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2797325750,
"Id": { "$oid": "67bbb592e1534511d6c1c1e5" }
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Guild/GuildAdvertisementGhost",
"ItemPrices": [
{
"ItemType": ["/Lotus/Types/Items/Research/BioFragment", "/Lotus/Types/Items/Research/ChemComponent", "/Lotus/Types/Items/Research/EnergyFragment"],
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [1, 1],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": { "$date": { "$numberLong": "604800000" } },
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 554932310,
"Id": { "$oid": "67bbb592e1534511d6c1c1e6" }
}
],
"PropertyTextHash": "255AFE2169BAE4130B4B20D7C55D14FA",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": { "$date": { "$numberLong": "604800000" } }
}
}

View File

@ -1,248 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5be4a159b144f3cdf1c22efa"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Venus/Resources/VenusCoconutItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/MiscItems/Circuits",
"ItemCount": 3664,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [87300, 87300],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1881404827,
"Id": {
"$oid": "670daf92d21f34757a5e73b4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleRareC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/NeuralSensor",
"ItemCount": 1,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [53300, 53300],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1943984533,
"Id": {
"$oid": "6710b5029e1a3080a65e73a7"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleCommonG",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/Salvage",
"ItemCount": 11540,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [27300, 27300],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 744199559,
"Id": {
"$oid": "67112582cc115756985e73a4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Solaris/FishParts/CorpusFishThermalLaserItem",
"ItemCount": 9,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [75800, 75800],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 3744711432,
"Id": {
"$oid": "670de7d28a6ec82cd25e73a2"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/Rubedo",
"ItemCount": 3343,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [52200, 52200],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1579000687,
"Id": {
"$oid": "670e58526171148e125e73ad"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleCommonA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Venus/Resources/CoolantItem",
"ItemCount": 9,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Solaris/FishParts/CorpusFishAnoscopicSensorItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [12400, 12400],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 3589081466,
"Id": {
"$oid": "67112582cc115756985e73a5"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Solaris/SolarisCommonOreBAlloyItem",
"ItemCount": 13,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [77500, 77500],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 1510234814,
"Id": {
"$oid": "670f0f21250ad046c35e73ee"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleUncommonD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Solaris/FishParts/CorpusFishParralelBiodeItem",
"ItemCount": 7,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Solaris/SolarisCommonGemBCutItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [94600, 94600],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 4222095721,
"Id": {
"$oid": "670f63827be40254f95e739d"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/DebtTokenBundles/DebtTokenBundleCommonJ",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/MiscItems/Nanospores",
"ItemCount": 14830,
"ProductCategory": "MiscItems"
}
],
"RegularPrice": [25600, 25600],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"LocTagRandSeed": 2694388669,
"Id": {
"$oid": "67112582cc115756985e73a6"
}
}
],
"PropertyTextHash": "A39621049CA3CA13761028CD21C239EF",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -0,0 +1,459 @@
{
"VendorInfo": {
"_id": {
"$oid": "67dadc30e4b6e0e5979c8d56"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleBlueprint",
"ItemPrices": [
{
"ItemCount": 195,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleSystemsBlueprint",
"ItemPrices": [
{
"ItemCount": 65,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleChassisBlueprint",
"ItemPrices": [
{
"ItemCount": 65,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18e"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TempleHelmetBlueprint",
"ItemPrices": [
{
"ItemCount": 65,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c18f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/1999EntHybridPistolBlueprint",
"ItemPrices": [
{
"ItemCount": 120,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c190"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolBarrelBlueprint",
"ItemPrices": [
{
"ItemCount": 60,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c191"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolReceiverBlueprint",
"ItemPrices": [
{
"ItemCount": 60,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c192"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Weapons/WeaponParts/1999EntHybridPistolStockBlueprint",
"ItemPrices": [
{
"ItemCount": 60,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c193"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCoreKitA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c194"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumCymbalA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c195"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumFloorTomA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c196"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumDrumSnareA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c197"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c198"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseB",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c199"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseC",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19a"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseD",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19b"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseE",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumEquipmentCaseF",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Hollvania/LASxStadiumSynthKeyboardA",
"ItemPrices": [
{
"ItemCount": 30,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c19e"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/PhotoBooth/Vania/PhotoboothTileVaniaObjTempleDefense",
"ItemPrices": [
{
"ItemCount": 100,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": false,
"Id": {
"$oid": "67dadc30641da66dc5c1c19f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
"ItemPrices": [
{
"ItemCount": 110,
"ItemType": "/Lotus/Types/Gameplay/1999Wf/Resources/1999ResourceDefense",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 6000,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"PurchaseQuantityLimit": 7,
"AllowMultipurchase": true,
"Id": {
"$oid": "67dadc30641da66dc5c1c1a5"
}
}
],
"PropertyTextHash": "20B13D9EB78FEC80EA32D0687F5BA1AE",
"RequiredGoalTag": "",
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
}
}
}

View File

@ -11,5 +11,11 @@
"ItemType": "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestEmailItem", "ItemType": "/Lotus/Types/Keys/RailJackBuildQuest/RailjackBuildQuestEmailItem",
"ItemCount": 1 "ItemCount": 1
} }
],
"/Lotus/Types/Keys/EntratiLab/EntratiQuestKeyChain": [
{
"ItemType": "/Lotus/Types/Keys/1999PrologueQuest/1999PrologueQuestKeyChain",
"ItemCount": 1
}
] ]
} }

View File

@ -0,0 +1,175 @@
{
"SettlementNode1": "CorpusShipTileset",
"SettlementNode11": "CorpusShipTileset",
"SettlementNode12": "CorpusShipTileset",
"SettlementNode14": "CorpusShipTileset",
"SettlementNode15": "CorpusShipTileset",
"SettlementNode2": "CorpusShipTileset",
"SettlementNode20": "CorpusShipTileset",
"SettlementNode3": "CorpusShipTileset",
"SolNode1": "CorpusOutpostTileset",
"SolNode10": "CorpusGasCityTileset",
"SolNode100": "CorpusGasCityTileset",
"SolNode101": "CorpusOutpostTileset",
"SolNode102": "CorpusShipTileset",
"SolNode103": "GrineerAsteroidTileset",
"SolNode104": "CorpusShipTileset",
"SolNode105": "GrineerOceanTilesetAnywhere",
"SolNode106": "GrineerSettlementTileset",
"SolNode107": "CorpusOutpostTileset",
"SolNode108": "GrineerAsteroidTileset",
"SolNode109": "CorpusOutpostTileset",
"SolNode11": "GrineerSettlementTileset",
"SolNode113": "GrineerSettlementTileset",
"SolNode118": "CorpusShipTileset",
"SolNode119": "GrineerAsteroidTileset",
"SolNode12": "GrineerAsteroidTileset",
"SolNode121": "CorpusGasCityTileset",
"SolNode122": "GrineerOceanTileset",
"SolNode123": "CorpusShipTileset",
"SolNode125": "CorpusGasCityTileset",
"SolNode126": "CorpusGasCityTileset",
"SolNode127": "CorpusShipTileset",
"SolNode128": "CorpusOutpostTileset",
"SolNode130": "GrineerAsteroidTileset",
"SolNode131": "GrineerShipyardsTileset",
"SolNode132": "GrineerShipyardsTileset",
"SolNode135": "GrineerGalleonTileset",
"SolNode137": "GrineerShipyardsTileset",
"SolNode138": "GrineerShipyardsTileset",
"SolNode139": "GrineerShipyardsTileset",
"SolNode14": "CorpusIcePlanetTilesetCaves",
"SolNode140": "GrineerShipyardsTileset",
"SolNode141": "GrineerShipyardsTileset",
"SolNode144": "GrineerShipyardsTileset",
"SolNode146": "GrineerAsteroidTileset",
"SolNode147": "GrineerShipyardsTileset",
"SolNode149": "GrineerShipyardsTileset",
"SolNode15": "GrineerGalleonTileset",
"SolNode16": "GrineerSettlementTileset",
"SolNode162": "InfestedCorpusShipTileset",
"SolNode164": "InfestedCorpusShipTileset",
"SolNode166": "InfestedCorpusShipTileset",
"SolNode17": "CorpusShipTileset",
"SolNode171": "InfestedCorpusShipTileset",
"SolNode172": "CorpusShipTileset",
"SolNode173": "InfestedCorpusShipTileset",
"SolNode175": "InfestedCorpusShipTileset",
"SolNode177": "GrineerGalleonTileset",
"SolNode18": "GrineerAsteroidTileset",
"SolNode181": "GrineerAsteroidTileset",
"SolNode184": "GrineerGalleonTileset",
"SolNode185": "GrineerGalleonTileset",
"SolNode187": "GrineerAsteroidTileset",
"SolNode188": "GrineerGalleonTileset",
"SolNode189": "GrineerGalleonTileset",
"SolNode19": "GrineerAsteroidTileset",
"SolNode191": "GrineerShipyardsTileset",
"SolNode193": "GrineerAsteroidTileset",
"SolNode195": "GrineerGalleonTileset",
"SolNode196": "GrineerGalleonTileset",
"SolNode2": "CorpusOutpostTileset",
"SolNode20": "GrineerGalleonTileset",
"SolNode203": "CorpusIcePlanetTileset",
"SolNode205": "CorpusIcePlanetTileset",
"SolNode209": "CorpusIcePlanetTileset",
"SolNode21": "CorpusOutpostTileset",
"SolNode210": "CorpusIcePlanetTileset",
"SolNode211": "CorpusIcePlanetTileset",
"SolNode212": "CorpusIcePlanetTileset",
"SolNode214": "CorpusIcePlanetTileset",
"SolNode215": "CorpusShipTileset",
"SolNode216": "CorpusIcePlanetTileset",
"SolNode217": "CorpusIcePlanetTileset",
"SolNode22": "CorpusOutpostTileset",
"SolNode220": "CorpusIcePlanetTileset",
"SolNode223": "GrineerAsteroidTileset",
"SolNode224": "GrineerGalleonTileset",
"SolNode225": "GrineerGalleonTileset",
"SolNode226": "GrineerGalleonTileset",
"SolNode228": "EidolonTileset",
"SolNode23": "CorpusShipTileset",
"SolNode24": "GrineerForestTileset",
"SolNode25": "CorpusGasCityTileset",
"SolNode26": "GrineerForestTileset",
"SolNode30": "GrineerSettlementTileset",
"SolNode300": "OrokinMoonTilesetGrineer",
"SolNode301": "OrokinMoonTilesetGrineer",
"SolNode302": "OrokinMoonTilesetCorpus",
"SolNode304": "OrokinMoonTilesetCorpus",
"SolNode305": "OrokinMoonTilesetGrineer",
"SolNode306": "OrokinMoonTilesetCorpus",
"SolNode307": "OrokinMoonTilesetCorpus",
"SolNode308": "OrokinMoonTilesetCorpus",
"SolNode31": "GrineerGalleonTileset",
"SolNode32": "GrineerGalleonTileset",
"SolNode36": "GrineerSettlementTileset",
"SolNode38": "CorpusOutpostTileset",
"SolNode39": "GrineerForestTileset",
"SolNode4": "CorpusShipTileset",
"SolNode400": "OrokinVoidTileset",
"SolNode401": "OrokinVoidTileset",
"SolNode402": "OrokinVoidTileset",
"SolNode403": "OrokinVoidTileset",
"SolNode404": "OrokinVoidTileset",
"SolNode405": "OrokinVoidTileset",
"SolNode406": "OrokinVoidTileset",
"SolNode407": "OrokinVoidTileset",
"SolNode408": "OrokinVoidTileset",
"SolNode409": "OrokinVoidTileset",
"SolNode41": "GrineerSettlementTileset",
"SolNode410": "OrokinVoidTileset",
"SolNode412": "OrokinVoidTileset",
"SolNode42": "GrineerGalleonTileset",
"SolNode43": "CorpusOutpostTileset",
"SolNode45": "GrineerSettlementTileset",
"SolNode46": "GrineerSettlementTileset",
"SolNode48": "CorpusOutpostTileset",
"SolNode49": "CorpusShipTileset",
"SolNode50": "GrineerAsteroidTileset",
"SolNode51": "CorpusOutpostTileset",
"SolNode53": "CorpusGasCityTileset",
"SolNode56": "CorpusShipTileset",
"SolNode57": "CorpusOutpostTileset",
"SolNode58": "GrineerSettlementTileset",
"SolNode59": "GrineerForestTileset",
"SolNode6": "CorpusOutpostTileset",
"SolNode61": "CorpusShipTileset",
"SolNode62": "CorpusIcePlanetTilesetCaves",
"SolNode64": "GrineerOceanTileset",
"SolNode66": "CorpusOutpostTileset",
"SolNode67": "GrineerAsteroidTileset",
"SolNode68": "GrineerGalleonTileset",
"SolNode70": "GrineerGalleonTileset",
"SolNode706": "OrokinDerelictTileset",
"SolNode707": "OrokinDerelictTileset",
"SolNode708": "OrokinDerelictTileset",
"SolNode709": "OrokinDerelictTileset",
"SolNode710": "OrokinDerelictTileset",
"SolNode711": "OrokinDerelictTileset",
"SolNode712": "OrokinDerelictTileset",
"SolNode713": "OrokinDerelictTileset",
"SolNode72": "CorpusOutpostTileset",
"SolNode73": "CorpusGasCityTileset",
"SolNode74": "CorpusGasCityTileset",
"SolNode741": "GrineerFortressTileset",
"SolNode742": "GrineerFortressTileset",
"SolNode743": "GrineerFortressTileset",
"SolNode744": "GrineerFortressTileset",
"SolNode745": "GrineerFortressTileset",
"SolNode746": "GrineerFortressTileset",
"SolNode747": "GrineerFortressTileset",
"SolNode748": "GrineerFortressTileset",
"SolNode75": "GrineerForestTileset",
"SolNode76": "CorpusShipTileset",
"SolNode78": "CorpusShipTileset",
"SolNode79": "GrineerForestTileset",
"SolNode81": "CorpusShipTileset",
"SolNode82": "GrineerGalleonTileset",
"SolNode84": "CorpusIcePlanetTilesetCaves",
"SolNode88": "CorpusShipTileset",
"SolNode93": "GrineerAsteroidTileset",
"SolNode96": "GrineerGalleonTileset",
"SolNode97": "CorpusGasCityTileset",
"SolNode99": "GrineerSettlementTileset"
}

View File

@ -0,0 +1,157 @@
[
"SettlementNode1",
"SettlementNode11",
"SettlementNode12",
"SettlementNode14",
"SettlementNode15",
"SettlementNode2",
"SettlementNode3",
"SolNode1",
"SolNode10",
"SolNode100",
"SolNode101",
"SolNode102",
"SolNode103",
"SolNode106",
"SolNode107",
"SolNode109",
"SolNode11",
"SolNode113",
"SolNode118",
"SolNode119",
"SolNode12",
"SolNode121",
"SolNode122",
"SolNode123",
"SolNode125",
"SolNode126",
"SolNode128",
"SolNode130",
"SolNode131",
"SolNode132",
"SolNode135",
"SolNode137",
"SolNode138",
"SolNode139",
"SolNode14",
"SolNode140",
"SolNode141",
"SolNode146",
"SolNode147",
"SolNode149",
"SolNode15",
"SolNode153",
"SolNode16",
"SolNode162",
"SolNode164",
"SolNode166",
"SolNode167",
"SolNode17",
"SolNode171",
"SolNode172",
"SolNode173",
"SolNode175",
"SolNode177",
"SolNode18",
"SolNode181",
"SolNode184",
"SolNode185",
"SolNode187",
"SolNode188",
"SolNode189",
"SolNode19",
"SolNode191",
"SolNode195",
"SolNode196",
"SolNode2",
"SolNode20",
"SolNode203",
"SolNode204",
"SolNode205",
"SolNode209",
"SolNode21",
"SolNode211",
"SolNode212",
"SolNode214",
"SolNode215",
"SolNode216",
"SolNode217",
"SolNode22",
"SolNode220",
"SolNode223",
"SolNode224",
"SolNode225",
"SolNode226",
"SolNode23",
"SolNode25",
"SolNode26",
"SolNode27",
"SolNode30",
"SolNode31",
"SolNode36",
"SolNode38",
"SolNode39",
"SolNode4",
"SolNode400",
"SolNode401",
"SolNode402",
"SolNode403",
"SolNode404",
"SolNode405",
"SolNode406",
"SolNode407",
"SolNode408",
"SolNode409",
"SolNode41",
"SolNode410",
"SolNode412",
"SolNode42",
"SolNode43",
"SolNode45",
"SolNode46",
"SolNode48",
"SolNode49",
"SolNode50",
"SolNode56",
"SolNode57",
"SolNode58",
"SolNode59",
"SolNode6",
"SolNode61",
"SolNode62",
"SolNode63",
"SolNode64",
"SolNode66",
"SolNode67",
"SolNode68",
"SolNode70",
"SolNode706",
"SolNode707",
"SolNode708",
"SolNode709",
"SolNode710",
"SolNode711",
"SolNode72",
"SolNode73",
"SolNode74",
"SolNode741",
"SolNode742",
"SolNode743",
"SolNode744",
"SolNode745",
"SolNode746",
"SolNode748",
"SolNode75",
"SolNode76",
"SolNode78",
"SolNode79",
"SolNode81",
"SolNode82",
"SolNode84",
"SolNode85",
"SolNode88",
"SolNode89",
"SolNode93",
"SolNode96",
"SolNode97"
]

View File

@ -63,22 +63,6 @@
} }
], ],
"SyndicateMissions": [ "SyndicateMissions": [
{
"_id": { "$oid": "663a4fc5ba6f84724fa48049" },
"Activation": { "$date": { "$numberLong": "1715097541439" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Tag": "ArbitersSyndicate",
"Seed": 24491,
"Nodes": ["SolNode223", "SolNode89", "SolNode146", "SolNode212", "SolNode167", "SolNode48", "SolNode78"]
},
{
"_id": { "$oid": "663a4fc5ba6f84724fa4804a" },
"Activation": { "$date": { "$numberLong": "1715097541439" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Tag": "CephalonSudaSyndicate",
"Seed": 12770,
"Nodes": ["SolNode36", "SolNode59", "SettlementNode12", "SolNode61", "SolNode12", "SolNode138", "SolNode72"]
},
{ {
"_id": { "$oid": "663a4fc5ba6f84724fa4804c" }, "_id": { "$oid": "663a4fc5ba6f84724fa4804c" },
"Activation": { "$date": { "$numberLong": "1715097541439" } }, "Activation": { "$date": { "$numberLong": "1715097541439" } },
@ -103,14 +87,6 @@
"Seed": 50102, "Seed": 50102,
"Nodes": [] "Nodes": []
}, },
{
"_id": { "$oid": "663a4fc5ba6f84724fa4804e" },
"Activation": { "$date": { "$numberLong": "1715097541439" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Tag": "NewLokaSyndicate",
"Seed": 16064,
"Nodes": ["SolNode101", "SolNode224", "SolNode205", "SettlementNode2", "SolNode171", "SolNode188", "SolNode75"]
},
{ {
"_id": { "$oid": "663a4fc5ba6f84724fa4804f" }, "_id": { "$oid": "663a4fc5ba6f84724fa4804f" },
"Activation": { "$date": { "$numberLong": "1715097541439" } }, "Activation": { "$date": { "$numberLong": "1715097541439" } },
@ -119,14 +95,6 @@
"Seed": 77721, "Seed": 77721,
"Nodes": [] "Nodes": []
}, },
{
"_id": { "$oid": "663a4fc5ba6f84724fa48050" },
"Activation": { "$date": { "$numberLong": "1715097541439" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Tag": "PerrinSyndicate",
"Seed": 9940,
"Nodes": ["SolNode39", "SolNode14", "SolNode203", "SolNode100", "SolNode130", "SolNode64", "SettlementNode15"]
},
{ {
"_id": { "$oid": "663a4fc5ba6f84724fa48052" }, "_id": { "$oid": "663a4fc5ba6f84724fa48052" },
"Activation": { "$date": { "$numberLong": "1715097541439" } }, "Activation": { "$date": { "$numberLong": "1715097541439" } },
@ -255,14 +223,6 @@
"Seed": 67257, "Seed": 67257,
"Nodes": [] "Nodes": []
}, },
{
"_id": { "$oid": "663a4fc5ba6f84724fa4805e" },
"Activation": { "$date": { "$numberLong": "1715097541439" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Tag": "RedVeilSyndicate",
"Seed": 46649,
"Nodes": ["SolNode226", "SolNode79", "SolNode216", "SettlementNode11", "SolNode56", "SolNode41", "SolNode23"]
},
{ {
"_id": { "$oid": "663a4fc5ba6f84724fa48060" }, "_id": { "$oid": "663a4fc5ba6f84724fa48060" },
"Activation": { "$date": { "$numberLong": "1715097541439" } }, "Activation": { "$date": { "$numberLong": "1715097541439" } },
@ -270,14 +230,6 @@
"Tag": "VoxSyndicate", "Tag": "VoxSyndicate",
"Seed": 77972, "Seed": 77972,
"Nodes": [] "Nodes": []
},
{
"_id": { "$oid": "663a4fc5ba6f84724fa48061" },
"Activation": { "$date": { "$numberLong": "1715097541439" } },
"Expiry": { "$date": { "$numberLong": "2000000000000" } },
"Tag": "SteelMeridianSyndicate",
"Seed": 42366,
"Nodes": ["SolNode27", "SolNode107", "SolNode214", "SettlementNode1", "SolNode177", "SolNode141", "SolNode408"]
} }
], ],
"ActiveMissions": [ "ActiveMissions": [

View File

@ -85,6 +85,7 @@
<input class="form-control" type="password" id="password" required /> <input class="form-control" type="password" id="password" required />
<br /> <br />
<button class="btn btn-primary" type="submit" data-loc="login_loginButton"></button> <button class="btn btn-primary" type="submit" data-loc="login_loginButton"></button>
<button class="btn btn-secondary" type="submit" onclick="registerSubmit = true;" data-loc="login_registerButton"></button>
</form> </form>
</div> </div>
<div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI"> <div data-route="/webui/inventory" data-title="Inventory | OpenWF WebUI">
@ -305,7 +306,7 @@
<input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" /> <input class="form-control" id="acquire-type-MoaPets" list="datalist-MoaPets" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button> <button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form> </form>
<form class="input-group mb-3" id="modular-MoaPets" style="display: none;"> <form class="input-group mb-3" id="modular-MoaPets-Moa" style="display: none;">
<input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" /> <input class="form-control" id="acquire-type-MoaPets-MOA_ENGINE" list="datalist-ModularParts-MOA_ENGINE" />
<input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" /> <input class="form-control" id="acquire-type-MoaPets-MOA_PAYLOAD" list="datalist-ModularParts-MOA_PAYLOAD" />
<input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" /> <input class="form-control" id="acquire-type-MoaPets-MOA_HEAD" list="datalist-ModularParts-MOA_HEAD" />
@ -325,6 +326,28 @@
</div> </div>
</div> </div>
<div class="row g-3"> <div class="row g-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_kubrowPets"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('KubrowPets');return false;">
<input class="form-control" id="acquire-type-KubrowPets" list="datalist-KubrowPets" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<form class="input-group mb-3" id="modular-KubrowPets-Catbrow" style="display: none;">
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_ANTIGEN" list="datalist-ModularParts-CATBROW_ANTIGEN" />
<input class="form-control" id="acquire-type-KubrowPets-CATBROW_MUTAGEN" list="datalist-ModularParts-CATBROW_MUTAGEN" />
</form>
<form class="input-group mb-3" id="modular-KubrowPets-Kubrow" style="display: none;">
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_ANTIGEN" list="datalist-ModularParts-KUBROW_ANTIGEN" />
<input class="form-control" id="acquire-type-KubrowPets-KUBROW_MUTAGEN" list="datalist-ModularParts-KUBROW_MUTAGEN" />
</form>
<table class="table table-hover w-100">
<tbody id="KubrowPets-list"></tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-6"> <div class="col-lg-6">
<div class="card mb-3" style="height: 400px;"> <div class="card mb-3" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5> <h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
@ -378,6 +401,22 @@
</div> </div>
</div> </div>
</div> </div>
<div class="row g-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_evolutionProgress"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEvolution();return false;">
<input class="form-control" id="acquire-type-EvolutionProgress" list="datalist-EvolutionProgress" />
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
<table class="table table-hover w-100">
<tbody id="EvolutionProgress-list"></tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body">
@ -388,6 +427,7 @@
<button class="btn btn-primary" onclick="addMissingEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkAddSpaceWeapons"></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(['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="addMissingEquipment(['SentinelWeapons']);" data-loc="inventory_bulkAddSentinelWeapons"></button>
<button class="btn btn-primary" onclick="addMissingEvolutionProgress();" data-loc="inventory_bulkAddEvolutionProgress"></button>
</div> </div>
<div class="mb-2 d-flex flex-wrap gap-2"> <div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button> <button class="btn btn-success" onclick="maxRankAllEquipment(['Suits']);" data-loc="inventory_bulkRankUpSuits"></button>
@ -396,6 +436,7 @@
<button class="btn btn-success" onclick="maxRankAllEquipment(['SpaceGuns', 'SpaceMelee']);" data-loc="inventory_bulkRankUpSpaceWeapons"></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(['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="maxRankAllEquipment(['SentinelWeapons']);" data-loc="inventory_bulkRankUpSentinelWeapons"></button>
<button class="btn btn-success" onclick="maxRankAllEvolutions();" data-loc="inventory_bulkRankUpEvolutionProgress"></button>
</div> </div>
</div> </div>
</div> </div>
@ -468,8 +509,10 @@
</div> </div>
<div class="card mb-3"> <div class="card mb-3">
<h5 class="card-header" data-loc="general_bulkActions"></h5> <h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body"> <div class="card-body d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_bulkAddMods"></button> <button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_addMissingUnrankedMods"></button>
<button class="btn btn-danger" onclick="doRemoveUnrankedMods();" data-loc="mods_removeUnranked"></button>
<button class="btn btn-primary" onclick="doAddMissingMaxRankMods();" data-loc="mods_addMissingMaxRankMods"></button>
</div> </div>
</div> </div>
</div> </div>
@ -551,6 +594,10 @@
<input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" /> <input class="form-check-input" type="checkbox" id="infiniteHelminthMaterials" />
<label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label> <label class="form-check-label" for="infiniteHelminthMaterials" data-loc="cheats_infiniteHelminthMaterials"></label>
</div> </div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="dontSubtractConsumables" />
<label class="form-check-label" for="dontSubtractConsumables" data-loc="cheats_dontSubtractConsumables"></label>
</div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" /> <input class="form-check-input" type="checkbox" id="unlockAllShipFeatures" />
<label class="form-check-label" for="unlockAllShipFeatures" data-loc="cheats_unlockAllShipFeatures"></label> <label class="form-check-label" for="unlockAllShipFeatures" data-loc="cheats_unlockAllShipFeatures"></label>
@ -700,6 +747,7 @@
<datalist id="datalist-Sentinels"></datalist> <datalist id="datalist-Sentinels"></datalist>
<datalist id="datalist-MechSuits"></datalist> <datalist id="datalist-MechSuits"></datalist>
<datalist id="datalist-MoaPets"></datalist> <datalist id="datalist-MoaPets"></datalist>
<datalist id="datalist-KubrowPets"></datalist>
<datalist id="datalist-QuestKeys"></datalist> <datalist id="datalist-QuestKeys"></datalist>
<datalist id="datalist-miscitems"></datalist> <datalist id="datalist-miscitems"></datalist>
<datalist id="datalist-mods"> <datalist id="datalist-mods">
@ -708,6 +756,7 @@
</datalist> </datalist>
<datalist id="datalist-archonCrystalUpgrades"></datalist> <datalist id="datalist-archonCrystalUpgrades"></datalist>
<datalist id="datalist-OperatorAmps"></datalist> <datalist id="datalist-OperatorAmps"></datalist>
<datalist id="datalist-EvolutionProgress"></datalist>
<datalist id="datalist-ModularParts"></datalist> <datalist id="datalist-ModularParts"></datalist>
<datalist id="datalist-ModularParts-HB_DECK"></datalist> <datalist id="datalist-ModularParts-HB_DECK"></datalist>
<datalist id="datalist-ModularParts-HB_ENGINE"></datalist> <datalist id="datalist-ModularParts-HB_ENGINE"></datalist>
@ -731,6 +780,10 @@
<datalist id="datalist-ModularParts-ZANUKA_HEAD"></datalist> <datalist id="datalist-ModularParts-ZANUKA_HEAD"></datalist>
<datalist id="datalist-ModularParts-ZANUKA_LEG"></datalist> <datalist id="datalist-ModularParts-ZANUKA_LEG"></datalist>
<datalist id="datalist-ModularParts-ZANUKA_TAIL"></datalist> <datalist id="datalist-ModularParts-ZANUKA_TAIL"></datalist>
<datalist id="datalist-ModularParts-CATBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-CATBROW_MUTAGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<script src="/webui/libs/jquery-3.6.0.min.js"></script> <script src="/webui/libs/jquery-3.6.0.min.js"></script>
<script src="/webui/libs/whirlpool-js.min.js"></script> <script src="/webui/libs/whirlpool-js.min.js"></script>
<script src="/webui/libs/single.js"></script> <script src="/webui/libs/single.js"></script>

View File

@ -1,11 +1,19 @@
let loginOrRegisterPending = false;
window.registerSubmit = false;
function doLogin() { function doLogin() {
if (loginOrRegisterPending) {
return;
}
loginOrRegisterPending = true;
localStorage.setItem("email", $("#email").val()); localStorage.setItem("email", $("#email").val());
localStorage.setItem("password", $("#password").val()); localStorage.setItem("password", $("#password").val());
$("#email, #password").val("");
loginFromLocalStorage(); loginFromLocalStorage();
registerSubmit = false;
} }
function loginFromLocalStorage() { function loginFromLocalStorage() {
const isRegister = registerSubmit;
doLoginRequest( doLoginRequest(
data => { data => {
if (single.getCurrentPath() == "/webui/") { if (single.getCurrentPath() == "/webui/") {
@ -21,7 +29,7 @@ function loginFromLocalStorage() {
}, },
() => { () => {
logout(); logout();
alert("Login failed"); alert(isRegister ? "Registration failed. Account already exists?" : "Login failed");
} }
); );
} }
@ -37,12 +45,15 @@ function doLoginRequest(succ_cb, fail_cb) {
s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind s: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==", // signature of some kind
lang: "en", lang: "en",
date: 1501230947855458660, // ??? date: 1501230947855458660, // ???
ClientType: "webui", ClientType: registerSubmit ? "webui-register" : "webui",
PS: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==" // anti-cheat data PS: "W0RFXVN0ZXZlIGxpa2VzIGJpZyBidXR0cw==" // anti-cheat data
}) })
}); });
req.done(succ_cb); req.done(succ_cb);
req.fail(fail_cb); req.fail(fail_cb);
req.always(() => {
loginOrRegisterPending = false;
});
} }
function revalidateAuthz(succ_cb) { function revalidateAuthz(succ_cb) {
@ -67,19 +78,23 @@ function logout() {
function renameAccount() { function renameAccount() {
const newname = window.prompt(loc("code_changeNameConfirm")); const newname = window.prompt(loc("code_changeNameConfirm"));
if (newname) { if (newname) {
revalidateAuthz(() => {
fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => { fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => {
$(".displayname").text(newname); $(".displayname").text(newname);
updateLocElements(); updateLocElements();
}); });
});
} }
} }
function deleteAccount() { function deleteAccount() {
if (window.confirm(loc("code_deleteAccountConfirm"))) { if (window.confirm(loc("code_deleteAccountConfirm"))) {
revalidateAuthz(() => {
fetch("/custom/deleteAccount?" + window.authz).then(() => { fetch("/custom/deleteAccount?" + window.authz).then(() => {
logout(); logout();
single.loadRoute("/webui/"); // Show login screen single.loadRoute("/webui/"); // Show login screen
}); });
});
} }
} }
@ -163,9 +178,25 @@ const webUiModularWeapons = [
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary", "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary", "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit", "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit" "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
]; ];
const permanentEvolutionWeapons = new Set([
"/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
"/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
"/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
]);
let uniqueLevelCaps = {}; let uniqueLevelCaps = {};
function fetchItemList() { function fetchItemList() {
window.itemListPromise = new Promise(resolve => { window.itemListPromise = new Promise(resolve => {
@ -308,7 +339,11 @@ function fetchItemList() {
"LWPT_ZANUKA_BODY", "LWPT_ZANUKA_BODY",
"LWPT_ZANUKA_HEAD", "LWPT_ZANUKA_HEAD",
"LWPT_ZANUKA_LEG", "LWPT_ZANUKA_LEG",
"LWPT_ZANUKA_TAIL" "LWPT_ZANUKA_TAIL",
"LWPT_CATBROW_ANTIGEN",
"LWPT_CATBROW_MUTAGEN",
"LWPT_KUBROW_ANTIGEN",
"LWPT_KUBROW_MUTAGEN"
]; ];
if (supportedModularParts.includes(item.partType)) { if (supportedModularParts.includes(item.partType)) {
const option = document.createElement("option"); const option = document.createElement("option");
@ -361,7 +396,13 @@ function updateInventory() {
"/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit", "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit", "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit", "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit" "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
"/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
]; ];
// Populate inventory route // Populate inventory route
@ -384,7 +425,8 @@ function updateInventory() {
"Hoverboards", "Hoverboards",
"OperatorAmps", "OperatorAmps",
"MechSuits", "MechSuits",
"MoaPets" "MoaPets",
"KubrowPets"
].forEach(category => { ].forEach(category => {
document.getElementById(category + "-list").innerHTML = ""; document.getElementById(category + "-list").innerHTML = "";
data[category].forEach(item => { data[category].forEach(item => {
@ -419,17 +461,35 @@ function updateInventory() {
category != "SpaceSuits" && category != "SpaceSuits" &&
category != "Sentinels" && category != "Sentinels" &&
category != "Hoverboards" && category != "Hoverboards" &&
category != "MechSuits" category != "MechSuits" &&
category != "MoaPets" &&
category != "KubrowPets"
) { ) {
maxXP /= 2; maxXP /= 2;
} }
if (item.XP < maxXP) { let anyExaltedMissingXP = false;
if (item.XP >= maxXP && "exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
if (exaltedItem) {
const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
if (exaltedItem.XP < exaltedCap) {
anyExaltedMissingXP = true;
break;
}
}
}
}
if (item.XP < maxXP || anyExaltedMissingXP) {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
event.preventDefault(); event.preventDefault();
if (item.XP < maxXP) {
addGearExp(category, item.ItemId.$oid, maxXP - item.XP); addGearExp(category, item.ItemId.$oid, maxXP - item.XP);
}
if ("exalted" in itemMap[item.ItemType]) { if ("exalted" in itemMap[item.ItemType]) {
for (const exaltedType of itemMap[item.ItemType].exalted) { for (const exaltedType of itemMap[item.ItemType].exalted) {
const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType); const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
@ -462,6 +522,23 @@ function updateInventory() {
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg>`; a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"/></svg>`;
td.appendChild(a); td.appendChild(a);
} }
if (category == "KubrowPets") {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
maturePet(item.ItemId.$oid, !item.Details.IsPuppy);
};
if (item.Details.IsPuppy) {
a.title = loc("code_mature");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M112 48a48 48 0 1 1 96 0 48 48 0 1 1 -96 0zm40 304l0 128c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-223.1L59.4 304.5c-9.1 15.1-28.8 20-43.9 10.9s-20-28.8-10.9-43.9l58.3-97c17.4-28.9 48.6-46.6 82.3-46.6l29.7 0c33.7 0 64.9 17.7 82.3 46.6l58.3 97c9.1 15.1 4.2 34.8-10.9 43.9s-34.8 4.2-43.9-10.9L232 256.9 232 480c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-128-16 0z"/></svg>`;
} else {
a.title = loc("code_unmature");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M256 64A64 64 0 1 0 128 64a64 64 0 1 0 128 0zM152.9 169.3c-23.7-8.4-44.5-24.3-58.8-45.8L74.6 94.2C64.8 79.5 45 75.6 30.2 85.4s-18.7 29.7-8.9 44.4L40.9 159c18.1 27.1 42.8 48.4 71.1 62.4L112 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-96 32 0 0 96c0 17.7 14.3 32 32 32s32-14.3 32-32l0-258.4c29.1-14.2 54.4-36.2 72.7-64.2l18.2-27.9c9.6-14.8 5.4-34.6-9.4-44.3s-34.6-5.5-44.3 9.4L291 122.4c-21.8 33.4-58.9 53.6-98.8 53.6c-12.6 0-24.9-2-36.6-5.8c-.9-.3-1.8-.7-2.7-.9z"/></svg>`;
}
td.appendChild(a);
}
if (category == "Suits") { if (category == "Suits") {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = "/webui/powersuit/" + item.ItemId.$oid; a.href = "/webui/powersuit/" + item.ItemId.$oid;
@ -500,6 +577,82 @@ function updateInventory() {
}); });
}); });
document.getElementById("EvolutionProgress-list").innerHTML = "";
data.EvolutionProgress?.forEach(item => {
const datalist = document.getElementById("datalist-EvolutionProgress");
const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
if (optionToRemove) {
datalist.removeChild(optionToRemove);
}
const tr = document.createElement("tr");
tr.setAttribute("data-item-type", item.ItemType);
{
const td = document.createElement("td");
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
if (item.Rank != null) {
td.textContent += " | " + loc("code_rank") + ": [" + item.Rank + "/5]";
}
tr.appendChild(td);
}
{
const td = document.createElement("td");
td.classList = "text-end text-nowrap";
if (item.Rank < 5) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: 5 }]);
};
a.title = loc("code_maxRank");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M214.6 41.4c-12.5-12.5-32.8-12.5-45.3 0l-160 160c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L160 141.2V448c0 17.7 14.3 32 32 32s32-14.3 32-32V141.2L329.4 246.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-160-160z"/></svg>`;
td.appendChild(a);
}
if ((permanentEvolutionWeapons.has(item.ItemType) && item.Rank > 0) || item.Rank > 1) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank - 1 }]);
};
a.title = loc("code_rankDown");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/></svg>`;
td.appendChild(a);
}
if (item.Rank < 5) {
const a = document.createElement("a");
a.href = "#";
a.onclick = function (event) {
event.preventDefault();
setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank + 1 }]);
};
a.title = loc("code_rankUp");
a.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path d="M233.4 105.4c12.5-12.5 32.8-12.5 45.3 0l192 192c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L256 173.3 86.6 342.6c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l192-192z"/></svg>`;
td.appendChild(a);
}
tr.appendChild(td);
}
document.getElementById("EvolutionProgress-list").appendChild(tr);
});
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;
}
// Populate quests route // Populate quests route
document.getElementById("QuestKeys-list").innerHTML = ""; document.getElementById("QuestKeys-list").innerHTML = "";
data.QuestKeys.forEach(item => { data.QuestKeys.forEach(item => {
@ -611,18 +764,18 @@ function updateInventory() {
}); });
const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option"); const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
const form = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]"); const formQuestKeys = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]"); const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
if (datalistQuestKeys.length === 0) { if (datalistQuestKeys.length === 0) {
form.classList.add("disabled"); formQuestKeys.classList.add("disabled");
form.querySelector("input").disabled = true; formQuestKeys.querySelector("input").disabled = true;
form.querySelector("button").disabled = true; formQuestKeys.querySelector("button").disabled = true;
giveAllQuestButton.disabled = true; giveAllQuestButton.disabled = true;
} else { } else {
form.classList.remove("disabled"); formQuestKeys.classList.remove("disabled");
form.querySelector("input").disabled = false; formQuestKeys.querySelector("input").disabled = false;
form.querySelector("button").disabled = false; formQuestKeys.querySelector("button").disabled = false;
giveAllQuestButton.disabled = false; giveAllQuestButton.disabled = false;
} }
@ -752,7 +905,7 @@ function updateInventory() {
{ {
const td = document.createElement("td"); const td = document.createElement("td");
td.classList = "text-end text-nowrap"; td.classList = "text-end text-nowrap";
{ if (maxRank != 0) {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = "#"; a.href = "#";
a.onclick = function (event) { a.onclick = function (event) {
@ -870,12 +1023,12 @@ function doAcquireEquipment(category) {
}); });
} }
function doAcquireModularEquipment(category, ItemType) { function doAcquireModularEquipment(category, WeaponType) {
let requiredParts; let requiredParts;
let ModularParts = []; let Parts = [];
switch (category) { switch (category) {
case "HoverBoards": case "HoverBoards":
ItemType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit"; WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit";
requiredParts = ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"]; requiredParts = ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
break; break;
case "OperatorAmps": case "OperatorAmps":
@ -891,20 +1044,33 @@ function doAcquireModularEquipment(category, ItemType) {
requiredParts = ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"]; requiredParts = ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
break; break;
case "MoaPets": case "MoaPets":
if (ItemType == "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") { if (WeaponType == "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
requiredParts = ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"]; requiredParts = ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"];
} else { } else {
requiredParts = ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"]; requiredParts = ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"];
} }
break; 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;
} }
requiredParts.forEach(part => { requiredParts.forEach(part => {
const partName = getKey(document.getElementById("acquire-type-" + category + "-" + part)); const partName = getKey(document.getElementById("acquire-type-" + category + "-" + part));
if (partName) { if (partName) {
ModularParts.push(partName); Parts.push(partName);
} }
}); });
if (ModularParts.length != requiredParts.length) { if (Parts.length != requiredParts.length) {
let isFirstPart = true; let isFirstPart = true;
requiredParts.forEach(part => { requiredParts.forEach(part => {
const partSelector = document.getElementById("acquire-type-" + category + "-" + part); const partSelector = document.getElementById("acquire-type-" + category + "-" + part);
@ -920,20 +1086,80 @@ function doAcquireModularEquipment(category, ItemType) {
} }
}); });
} else { } else {
const mapping = {
LongGuns: {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
"/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
},
Pistols: {
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
"/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
"/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
"/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
},
MoaPets: {
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
}
};
Parts.forEach(part => {
const categoryMap = mapping[category];
if (categoryMap && categoryMap[part]) {
WeaponType = categoryMap[part];
}
});
if (category == "KubrowPets") Parts.unshift(WeaponType);
revalidateAuthz(() => { revalidateAuthz(() => {
const req = $.post({ const req = $.post({
url: "/custom/addModularEquipment?" + window.authz, url: "/api/modularWeaponCrafting.php?" + window.authz,
contentType: "application/json", contentType: "application/octet-stream",
data: JSON.stringify({ data: JSON.stringify({
ItemType, WeaponType,
ModularParts Parts,
isWebUi: true
}) })
}); });
req.done(() => { req.done(() => {
const mainInput = document.getElementById("acquire-type-" + category); const mainInput = document.getElementById("acquire-type-" + category);
if (mainInput) { if (mainInput) {
mainInput.value = ""; mainInput.value = "";
document.getElementById("modular-" + category).style.display = "none"; if (category === "MoaPets") {
const modularFieldsMoa = document.getElementById("modular-MoaPets-Moa");
const modularFieldsZanuka = document.getElementById("modular-MoaPets-Zanuka");
modularFieldsZanuka.style.display = "none";
modularFieldsMoa.style.display = "none";
} else if (category === "KubrowPets") {
const modularFieldsCatbrow = document.getElementById("modular-KubrowPets-Catbrow");
const modularFieldsKubrow = document.getElementById("modular-KubrowPets-Kubrow");
modularFieldsCatbrow.style.display = "none";
modularFieldsKubrow.style.display = "none";
} else {
const modularFields = document.getElementById("modular-" + category);
modularFields.style.display = "none";
}
} }
requiredParts.forEach(part => { requiredParts.forEach(part => {
document.getElementById("acquire-type-" + category + "-" + part).value = ""; document.getElementById("acquire-type-" + category + "-" + part).value = "";
@ -944,6 +1170,16 @@ function doAcquireModularEquipment(category, ItemType) {
} }
} }
function doAcquireEvolution() {
const uniqueName = getKey(document.getElementById("acquire-type-EvolutionProgress"));
if (!uniqueName) {
$("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
return;
}
setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
}
$("input[list]").on("input", function () { $("input[list]").on("input", function () {
$(this).removeClass("is-invalid"); $(this).removeClass("is-invalid");
}); });
@ -981,6 +1217,40 @@ function addMissingEquipment(categories) {
} }
} }
function addMissingEvolutionProgress() {
const requests = [];
document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
const uniqueName = elm.getAttribute("data-key");
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);
}
}
function maxRankAllEvolutions() {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(data => {
const requests = [];
data.EvolutionProgress.forEach(item => {
if (item.Rank < 5) {
requests.push({
ItemType: item.ItemType,
Rank: 5
});
}
});
if (Object.keys(requests).length > 0) {
return setEvolutionProgress(requests);
}
toast(loc("code_noEquipmentToRankUp"));
});
}
function maxRankAllEquipment(categories) { function maxRankAllEquipment(categories) {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1"); const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
@ -1085,6 +1355,18 @@ function renameGear(category, oid, name) {
} }
function disposeOfGear(category, oid) { function disposeOfGear(category, oid) {
if (category == "KubrowPets") {
revalidateAuthz(() => {
$.post({
url: "/api/releasePet.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
Recipe: "webui",
petId: oid
})
});
});
} else {
const data = { const data = {
SellCurrency: "SC_RegularCredits", SellCurrency: "SC_RegularCredits",
SellPrice: 0, SellPrice: 0,
@ -1104,6 +1386,7 @@ function disposeOfGear(category, oid) {
}); });
}); });
} }
}
function disposeOfItems(category, type, count) { function disposeOfItems(category, type, count) {
const data = { const data = {
@ -1140,6 +1423,34 @@ function gildEquipment(category, oid) {
}); });
} }
function maturePet(oid, revert) {
revalidateAuthz(() => {
$.post({
url: "/api/maturePet.php?" + window.authz,
contentType: "application/octet-stream",
data: JSON.stringify({
petId: oid,
revert
})
}).done(function () {
updateInventory();
});
});
}
function setEvolutionProgress(requests) {
revalidateAuthz(() => {
const req = $.post({
url: "/custom/setEvolutionProgress?" + window.authz,
contentType: "application/json",
data: JSON.stringify(requests)
});
req.done(() => {
updateInventory();
});
});
}
function doAcquireMiscItems() { function doAcquireMiscItems() {
const uniqueName = getKey(document.getElementById("miscitem-type")); const uniqueName = getKey(document.getElementById("miscitem-type"));
if (!uniqueName) { if (!uniqueName) {
@ -1459,6 +1770,39 @@ function doAddAllMods() {
}); });
} }
function doRemoveUnrankedMods() {
revalidateAuthz(() => {
const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
req.done(inventory => {
window.itemListPromise.then(itemMap => {
$.post({
url: "/api/sell.php?" + window.authz,
contentType: "text/plain",
data: JSON.stringify({
SellCurrency: "SC_RegularCredits",
SellPrice: 0,
Items: {
Upgrades: inventory.RawUpgrades.filter(
x => !itemMap[x.ItemType]?.parazon && x.ItemCount > 0
).map(x => ({ String: x.ItemType, Count: x.ItemCount }))
}
})
}).done(function () {
updateInventory();
});
});
});
});
}
function doAddMissingMaxRankMods() {
revalidateAuthz(() => {
fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => {
updateInventory();
});
});
}
// Powersuit Route // Powersuit Route
single.getRoute("#powersuit-route").on("beforeload", function () { single.getRoute("#powersuit-route").on("beforeload", function () {
@ -1589,32 +1933,60 @@ function handleModularSelection(category) {
} }
} }
{ {
const supportedModularInventoryCategory = ["OperatorAmps", "Melee", "LongGuns", "Pistols", "MoaPets"]; const supportedModularInventoryCategory = ["OperatorAmps", "Melee", "LongGuns", "Pistols", "MoaPets", "KubrowPets"];
supportedModularInventoryCategory.forEach(inventoryCategory => { supportedModularInventoryCategory.forEach(inventoryCategory => {
document.getElementById("acquire-type-" + inventoryCategory).addEventListener("input", function () { document.getElementById("acquire-type-" + inventoryCategory).addEventListener("input", function () {
const modularFields = document.getElementById("modular-" + inventoryCategory); const modularFields = document.getElementById("modular-" + inventoryCategory);
const modularFieldsZanuka = const modularFieldsMoa = document.getElementById("modular-MoaPets-Moa");
inventoryCategory === "MoaPets" const modularFieldsZanuka = document.getElementById("modular-MoaPets-Zanuka");
? document.getElementById("modular-" + inventoryCategory + "-Zanuka") const modularFieldsCatbrow = document.getElementById("modular-KubrowPets-Catbrow");
: null; const modularFieldsKubrow = document.getElementById("modular-KubrowPets-Kubrow");
const key = getKey(this); const key = getKey(this);
if (webUiModularWeapons.includes(key)) { if (webUiModularWeapons.includes(key)) {
if (key === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit" && modularFieldsZanuka) { if (inventoryCategory === "MoaPets") {
modularFields.style.display = "none"; if (key === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
modularFieldsMoa.style.display = "none";
modularFieldsZanuka.style.display = ""; modularFieldsZanuka.style.display = "";
} else if (key === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") { } else if (key === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
modularFields.style.display = ""; modularFieldsMoa.style.display = "";
if (modularFieldsZanuka) {
modularFieldsZanuka.style.display = "none"; 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)
) {
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)
) {
modularFieldsCatbrow.style.display = "none";
modularFieldsKubrow.style.display = "";
} else {
modularFieldsCatbrow.style.display = "none";
modularFieldsKubrow.style.display = "none";
}
} else { } else {
modularFields.style.display = ""; modularFields.style.display = "";
} }
} else {
if (inventoryCategory === "MoaPets") {
modularFieldsZanuka.style.display = "none";
modularFieldsMoa.style.display = "none";
} else if (inventoryCategory === "KubrowPets") {
modularFieldsCatbrow.style.display = "none";
modularFieldsKubrow.style.display = "none";
} else { } else {
modularFields.style.display = "none"; modularFields.style.display = "none";
if (modularFieldsZanuka) {
modularFieldsZanuka.style.display = "none";
} }
} }
}); });

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Anzahl der Umrollversuche`, code_rerollsNumber: `Anzahl der Umrollversuche`,
code_viewStats: `Statistiken anzeigen`, code_viewStats: `Statistiken anzeigen`,
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `Anzahl`, code_count: `Anzahl`,
code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`, code_focusAllUnlocked: `Alle Fokus-Schulen sind bereits freigeschaltet.`,
code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`, code_focusUnlocked: `|COUNT| neue Fokus-Schulen freigeschaltet! Ein Inventar-Update wird benötigt, damit die Änderungen im Spiel sichtbar werden. Die Sternenkarte zu besuchen, sollte der einfachste Weg sein, dies auszulösen.`,
@ -54,10 +56,13 @@ dict = {
code_completed: `Abgeschlossen`, code_completed: `Abgeschlossen`,
code_active: `Aktiv`, code_active: `Aktiv`,
code_pigment: `Pigment`, code_pigment: `Pigment`,
code_mature: `Für den Kampf auswachsen lassen`,
code_unmature: `Genetisches Altern zurücksetzen`,
login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`, login_description: `Melde dich mit deinem OpenWF-Account an (denselben Angaben wie im Spiel, wenn du dich mit diesem Server verbindest).`,
login_emailLabel: `E-Mail-Adresse`, login_emailLabel: `E-Mail-Adresse`,
login_passwordLabel: `Passwort`, login_passwordLabel: `Passwort`,
login_loginButton: `Anmelden`, login_loginButton: `Anmelden`,
login_registerButton: `Registrieren`,
navbar_logout: `Abmelden`, navbar_logout: `Abmelden`,
navbar_renameAccount: `Account umbenennen`, navbar_renameAccount: `Account umbenennen`,
navbar_deleteAccount: `Account löschen`, navbar_deleteAccount: `Account löschen`,
@ -80,18 +85,22 @@ dict = {
inventory_operatorAmps: `Verstärker`, inventory_operatorAmps: `Verstärker`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bestien`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`, inventory_bulkAddSuits: `Fehlende Warframes hinzufügen`,
inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`, inventory_bulkAddWeapons: `Fehlende Waffen hinzufügen`,
inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`, inventory_bulkAddSpaceSuits: `Fehlende Archwings hinzufügen`,
inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`, inventory_bulkAddSpaceWeapons: `Fehlende Archwing-Waffen hinzufügen`,
inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`, inventory_bulkAddSentinels: `Fehlende Wächter hinzufügen`,
inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`, inventory_bulkAddSentinelWeapons: `Fehlende Wächter-Waffen hinzufügen`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`, inventory_bulkRankUpSuits: `Alle Warframes auf Max. Rang`,
inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`, inventory_bulkRankUpWeapons: `Alle Waffen auf Max. Rang`,
inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`, inventory_bulkRankUpSpaceSuits: `Alle Archwings auf Max. Rang`,
inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`, inventory_bulkRankUpSpaceWeapons: `Alle Archwing-Waffen auf Max. Rang`,
inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`, inventory_bulkRankUpSentinels: `Alle Wächter auf Max. Rang`,
inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`, inventory_bulkRankUpSentinelWeapons: `Alle Wächter-Waffen auf Max. Rang`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Alle Quests abschließen`, quests_completeAll: `Alle Quests abschließen`,
@ -111,7 +120,9 @@ dict = {
mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`, mods_fingerprintHelp: `Benötigst du Hilfe mit dem Fingerabdruck?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Fehlende Mods hinzufügen`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `Mods ohne Rang entfernen`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`, cheats_administratorRequirement: `Du musst Administrator sein, um diese Funktion nutzen zu können. Um Administrator zu werden, füge <code>|DISPLAYNAME|</code> zu <code>administratorNames</code> in der config.json hinzu.`,
cheats_server: `Server`, cheats_server: `Server`,
cheats_skipTutorial: `Tutorial überspringen`, cheats_skipTutorial: `Tutorial überspringen`,
@ -123,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Unendlich Endo`, cheats_infiniteEndo: `Unendlich Endo`,
cheats_infiniteRegalAya: `Unendlich Reines Aya`, cheats_infiniteRegalAya: `Unendlich Reines Aya`,
cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`, cheats_infiniteHelminthMaterials: `Unendlich Helminth-Materialien`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`, cheats_unlockAllShipFeatures: `Alle Schiffs-Funktionen freischalten`,
cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`, cheats_unlockAllShipDecorations: `Alle Schiffsdekorationen freischalten`,
cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`, cheats_unlockAllFlavourItems: `Alle <abbr title=\"Animationssets, Glyphen, Farbpaletten usw.\">Sammlerstücke</abbr> freischalten`,

View File

@ -33,6 +33,8 @@ dict = {
code_rerollsNumber: `Number of rerolls`, code_rerollsNumber: `Number of rerolls`,
code_viewStats: `View Stats`, code_viewStats: `View Stats`,
code_rank: `Rank`, code_rank: `Rank`,
code_rankUp: `Rank up`,
code_rankDown: `Rank down`,
code_count: `Count`, code_count: `Count`,
code_focusAllUnlocked: `All focus schools are already unlocked.`, code_focusAllUnlocked: `All focus schools are already unlocked.`,
code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`, code_focusUnlocked: `Unlocked |COUNT| new focus schools! An inventory update will be needed for the changes to be reflected in-game. Visiting the navigation should be the easiest way to trigger that.`,
@ -53,10 +55,13 @@ dict = {
code_completed: `Completed`, code_completed: `Completed`,
code_active: `Active`, code_active: `Active`,
code_pigment: `Pigment`, code_pigment: `Pigment`,
code_mature: `Mature for combat`,
code_unmature: `Regress genetic aging`,
login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`, login_description: `Login using your OpenWF account credentials (same as in-game when connecting to this server).`,
login_emailLabel: `Email address`, login_emailLabel: `Email address`,
login_passwordLabel: `Password`, login_passwordLabel: `Password`,
login_loginButton: `Login`, login_loginButton: `Login`,
login_registerButton: `Register`,
navbar_logout: `Logout`, navbar_logout: `Logout`,
navbar_renameAccount: `Rename Account`, navbar_renameAccount: `Rename Account`,
navbar_deleteAccount: `Delete Account`, navbar_deleteAccount: `Delete Account`,
@ -79,18 +84,22 @@ dict = {
inventory_operatorAmps: `Amps`, inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Beasts`,
inventory_evolutionProgress: `Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Add Missing Warframes`, inventory_bulkAddSuits: `Add Missing Warframes`,
inventory_bulkAddWeapons: `Add Missing Weapons`, inventory_bulkAddWeapons: `Add Missing Weapons`,
inventory_bulkAddSpaceSuits: `Add Missing Archwings`, inventory_bulkAddSpaceSuits: `Add Missing Archwings`,
inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`, inventory_bulkAddSpaceWeapons: `Add Missing Archwing Weapons`,
inventory_bulkAddSentinels: `Add Missing Sentinels`, inventory_bulkAddSentinels: `Add Missing Sentinels`,
inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`, inventory_bulkAddSentinelWeapons: `Add Missing Sentinel Weapons`,
inventory_bulkAddEvolutionProgress: `Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Max Rank All Warframes`, inventory_bulkRankUpSuits: `Max Rank All Warframes`,
inventory_bulkRankUpWeapons: `Max Rank All Weapons`, inventory_bulkRankUpWeapons: `Max Rank All Weapons`,
inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`, inventory_bulkRankUpSpaceSuits: `Max Rank All Archwings`,
inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`, inventory_bulkRankUpSpaceWeapons: `Max Rank All Archwing Weapons`,
inventory_bulkRankUpSentinels: `Max Rank All Sentinels`, inventory_bulkRankUpSentinels: `Max Rank All Sentinels`,
inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`, inventory_bulkRankUpSentinelWeapons: `Max Rank All Sentinel Weapons`,
inventory_bulkRankUpEvolutionProgress: `Max Rank All Incarnon Evolution Progress`,
quests_list: `Quests`, quests_list: `Quests`,
quests_completeAll: `Complete All Quests`, quests_completeAll: `Complete All Quests`,
@ -110,7 +119,9 @@ dict = {
mods_fingerprintHelp: `Need help with the fingerprint?`, mods_fingerprintHelp: `Need help with the fingerprint?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Add Missing Mods`, mods_addMissingUnrankedMods: `Add Missing Unranked Mods`,
mods_removeUnranked: `Remove Unranked Mods`,
mods_addMissingMaxRankMods: `Add Missing Max Rank Mods`,
cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`, cheats_administratorRequirement: `You must be an administrator to use this feature. To become an administrator, add <code>|DISPLAYNAME|</code> to <code>administratorNames</code> in the config.json.`,
cheats_server: `Server`, cheats_server: `Server`,
cheats_skipTutorial: `Skip Tutorial`, cheats_skipTutorial: `Skip Tutorial`,
@ -122,6 +133,7 @@ dict = {
cheats_infiniteEndo: `Infinite Endo`, cheats_infiniteEndo: `Infinite Endo`,
cheats_infiniteRegalAya: `Infinite Regal Aya`, cheats_infiniteRegalAya: `Infinite Regal Aya`,
cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`, cheats_infiniteHelminthMaterials: `Infinite Helminth Materials`,
cheats_dontSubtractConsumables: `Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Unlock All Ship Features`, cheats_unlockAllShipFeatures: `Unlock All Ship Features`,
cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`, cheats_unlockAllShipDecorations: `Unlock All Ship Decorations`,
cheats_unlockAllFlavourItems: `Unlock All <abbr title=\"Animation Sets, Glyphs, Palettes, etc.\">Flavor Items</abbr>`, cheats_unlockAllFlavourItems: `Unlock All <abbr title=\"Animation Sets, Glyphs, Palettes, etc.\">Flavor Items</abbr>`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Cantidad de reintentos`, code_rerollsNumber: `Cantidad de reintentos`,
code_viewStats: `Ver estadísticas`, code_viewStats: `Ver estadísticas`,
code_rank: `Rango`, code_rank: `Rango`,
code_rankUp: `Subir de rango`,
code_rankDown: `Bajar de rango`,
code_count: `Cantidad`, code_count: `Cantidad`,
code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`, code_focusAllUnlocked: `Todas las escuelas de enfoque ya están desbloqueadas.`,
code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`, code_focusUnlocked: `¡Desbloqueadas |COUNT| nuevas escuelas de enfoque! Se necesita una actualización del inventario para reflejar los cambios en el juego. Visitar la navegación debería ser la forma más sencilla de activarlo.`,
@ -54,10 +56,13 @@ dict = {
code_completed: `Completada`, code_completed: `Completada`,
code_active: `Activa`, code_active: `Activa`,
code_pigment: `Pigmento`, code_pigment: `Pigmento`,
code_mature: `Listo para el combate`,
code_unmature: `Regresar el envejecimiento genético`,
login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`, login_description: `Inicia sesión con las credenciales de tu cuenta OpenWF (las mismas que usas en el juego al conectarte a este servidor).`,
login_emailLabel: `Dirección de correo electrónico`, login_emailLabel: `Dirección de correo electrónico`,
login_passwordLabel: `Contraseña`, login_passwordLabel: `Contraseña`,
login_loginButton: `Iniciar sesión`, login_loginButton: `Iniciar sesión`,
login_registerButton: `Registrarse`,
navbar_logout: `Cerrar sesión`, navbar_logout: `Cerrar sesión`,
navbar_renameAccount: `Renombrar cuenta`, navbar_renameAccount: `Renombrar cuenta`,
navbar_deleteAccount: `Eliminar cuenta`, navbar_deleteAccount: `Eliminar cuenta`,
@ -80,18 +85,22 @@ dict = {
inventory_operatorAmps: `Amps`, inventory_operatorAmps: `Amps`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bestias`,
inventory_evolutionProgress: `Progreso de evolución Incarnon`,
inventory_bulkAddSuits: `Agregar Warframes faltantes`, inventory_bulkAddSuits: `Agregar Warframes faltantes`,
inventory_bulkAddWeapons: `Agregar armas faltantes`, inventory_bulkAddWeapons: `Agregar armas faltantes`,
inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`, inventory_bulkAddSpaceSuits: `Agregar Archwings faltantes`,
inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`, inventory_bulkAddSpaceWeapons: `Agregar armas Archwing faltantes`,
inventory_bulkAddSentinels: `Agregar centinelas faltantes`, inventory_bulkAddSentinels: `Agregar centinelas faltantes`,
inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`, inventory_bulkAddSentinelWeapons: `Agregar armas de centinela faltantes`,
inventory_bulkAddEvolutionProgress: `Completar el progreso de evolución Incarnon faltante`,
inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`, inventory_bulkRankUpSuits: `Maximizar rango de todos los Warframes`,
inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`, inventory_bulkRankUpWeapons: `Maximizar rango de todas las armas`,
inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`, inventory_bulkRankUpSpaceSuits: `Maximizar rango de todos los Archwings`,
inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`, inventory_bulkRankUpSpaceWeapons: `Maximizar rango de todas las armas Archwing`,
inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`, inventory_bulkRankUpSentinels: `Maximizar rango de todos los centinelas`,
inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`, inventory_bulkRankUpSentinelWeapons: `Maximizar rango de todas las armas de centinela`,
inventory_bulkRankUpEvolutionProgress: `Maximizar todo el progreso de evolución Incarnon`,
quests_list: `Misiones`, quests_list: `Misiones`,
quests_completeAll: `Completar todas las misiones`, quests_completeAll: `Completar todas las misiones`,
@ -111,7 +120,9 @@ dict = {
mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`, mods_fingerprintHelp: `¿Necesitas ayuda con la huella digital?`,
mods_rivens: `Agrietados`, mods_rivens: `Agrietados`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Agregar mods faltantes`, mods_addMissingUnrankedMods: `Agregar mods sin rango faltantes`,
mods_removeUnranked: `Quitar mods sin rango`,
mods_addMissingMaxRankMods: `Agregar mods de rango máximo faltantes`,
cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`, cheats_administratorRequirement: `Debes ser administrador para usar esta función. Para convertirte en administrador, agrega <code>|DISPLAYNAME|</code> a <code>administratorNames</code> en el archivo config.json.`,
cheats_server: `Servidor`, cheats_server: `Servidor`,
cheats_skipTutorial: `Omitir tutorial`, cheats_skipTutorial: `Omitir tutorial`,
@ -123,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Endo infinito`, cheats_infiniteEndo: `Endo infinito`,
cheats_infiniteRegalAya: `Aya Real infinita`, cheats_infiniteRegalAya: `Aya Real infinita`,
cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`, cheats_infiniteHelminthMaterials: `Materiales Helminto infinitos`,
cheats_dontSubtractConsumables: `No restar consumibles`,
cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`, cheats_unlockAllShipFeatures: `Desbloquear todas las funciones de nave`,
cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`, cheats_unlockAllShipDecorations: `Desbloquear todas las decoraciones de nave`,
cheats_unlockAllFlavourItems: `Desbloquear todos los <abbr title="Conjuntos de animaciones, glifos, paletas, etc.">ítems estéticos</abbr>`, cheats_unlockAllFlavourItems: `Desbloquear todos los <abbr title="Conjuntos de animaciones, glifos, paletas, etc.">ítems estéticos</abbr>`,

View File

@ -25,15 +25,17 @@ dict = {
code_renamePrompt: `Nouveau nom :`, code_renamePrompt: `Nouveau nom :`,
code_remove: `Retirer`, code_remove: `Retirer`,
code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`, code_addItemsConfirm: `Ajouter |COUNT| items à l'inventaire ?`,
code_succRankUp: `[UNTRANSLATED] Successfully ranked up.`, code_succRankUp: `Montée de niveau effectuée.`,
code_noEquipmentToRankUp: `No equipment to rank up.`, code_noEquipmentToRankUp: `Aucun équipement à monter de niveau.`,
code_succAdded: `Ajouté.`, code_succAdded: `Ajouté.`,
code_succRemoved: `[UNTRANSLATED] Successfully removed.`, code_succRemoved: `Retiré.`,
code_buffsNumber: `Nombre de buffs`, code_buffsNumber: `Nombre de buffs`,
code_cursesNumber: `Nombre de débuffs`, code_cursesNumber: `Nombre de débuffs`,
code_rerollsNumber: `Nombre de rerolls`, code_rerollsNumber: `Nombre de rerolls`,
code_viewStats: `Voir les stats`, code_viewStats: `Voir les stats`,
code_rank: `Rang`, code_rank: `Rang`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `Quantité`, code_count: `Quantité`,
code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`, code_focusAllUnlocked: `Les écoles de Focus sont déjà déverrouillées.`,
code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`, code_focusUnlocked: `|COUNT| écoles de Focus déverrouillées ! Synchronisation de l'inventaire nécessaire.`,
@ -45,19 +47,22 @@ dict = {
code_zanukaA: `Molosse Dorma`, code_zanukaA: `Molosse Dorma`,
code_zanukaB: `Molosse Bhaira`, code_zanukaB: `Molosse Bhaira`,
code_zanukaC: `Molosse Hec`, code_zanukaC: `Molosse Hec`,
code_stage: `[UNTRANSLATED] Stage`, code_stage: `Étape`,
code_complete: `[UNTRANSLATED] Complete`, code_complete: `Compléter`,
code_nextStage: `[UNTRANSLATED] Next stage`, code_nextStage: `Étape suivante`,
code_prevStage: `[UNTRANSLATED] Previous stage`, code_prevStage: `Étape précédente`,
code_reset: `[UNTRANSLATED] Reset`, code_reset: `Réinitialiser`,
code_setInactive: `[UNTRANSLATED] Make the quest inactive`, code_setInactive: `Rendre la quête inactive`,
code_completed: `[UNTRANSLATED] Completed`, code_completed: `Complétée`,
code_active: `[UNTRANSLATED] Active`, code_active: `Active`,
code_pigment: `Pigment`, code_pigment: `Pigment`,
code_mature: `Maturer pour le combat`,
code_unmature: `Régrésser l'âge génétique`,
login_description: `Connexion avec les informations de connexion OpenWF.`, login_description: `Connexion avec les informations de connexion OpenWF.`,
login_emailLabel: `Email`, login_emailLabel: `Email`,
login_passwordLabel: `Mot de passe`, login_passwordLabel: `Mot de passe`,
login_loginButton: `Connexion`, login_loginButton: `Connexion`,
login_registerButton: `[UNTRANSLATED] Register`,
navbar_logout: `Déconnexion`, navbar_logout: `Déconnexion`,
navbar_renameAccount: `Renommer le compte`, navbar_renameAccount: `Renommer le compte`,
navbar_deleteAccount: `Supprimer le compte`, navbar_deleteAccount: `Supprimer le compte`,
@ -80,18 +85,22 @@ dict = {
inventory_operatorAmps: `Amplificateurs`, inventory_operatorAmps: `Amplificateurs`,
inventory_hoverboards: `K-Drives`, inventory_hoverboards: `K-Drives`,
inventory_moaPets: `Moa`, inventory_moaPets: `Moa`,
inventory_kubrowPets: `Bêtes`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `Ajouter les Warframes manquantes`, inventory_bulkAddSuits: `Ajouter les Warframes manquantes`,
inventory_bulkAddWeapons: `Ajouter les armes manquantes`, inventory_bulkAddWeapons: `Ajouter les armes manquantes`,
inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`, inventory_bulkAddSpaceSuits: `Ajouter les Archwings manquants`,
inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`, inventory_bulkAddSpaceWeapons: `Ajouter les armes d'Archwing manquantes`,
inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`, inventory_bulkAddSentinels: `Ajouter les Sentinelles manquantes`,
inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`, inventory_bulkAddSentinelWeapons: `Ajouter les armes de Sentinelles manquantes`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `Toutes les Warframes rang max`, inventory_bulkRankUpSuits: `Toutes les Warframes rang max`,
inventory_bulkRankUpWeapons: `Toutes les armes rang max`, inventory_bulkRankUpWeapons: `Toutes les armes rang max`,
inventory_bulkRankUpSpaceSuits: `Tous les Archwings rang max`, inventory_bulkRankUpSpaceSuits: `Tous les Archwings rang max`,
inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing rang max`, inventory_bulkRankUpSpaceWeapons: `Toutes les armes d'Archwing rang max`,
inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`, inventory_bulkRankUpSentinels: `Toutes les Sentinelles rang max`,
inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`, inventory_bulkRankUpSentinelWeapons: `Toutes les armes de Sentinelles rang max`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `Quêtes`, quests_list: `Quêtes`,
quests_completeAll: `Compléter toutes les quêtes`, quests_completeAll: `Compléter toutes les quêtes`,
@ -104,14 +113,16 @@ dict = {
currency_PrimeTokens: `Aya Raffiné`, currency_PrimeTokens: `Aya Raffiné`,
currency_owned: `|COUNT| possédés.`, currency_owned: `|COUNT| possédés.`,
powersuit_archonShardsLabel: `Emplacements de fragments d'Archonte`, powersuit_archonShardsLabel: `Emplacements de fragments d'Archonte`,
powersuit_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations.`, powersuit_archonShardsDescription: `Slots illimités pour appliquer plusieurs améliorations`,
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`, powersuit_archonShardsDescription2: `Un délai sera présent entre l'application des éclats et le chargement en jeu.`,
mods_addRiven: `Ajouter un riven`, mods_addRiven: `Ajouter un riven`,
mods_fingerprint: `Empreinte`, mods_fingerprint: `Empreinte`,
mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`, mods_fingerprintHelp: `Besoin d'aide pour l'empreinte ?`,
mods_rivens: `Rivens`, mods_rivens: `Rivens`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `Ajouter les mods manquants`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`, cheats_administratorRequirement: `Rôle d'administrateur requis pour cette fonctionnalité. Ajoutez <code>|DISPLAYNAME|</code> à la ligne <code>administratorNames</code> dans le fichier config.json.`,
cheats_server: `Serveur`, cheats_server: `Serveur`,
cheats_skipTutorial: `Passer le tutoriel`, cheats_skipTutorial: `Passer le tutoriel`,
@ -123,37 +134,38 @@ dict = {
cheats_infiniteEndo: `Endo infini`, cheats_infiniteEndo: `Endo infini`,
cheats_infiniteRegalAya: `Aya Raffiné infini`, cheats_infiniteRegalAya: `Aya Raffiné infini`,
cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`, cheats_infiniteHelminthMaterials: `Ressources d'Helminth infinies`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`, cheats_unlockAllShipFeatures: `Débloquer tous les segments du vaisseau`,
cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`, cheats_unlockAllShipDecorations: `Débloquer toutes les décorations du vaisseau`,
cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`, cheats_unlockAllFlavourItems: `Débloquer tous les <abbr title=\"Animations, Glyphes, Palettes, etc.\">Flavor Items</abbr>`,
cheats_unlockAllSkins: `Débloquer tous les skins`, cheats_unlockAllSkins: `Débloquer tous les skins`,
cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`, cheats_unlockAllCapturaScenes: `Débloquer toutes les scènes captura`,
cheats_unlockAllDecoRecipes: `[UNTRANSLATED] Unlock All Dojo Deco Recipes`, cheats_unlockAllDecoRecipes: `Débloquer toutes les recherches dojo`,
cheats_universalPolarityEverywhere: `Polarités universelles partout`, cheats_universalPolarityEverywhere: `Polarités universelles partout`,
cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Réacteurs et Catalyseurs partout`,
cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`, cheats_unlockExilusEverywhere: `Adaptateurs Exilus partout`,
cheats_unlockArcanesEverywhere: `Adaptateur d'Arcanes partout`, cheats_unlockArcanesEverywhere: `Adaptateur d'Arcanes partout`,
cheats_noDailyStandingLimits: `Pas de limite de réputation journalière`, cheats_noDailyStandingLimits: `Aucune limite de réputation journalière`,
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`, cheats_noDailyFocusLimit: `Aucune limite journalière de focus`,
cheats_noArgonCrystalDecay: `[UNTRANSLATED] No Argon Crystal Decay`, cheats_noArgonCrystalDecay: `Aucune désintégration des Cristaux d'Argon`,
cheats_noMasteryRankUpCooldown: `[UNTRANSLATED] No Mastery Rank Up Cooldown`, cheats_noMasteryRankUpCooldown: `Aucune attente pour la montée de rang de maîtrise`,
cheats_noVendorPurchaseLimits: `[UNTRANSLATED] No Vendor Purchase Limits`, cheats_noVendorPurchaseLimits: `Aucune limite d'achat chez les PNJ`,
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`, cheats_noDeathMarks: `Aucune marque d'assassin`,
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`, cheats_noKimCooldowns: `Aucun cooldown sur le KIM`,
cheats_instantResourceExtractorDrones: `Ressources de drone d'extraction instantannées`, cheats_instantResourceExtractorDrones: `Ressources de drones d'extraction instantannées`,
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `Aucun dégâts aux drones d'extraction de resources`,
cheats_noDojoRoomBuildStage: `No Dojo Room Build Stage`, cheats_noDojoRoomBuildStage: `Aucune attente (construction des salles)`,
cheats_noDojoDecoBuildStage: `[UNTRANSLATED] No Dojo Deco Build Stage`, cheats_noDojoDecoBuildStage: `Aucune attente (construction des décorations)`,
cheats_fastDojoRoomDestruction: `[UNTRANSLATED] Fast Dojo Room Destruction`, cheats_fastDojoRoomDestruction: `Destruction de salle instantanée (Dojo)`,
cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`, cheats_noDojoResearchCosts: `Aucun coût de recherche (Dojo)`,
cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`, cheats_noDojoResearchTime: `Aucun temps de recherche (Dojo)`,
cheats_fastClanAscension: `[UNTRANSLATED] Fast Clan Ascension`, cheats_fastClanAscension: `Ascension de clan rapide`,
cheats_spoofMasteryRank: `Spoofed Mastery Rank (-1 to disable)`, cheats_spoofMasteryRank: `Rang de maîtrise personnalisé (-1 pour désactiver)`,
cheats_saveSettings: `Sauvegarder les paramètres`, cheats_saveSettings: `Sauvegarder les paramètres`,
cheats_account: `Compte`, cheats_account: `Compte`,
cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`, cheats_unlockAllFocusSchools: `Débloquer toutes les écoles de focus`,
cheats_helminthUnlockAll: `Helminth niveau max`, cheats_helminthUnlockAll: `Helminth niveau max`,
cheats_intrinsicsUnlockAll: `[UNTRANSLATED] Max Rank All Intrinsics`, cheats_intrinsicsUnlockAll: `Inhérences niveau max`,
cheats_changeSupportedSyndicate: `Allégeance`, cheats_changeSupportedSyndicate: `Allégeance`,
cheats_changeButton: `Changer`, cheats_changeButton: `Changer`,
cheats_none: `Aucun`, cheats_none: `Aucun`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `Количество циклов`, code_rerollsNumber: `Количество циклов`,
code_viewStats: `Просмотр характеристики`, code_viewStats: `Просмотр характеристики`,
code_rank: `Ранг`, code_rank: `Ранг`,
code_rankUp: `Повысить Ранг`,
code_rankDown: `Понизить Ранг`,
code_count: `Количество`, code_count: `Количество`,
code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`, code_focusAllUnlocked: `Все школы фокуса уже разблокированы.`,
code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`, code_focusUnlocked: `Разблокировано |COUNT| новых школ фокуса! Для отображения изменений в игре потребуется обновление инвентаря. Посещение навигации — самый простой способ этого добиться.`,
@ -54,10 +56,13 @@ dict = {
code_completed: `Завершено`, code_completed: `Завершено`,
code_active: `Активный`, code_active: `Активный`,
code_pigment: `Пигмент`, code_pigment: `Пигмент`,
code_mature: `Подготовить к сражениям`,
code_unmature: `Регрессия генетического старения`,
login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`, login_description: `Войдите, используя учетные данные OpenWF (те же, что и в игре при подключении к этому серверу).`,
login_emailLabel: `Адрес электронной почты`, login_emailLabel: `Адрес электронной почты`,
login_passwordLabel: `Пароль`, login_passwordLabel: `Пароль`,
login_loginButton: `Войти`, login_loginButton: `Войти`,
login_registerButton: `[UNTRANSLATED] Register`,
navbar_logout: `Выйти`, navbar_logout: `Выйти`,
navbar_renameAccount: `Переименовать аккаунт`, navbar_renameAccount: `Переименовать аккаунт`,
navbar_deleteAccount: `Удалить аккаунт`, navbar_deleteAccount: `Удалить аккаунт`,
@ -80,18 +85,22 @@ dict = {
inventory_operatorAmps: `Усилители`, inventory_operatorAmps: `Усилители`,
inventory_hoverboards: `К-Драйвы`, inventory_hoverboards: `К-Драйвы`,
inventory_moaPets: `МОА`, inventory_moaPets: `МОА`,
inventory_kubrowPets: `Звери`,
inventory_evolutionProgress: `Прогресс эволюции Инкарнонов`,
inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`, inventory_bulkAddSuits: `Добавить отсутствующие варфреймы`,
inventory_bulkAddWeapons: `Добавить отсутствующее оружие`, inventory_bulkAddWeapons: `Добавить отсутствующее оружие`,
inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`, inventory_bulkAddSpaceSuits: `Добавить отсутствующие арчвинги`,
inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`, inventory_bulkAddSpaceWeapons: `Добавить отсутствующее оружие арчвингов`,
inventory_bulkAddSentinels: `Добавить отсутствующих стражей`, inventory_bulkAddSentinels: `Добавить отсутствующих стражей`,
inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`, inventory_bulkAddSentinelWeapons: `Добавить отсутствующее оружие стражей`,
inventory_bulkAddEvolutionProgress: `Добавить отсуствующий прогресс эволюции Инкарнонов`,
inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`, inventory_bulkRankUpSuits: `Максимальный ранг всех варфреймов`,
inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`, inventory_bulkRankUpWeapons: `Максимальный ранг всего оружия`,
inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`, inventory_bulkRankUpSpaceSuits: `Максимальный ранг всех арчвингов`,
inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`, inventory_bulkRankUpSpaceWeapons: `Максимальный ранг всего оружия арчвингов`,
inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`, inventory_bulkRankUpSentinels: `Максимальный ранг всех стражей`,
inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`, inventory_bulkRankUpSentinelWeapons: `Максимальный ранг всего оружия стражей`,
inventory_bulkRankUpEvolutionProgress: `Максимальный ранг всех эволюций Инкарнонов`,
quests_list: `Квесты`, quests_list: `Квесты`,
quests_completeAll: `Завершить все квесты`, quests_completeAll: `Завершить все квесты`,
@ -105,13 +114,15 @@ dict = {
currency_owned: `У тебя |COUNT|.`, currency_owned: `У тебя |COUNT|.`,
powersuit_archonShardsLabel: `Ячейки осколков архонта`, powersuit_archonShardsLabel: `Ячейки осколков архонта`,
powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`, powersuit_archonShardsDescription: `Вы можете использовать эти неограниченные ячейки для установки множества улучшений.`,
powersuit_archonShardsDescription2: `[UNTRANSLATED] Note that each archon shard takes some time to be applied when loading in.`, powersuit_archonShardsDescription2: `Обратите внимание: каждый фрагмент архонта применяется с задержкой при загрузке.`,
mods_addRiven: `Добавить Мод Разлома`, mods_addRiven: `Добавить Мод Разлома`,
mods_fingerprint: `Отпечаток`, mods_fingerprint: `Отпечаток`,
mods_fingerprintHelp: `Нужна помощь с отпечатком?`, mods_fingerprintHelp: `Нужна помощь с отпечатком?`,
mods_rivens: `Моды Разлома`, mods_rivens: `Моды Разлома`,
mods_mods: `Моды`, mods_mods: `Моды`,
mods_bulkAddMods: `Добавить отсутствующие моды`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`, cheats_administratorRequirement: `Вы должны быть администратором для использования этой функции. Чтобы стать администратором, добавьте <code>\"|DISPLAYNAME|\"</code> в <code>administratorNames</code> в config.json.`,
cheats_server: `Сервер`, cheats_server: `Сервер`,
cheats_skipTutorial: `Пропустить обучение`, cheats_skipTutorial: `Пропустить обучение`,
@ -123,6 +134,7 @@ dict = {
cheats_infiniteEndo: `Бесконечное эндо`, cheats_infiniteEndo: `Бесконечное эндо`,
cheats_infiniteRegalAya: `Бесконечная Королевская Айя`, cheats_infiniteRegalAya: `Бесконечная Королевская Айя`,
cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`, cheats_infiniteHelminthMaterials: `Бесконечные Выделения Гельминта`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`, cheats_unlockAllShipFeatures: `Разблокировать все функции корабля`,
cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`, cheats_unlockAllShipDecorations: `Разблокировать все украшения корабля`,
cheats_unlockAllFlavourItems: `Разблокировать все <abbr title=\"Наборы анимаций, глифы, палитры и т. д.\">уникальные предметы</abbr>`, cheats_unlockAllFlavourItems: `Разблокировать все <abbr title=\"Наборы анимаций, глифы, палитры и т. д.\">уникальные предметы</abbr>`,
@ -133,15 +145,15 @@ dict = {
cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`, cheats_unlockDoubleCapacityPotatoesEverywhere: `Катализаторы везде`,
cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`, cheats_unlockExilusEverywhere: `Адаптеры Эксилус везде`,
cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`, cheats_unlockArcanesEverywhere: `Адаптеры для мистификаторов везде`,
cheats_noDailyStandingLimits: `Без ежедневных ограничений репутации`, cheats_noDailyStandingLimits: `Без ежедневных лимитов репутации`,
cheats_noDailyFocusLimit: `[UNTRANSLATED] No Daily Focus Limits`, cheats_noDailyFocusLimit: `Без ежедневных лимитов фокуса`,
cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`, cheats_noArgonCrystalDecay: `Без распада аргоновых кристаллов`,
cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`, cheats_noMasteryRankUpCooldown: `Повышение ранга мастерства без кулдауна`,
cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`, cheats_noVendorPurchaseLimits: `Отсутствие лимитов на покупки у вендоров`,
cheats_noDeathMarks: `[UNTRANSLATED] No Death Marks`, cheats_noDeathMarks: `Без меток сметри`,
cheats_noKimCooldowns: `[UNTRANSLATED] No KIM Cooldowns`, cheats_noKimCooldowns: `Чаты KIM без кулдауна`,
cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`, cheats_instantResourceExtractorDrones: `Мгновенные Экстракторы Ресурсов`,
cheats_noResourceExtractorDronesDamage: `[UNTRANSLATED] No Resource Extractor Drones Damage`, cheats_noResourceExtractorDronesDamage: `Без урона по дронам-сборщикам`,
cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`, cheats_noDojoRoomBuildStage: `Мгновенное Строительтво Комнат Додзё`,
cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`, cheats_noDojoDecoBuildStage: `Мгновенное Строительтво Декораций Додзё`,
cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`, cheats_fastDojoRoomDestruction: `Мгновенные Уничтожение Комнат Додзё`,

View File

@ -34,6 +34,8 @@ dict = {
code_rerollsNumber: `洗卡次数`, code_rerollsNumber: `洗卡次数`,
code_viewStats: `查看属性`, code_viewStats: `查看属性`,
code_rank: `等级`, code_rank: `等级`,
code_rankUp: `[UNTRANSLATED] Rank up`,
code_rankDown: `[UNTRANSLATED] Rank down`,
code_count: `数量`, code_count: `数量`,
code_focusAllUnlocked: `所有专精学派均已解锁。`, code_focusAllUnlocked: `所有专精学派均已解锁。`,
code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`, code_focusUnlocked: `已解锁 |COUNT| 个新专精学派!需要游戏内仓库更新才能生效,您可以通过访问星图来触发仓库更新。`,
@ -54,10 +56,13 @@ dict = {
code_completed: `[UNTRANSLATED] Completed`, code_completed: `[UNTRANSLATED] Completed`,
code_active: `[UNTRANSLATED] Active`, code_active: `[UNTRANSLATED] Active`,
code_pigment: `颜料`, code_pigment: `颜料`,
code_mature: `[UNTRANSLATED] Mature for combat`,
code_unmature: `[UNTRANSLATED] Regress genetic aging`,
login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`, login_description: `使用您的 OpenWF 账户凭证登录(与游戏内连接本服务器时使用的昵称相同)。`,
login_emailLabel: `电子邮箱`, login_emailLabel: `电子邮箱`,
login_passwordLabel: `密码`, login_passwordLabel: `密码`,
login_loginButton: `登录`, login_loginButton: `登录`,
login_registerButton: `[UNTRANSLATED] Register`,
navbar_logout: `退出登录`, navbar_logout: `退出登录`,
navbar_renameAccount: `重命名账户`, navbar_renameAccount: `重命名账户`,
navbar_deleteAccount: `删除账户`, navbar_deleteAccount: `删除账户`,
@ -80,18 +85,22 @@ dict = {
inventory_operatorAmps: `增幅器`, inventory_operatorAmps: `增幅器`,
inventory_hoverboards: `K式悬浮板`, inventory_hoverboards: `K式悬浮板`,
inventory_moaPets: `恐鸟`, inventory_moaPets: `恐鸟`,
inventory_kubrowPets: `[UNTRANSLATED] Beasts`,
inventory_evolutionProgress: `[UNTRANSLATED] Incarnon Evolution Progress`,
inventory_bulkAddSuits: `添加缺失战甲`, inventory_bulkAddSuits: `添加缺失战甲`,
inventory_bulkAddWeapons: `添加缺失武器`, inventory_bulkAddWeapons: `添加缺失武器`,
inventory_bulkAddSpaceSuits: `添加缺失Archwing`, inventory_bulkAddSpaceSuits: `添加缺失Archwing`,
inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`, inventory_bulkAddSpaceWeapons: `添加缺失Archwing武器`,
inventory_bulkAddSentinels: `添加缺失守护`, inventory_bulkAddSentinels: `添加缺失守护`,
inventory_bulkAddSentinelWeapons: `添加缺失守护武器`, inventory_bulkAddSentinelWeapons: `添加缺失守护武器`,
inventory_bulkAddEvolutionProgress: `[UNTRANSLATED] Add Missing Incarnon Evolution Progress`,
inventory_bulkRankUpSuits: `所有战甲升满级`, inventory_bulkRankUpSuits: `所有战甲升满级`,
inventory_bulkRankUpWeapons: `所有武器升满级`, inventory_bulkRankUpWeapons: `所有武器升满级`,
inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`, inventory_bulkRankUpSpaceSuits: `所有Archwing升满级`,
inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`, inventory_bulkRankUpSpaceWeapons: `所有Archwing武器升满级`,
inventory_bulkRankUpSentinels: `所有守护升满级`, inventory_bulkRankUpSentinels: `所有守护升满级`,
inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`, inventory_bulkRankUpSentinelWeapons: `所有守护武器升满级`,
inventory_bulkRankUpEvolutionProgress: `[UNTRANSLATED] Max Rank All Incarnon Evolution Progress`,
quests_list: `任务`, quests_list: `任务`,
quests_completeAll: `完成所有任务`, quests_completeAll: `完成所有任务`,
@ -111,7 +120,9 @@ dict = {
mods_fingerprintHelp: `需要印记相关的帮助?`, mods_fingerprintHelp: `需要印记相关的帮助?`,
mods_rivens: `裂罅MOD`, mods_rivens: `裂罅MOD`,
mods_mods: `Mods`, mods_mods: `Mods`,
mods_bulkAddMods: `添加缺失MOD`, mods_addMissingUnrankedMods: `[UNTRANSLATED] Add Missing Unranked Mods`,
mods_removeUnranked: `[UNTRANSLATED] Remove Unranked Mods`,
mods_addMissingMaxRankMods: `[UNTRANSLATED] Add Missing Max Rank Mods`,
cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`, cheats_administratorRequirement: `您必须是管理员才能使用此功能。要成为管理员,请将 <code>|DISPLAYNAME|</code> 添加到 config.json 的 <code>administratorNames</code> 中。`,
cheats_server: `服务器`, cheats_server: `服务器`,
cheats_skipTutorial: `跳过教程`, cheats_skipTutorial: `跳过教程`,
@ -123,6 +134,7 @@ dict = {
cheats_infiniteEndo: `无限内融核心`, cheats_infiniteEndo: `无限内融核心`,
cheats_infiniteRegalAya: `无限御品阿耶`, cheats_infiniteRegalAya: `无限御品阿耶`,
cheats_infiniteHelminthMaterials: `无限Helminth材料`, cheats_infiniteHelminthMaterials: `无限Helminth材料`,
cheats_dontSubtractConsumables: `[UNTRANSLATED] Don't Subtract Consumables`,
cheats_unlockAllShipFeatures: `解锁所有飞船功能`, cheats_unlockAllShipFeatures: `解锁所有飞船功能`,
cheats_unlockAllShipDecorations: `解锁所有飞船装饰`, cheats_unlockAllShipDecorations: `解锁所有飞船装饰`,
cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`, cheats_unlockAllFlavourItems: `解锁所有<abbr title=\"动画组合、图标、调色板等\">装饰物品</abbr>`,