Compare commits

...

91 Commits

Author SHA1 Message Date
1a2d8ab19a chore: continue execution if subsequent JSON.parse on config failed (#2353)
By calling JSON.parse before setting everything to undefined, we don't lose the old config in case of an error.

Reviewed-on: OpenWF/SpaceNinjaServer#2353
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-29 11:54:20 -07:00
8c19aec340 fix(webui): "all focus schools maxed out" doesn't have squad regen maxed 2025-06-29 19:32:49 +02:00
d1c860c693 feat(webui): Valence Bonus (#2348)
Closes #1181

Reviewed-on: OpenWF/SpaceNinjaServer#2348
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-06-29 09:28:28 -07:00
69f9d5ebc5 fix: handle setPlacedDecoInfo for non-ship boot locations (#2349)
Closes #2347

Reviewed-on: OpenWF/SpaceNinjaServer#2349
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-29 09:08:44 -07:00
d66f1c58d8 feat(webui): light/dark theme selection (#2343)
Closes #2093

Reviewed-on: OpenWF/SpaceNinjaServer#2343
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-29 09:07:53 -07:00
5234cf213e chore(webui): explain star chart refreshing for unlock all missions (#2342)
This is relevant for U39 at least

Reviewed-on: OpenWF/SpaceNinjaServer#2342
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-29 09:07:33 -07:00
7c7d2b9061 chore(webui): update to Spanish translation (#2351)
Reviewed-on: OpenWF/SpaceNinjaServer#2351
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-06-29 09:07:12 -07:00
3d46d05a6c chore(webui): update Chinese translation (#2350)
Reviewed-on: OpenWF/SpaceNinjaServer#2350
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-29 09:07:05 -07:00
00cea6788e fix: sync config with db only after connection is established (#2346)
The error message had regressed in the case where connection could not be established.

Reviewed-on: OpenWF/SpaceNinjaServer#2346
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 15:08:36 -07:00
58bdb2d2ec chore(webui): improving inconsistent, long string (#2344)
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2344
Co-authored-by: Animan8000 <animan8000@noreply.localhost>
Co-committed-by: Animan8000 <animan8000@noreply.localhost>
2025-06-28 10:28:09 -07:00
c4c622d82b feat: baro's void surplus (#2334)
Closes #2284

Reviewed-on: OpenWF/SpaceNinjaServer#2334
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:50:40 -07:00
44a129ab0b feat: create genetic imprint (#2337)
Re #2212

Reviewed-on: OpenWF/SpaceNinjaServer#2337
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:48:12 -07:00
5a7caa5ba9 feat: disableDailyTribute config (#2338)
Reviewed-on: OpenWF/SpaceNinjaServer#2338
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:47:39 -07:00
a9c5e30994 chore: deal with visiting navigation not resyncing inventory (#2340)
Closes #2339

Reviewed-on: OpenWF/SpaceNinjaServer#2340
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:46:45 -07:00
f0547cb9e6 chore(webui): update Chinese translation (#2341)
Reviewed-on: OpenWF/SpaceNinjaServer#2341
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-28 09:45:45 -07:00
ef3d3b92c7 feat: darvo deal (#2261)
Closes #2260

Reviewed-on: OpenWF/SpaceNinjaServer#2261
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-28 09:39:41 -07:00
a9359bd989 feat(webui): the circuit override (#2335)
Re #2312

Reviewed-on: OpenWF/SpaceNinjaServer#2335
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 20:28:44 -07:00
3d21813a79 fix(vscode): update launch.json 2025-06-28 01:28:45 +02:00
7cad831702 chore: update PE+ (#2336)
Closes #2332

Reviewed-on: OpenWF/SpaceNinjaServer#2336
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 15:16:22 -07:00
0f2b6c68cd chore: use some instead of find 2025-06-27 19:29:55 +02:00
4fcac6dc37 feat: duviri murmur reward tiers (#2331)
Reviewed-on: OpenWF/SpaceNinjaServer#2331
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:22:00 -07:00
d2cae012a7 chore: add operation eight claw trophies to allDecoRecipes (#2330)
Closes #2295

Reviewed-on: OpenWF/SpaceNinjaServer#2330
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:48 -07:00
b36d524953 feat: personal deco capacity costs (#2329)
Closes #2278

Reviewed-on: OpenWF/SpaceNinjaServer#2329
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:27 -07:00
abb5b8880f chore(webui): keep config in sync with multiple tabs (#2325)
Adding "wsid" to uniquely identify a given tab (by the websocket connection) so we can avoid needless refreshing on the same tab.

Closes #2316

Reviewed-on: OpenWF/SpaceNinjaServer#2325
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:21:05 -07:00
4895b4630b feat: credit boosters (+ daily first win) (#2324)
Daily first win is kinda weird because the client doesn't even seem to acknowledge it.

Also fixed missionCompletionCredits being added to inventory inconsistently (sometimes once, sometimes twice).

Closes #1086
Closes #2322

Reviewed-on: OpenWF/SpaceNinjaServer#2324
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-27 08:20:37 -07:00
690b872b5e chore(webui): update Chinese translation (#2328)
Reviewed-on: OpenWF/SpaceNinjaServer#2328
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-26 22:26:35 -07:00
d77fe60cd8 fix(webui): apply consistent margins (#2327)
Reviewed-on: OpenWF/SpaceNinjaServer#2327
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 22:26:22 -07:00
3cae42c7d6 fix: acrithis vendor freezing with fullyStockedVendors (#2326)
Permanent offers were not satisfying bin constraints

Reviewed-on: OpenWF/SpaceNinjaServer#2326
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 22:25:31 -07:00
bbccee0637 fix: ignore purchaseQuantity for login reward items (#2321)
cryotic amount should not be multiplied by 3000...

Reviewed-on: OpenWF/SpaceNinjaServer#2321
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:40:06 -07:00
31e24c27ad chore: ignore invalid item ids in saveLoadout (#2320)
With the 'IsNew' flag + webui delete item, this is quite easy to trigger and shouldn't prevent the other changes from going through.

Reviewed-on: OpenWF/SpaceNinjaServer#2320
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:39:46 -07:00
4acd87aae6 chore: handle CalendarProgress in updateChallengeProgress 2025-06-26 19:35:03 -07:00
d8ff601be7 fix: array out of bounds when processing CalendarProgress 2025-06-26 19:35:03 -07:00
d79e7c0274 feat(webui): world state config (#2318)
Re #2312. Will need some follow-up considerations for circuit game modes.

Reviewed-on: OpenWF/SpaceNinjaServer#2318
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 19:32:53 -07:00
4f1f9592b0 chore: use chokidar for configWatcherService (#2315)
It's just a lot snappier + works flawlessly under Bun.

Reviewed-on: OpenWF/SpaceNinjaServer#2315
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 15:30:01 -07:00
764cdd1ab8 feat: worldState.allTheFissures (#2313)
Closes #2294

Reviewed-on: OpenWF/SpaceNinjaServer#2313
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:32:26 -07:00
0ba641a2ac chore: update PE+ (#2311)
Closes #2309

Reviewed-on: OpenWF/SpaceNinjaServer#2311
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:31:52 -07:00
eb7b51852b fix: use exact quantity when adding gear items by StoreItem (#2310)
Closes #2304

Reviewed-on: OpenWF/SpaceNinjaServer#2310
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-26 14:31:40 -07:00
a3be376489 chore(webui): add Thalys to Incarnon List (#2299)
Closes #2298

Reviewed-on: OpenWF/SpaceNinjaServer#2299
Co-authored-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
Co-committed-by: AMelonInsideLemon <166175391+AMelonInsideLemon@users.noreply.github.com>
2025-06-26 11:02:40 -07:00
d94cd38120 chore(webui): update Chinese translation (#2291)
Reviewed-on: OpenWF/SpaceNinjaServer#2291
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-26 06:44:03 -07:00
8c22555904 feat(webui): add missing subsumed abilities (#2287)
Closes #1984

Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Reviewed-on: OpenWF/SpaceNinjaServer#2287
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-06-25 20:24:29 -07:00
c9edef39f8 feat: claimJunctionChallengeReward (#2289)
Closes #2285

Reviewed-on: OpenWF/SpaceNinjaServer#2289
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 20:24:16 -07:00
b42182c85f fix(webui): handle existing entries for unlock all missions (#2290)
Closes #2283

Reviewed-on: OpenWF/SpaceNinjaServer#2290
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 20:22:24 -07:00
86f86d0476 chore: fully adopt tsgo
It's finally able to emit this project without issues. It doesn't yet support incremental builds, but a full build with it is faster than an incremental build with the old tsc, so we're not losing anything.
2025-06-26 05:20:29 +02:00
0fdf8b2c75 chore(webui): update fr
Co-authored-by: Vitruvio <vitruvio@noreply.localhost>
2025-06-25 19:33:21 +02:00
285b1bbf60 chore(webui): update Chinese translation (#2281)
Reviewed-on: OpenWF/SpaceNinjaServer#2281
Co-authored-by: Corvus <corvus@noreply.localhost>
Co-committed-by: Corvus <corvus@noreply.localhost>
2025-06-25 08:04:46 -07:00
731ce6c215 feat: galleon of ghouls (#2280)
Re #1103

Reviewed-on: OpenWF/SpaceNinjaServer#2280
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 08:04:03 -07:00
39630c5af7 fix: properly convert personal room decos to and from inventory types (#2279)
Closes #2277

Reviewed-on: OpenWF/SpaceNinjaServer#2279
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 08:03:56 -07:00
d5be202835 fix: ensure every bounty tier has a unique job type (#2273)
I saw "trash their traps" show up twice on Eudico in different tiers, I don't think that's correct.

Reviewed-on: OpenWF/SpaceNinjaServer#2273
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-25 08:03:49 -07:00
3a6e4ac2e1 feat: Arcana Isolation Vault rewards (#2276)
Closes #388

Reviewed-on: OpenWF/SpaceNinjaServer#2276
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-06-24 19:05:19 -07:00
e234af098d fix(webui): incorrect value of upgrade_AvatarTimeLimitIncrease string (#2274)
Reviewed-on: OpenWF/SpaceNinjaServer#2274
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-24 17:12:39 -07:00
4a434cea2b chore(webui): update to Spanish translation (#2275)
Reviewed-on: OpenWF/SpaceNinjaServer#2275
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-06-24 12:02:21 -07:00
36f2828d37 feat: void trader (#2269)
Closes #2245

Reviewed-on: OpenWF/SpaceNinjaServer#2269
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-24 11:19:32 -07:00
ca3cfb5299 feat(webui): max focus schools (#2270)
Closes #1433

Reviewed-on: OpenWF/SpaceNinjaServer#2270
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-24 10:03:18 -07:00
f242d9f873 chore: make ws self test work under bun (#2268)
Reviewed-on: OpenWF/SpaceNinjaServer#2268
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 21:52:02 -07:00
9a034b1c8a feat: unfaithful bug fixes (#2267)
Closes #2257

Reviewed-on: OpenWF/SpaceNinjaServer#2267
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 21:51:48 -07:00
122950034e chore: cleanup purchase stuff (#2266)
Reviewed-on: OpenWF/SpaceNinjaServer#2266
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 21:51:09 -07:00
636d3100f3 fixup for 444c92f0c60d7bafc320b487105d610f0e1ff6af
I forgot to save this file
2025-06-24 01:34:47 +02:00
444c92f0c6 fix: use shared count for calendar day indecies (#2265)
I'm not sure if this was always this way and I was just really confused when I initially implemented this, or if this was changed in a later version, but at least now it seems to be tracking everything correctly for 38.6.0.

Closes #2264

Reviewed-on: OpenWF/SpaceNinjaServer#2265
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 15:02:30 -07:00
653798b987 fix: use correct dropTable for bounty stage reward (#2263)
Re #388

Reviewed-on: OpenWF/SpaceNinjaServer#2263
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-06-23 14:56:18 -07:00
7a88f6f486 chore: create AGENTS.md (#2262)
Reviewed-on: OpenWF/SpaceNinjaServer#2262
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 14:56:08 -07:00
82b203e00b fix(nemesis): subtract charge from installed mods instead of ideal mods (#2259)
Because oull might substitute one of them.

Closes #2258

Reviewed-on: OpenWF/SpaceNinjaServer#2259
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 14:55:48 -07:00
271f5bd47a fix: also increment LastCompletedDayIdx when completing a 1999 challenge (#2256)
Fixes #2255

Reviewed-on: OpenWF/SpaceNinjaServer#2256
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 04:58:21 -07:00
f61d15b496 chore: replace 'websocket' with 'undici' (#2253)
This is a lot more lightweight

Reviewed-on: OpenWF/SpaceNinjaServer#2253
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 04:55:19 -07:00
cfd50e7402 feat: unlockAllSimarisResearchEntries cheat (#2252)
Closes #1869

Reviewed-on: OpenWF/SpaceNinjaServer#2252
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 04:54:54 -07:00
2421a16b2c fix: cap spy rotations at C (#2251)
for Jade Shadows' spy mission with 4 vaults. will simply do ABCC in this case.

Closes #2250

Reviewed-on: OpenWF/SpaceNinjaServer#2251
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-23 04:54:36 -07:00
cee622d5e9 chore: add bun support (#2254)
It definitely has some benefits:
- It starts up insanely quickly compared to Node.
- It can run typescript directly, allow the build step to be reduced to verify/noEmit.

It does not implement NodeJS APIs perfectly, so I've had to add some special handling for Bun, but I think that's okay.

Reviewed-on: OpenWF/SpaceNinjaServer#2254
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 19:58:48 -07:00
84f081312b feat: fullyStockedVendors cheat (#2246)
Reviewed-on: OpenWF/SpaceNinjaServer#2246
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:55:44 -07:00
7ca7147b78 fix(docker): install node-gyp deps to fix arm64 build 2025-06-22 15:52:37 +02:00
558af66965 chore: verify that httpsPort has actually been bound by us (#2243)
I'm not the biggest fan of this, but it's a semi-regular problem and this should help affected users quickly discover it.

Reviewed-on: OpenWF/SpaceNinjaServer#2243
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:41:43 -07:00
d7b9fb1ab5 fix(webui): once awake stage 2 not consistently showing up in-game (#2244)
Fixes #2040

Reviewed-on: OpenWF/SpaceNinjaServer#2244
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:41:06 -07:00
bf12f90c88 chore: replace unlockAllMissions config with an account cheats button (#2241)
This way, mission completion rewards are given. This is especially import for junction rewards like quest keys (Closes #2229).

Reviewed-on: OpenWF/SpaceNinjaServer#2241
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:37:17 -07:00
6dd9b42f40 feat(webui): update inventory when in-game changes are made (#2239)
A bit of a rough initial implementation, but already works pretty well.

Closes #2224

Reviewed-on: OpenWF/SpaceNinjaServer#2239
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-22 06:36:47 -07:00
3bcd5827f9 chore(webui): unset nonce when logging out (#2242)
Reviewed-on: OpenWF/SpaceNinjaServer#2242
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 14:38:48 -07:00
d16d763977 chore: handle logout POST request for older versions (#2240)
Reviewed-on: OpenWF/SpaceNinjaServer#2240
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 14:38:24 -07:00
ff8ec8dbed chore(webui): update bootstrap, add sourcemaps for it (#2238)
Reviewed-on: OpenWF/SpaceNinjaServer#2238
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 14:38:04 -07:00
6cdd103c3d chore(dev): improve bulk change handling (#2234)
Fixed abandoned build processes sometimes still triggering a start (causing double-starts) made it more robust in regards to webui changes being intermixed: making the fetch a fire-and-forget to avoid errors, and waiting for the websocket connection to be re-established to avoid the browser attempting to reload when the server may not be up for a few seconds.

Reviewed-on: OpenWF/SpaceNinjaServer#2234
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 11:33:59 -07:00
b6f79c1e5c fix: save steel path mission completion (#2233)
Fixes #2228

Reviewed-on: OpenWF/SpaceNinjaServer#2233
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 11:25:42 -07:00
2bb3e2afdd chore(webui): update to Spanish translation (#2236)
Reviewed-on: OpenWF/SpaceNinjaServer#2236
Co-authored-by: hxedcl <hxedcl@noreply.localhost>
Co-committed-by: hxedcl <hxedcl@noreply.localhost>
2025-06-21 10:51:57 -07:00
6a60537cd0 chore: remove unused string
Fixup for 2fa6dcc7edb34c9382c31739d8b61a84803d69c2
2025-06-21 17:46:38 +02:00
2fa6dcc7ed feat(webui): handle auth via websocket (#2226)
Now when logging in and out of the game, the webui is notified so it can refresh the nonce, removing the need for constant login requests to revalidate it.

Closes #2223

Reviewed-on: OpenWF/SpaceNinjaServer#2226
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 07:26:43 -07:00
93ef9a5348 feat: autogenerate all vendors (#2225)
I went through a few of the still-hardcoded vendors and they seemed to "just work" with autogeneration so I think it's time to say: Closes #1225 and disable the `noVendorPurchaseLimits` cheat by default.

Reviewed-on: OpenWF/SpaceNinjaServer#2225
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-21 07:26:11 -07:00
5d5d0ee560 chore(webui): update Chinese translation (#2227)
Reviewed-on: OpenWF/SpaceNinjaServer#2227
Co-authored-by: CrazyZhang <crazyzhang@noreply.localhost>
Co-committed-by: CrazyZhang <crazyzhang@noreply.localhost>
2025-06-21 07:25:48 -07:00
f84cc54c97 chore: use build & start process for development as well (#2222)
Reviewed-on: OpenWF/SpaceNinjaServer#2222
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 18:30:52 -07:00
4cb0f8b167 feat(webui): initial websocket integration to be more responsive (#2221)
For now just handles changes to config.json but in the future might keep the inventory tabs up-to-date with in-game actions.

Reviewed-on: OpenWF/SpaceNinjaServer#2221
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 14:00:55 -07:00
eadc9c4ecb feat(webui): max rank plexus (#2219)
Closes #1740

Reviewed-on: OpenWF/SpaceNinjaServer#2219
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 14:00:39 -07:00
f41377bb81 chore: npm audit fix (#2220)
Reviewed-on: OpenWF/SpaceNinjaServer#2220
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 09:40:53 -07:00
95136e6059 feat: dynamic void fissure missions (#2214)
Closes #1512

Reviewed-on: OpenWF/SpaceNinjaServer#2214
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 04:47:45 -07:00
3c64f17e34 feat: missionsCanGiveAllRelics cheat (#2217)
Closes #1060

Reviewed-on: OpenWF/SpaceNinjaServer#2217
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 04:42:11 -07:00
3619bdfdb5 feat: autogenerate mask vendor (#2216)
Reviewed-on: OpenWF/SpaceNinjaServer#2216
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 04:41:47 -07:00
97064826b2 feat: unlockAllProfitTakerStages cheat (#2215)
Closes #2081

Reviewed-on: OpenWF/SpaceNinjaServer#2215
Co-authored-by: Sainan <63328889+Sainan@users.noreply.github.com>
Co-committed-by: Sainan <63328889+Sainan@users.noreply.github.com>
2025-06-20 04:41:13 -07:00
c6c7a2966b fix: deimos vault bounty detection (#2207)
Related to #388
this should fix incorect rewards for deimos filed bounties

Reviewed-on: OpenWF/SpaceNinjaServer#2207
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-06-20 04:40:27 -07:00
111 changed files with 4792 additions and 7184 deletions

View File

@ -11,17 +11,17 @@
"node": true
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/restrict-template-expressions": "warn",
"@typescript-eslint/restrict-plus-operands": "warn",
"@typescript-eslint/no-unsafe-member-access": "warn",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/restrict-template-expressions": "error",
"@typescript-eslint/restrict-plus-operands": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_", "caughtErrors": "none" }],
"@typescript-eslint/no-unsafe-argument": "error",
"@typescript-eslint/no-unsafe-call": "warn",
"@typescript-eslint/no-unsafe-assignment": "warn",
"@typescript-eslint/no-explicit-any": "warn",
"no-loss-of-precision": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-assignment": "error",
"@typescript-eslint/no-explicit-any": "error",
"no-loss-of-precision": "error",
"@typescript-eslint/no-unnecessary-condition": "error",
"@typescript-eslint/no-base-to-string": "off",
"no-case-declarations": "error",
"prettier/prettier": "error",

3
.vscode/launch.json vendored
View File

@ -8,8 +8,7 @@
"type": "node",
"request": "launch",
"name": "Debug and Watch",
"runtimeArgs": ["-r", "tsconfig-paths/register", "-r", "ts-node/register", "--watch-path", "src"],
"args": ["${workspaceFolder}/src/index.ts"],
"args": ["${workspaceFolder}/scripts/dev.js"],
"console": "integratedTerminal"
}
]

17
AGENTS.md Normal file
View File

@ -0,0 +1,17 @@
## In General
### Prerequisites
Use `npm i` or `npm ci` to install all dependencies.
### Testing
Use `npm run verify` to verify that your changes pass TypeScript's checks.
### Formatting
Use `npm run prettier` to ensure your formatting matches the expected format. Failing to do so will cause CI failure.
## WebUI Specific
The translation system is designed around additions being made to `static/webui/translations/en.js`. They are copied over for translation via `npm run update-translations`. DO NOT produce non-English strings; we want them to be translated by humans who can understand the full context.

View File

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

View File

@ -13,7 +13,6 @@
"skipTutorial": false,
"skipAllDialogue": false,
"unlockAllScans": false,
"unlockAllMissions": false,
"infiniteCredits": false,
"infinitePlatinum": false,
"infiniteEndo": false,
@ -39,10 +38,14 @@
"noDailyFocusLimit": false,
"noArgonCrystalDecay": false,
"noMasteryRankUpCooldown": false,
"noVendorPurchaseLimits": true,
"noVendorPurchaseLimits": false,
"noDeathMarks": false,
"noKimCooldowns": false,
"fullyStockedVendors": false,
"baroAlwaysAvailable": false,
"baroFullyStocked": false,
"syndicateMissionsRepeatable": false,
"unlockAllProfitTakerStages": false,
"instantFinishRivenChallenge": false,
"instantResourceExtractorDrones": false,
"noResourceExtractorDronesDamage": false,
@ -53,18 +56,28 @@
"noDojoResearchCosts": false,
"noDojoResearchTime": false,
"fastClanAscension": false,
"missionsCanGiveAllRelics": false,
"unlockAllSimarisResearchEntries": false,
"disableDailyTribute": false,
"spoofMasteryRank": -1,
"nightwaveStandingMultiplier": 1,
"unfaithfulBugFixes": {
"ignore1999LastRegionPlayed": false,
"fixXtraCheeseTimer": false
},
"worldState": {
"creditBoost": false,
"affinityBoost": false,
"resourceBoost": false,
"starDays": true,
"galleonOfGhouls": 0,
"eidolonOverride": "",
"vallisOverride": "",
"duviriOverride": "",
"nightwaveOverride": "",
"circuitGameModes": null
"allTheFissures": "",
"circuitGameModes": null,
"darvoStockMultiplier": 1
},
"dev": {
"keepVendorsExpired": false

View File

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

731
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,9 +5,16 @@
"main": "index.ts",
"scripts": {
"start": "node --enable-source-maps --import ./build/src/pathman.js build/src/index.js",
"dev": "ts-node-dev --openssl-legacy-provider -r tsconfig-paths/register src/index.ts ",
"build": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
"build": "tsgo --sourceMap && ncp static/webui build/static/webui",
"build:tsc": "tsc --incremental --sourceMap && ncp static/webui build/static/webui",
"build:dev": "tsgo --sourceMap",
"build:dev:tsc": "tsc --incremental --sourceMap",
"build-and-start": "npm run build && npm run start",
"build-and-start:bun": "npm run verify && npm run bun-run",
"dev": "node scripts/dev.js",
"dev:bun": "bun scripts/dev.js",
"verify": "tsgo --noEmit",
"bun-run": "bun src/index.ts",
"lint": "eslint --ext .ts .",
"lint:ci": "eslint --ext .ts --rule \"prettier/prettier: off\" .",
"lint:fix": "eslint --fix --ext .ts .",
@ -18,6 +25,10 @@
"dependencies": {
"@types/express": "^5",
"@types/morgan": "^1.9.9",
"@types/websocket": "^1.0.10",
"@types/ws": "^8.18.1",
"@typescript/native-preview": "^7.0.0-dev.20250625.1",
"chokidar": "^4.0.3",
"crc-32": "^1.2.2",
"express": "^5",
"json-with-bigint": "^3.4.4",
@ -25,19 +36,19 @@
"morgan": "^1.10.0",
"ncp": "^2.0.0",
"typescript": "^5.5",
"warframe-public-export-plus": "^0.5.68",
"undici": "^7.10.0",
"warframe-public-export-plus": "^0.5.74",
"warframe-riven-info": "^0.1.2",
"winston": "^3.17.0",
"winston-daily-rotate-file": "^5.0.0"
"winston-daily-rotate-file": "^5.0.0",
"ws": "^8.18.2"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"@typescript/native-preview": "^7.0.0-dev.20250523.1",
"eslint": "^8",
"eslint-plugin-prettier": "^5.2.5",
"prettier": "^3.5.3",
"ts-node-dev": "^2.0.0",
"tsconfig-paths": "^4.2.0"
"tree-kill": "^1.2.2"
}
}

58
scripts/dev.js Normal file
View File

@ -0,0 +1,58 @@
/* eslint-disable */
const { spawn } = require("child_process");
const chokidar = require("chokidar");
const kill = require("tree-kill");
let secret = "";
for (let i = 0; i != 10; ++i) {
secret += String.fromCharCode(Math.floor(Math.random() * 26) + 0x41);
}
const args = [...process.argv].splice(2);
args.push("--dev");
args.push("--secret");
args.push(secret);
let buildproc, runproc;
const spawnopts = { stdio: "inherit", shell: true };
function run(changedFile) {
if (changedFile) {
console.log(`Change to ${changedFile} detected`);
}
if (buildproc) {
kill(buildproc.pid);
buildproc = undefined;
}
if (runproc) {
kill(runproc.pid);
runproc = undefined;
}
const thisbuildproc = spawn("npm", ["run", process.versions.bun ? "verify" : "build:dev"], spawnopts);
const thisbuildstart = Date.now();
buildproc = thisbuildproc;
buildproc.on("exit", code => {
if (buildproc !== thisbuildproc) {
return;
}
buildproc = undefined;
if (code === 0) {
console.log(`${process.versions.bun ? "Verified" : "Built"} in ${Date.now() - thisbuildstart} ms`);
runproc = spawn("npm", ["run", process.versions.bun ? "bun-run" : "start", "--", ...args], spawnopts);
runproc.on("exit", () => {
runproc = undefined;
});
}
});
}
run();
chokidar.watch("src").on("change", run);
chokidar.watch("static/fixed_responses").on("change", run);
chokidar.watch("static/webui").on("change", async () => {
try {
await fetch("http://localhost/custom/webuiFileChangeDetected?secret=" + secret);
} catch (e) {}
});

View File

@ -1,6 +1,7 @@
// Based on https://onlyg.it/OpenWF/Translations/src/branch/main/update.php
// Converted via ChatGPT-4o
/* eslint-disable */
const fs = require("fs");
function extractStrings(content) {

View File

@ -1,16 +1,12 @@
import { getAccountForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
const checkDailyMissionBonusController: RequestHandler = (_req, res) => {
const data = Buffer.from([
0x44, 0x61, 0x69, 0x6c, 0x79, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73, 0x3a,
0x31, 0x2d, 0x44, 0x61, 0x69, 0x6c, 0x79, 0x50, 0x56, 0x50, 0x57, 0x69, 0x6e, 0x42, 0x6f, 0x6e, 0x75, 0x73,
0x3a, 0x31, 0x0a
]);
res.writeHead(200, {
"Content-Type": "text/html",
"Content-Length": data.length
});
res.end(data);
export const checkDailyMissionBonusController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
const today = Math.trunc(Date.now() / 86400000) * 86400;
if (account.DailyFirstWinDate != today) {
res.send("DailyMissionBonus:1-DailyPVPWinBonus:1\n");
} else {
res.send("DailyMissionBonus:0-DailyPVPWinBonus:1\n");
}
};
export { checkDailyMissionBonusController };

View File

@ -13,7 +13,8 @@ import {
addItem,
addRecipes,
occupySlot,
combineInventoryChanges
combineInventoryChanges,
addKubrowPetPrint
} from "@/src/services/inventoryService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
@ -119,6 +120,9 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
}
}
pet.Details!.Status = canSetActive ? Status.StatusAvailable : Status.StatusStasis;
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
const pet = inventory.KubrowPets.id(pendingRecipe.KubrowPet!)!;
addKubrowPetPrint(inventory, pet, InventoryChanges);
} else if (recipe.secretIngredientAction != "SIA_UNBRAND") {
InventoryChanges = {
...InventoryChanges,

View File

@ -0,0 +1,35 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { RequestHandler } from "express";
import { ExportChallenges } from "warframe-public-export-plus";
export const claimJunctionChallengeRewardController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const data = getJSONfromString<IClaimJunctionChallengeRewardRequest>(String(req.body));
const challengeProgress = inventory.ChallengeProgress.find(x => x.Name == data.Challenge)!;
if (challengeProgress.ReceivedJunctionReward) {
throw new Error(`attempt to double-claim junction reward`);
}
challengeProgress.ReceivedJunctionReward = true;
inventory.ClaimedJunctionChallengeRewards ??= [];
inventory.ClaimedJunctionChallengeRewards.push(data.Challenge);
const challengeMeta = Object.entries(ExportChallenges).find(arr => arr[0].endsWith("/" + data.Challenge))![1];
const inventoryChanges = {};
for (const reward of challengeMeta.countedRewards!) {
combineInventoryChanges(
inventoryChanges,
(await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount)).InventoryChanges
);
}
await inventory.save();
res.json({
inventoryChanges: inventoryChanges // Yeah, it's "inventoryChanges" in the response here.
});
};
interface IClaimJunctionChallengeRewardRequest {
Challenge: string;
}

View File

@ -1,4 +1,4 @@
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { checkCalendarChallengeCompletion, getCalendarProgress, getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { getWorldState } from "@/src/services/worldStateService";
@ -12,27 +12,23 @@ export const completeCalendarEventController: RequestHandler = async (req, res)
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;
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
const day = currentSeason.Days[dayIndex];
if (day.events.length != 0) {
if (day.events[0].type == "CET_CHALLENGE") {
throw new Error(`completeCalendarEvent should not be used for challenges`);
}
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}`);
}
}
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
calendarProgress.SeasonProgress.LastCompletedDayIdx = dayIndex;
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
await inventory.save();
res.json({
InventoryChanges: inventoryChanges,

View File

@ -1,8 +1,10 @@
import { DailyDeal } from "@/src/models/worldStateModel";
import { RequestHandler } from "express";
export const getDailyDealStockLevelsController: RequestHandler = (req, res) => {
export const getDailyDealStockLevelsController: RequestHandler = async (req, res) => {
const dailyDeal = (await DailyDeal.findOne({ StoreItem: req.query.productName }, "AmountSold"))!;
res.json({
StoreItem: req.query.productName,
AmountSold: 0
AmountSold: dailyDeal.AmountSold
});
};

View File

@ -9,15 +9,26 @@ import {
updateCurrency
} from "@/src/services/inventoryService";
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { handleDailyDealPurchase, handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IOid } from "@/src/types/commonTypes";
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
import { IPurchaseParams, IPurchaseResponse, PurchaseSource } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
const checkPurchaseParams = (params: IPurchaseParams): boolean => {
switch (params.Source) {
case PurchaseSource.Market:
return params.UsePremium;
case PurchaseSource.DailyDeal:
return true;
}
return false;
};
export const giftingController: RequestHandler = async (req, res) => {
const data = getJSONfromString<IGiftingRequest>(String(req.body));
if (data.PurchaseParams.Source != 0 || !data.PurchaseParams.UsePremium) {
if (!checkPurchaseParams(data.PurchaseParams)) {
throw new Error(`unexpected purchase params in gifting request: ${String(req.body)}`);
}
@ -58,16 +69,19 @@ export const giftingController: RequestHandler = async (req, res) => {
}
senderInventory.GiftsRemaining -= 1;
const inventoryChanges: IInventoryChanges = updateCurrency(
senderInventory,
data.PurchaseParams.ExpectedPrice,
true
);
const response: IPurchaseResponse = {
InventoryChanges: {}
};
if (data.PurchaseParams.Source == PurchaseSource.DailyDeal) {
await handleDailyDealPurchase(senderInventory, data.PurchaseParams, response);
} else {
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true, response.InventoryChanges);
}
if (data.PurchaseParams.StoreItem in ExportBundles) {
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
if (bundle.giftingBonus) {
combineInventoryChanges(
inventoryChanges,
response.InventoryChanges,
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
);
}
@ -99,9 +113,7 @@ export const giftingController: RequestHandler = async (req, res) => {
}
]);
res.json({
InventoryChanges: inventoryChanges
});
res.json(response);
};
interface IGiftingRequest {

View File

@ -6,13 +6,7 @@ import allDialogue from "@/static/fixed_responses/allDialogue.json";
import { ILoadoutDatabase } from "@/src/types/saveLoadoutTypes";
import { IInventoryClient, IShipInventory, equipmentKeys } from "@/src/types/inventoryTypes/inventoryTypes";
import { IPolarity, ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
import {
ExportCustoms,
ExportFlavour,
ExportRegions,
ExportResources,
ExportVirtuals
} from "warframe-public-export-plus";
import { ExportCustoms, ExportFlavour, ExportResources, ExportVirtuals } from "warframe-public-export-plus";
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
import {
addMiscItems,
@ -22,13 +16,16 @@ import {
generateRewardSeed
} from "@/src/services/inventoryService";
import { logger } from "@/src/utils/logger";
import { catBreadHash } from "@/src/helpers/stringHelpers";
import { addString, catBreadHash } from "@/src/helpers/stringHelpers";
import { Types } from "mongoose";
import { getNemesisManifest } from "@/src/helpers/nemesisHelpers";
import { getPersonalRooms } from "@/src/services/personalRoomsService";
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
import { Ship } from "@/src/models/shipModel";
import { toLegacyOid, version_compare } from "@/src/helpers/inventoryHelpers";
import { toLegacyOid, toOid, version_compare } from "@/src/helpers/inventoryHelpers";
import { Inbox } from "@/src/models/inboxModel";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { DailyDeal } from "@/src/models/worldStateModel";
export const inventoryController: RequestHandler = async (request, response) => {
const account = await getAccountForRequest(request);
@ -42,6 +39,8 @@ export const inventoryController: RequestHandler = async (request, response) =>
// Handle daily reset
if (!inventory.NextRefill || Date.now() >= inventory.NextRefill.getTime()) {
const today = Math.trunc(Date.now() / 86400000);
for (const key of allDailyAffiliationKeys) {
inventory[key] = 16000 + inventory.PlayerLevel * 500;
}
@ -52,12 +51,12 @@ export const inventoryController: RequestHandler = async (request, response) =>
inventory.LibraryAvailableDailyTaskInfo = createLibraryDailyTask();
if (inventory.NextRefill) {
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
const daysPassed = today - lastLoginDay;
if (config.noArgonCrystalDecay) {
inventory.FoundToday = undefined;
} else {
const lastLoginDay = Math.trunc(inventory.NextRefill.getTime() / 86400000) - 1;
const today = Math.trunc(Date.now() / 86400000);
const daysPassed = today - lastLoginDay;
for (let i = 0; i != daysPassed; ++i) {
const numArgonCrystals =
inventory.MiscItems.find(x => x.ItemType == "/Lotus/Types/Items/MiscItems/ArgonCrystal")
@ -89,11 +88,29 @@ export const inventoryController: RequestHandler = async (request, response) =>
inventory.FoundToday = undefined;
}
}
if (inventory.UsedDailyDeals.length != 0) {
if (daysPassed == 1) {
const todayAt0Utc = today * 86400000;
const darvoIndex = Math.trunc((todayAt0Utc - 25200000) / (26 * unixTimesInMs.hour));
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
const darvoOid =
((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
const deal = await DailyDeal.findById(darvoOid);
if (deal) {
inventory.UsedDailyDeals = inventory.UsedDailyDeals.filter(x => x == deal.StoreItem); // keep only the deal that came into this new day with us
} else {
inventory.UsedDailyDeals = [];
}
} else {
inventory.UsedDailyDeals = [];
}
}
}
cleanupInventory(inventory);
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
inventory.NextRefill = new Date((today + 1) * 86400000); // tomorrow at 0 UTC
//await inventory.save();
}
@ -134,13 +151,21 @@ export const getInventoryResponse = async (
xpBasedLevelCapDisabled: boolean,
buildLabel: string | undefined
): Promise<IInventoryClient> => {
const [inventoryWithLoadOutPresets, ships] = await Promise.all([
const [inventoryWithLoadOutPresets, ships, latestMessage] = await Promise.all([
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
Ship.find({ ShipOwnerId: inventory.accountOwnerId })
Ship.find({ ShipOwnerId: inventory.accountOwnerId }),
Inbox.findOne({ ownerId: inventory.accountOwnerId }, "_id").sort({ date: -1 })
]);
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
// In case mission inventory update added an inbox message, we need to send the Mailbox part so the client knows to refresh it.
if (latestMessage) {
inventoryResponse.Mailbox = {
LastInboxId: toOid(latestMessage._id)
};
}
if (config.infiniteCredits) {
inventoryResponse.RegularCredits = 999999999;
}
@ -167,18 +192,6 @@ export const getInventoryResponse = async (
}
}
if (config.unlockAllMissions) {
inventoryResponse.Missions = [];
for (const tag of Object.keys(ExportRegions)) {
inventoryResponse.Missions.push({
Completes: 1,
Tier: 1,
Tag: tag
});
}
addString(inventoryResponse.NodeIntrosCompleted, "TeshinHardModeUnlocked");
}
if (config.unlockAllShipDecorations) {
inventoryResponse.ShipDecorations = [];
for (const [uniqueName, item] of Object.entries(ExportResources)) {
@ -300,9 +313,6 @@ export const getInventoryResponse = async (
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
}
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
inventoryResponse.LastInventorySync = undefined;
// Set 2FA enabled so trading post can be used
inventoryResponse.HWIDProtectEnabled = true;
@ -339,14 +349,41 @@ export const getInventoryResponse = async (
}
}
if (config.unlockAllProfitTakerStages) {
inventoryResponse.CompletedJobChains ??= [];
const EudicoHeists = inventoryResponse.CompletedJobChains.find(x => x.LocationTag == "EudicoHeists");
if (EudicoHeists) {
EudicoHeists.Jobs = allEudicoHeistJobs;
} else {
inventoryResponse.CompletedJobChains.push({
LocationTag: "EudicoHeists",
Jobs: allEudicoHeistJobs
});
}
}
if (config.unlockAllSimarisResearchEntries) {
inventoryResponse.LibraryPersonalTarget = undefined;
inventoryResponse.LibraryPersonalProgress = [
"/Lotus/Types/Game/Library/Targets/Research1Target",
"/Lotus/Types/Game/Library/Targets/Research2Target",
"/Lotus/Types/Game/Library/Targets/Research3Target",
"/Lotus/Types/Game/Library/Targets/Research4Target",
"/Lotus/Types/Game/Library/Targets/Research5Target",
"/Lotus/Types/Game/Library/Targets/Research6Target",
"/Lotus/Types/Game/Library/Targets/Research7Target"
].map(type => ({ TargetType: type, Scans: 10, Completed: true }));
}
return inventoryResponse;
};
const addString = (arr: string[], str: string): void => {
if (arr.indexOf(str) == -1) {
arr.push(str);
}
};
const allEudicoHeistJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyOne",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyTwo",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyThree",
"/Lotus/Types/Gameplay/Venus/Jobs/Heists/HeistProfitTakerBountyFour"
];
const getExpRequiredForMr = (rank: number): number => {
if (rank <= 30) {

View File

@ -4,16 +4,16 @@ import { config } from "@/src/services/configService";
import { buildConfig } from "@/src/services/buildConfigService";
import { Account } from "@/src/models/loginModel";
import { createAccount, isCorrectPassword, isNameTaken } from "@/src/services/loginService";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "@/src/services/loginService";
import { IDatabaseAccountJson, ILoginRequest, ILoginResponse } from "@/src/types/loginTypes";
import { logger } from "@/src/utils/logger";
import { version_compare } from "@/src/helpers/inventoryHelpers";
import { sendWsBroadcastTo } from "@/src/services/webService";
export const loginController: RequestHandler = async (request, response) => {
const loginRequest = JSON.parse(String(request.body)) as ILoginRequest; // parse octet stream of json data to json object
const account = await Account.findOne({ email: loginRequest.email });
const nonce = Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
const buildLabel: string =
typeof request.query.buildLabel == "string"
@ -42,26 +42,14 @@ export const loginController: RequestHandler = async (request, response) => {
loginRequest.ClientType == "webui-register")
) {
try {
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
if (await isNameTaken(name)) {
let suffix = 0;
do {
++suffix;
name = nameFromEmail + suffix;
} while (await isNameTaken(name));
}
const name = await getUsernameFromEmail(loginRequest.email);
const newAccount = await createAccount({
email: loginRequest.email,
password: loginRequest.password,
DisplayName: name,
CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
CrossPlatformAllowed: true,
ForceLogoutVersion: 0,
ConsentNeeded: false,
TrackedSettings: [],
Nonce: nonce,
ClientType: loginRequest.ClientType,
Nonce: createNonce(),
BuildLabel: buildLabel,
LastLogin: new Date()
});
@ -80,38 +68,29 @@ export const loginController: RequestHandler = async (request, response) => {
return;
}
if (loginRequest.ClientType == "webui-register") {
response.status(400).json({ error: "account already exists" });
return;
}
if (!isCorrectPassword(loginRequest.password, account.password)) {
response.status(400).json({ error: "incorrect login data" });
return;
}
if (loginRequest.ClientType == "webui") {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = nonce;
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
// U17 seems to handle "nonce still set" like a login failure.
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
response.status(400).send({ error: "nonce still set" });
return;
}
} else {
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
// U17 seems to handle "nonce still set" like a login failure.
if (version_compare(buildLabel, "2015.12.05.18.07") >= 0) {
response.status(400).send({ error: "nonce still set" });
return;
}
}
account.ClientType = loginRequest.ClientType;
account.Nonce = nonce;
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
account.BuildLabel = buildLabel;
account.LastLogin = new Date();
}
account.ClientType = loginRequest.ClientType;
account.Nonce = createNonce();
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
account.BuildLabel = buildLabel;
account.LastLogin = new Date();
await account.save();
// Tell WebUI its nonce has been invalidated
sendWsBroadcastTo(account._id.toString(), { logged_out: true });
response.json(createLoginResponse(myAddress, myUrlBase, account.toJSON(), buildLabel));
};

View File

@ -8,6 +8,7 @@ import {
setAccountGotLoginRewardToday
} from "@/src/services/loginRewardService";
import { getInventory } from "@/src/services/inventoryService";
import { config } from "@/src/services/configService";
export const loginRewardsController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
@ -15,7 +16,7 @@ export const loginRewardsController: RequestHandler = async (req, res) => {
const isMilestoneDay = account.LoginDays == 5 || account.LoginDays % 50 == 0;
const nextMilestoneDay = account.LoginDays < 5 ? 5 : (Math.trunc(account.LoginDays / 50) + 1) * 50;
if (today == account.LastLoginRewardDate) {
if (today == account.LastLoginRewardDate || config.disableDailyTribute) {
res.json({
DailyTributeInfo: {
IsMilestoneDay: isMilestoneDay,

View File

@ -1,5 +1,6 @@
import { RequestHandler } from "express";
import { Account } from "@/src/models/loginModel";
import { sendWsBroadcastTo } from "@/src/services/webService";
export const logoutController: RequestHandler = async (req, res) => {
if (!req.query.accountId) {
@ -10,7 +11,7 @@ export const logoutController: RequestHandler = async (req, res) => {
throw new Error("Request is missing nonce parameter");
}
await Account.updateOne(
const stat = await Account.updateOne(
{
_id: req.query.accountId,
Nonce: nonce
@ -19,6 +20,10 @@ export const logoutController: RequestHandler = async (req, res) => {
Nonce: 0
}
);
if (stat.modifiedCount) {
// Tell WebUI its nonce has been invalidated
sendWsBroadcastTo(req.query.accountId as string, { logged_out: true });
}
res.writeHead(200, {
"Content-Type": "text/html",

View File

@ -7,6 +7,7 @@ import { generateRewardSeed, getInventory } from "@/src/services/inventoryServic
import { getInventoryResponse } from "./inventoryController";
import { logger } from "@/src/utils/logger";
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
import { sendWsBroadcastTo } from "@/src/services/webService";
/*
**** INPUT ****
@ -76,6 +77,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
InventoryJson: JSON.stringify(inventoryResponse),
MissionRewards: []
});
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
return;
}
@ -86,7 +88,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
AffiliationMods,
SyndicateXPItemReward,
ConquestCompletedMissionsCount
} = await addMissionRewards(inventory, missionReport, firstCompletion);
} = await addMissionRewards(account, inventory, missionReport, firstCompletion);
if (missionReport.EndOfMatchUpload) {
inventory.RewardSeed = generateRewardSeed();
@ -106,6 +108,7 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
AffiliationMods,
ConquestCompletedMissionsCount
} satisfies IMissionInventoryUpdateResponse);
sendWsBroadcastTo(account._id.toString(), { update_inventory: true });
};
/*

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import { sendWsBroadcastTo } from "@/src/services/webService";
interface INameWeaponRequest {
ItemName: string;
@ -27,4 +28,5 @@ export const nameWeaponController: RequestHandler = async (req, res) => {
res.json({
InventoryChanges: currencyChanges
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -8,16 +8,15 @@ import {
getKnifeUpgrade,
getNemesisManifest,
getNemesisPasscode,
getNemesisPasscodeModTypes,
GUESS_CORRECT,
GUESS_INCORRECT,
GUESS_NEUTRAL,
GUESS_NONE,
GUESS_WILDCARD,
IKnifeResponse
IKnifeResponse,
parseUpgrade
} from "@/src/helpers/nemesisHelpers";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
import { getAccountForRequest } from "@/src/services/loginService";
@ -215,7 +214,19 @@ export const nemesisController: RequestHandler = async (req, res) => {
}
];
inventory.Nemesis!.Weakened = true;
await consumePasscodeModCharges(inventory, response);
// Subtract a charge from all requiem mods installed on parazon
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(
inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid
);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
for (let i = 3; i != 6; ++i) {
//logger.debug(`subtracting a charge from ${dataknifeUpgrades[i]}`);
const upgrade = parseUpgrade(inventory, dataknifeUpgrades[i]);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
}
}
} else {
// Guess was incorrect, increase rank
@ -380,18 +391,3 @@ interface IKnife {
AttachedUpgrades: IUpgradeClient[];
HiddenWhenHolstered: boolean;
}
const consumePasscodeModCharges = async (
inventory: TInventoryDatabaseDocument,
response: IKnifeResponse
): Promise<void> => {
const loadout = (await Loadout.findById(inventory.LoadOutPresets, "DATAKNIFE"))!;
const dataknifeLoadout = loadout.DATAKNIFE.id(inventory.CurrentLoadOutIds[LoadoutIndex.DATAKNIFE].$oid);
const dataknifeConfigIndex = dataknifeLoadout?.s?.mod ?? 0;
const dataknifeUpgrades = inventory.DataKnives[0].Configs[dataknifeConfigIndex].Upgrades!;
const modTypes = getNemesisPasscodeModTypes(inventory.Nemesis!);
for (const modType of modTypes) {
const upgrade = getKnifeUpgrade(inventory, dataknifeUpgrades, modType);
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
}
};

View File

@ -57,7 +57,11 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
component.DecoCapacity -= meta.capacityCost;
}
} else {
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
if (!itemType || meta.dojoCapacityCost === undefined) {
throw new Error(`unknown deco type: ${deco.Type}`);
}
component.DecoCapacity -= meta.dojoCapacityCost;
if (deco.Sockets !== undefined) {
guild.VaultFusionTreasures!.find(x => x.ItemType == itemType && x.Sockets == deco.Sockets)!.ItemCount -=
1;

View File

@ -3,6 +3,7 @@ import { getAccountIdForRequest } from "@/src/services/loginService";
import { IPurchaseRequest } from "@/src/types/purchaseTypes";
import { handlePurchase } from "@/src/services/purchaseService";
import { getInventory } from "@/src/services/inventoryService";
import { sendWsBroadcastTo } from "@/src/services/webService";
export const purchaseController: RequestHandler = async (req, res) => {
const purchaseRequest = JSON.parse(String(req.body)) as IPurchaseRequest;
@ -10,5 +11,7 @@ export const purchaseController: RequestHandler = async (req, res) => {
const inventory = await getInventory(accountId);
const response = await handlePurchase(purchaseRequest, inventory);
await inventory.save();
//console.log(JSON.stringify(response, null, 2));
res.json(response);
sendWsBroadcastTo(accountId, { update_inventory: true });
};

View File

@ -1,6 +1,7 @@
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { sendWsBroadcastTo } from "@/src/services/webService";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { RequestHandler } from "express";
@ -22,6 +23,7 @@ export const renamePetController: RequestHandler = async (req, res) => {
...data,
inventoryChanges: inventoryChanges
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};
interface IRenamePetRequest {

View File

@ -15,6 +15,7 @@ import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
import { ExportDojoRecipes } from "warframe-public-export-plus";
import { IInventoryChanges } from "@/src/types/purchaseTypes";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { sendWsBroadcastTo } from "@/src/services/webService";
export const sellController: RequestHandler = async (req, res) => {
const payload = JSON.parse(String(req.body)) as ISellRequest;
@ -279,6 +280,7 @@ export const sellController: RequestHandler = async (req, res) => {
res.json({
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
});
sendWsBroadcastTo(accountId, { update_inventory: true });
};
interface ISellRequest {

View File

@ -45,9 +45,9 @@ export const startRecipeController: RequestHandler = async (req, res) => {
for (let i = 0; i != recipe.ingredients.length; ++i) {
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
if (recipe.ingredients[i].ItemType == "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
const index = inventory.KubrowPetEggs!.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
const index = inventory.KubrowPetEggs.findIndex(x => x._id.equals(startRecipeRequest.Ids[i]));
if (index != -1) {
inventory.KubrowPetEggs!.splice(index, 1);
inventory.KubrowPetEggs.splice(index, 1);
}
} else {
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
@ -72,6 +72,10 @@ export const startRecipeController: RequestHandler = async (req, res) => {
if (recipe.secretIngredientAction == "SIA_CREATE_KUBROW") {
inventoryChanges = addKubrowPet(inventory, getRandomElement(recipe.secretIngredients!)!.ItemType);
pr.KubrowPet = new Types.ObjectId(fromOid(inventoryChanges.KubrowPets![0].ItemId));
} else if (recipe.secretIngredientAction == "SIA_DISTILL_PRINT") {
pr.KubrowPet = new Types.ObjectId(startRecipeRequest.Ids[recipe.ingredients.length]);
const pet = inventory.KubrowPets.id(pr.KubrowPet)!;
pet.Details!.PrintsRemaining -= 1;
} else if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
const spectreLoadout: ISpectreLoadout = {
ItemType: recipe.resultType,

View File

@ -1,7 +1,7 @@
import { RequestHandler } from "express";
import { getJSONfromString } from "@/src/helpers/stringHelpers";
import { getAccountForRequest } from "@/src/services/loginService";
import { addChallenges, getInventory } from "@/src/services/inventoryService";
import { addCalendarProgress, addChallenges, getInventory } from "@/src/services/inventoryService";
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
import { IAffiliationMods } from "@/src/types/purchaseTypes";
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
@ -13,7 +13,7 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
const inventory = await getInventory(
account._id.toString(),
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations"
"ChallengesFixVersion ChallengeProgress SeasonChallengeHistory Affiliations CalendarProgress"
);
let affiliationMods: IAffiliationMods[] = [];
if (challenges.ChallengeProgress) {
@ -25,13 +25,17 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
);
}
for (const [key, value] of getEntriesUnsafe(challenges)) {
if (value === undefined) {
logger.error(`Challenge progress update key ${key} has no value`);
continue;
}
switch (key) {
case "ChallengesFixVersion":
inventory.ChallengesFixVersion = value;
break;
case "SeasonChallengeHistory":
value!.forEach(({ challenge, id }) => {
value.forEach(({ challenge, id }) => {
const itemIndex = inventory.SeasonChallengeHistory.findIndex(i => i.challenge === challenge);
if (itemIndex !== -1) {
inventory.SeasonChallengeHistory[itemIndex].id = id;
@ -41,6 +45,10 @@ export const updateChallengeProgressController: RequestHandler = async (req, res
});
break;
case "CalendarProgress":
addCalendarProgress(inventory, value);
break;
case "ChallengeProgress":
case "SeasonChallengeCompletions":
case "ChallengePTS":
@ -63,5 +71,6 @@ interface IUpdateChallengeProgressRequest {
ChallengeProgress?: IChallengeProgress[];
SeasonChallengeHistory?: ISeasonChallenge[];
SeasonChallengeCompletions?: ISeasonChallenge[];
CalendarProgress?: { challenge: string }[];
crossPlaySetting?: string;
}

View File

@ -0,0 +1,24 @@
import { getAccountIdForRequest } from "@/src/services/loginService";
import { getInventory, addRecipes } from "@/src/services/inventoryService";
import { RequestHandler } from "express";
import { ExportRecipes } from "warframe-public-export-plus";
export const addMissingHelminthBlueprintsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId, "Recipes");
const allHelminthRecipes = Object.keys(ExportRecipes).filter(
key => ExportRecipes[key].secretIngredientAction === "SIA_WARFRAME_ABILITY"
);
const inventoryHelminthRecipes = inventory.Recipes.filter(recipe =>
recipe.ItemType.startsWith("/Lotus/Types/Recipes/AbilityOverrides/")
).map(recipe => recipe.ItemType);
const missingHelminthRecipes = allHelminthRecipes
.filter(key => !inventoryHelminthRecipes.includes(key))
.map(ItemType => ({ ItemType, ItemCount: 1 }));
addRecipes(inventory, missingHelminthRecipes);
await inventory.save();
res.end();
};

View File

@ -0,0 +1,40 @@
import { addString } from "@/src/helpers/stringHelpers";
import { getInventory } from "@/src/services/inventoryService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { addFixedLevelRewards } from "@/src/services/missionInventoryUpdateService";
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
import { IMissionReward } from "@/src/types/missionTypes";
import { RequestHandler } from "express";
import { ExportRegions } from "warframe-public-export-plus";
export const completeAllMissionsController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const inventory = await getInventory(accountId);
const MissionRewards: IMissionReward[] = [];
for (const [tag, node] of Object.entries(ExportRegions)) {
let mission = inventory.Missions.find(x => x.Tag == tag);
if (!mission) {
mission =
inventory.Missions[
inventory.Missions.push({
Completes: 0,
Tier: 0,
Tag: tag
}) - 1
];
}
if (mission.Completes == 0) {
mission.Completes++;
if (node.missionReward) {
addFixedLevelRewards(node.missionReward, MissionRewards);
}
}
mission.Tier = 1;
}
for (const reward of MissionRewards) {
await handleStoreItemAcquisition(reward.StoreItem, inventory, reward.ItemCount, undefined, true);
}
addString(inventory.NodeIntrosCompleted, "TeshinHardModeUnlocked");
await inventory.save();
res.end();
};

View File

@ -0,0 +1,44 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { saveConfig } from "@/src/services/configWatcherService";
import { sendWsBroadcastExcept } from "@/src/services/webService";
export const getConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
const responseData: Record<string, boolean | string | number | null> = {};
for (const id of req.body as string[]) {
const [obj, idx] = configIdToIndexable(id);
responseData[id] = obj[idx] ?? null;
}
res.json(responseData);
} else {
res.status(401).end();
}
};
export const setConfigController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
for (const [id, value] of Object.entries(req.body as Record<string, boolean | string | number>)) {
const [obj, idx] = configIdToIndexable(id);
obj[idx] = value;
}
sendWsBroadcastExcept(parseInt(String(req.query.wsid)), { config_reloaded: true });
await saveConfig();
res.end();
} else {
res.status(401).end();
}
};
const configIdToIndexable = (id: string): [Record<string, boolean | string | number | undefined>, string] => {
let obj = config as unknown as Record<string, never>;
const arr = id.split(".");
while (arr.length > 1) {
obj = obj[arr[0]];
arr.splice(0, 1);
}
return [obj, arr[0]];
};

View File

@ -1,14 +0,0 @@
import { RequestHandler } from "express";
import { config } from "@/src/services/configService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
const getConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
res.json(config);
} else {
res.status(401).end();
}
};
export { getConfigDataController };

View File

@ -55,6 +55,7 @@ interface ItemLists {
EvolutionProgress: ListedItem[];
mods: ListedItem[];
Boosters: ListedItem[];
//circuitGameModes: ListedItem[];
}
const relicQualitySuffixes: Record<TRelicQuality, string> = {
@ -64,6 +65,10 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
VPQ_PLATINUM: " [Exceptional]"
};
/*const toTitleCase = (str: string): string => {
return str.replace(/[^\s-]+/g, word => word.charAt(0).toUpperCase() + word.substr(1).toLowerCase());
};*/
const getItemListsController: RequestHandler = (req, response) => {
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
const res: ItemLists = {
@ -87,6 +92,36 @@ const getItemListsController: RequestHandler = (req, response) => {
EvolutionProgress: [],
mods: [],
Boosters: []
/*circuitGameModes: [
{
uniqueName: "Survival",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Survival", lang))
},
{
uniqueName: "VoidFlood",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Corruption", lang))
},
{
uniqueName: "Excavation",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Excavation", lang))
},
{
uniqueName: "Defense",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Defense", lang))
},
{
uniqueName: "Exterminate",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Exterminate", lang))
},
{
uniqueName: "Assassination",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Assassination", lang))
},
{
uniqueName: "Alchemy",
name: toTitleCase(getString("/Lotus/Language/Missions/MissionName_Alchemy", lang))
}
]*/
};
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
res[item.productCategory].push({

View File

@ -128,7 +128,7 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
await completeQuest(inventory, questKey.ItemType);
} else {
const progress = {
c: questManifest.chainStages![currentStage].key ? -1 : 0,
c: 0,
i: false,
m: false,
b: []

View File

@ -23,9 +23,9 @@ export const setBoosterController: RequestHandler = async (req, res) => {
res.status(400).send("Invalid ItemType provided.");
return;
}
const now = Math.floor(Date.now() / 1000);
const now = Math.trunc(Date.now() / 1000);
for (const { ItemType, ExpiryDate } of requests) {
if (ExpiryDate < now) {
if (ExpiryDate <= now) {
// remove expired boosters
const index = boosters.findIndex(item => item.ItemType === ItemType);
if (index !== -1) {

View File

@ -1,21 +0,0 @@
import { RequestHandler } from "express";
import { saveConfig } from "@/src/services/configWatcherService";
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
import { config, IConfig } from "@/src/services/configService";
export const updateConfigDataController: RequestHandler = async (req, res) => {
const account = await getAccountForRequest(req);
if (isAdministrator(account)) {
const data = req.body as IUpdateConfigDataRequest;
config[data.key] = data.value;
await saveConfig();
res.end();
} else {
res.status(401).end();
}
};
interface IUpdateConfigDataRequest {
key: keyof IConfig;
value: never;
}

View File

@ -0,0 +1,39 @@
import { getInventory } from "@/src/services/inventoryService";
import { WeaponTypeInternal } from "@/src/services/itemDataService";
import { getAccountIdForRequest } from "@/src/services/loginService";
import { RequestHandler } from "express";
export const updateFingerprintController: RequestHandler = async (req, res) => {
const accountId = await getAccountIdForRequest(req);
const request = req.body as IUpdateFingerPrintRequest;
const inventory = await getInventory(accountId, request.category);
const item = inventory[request.category].id(request.oid);
if (item) {
if (request.action == "set" && request.upgradeFingerprint.buffs[0].Tag) {
const newUpgradeFingerprint = request.upgradeFingerprint;
if (!newUpgradeFingerprint.compact) newUpgradeFingerprint.compact = item.ItemType;
item.UpgradeType = request.upgradeType;
item.UpgradeFingerprint = JSON.stringify(newUpgradeFingerprint);
} else if (request.action == "remove") {
item.UpgradeFingerprint = undefined;
item.UpgradeType = undefined;
}
await inventory.save();
}
res.end();
};
interface IUpdateFingerPrintRequest {
category: WeaponTypeInternal;
oid: string;
action: "set" | "remove";
upgradeType: string;
upgradeFingerprint: {
compact?: string;
buffs: {
Tag: string;
Value: number;
}[];
};
}

View File

@ -0,0 +1,10 @@
import { args } from "@/src/helpers/commandLineArguments";
import { sendWsBroadcast } from "@/src/services/webService";
import { RequestHandler } from "express";
export const webuiFileChangeDetectedController: RequestHandler = (req, res) => {
if (args.dev && args.secret && req.query.secret == args.secret) {
sendWsBroadcast({ reload: true });
}
res.end();
};

View File

@ -1,6 +1,19 @@
import { RequestHandler } from "express";
import { getWorldState } from "@/src/services/worldStateService";
import { getWorldState, populateDailyDeal, populateFissures } from "@/src/services/worldStateService";
import { version_compare } from "@/src/helpers/inventoryHelpers";
export const worldStateController: RequestHandler = (req, res) => {
res.json(getWorldState(req.query.buildLabel as string | undefined));
export const worldStateController: RequestHandler = async (req, res) => {
const buildLabel = req.query.buildLabel as string | undefined;
const worldState = getWorldState(buildLabel);
const populatePromises = [populateDailyDeal(worldState)];
// Omitting void fissures for versions prior to Dante Unbound to avoid script errors.
if (!buildLabel || version_compare(buildLabel, "2024.03.24.20.00") >= 0) {
populatePromises.push(populateFissures(worldState));
}
await Promise.all(populatePromises);
res.json(worldState);
};

View File

@ -0,0 +1,23 @@
interface IArguments {
configPath?: string;
dev?: boolean;
secret?: string;
}
export const args: IArguments = {};
for (let i = 2; i < process.argv.length; ) {
switch (process.argv[i++]) {
case "--configPath":
args.configPath = process.argv[i++];
break;
case "--dev":
args.dev = true;
break;
case "--secret":
args.secret = process.argv[i++];
break;
}
}

View File

@ -237,7 +237,7 @@ export const getNemesisPasscode = (nemesis: { fp: bigint; Faction: TNemesisFacti
return passcode;
};
const requiemMods: readonly string[] = [
/*const requiemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
@ -246,7 +246,7 @@ const requiemMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod"
];
];*/
export const antivirusMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
@ -259,12 +259,12 @@ export const antivirusMods: readonly string[] = [
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
];
export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
/*export const getNemesisPasscodeModTypes = (nemesis: { fp: bigint; Faction: TNemesisFaction }): string[] => {
const passcode = getNemesisPasscode(nemesis);
return nemesis.Faction == "FC_INFESTATION"
? passcode.map(i => antivirusMods[i])
: passcode.map(i => requiemMods[i]);
};
};*/
// Symbols; 0-7 are the normal requiem mods.
export const GUESS_NONE = 8;
@ -343,6 +343,27 @@ export const getKnifeUpgrade = (
throw new Error(`${type} does not seem to be installed on parazon?!`);
};
export const parseUpgrade = (
inventory: TInventoryDatabaseDocument,
str: string
): { ItemId: IOid; ItemType: string } => {
if (str.length == 24) {
const upgrade = inventory.Upgrades.id(str);
if (upgrade) {
return {
ItemId: { $oid: str },
ItemType: upgrade.ItemType
};
}
throw new Error(`Could not resolve oid ${str}`);
} else {
return {
ItemId: { $oid: "000000000000000000000000" },
ItemType: str
};
}
};
export const consumeModCharge = (
response: IKnifeResponse,
inventory: TInventoryDatabaseDocument,

View File

@ -1,5 +1,4 @@
import path from "path";
export const rootDir = path.join(__dirname, "../..");
export const isDev = path.basename(rootDir) != "build";
export const repoDir = isDev ? rootDir : path.join(rootDir, "..");
export const repoDir = path.basename(rootDir) != "build" ? rootDir : path.join(rootDir, "..");

View File

@ -54,3 +54,9 @@ export const regexEscape = (str: string): string => {
str = str.split("}").join("\\}");
return str;
};
export const addString = (arr: string[], str: string): void => {
if (arr.indexOf(str) == -1) {
arr.push(str);
}
};

View File

@ -21,7 +21,8 @@ import mongoose from "mongoose";
import { JSONStringify } from "json-with-bigint";
import { startWebServer } from "./services/webService";
import { validateConfig } from "@/src/services/configWatcherService";
import { syncConfigWithDatabase, validateConfig } from "@/src/services/configWatcherService";
import { updateWorldStateCollections } from "./services/worldStateService";
// Patch JSON.stringify to work flawlessly with Bigints.
JSON.stringify = JSONStringify;
@ -32,7 +33,14 @@ mongoose
.connect(config.mongodbUrl)
.then(() => {
logger.info("Connected to MongoDB");
syncConfigWithDatabase();
startWebServer();
void updateWorldStateCollections();
setInterval(() => {
void updateWorldStateCollections();
}, 60_000);
})
.catch(error => {
if (error instanceof Error) {

View File

@ -17,7 +17,6 @@ export interface IMessageDatabase extends IMessage {
ownerId: Types.ObjectId;
date: Date; //created at
attVisualOnly?: boolean;
expiry?: Date;
_id: Types.ObjectId;
}
@ -28,11 +27,13 @@ export interface IMessage {
icon?: string;
highPriority?: boolean;
lowPrioNewPlayers?: boolean;
startDate?: Date;
endDate?: Date;
transmission?: string;
att?: string[];
countedAtt?: ITypeCount[];
transmission?: string;
startDate?: Date;
endDate?: Date;
goalTag?: string;
CrossPlatform?: boolean;
arg?: Arg[];
gifts?: IGift[];
r?: boolean;
@ -107,7 +108,10 @@ const messageSchema = new Schema<IMessageDatabase>(
lowPrioNewPlayers: Boolean,
startDate: Date,
endDate: Date,
goalTag: String,
date: { type: Date, required: true },
r: Boolean,
CrossPlatform: Boolean,
att: { type: [String], default: undefined },
gifts: { type: [giftSchema], default: undefined },
countedAtt: { type: [typeCountSchema], default: undefined },
@ -128,7 +132,7 @@ const messageSchema = new Schema<IMessageDatabase>(
declineAction: String,
hasAccountAction: Boolean
},
{ timestamps: { createdAt: "date", updatedAt: false }, id: false }
{ id: false }
);
messageSchema.virtual("messageId").get(function (this: IMessageDatabase) {
@ -151,13 +155,15 @@ messageSchema.set("toJSON", {
if (messageDatabase.startDate && messageDatabase.endDate) {
messageClient.startDate = toMongoDate(messageDatabase.startDate);
messageClient.endDate = toMongoDate(messageDatabase.endDate);
} else {
delete messageClient.startDate;
delete messageClient.endDate;
}
}
});
messageSchema.index({ ownerId: 1 });
messageSchema.index({ expiry: 1 }, { expireAfterSeconds: 0 });
messageSchema.index({ endDate: 1 }, { expireAfterSeconds: 0 });
export const Inbox = model<IMessageDatabase>("Inbox", messageSchema, "inbox");

View File

@ -1,4 +1,4 @@
import { Document, HydratedDocument, Model, Schema, Types, model } from "mongoose";
import { Document, Model, Schema, Types, model } from "mongoose";
import {
IFlavourItem,
IRawUpgrade,
@ -7,7 +7,6 @@ import {
IBooster,
IInventoryClient,
ISlots,
IMailboxDatabase,
IDuviriInfo,
IPendingRecipeDatabase,
IPendingRecipeClient,
@ -54,7 +53,6 @@ import {
IUpgradeDatabase,
ICrewShipMemberDatabase,
ICrewShipMemberClient,
IMailboxClient,
TEquipmentKey,
equipmentKeys,
IKubrowPetDetailsDatabase,
@ -93,13 +91,17 @@ import {
ICrewMemberSkillEfficiency,
ICrewMemberDatabase,
ICrewMemberClient,
ISortieRewardAttenuation,
IRewardAttenuation,
IInvasionProgressDatabase,
IInvasionProgressClient,
IAccolades,
IHubNpcCustomization,
ILotusCustomization,
IEndlessXpReward
IEndlessXpReward,
IPersonalGoalProgressDatabase,
IPersonalGoalProgressClient,
IKubrowPetPrintClient,
IKubrowPetPrintDatabase
} from "../../types/inventoryTypes/inventoryTypes";
import { IOid } from "../../types/commonTypes";
import {
@ -371,7 +373,7 @@ FlavourItemSchema.set("toJSON", {
}
});
const MailboxSchema = new Schema<IMailboxDatabase>(
/*const MailboxSchema = new Schema<IMailboxDatabase>(
{
LastInboxId: Schema.Types.ObjectId
},
@ -384,7 +386,7 @@ MailboxSchema.set("toJSON", {
delete mailboxDatabase.__v;
(returnedObject as IMailboxClient).LastInboxId = toOid(mailboxDatabase.LastInboxId);
}
});
});*/
const DuviriInfoSchema = new Schema<IDuviriInfo>(
{
@ -457,11 +459,35 @@ const discoveredMarkerSchema = new Schema<IDiscoveredMarker>(
{ _id: false }
);
const personalGoalProgressSchema = new Schema<IPersonalGoalProgressDatabase>(
{
Best: Number,
Count: Number,
Tag: String,
goalId: Types.ObjectId
},
{ _id: false }
);
personalGoalProgressSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const db = obj as IPersonalGoalProgressDatabase;
const client = obj as IPersonalGoalProgressClient;
client._id = toOid(db.goalId);
delete obj.goalId;
delete obj.__v;
}
});
const challengeProgressSchema = new Schema<IChallengeProgress>(
{
Progress: Number,
Name: String,
Completed: [String]
Completed: { type: [String], default: undefined },
ReceivedJunctionReward: Boolean,
Name: { type: String, required: true }
},
{ _id: false }
);
@ -984,6 +1010,27 @@ const traitsSchema = new Schema<ITraits>(
{ _id: false }
);
const kubrowPetPrintSchema = new Schema<IKubrowPetPrintDatabase>({
ItemType: String,
Name: String,
IsMale: Boolean,
Size: Number,
DominantTraits: traitsSchema,
RecessiveTraits: traitsSchema
});
kubrowPetPrintSchema.set("toJSON", {
virtuals: true,
transform(_doc, obj) {
const db = obj as IKubrowPetPrintDatabase;
const client = obj as IKubrowPetPrintClient;
client.ItemId = toOid(db._id);
delete obj._id;
delete obj.__v;
}
});
const detailsSchema = new Schema<IKubrowPetDetailsDatabase>(
{
Name: String,
@ -1370,10 +1417,10 @@ lastSortieRewardSchema.set("toJSON", {
}
});
const sortieRewardAttenutationSchema = new Schema<ISortieRewardAttenuation>(
const rewardAttenutationSchema = new Schema<IRewardAttenuation>(
{
Tag: String,
Atten: Number
Tag: { type: String, required: true },
Atten: { type: Number, required: true }
},
{ _id: false }
);
@ -1487,7 +1534,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
KubrowPetEggs: [kubrowPetEggSchema],
//Prints Cat(3 Prints)\Kubrow(2 Prints) Pets
//KubrowPetPrints: [Schema.Types.Mixed],
KubrowPetPrints: [kubrowPetPrintSchema],
//Item for EquippedGear example:Scaner,LoadoutTechSummon etc
Consumables: [typeCountSchema],
@ -1601,6 +1648,9 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
PendingSpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
SpectreLoadouts: { type: [spectreLoadoutsSchema], default: undefined },
//Darvo Deal
UsedDailyDeals: [String],
//New Quest Email
EmailItems: [typeCountSchema],
@ -1616,7 +1666,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
CompletedSorties: [String],
LastSortieReward: { type: [lastSortieRewardSchema], default: undefined },
LastLiteSortieReward: { type: [lastSortieRewardSchema], default: undefined },
SortieRewardAttenuation: { type: [sortieRewardAttenutationSchema], default: undefined },
SortieRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined },
// Resource Extractor Drones
Drones: [droneSchema],
@ -1630,7 +1680,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//CompletedJobs: [Schema.Types.Mixed],
//Game mission\ivent score example "Tag": "WaterFight", "Best": 170, "Count": 1258,
//PersonalGoalProgress: [Schema.Types.Mixed],
PersonalGoalProgress: { type: [personalGoalProgressSchema], default: undefined },
//Setting interface Style
ThemeStyle: String,
@ -1701,7 +1751,7 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//Unknown and system
DuviriInfo: DuviriInfoSchema,
LastInventorySync: Schema.Types.ObjectId,
Mailbox: MailboxSchema,
//Mailbox: MailboxSchema,
HandlerPoints: Number,
ChallengesFixVersion: Number,
PlayedParkourTutorial: Boolean,
@ -1717,7 +1767,6 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
//ChallengeInstanceStates: [Schema.Types.Mixed],
RecentVendorPurchases: { type: [recentVendorPurchaseSchema], default: undefined },
//Robotics: [Schema.Types.Mixed],
//UsedDailyDeals: [Schema.Types.Mixed],
CollectibleSeries: { type: [collectibleEntrySchema], default: undefined },
HasResetAccount: { type: Boolean, default: false },
@ -1754,7 +1803,11 @@ const inventorySchema = new Schema<IInventoryDatabase, InventoryDocumentProps>(
BrandedSuits: { type: [Schema.Types.ObjectId], default: undefined },
LockedWeaponGroup: { type: lockedWeaponGroupSchema, default: undefined },
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined }
HubNpcCustomizations: { type: [hubNpcCustomizationSchema], default: undefined },
ClaimedJunctionChallengeRewards: { type: [String], default: undefined },
SpecialItemRewardAttenuation: { type: [rewardAttenutationSchema], default: undefined }
},
{ timestamps: { createdAt: "Created", updatedAt: false } }
);
@ -1824,6 +1877,7 @@ export type InventoryDocumentProps = {
CrewShipSalvagedWeaponSkins: Types.DocumentArray<IUpgradeDatabase>;
PersonalTechProjects: Types.DocumentArray<IPersonalTechProjectDatabase>;
CrewMembers: Types.DocumentArray<ICrewMemberDatabase>;
KubrowPetPrints: Types.DocumentArray<IKubrowPetPrintDatabase>;
} & { [K in TEquipmentKey]: Types.DocumentArray<IEquipmentDatabase> };
// eslint-disable-next-line @typescript-eslint/no-empty-object-type

View File

@ -11,13 +11,13 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
DisplayName: { type: String, required: true, unique: true },
CountryCode: { type: String, required: true },
CountryCode: { type: String, default: "" },
ClientType: { type: String },
CrossPlatformAllowed: { type: Boolean, required: true },
ForceLogoutVersion: { type: Number, required: true },
CrossPlatformAllowed: { type: Boolean, default: true },
ForceLogoutVersion: { type: Number, default: 0 },
AmazonAuthToken: { type: String },
AmazonRefreshToken: { type: String },
ConsentNeeded: { type: Boolean, required: true },
ConsentNeeded: { type: Boolean, default: false },
TrackedSettings: { type: [String], default: [] },
Nonce: { type: Number, default: 0 },
BuildLabel: String,
@ -25,7 +25,8 @@ const databaseAccountSchema = new Schema<IDatabaseAccountJson>(
LastLogin: { type: Date, default: 0 },
LatestEventMessageDate: { type: Date, default: 0 },
LastLoginRewardDate: { type: Number, default: 0 },
LoginDays: { type: Number, default: 1 }
LoginDays: { type: Number, default: 1 },
DailyFirstWinDate: { type: Number, default: 0 }
},
opts
);

View File

@ -40,6 +40,7 @@ const placedDecosSchema = new Schema<IPlacedDecosDatabase>(
Pos: [Number],
Rot: [Number],
Scale: Number,
Sockets: Number,
PictureFrameInfo: { type: pictureFrameInfoSchema, default: undefined }
},
{ id: false }

View File

@ -0,0 +1,30 @@
import { IDailyDealDatabase, IFissureDatabase } from "@/src/types/worldStateTypes";
import { model, Schema } from "mongoose";
const fissureSchema = new Schema<IFissureDatabase>({
Activation: Date,
Expiry: Date,
Node: String, // must be unique
Modifier: String,
Hard: Boolean
});
fissureSchema.index({ Expiry: 1 }, { expireAfterSeconds: 0 }); // With this, MongoDB will automatically delete expired entries.
export const Fissure = model<IFissureDatabase>("Fissure", fissureSchema);
const dailyDealSchema = new Schema<IDailyDealDatabase>({
StoreItem: { type: String, required: true },
Activation: { type: Date, required: true },
Expiry: { type: Date, required: true },
Discount: { type: Number, required: true },
OriginalPrice: { type: Number, required: true },
SalePrice: { type: Number, required: true },
AmountTotal: { type: Number, required: true },
AmountSold: { type: Number, required: true }
});
dailyDealSchema.index({ StoreItem: 1 }, { unique: true });
dailyDealSchema.index({ Expiry: 1 }, { expireAfterSeconds: 86400 });
export const DailyDeal = model<IDailyDealDatabase>("DailyDeal", dailyDealSchema);

View File

@ -19,6 +19,7 @@ import { changeDojoRootController } from "@/src/controllers/api/changeDojoRootCo
import { changeGuildRankController } from "@/src/controllers/api/changeGuildRankController";
import { checkDailyMissionBonusController } from "@/src/controllers/api/checkDailyMissionBonusController";
import { claimCompletedRecipeController } from "@/src/controllers/api/claimCompletedRecipeController";
import { claimJunctionChallengeRewardController } from "@/src/controllers/api/claimJunctionChallengeRewardController";
import { claimLibraryDailyTaskRewardController } from "@/src/controllers/api/claimLibraryDailyTaskRewardController";
import { clearDialogueHistoryController } from "@/src/controllers/api/clearDialogueHistoryController";
import { clearNewEpisodeRewardController } from "@/src/controllers/api/clearNewEpisodeRewardController";
@ -237,6 +238,7 @@ apiRouter.post("/artifacts.php", artifactsController);
apiRouter.post("/artifactTransmutation.php", artifactTransmutationController);
apiRouter.post("/changeDojoRoot.php", changeDojoRootController);
apiRouter.post("/claimCompletedRecipe.php", claimCompletedRecipeController);
apiRouter.post("/claimJunctionChallengeReward.php", claimJunctionChallengeRewardController);
apiRouter.post("/clearDialogueHistory.php", clearDialogueHistoryController);
apiRouter.post("/clearNewEpisodeReward.php", clearNewEpisodeRewardController);
apiRouter.post("/commitStoryModeDecision.php", (_req, res) => { res.end(); }); // U14 (maybe wanna actually unlock the ship features?)
@ -284,6 +286,7 @@ apiRouter.post("/inventorySlots.php", inventorySlotsController);
apiRouter.post("/joinSession.php", joinSessionController);
apiRouter.post("/login.php", loginController);
apiRouter.post("/loginRewardsSelection.php", loginRewardsSelectionController);
apiRouter.post("/logout.php", logoutController); // from ~U16, don't know when they changed it to GET
apiRouter.post("/maturePet.php", maturePetController);
apiRouter.post("/missionInventoryUpdate.php", missionInventoryUpdateController);
apiRouter.post("/modularWeaponCrafting.php", modularWeaponCraftingController);

View File

@ -11,6 +11,9 @@ import { renameAccountController } from "@/src/controllers/custom/renameAccountC
import { ircDroppedController } from "@/src/controllers/custom/ircDroppedController";
import { unlockAllIntrinsicsController } from "@/src/controllers/custom/unlockAllIntrinsicsController";
import { addMissingMaxRankModsController } from "@/src/controllers/custom/addMissingMaxRankModsController";
import { webuiFileChangeDetectedController } from "@/src/controllers/custom/webuiFileChangeDetectedController";
import { completeAllMissionsController } from "@/src/controllers/custom/completeAllMissionsController";
import { addMissingHelminthBlueprintsController } from "@/src/controllers/custom/addMissingHelminthBlueprintsController";
import { createAccountController } from "@/src/controllers/custom/createAccountController";
import { createMessageController } from "@/src/controllers/custom/createMessageController";
@ -20,10 +23,10 @@ import { addXpController } from "@/src/controllers/custom/addXpController";
import { importController } from "@/src/controllers/custom/importController";
import { manageQuestsController } from "@/src/controllers/custom/manageQuestsController";
import { setEvolutionProgressController } from "@/src/controllers/custom/setEvolutionProgressController";
import { setBoosterController } from "@/src/controllers/custom/setBoosterController";
import { updateFingerprintController } from "@/src/controllers/custom/updateFingerprintController";
import { getConfigDataController } from "@/src/controllers/custom/getConfigDataController";
import { updateConfigDataController } from "@/src/controllers/custom/updateConfigDataController";
import { setBoosterController } from "../controllers/custom/setBoosterController";
import { getConfigController, setConfigController } from "@/src/controllers/custom/configController";
const customRouter = express.Router();
@ -38,6 +41,9 @@ customRouter.get("/renameAccount", renameAccountController);
customRouter.get("/ircDropped", ircDroppedController);
customRouter.get("/unlockAllIntrinsics", unlockAllIntrinsicsController);
customRouter.get("/addMissingMaxRankMods", addMissingMaxRankModsController);
customRouter.get("/webuiFileChangeDetected", webuiFileChangeDetectedController);
customRouter.get("/completeAllMissions", completeAllMissionsController);
customRouter.get("/addMissingHelminthBlueprints", addMissingHelminthBlueprintsController);
customRouter.post("/createAccount", createAccountController);
customRouter.post("/createMessage", createMessageController);
@ -48,8 +54,9 @@ customRouter.post("/import", importController);
customRouter.post("/manageQuests", manageQuestsController);
customRouter.post("/setEvolutionProgress", setEvolutionProgressController);
customRouter.post("/setBooster", setBoosterController);
customRouter.post("/updateFingerprint", updateFingerprintController);
customRouter.get("/config", getConfigDataController);
customRouter.post("/config", updateConfigDataController);
customRouter.post("/getConfig", getConfigController);
customRouter.post("/setConfig", setConfigController);
export { customRouter };

View File

@ -1,6 +1,9 @@
import express from "express";
import path from "path";
import { repoDir, rootDir } from "@/src/helpers/pathHelper";
import { args } from "@/src/helpers/commandLineArguments";
const baseDir = args.dev ? repoDir : rootDir;
const webuiRouter = express.Router();
@ -19,29 +22,29 @@ webuiRouter.use("/webui", (req, res, next) => {
// Serve virtual routes
webuiRouter.get("/webui/inventory", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
webuiRouter.get(/webui\/powersuit\/(.+)/, (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
webuiRouter.get("/webui/detailedView", (_req, res) => {
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/mods", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/settings", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/quests", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/cheats", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
webuiRouter.get("/webui/import", (_req, res) => {
res.sendFile(path.join(rootDir, "static/webui/index.html"));
res.sendFile(path.join(baseDir, "static/webui/index.html"));
});
// Serve static files
webuiRouter.use("/webui", express.static(path.join(rootDir, "static/webui")));
webuiRouter.use("/webui", express.static(path.join(baseDir, "static/webui")));
// Serve favicon
webuiRouter.get("/favicon.ico", (_req, res) => {
@ -58,7 +61,7 @@ webuiRouter.get("/webui/riven-tool/RivenParser.js", (_req, res) => {
// Serve translations
webuiRouter.get("/translations/:file", (req, res) => {
res.sendFile(path.join(rootDir, `static/webui/translations/${req.params.file}`));
res.sendFile(path.join(baseDir, `static/webui/translations/${req.params.file}`));
});
export { webuiRouter };

View File

@ -1,6 +1,7 @@
import fs from "fs";
import path from "path";
import { repoDir } from "@/src/helpers/pathHelper";
import { args } from "@/src/helpers/commandLineArguments";
export interface IConfig {
mongodbUrl: string;
@ -18,7 +19,6 @@ export interface IConfig {
skipTutorial?: boolean;
skipAllDialogue?: boolean;
unlockAllScans?: boolean;
unlockAllMissions?: boolean;
infiniteCredits?: boolean;
infinitePlatinum?: boolean;
infiniteEndo?: boolean;
@ -48,7 +48,11 @@ export interface IConfig {
noVendorPurchaseLimits?: boolean;
noDeathMarks?: boolean;
noKimCooldowns?: boolean;
fullyStockedVendors?: boolean;
baroAlwaysAvailable?: boolean;
baroFullyStocked?: boolean;
syndicateMissionsRepeatable?: boolean;
unlockAllProfitTakerStages?: boolean;
instantFinishRivenChallenge?: boolean;
instantResourceExtractorDrones?: boolean;
noResourceExtractorDronesDamage?: boolean;
@ -59,25 +63,35 @@ export interface IConfig {
noDojoResearchCosts?: boolean;
noDojoResearchTime?: boolean;
fastClanAscension?: boolean;
missionsCanGiveAllRelics?: boolean;
unlockAllSimarisResearchEntries?: boolean;
disableDailyTribute?: boolean;
spoofMasteryRank?: number;
nightwaveStandingMultiplier?: number;
unfaithfulBugFixes?: {
ignore1999LastRegionPlayed?: boolean;
fixXtraCheeseTimer?: boolean;
};
worldState?: {
creditBoost?: boolean;
affinityBoost?: boolean;
resourceBoost?: boolean;
starDays?: boolean;
galleonOfGhouls?: number;
eidolonOverride?: string;
vallisOverride?: string;
duviriOverride?: string;
nightwaveOverride?: string;
allTheFissures?: string;
circuitGameModes?: string[];
darvoStockMultiplier?: number;
};
dev?: {
keepVendorsExpired?: boolean;
};
}
export const configPath = path.join(repoDir, process.argv[2] ?? "config.json");
export const configPath = path.join(repoDir, args.configPath ?? "config.json");
export const config: IConfig = {
mongodbUrl: "mongodb://127.0.0.1:27017/openWF",
@ -89,11 +103,13 @@ export const config: IConfig = {
};
export const loadConfig = (): void => {
const newConfig = JSON.parse(fs.readFileSync(configPath, "utf-8")) as IConfig;
// Set all values to undefined now so if the new config.json omits some fields that were previously present, it's correct in-memory.
for (const key of Object.keys(config)) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
(config as any)[key] = undefined;
}
Object.assign(config, JSON.parse(fs.readFileSync(configPath, "utf-8")));
Object.assign(config, newConfig);
};

View File

@ -1,11 +1,12 @@
import fs from "fs";
import chokidar from "chokidar";
import fsPromises from "fs/promises";
import { logger } from "../utils/logger";
import { config, configPath, loadConfig } from "./configService";
import { getWebPorts, startWebServer, stopWebServer } from "./webService";
import { getWebPorts, sendWsBroadcast, startWebServer, stopWebServer } from "./webService";
import { Inbox } from "../models/inboxModel";
let amnesia = false;
fs.watchFile(configPath, () => {
chokidar.watch(configPath).on("change", () => {
if (amnesia) {
amnesia = false;
} else {
@ -13,15 +14,22 @@ fs.watchFile(configPath, () => {
try {
loadConfig();
} catch (e) {
logger.error("FATAL ERROR: Config failed to be reloaded: " + (e as Error).message);
process.exit(1);
logger.error("Config changes were not applied: " + (e as Error).message);
return;
}
validateConfig();
syncConfigWithDatabase();
const webPorts = getWebPorts();
if (config.httpPort != webPorts.http || config.httpsPort != webPorts.https) {
logger.info(`Restarting web server to apply port changes.`);
// Tell webui clients to reload with new port
sendWsBroadcast({ ports: { http: config.httpPort, https: config.httpsPort } });
void stopWebServer().then(startWebServer);
} else {
sendWsBroadcast({ config_reloaded: true });
}
}
});
@ -40,6 +48,15 @@ export const validateConfig = (): void => {
}
}
}
if (
config.worldState?.galleonOfGhouls &&
config.worldState.galleonOfGhouls != 1 &&
config.worldState.galleonOfGhouls != 2 &&
config.worldState.galleonOfGhouls != 3
) {
config.worldState.galleonOfGhouls = 0;
modified = true;
}
if (modified) {
logger.info(`Updating config file to fix some issues with it.`);
void saveConfig();
@ -50,3 +67,10 @@ export const saveConfig = async (): Promise<void> => {
amnesia = true;
await fsPromises.writeFile(configPath, JSON.stringify(config, null, 2));
};
export const syncConfigWithDatabase = (): void => {
// Event messages are deleted after endDate. Since we don't use beginDate/endDate and instead have config toggles, we need to delete the messages once those bools are false.
if (!config.worldState?.galleonOfGhouls) {
void Inbox.deleteMany({ goalTag: "GalleonRobbery" }).then(() => {}); // For some reason, I can't just do `Inbox.deleteMany(...)`; it needs this whole circus.
}
};

View File

@ -349,7 +349,8 @@ export const removeDojoDeco = (
component.DecoCapacity! += meta.capacityCost;
}
} else {
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)![0];
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == deco.Type)!;
component.DecoCapacity! += meta.dojoCapacityCost!;
if (deco.Sockets !== undefined) {
addVaultFusionTreasures(guild, [
{

View File

@ -296,6 +296,12 @@ export const importInventory = (db: TInventoryDatabaseDocument, client: Partial<
db[key] = client[key];
}
}
// IRewardAtten[]
for (const key of ["SortieRewardAttenuation", "SpecialItemRewardAttenuation"] as const) {
if (client[key] !== undefined) {
db[key] = client[key];
}
}
if (client.XPInfo !== undefined) {
db.XPInfo = client.XPInfo;
}

View File

@ -2,8 +2,8 @@ import { IMessageDatabase, Inbox } from "@/src/models/inboxModel";
import { getAccountForRequest } from "@/src/services/loginService";
import { HydratedDocument, Types } from "mongoose";
import { Request } from "express";
import eventMessages from "@/static/fixed_responses/eventMessages.json";
import { logger } from "@/src/utils/logger";
import { unixTimesInMs } from "../constants/timeConstants";
import { config } from "./configService";
export const getAllMessagesSorted = async (accountId: string): Promise<HydratedDocument<IMessageDatabase>[]> => {
const inbox = await Inbox.find({ ownerId: accountId }).sort({ date: -1 });
@ -29,40 +29,72 @@ export const deleteAllMessagesRead = async (accountId: string): Promise<void> =>
export const createNewEventMessages = async (req: Request): Promise<void> => {
const account = await getAccountForRequest(req);
const latestEventMessageDate = account.LatestEventMessageDate;
const newEventMessages: IMessageCreationTemplate[] = [];
//TODO: is baroo there? create these kind of messages too (periodical messages)
const newEventMessages = eventMessages.Messages.filter(m => new Date(m.eventMessageDate) > latestEventMessageDate);
// Baro
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
if (account.LatestEventMessageDate.getTime() < baroActualStart) {
newEventMessages.push({
sndr: "/Lotus/Language/G1Quests/VoidTraderName",
sub: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceTitle",
msg: "/Lotus/Language/CommunityMessages/VoidTraderAppearanceMessage",
icon: "/Lotus/Interface/Icons/Npcs/BaroKiTeerPortrait.png",
startDate: new Date(baroActualStart),
endDate: new Date(baroStart + unixTimesInMs.day * 14),
CrossPlatform: true,
arg: [
{
Key: "NODE_NAME",
Tag: ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4]
}
],
date: new Date(baroActualStart)
});
}
// BUG: Deleting the inbox message manually means it'll just be automatically re-created. This is because we don't use startDate/endDate for these config-toggled events.
if (config.worldState?.galleonOfGhouls) {
if (!(await Inbox.exists({ ownerId: account._id, goalTag: "GalleonRobbery" }))) {
newEventMessages.push({
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
sub: "/Lotus/Language/Events/GalleonRobberyIntroMsgTitle",
msg: "/Lotus/Language/Events/GalleonRobberyIntroMsgDesc",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png",
transmission: "/Lotus/Sounds/Dialog/GalleonOfGhouls/DGhoulsWeekOneInbox0010VayHek",
att: ["/Lotus/Upgrades/Skins/Events/OgrisOldSchool"],
startDate: new Date(),
goalTag: "GalleonRobbery"
});
}
}
if (newEventMessages.length === 0) {
logger.debug(`No new event messages. Latest event message date: ${latestEventMessageDate.toISOString()}`);
return;
}
const savedEventMessages = await createMessage(account._id, newEventMessages);
logger.debug("created event messages", savedEventMessages);
await createMessage(account._id, newEventMessages);
const latestEventMessage = newEventMessages.reduce((prev, current) =>
prev.eventMessageDate > current.eventMessageDate ? prev : current
prev.startDate! > current.startDate! ? prev : current
);
account.LatestEventMessageDate = new Date(latestEventMessage.eventMessageDate);
account.LatestEventMessageDate = new Date(latestEventMessage.startDate!);
await account.save();
};
export const createMessage = async (
accountId: string | Types.ObjectId,
messages: IMessageCreationTemplate[]
): Promise<HydratedDocument<IMessageDatabase>[]> => {
): Promise<void> => {
const ownerIdMessages = messages.map(m => ({
...m,
date: m.date ?? new Date(),
ownerId: accountId
}));
const savedMessages = await Inbox.insertMany(ownerIdMessages);
return savedMessages as HydratedDocument<IMessageDatabase>[];
await Inbox.insertMany(ownerIdMessages);
};
export interface IMessageCreationTemplate extends Omit<IMessageDatabase, "_id" | "date" | "ownerId"> {
ownerId?: string;
date?: Date;
}

View File

@ -29,7 +29,8 @@ import {
ICalendarProgress,
INemesisWeaponTargetFingerprint,
INemesisPetTargetFingerprint,
IDialogueDatabase
IDialogueDatabase,
IKubrowPetPrintClient
} from "@/src/types/inventoryTypes/inventoryTypes";
import { IGenericUpdate, IUpdateNodeIntrosResponse } from "../types/genericUpdate";
import { IKeyChainRequest, IMissionInventoryUpdateRequest } from "../types/requestTypes";
@ -43,6 +44,7 @@ import {
} from "../types/inventoryTypes/commonInventoryTypes";
import {
ExportArcanes,
ExportBoosters,
ExportBundles,
ExportChallenges,
ExportCustoms,
@ -84,9 +86,11 @@ import { getRandomElement, getRandomInt, getRandomWeightedReward, SRng } from ".
import { createMessage } from "./inboxService";
import { getMaxStanding, getMinStanding } from "@/src/helpers/syndicateStandingHelper";
import { getNightwaveSyndicateTag, getWorldState } from "./worldStateService";
import { ICalendarSeason } from "@/src/types/worldStateTypes";
import { generateNemesisProfile, INemesisProfile } from "../helpers/nemesisHelpers";
import { TAccountDocument } from "./loginService";
import { unixTimesInMs } from "../constants/timeConstants";
import { addString } from "../helpers/stringHelpers";
export const createInventory = async (
accountOwnerId: Types.ObjectId,
@ -422,7 +426,6 @@ export const addItem = async (
ItemType: "/Lotus/Types/Game/KubrowPet/Eggs/KubrowEgg",
_id: new Types.ObjectId()
};
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push(egg);
changes.push({
ItemType: egg.ItemType,
@ -497,6 +500,7 @@ export const addItem = async (
// - Blueprints for Ancient Protector Specter, Shield Osprey Specter, etc. have num=1 despite giving their purchaseQuantity.
if (!exactQuantity) {
quantity *= ExportGear[typeName].purchaseQuantity ?? 1;
logger.debug(`non-exact acquisition of ${typeName}; factored quantity is ${quantity}`);
}
const consumablesChanges = [
{
@ -668,6 +672,17 @@ export const addItem = async (
return await addEmailItem(inventory, typeName);
}
// Boosters are an odd case. They're only added like this via Baro's Void Surplus afaik.
{
const boosterEntry = Object.entries(ExportBoosters).find(arr => arr[1].typeName == typeName);
if (boosterEntry) {
addBooster(typeName, quantity, inventory);
return {
Boosters: [{ ItemType: typeName, ExpiryDate: quantity }]
};
}
}
// Path-based duck typing
switch (typeName.substr(1).split("/")[1]) {
case "Powersuits":
@ -781,7 +796,11 @@ export const addItem = async (
typeName.substr(1).split("/")[3] == "CatbrowPet" ||
typeName.substr(1).split("/")[3] == "KubrowPet"
) {
if (typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem") {
if (
typeName != "/Lotus/Types/Game/KubrowPet/Eggs/KubrowPetEggItem" &&
typeName != "/Lotus/Types/Game/KubrowPet/BlankTraitPrint" &&
typeName != "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
) {
return addKubrowPet(inventory, typeName, undefined, premiumPurchase);
}
} else if (typeName.startsWith("/Lotus/Types/Game/CrewShip/CrewMember/")) {
@ -1045,8 +1064,13 @@ export const addKubrowPet = (
const configs: IItemConfig[] = applyDefaultUpgrades(inventory, kubrowPet?.defaultUpgrades);
if (!details) {
let traits: ITraits;
const isCatbrow = [
"/Lotus/Types/Game/CatbrowPet/CheshireCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/MirrorCatbrowPetPowerSuit",
"/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit"
].includes(kubrowPetName);
let traits: ITraits;
if (kubrowPetName == "/Lotus/Types/Game/CatbrowPet/VampireCatbrowPetPowerSuit") {
traits = {
BaseColor: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseVampire",
@ -1061,12 +1085,7 @@ export const addKubrowPet = (
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,
@ -1085,7 +1104,7 @@ export const addKubrowPet = (
Name: "",
IsPuppy: !premiumPurchase,
HasCollar: true,
PrintsRemaining: 3,
PrintsRemaining: isCatbrow ? 3 : 2,
Status: premiumPurchase ? Status.StatusStasis : Status.StatusIncubating,
HatchDate: premiumPurchase ? new Date() : new Date(Date.now() + 10 * unixTimesInMs.hour), // On live, this seems to be somewhat randomised so that the pet hatches 9~11 hours after start.
IsMale: !!getRandomInt(0, 1),
@ -1109,6 +1128,26 @@ export const addKubrowPet = (
return inventoryChanges;
};
export const addKubrowPetPrint = (
inventory: TInventoryDatabaseDocument,
pet: IEquipmentDatabase,
inventoryChanges: IInventoryChanges
): void => {
inventoryChanges.KubrowPetPrints ??= [];
inventoryChanges.KubrowPetPrints.push(
inventory.KubrowPetPrints[
inventory.KubrowPetPrints.push({
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint",
Name: pet.Details!.Name,
IsMale: pet.Details!.IsMale,
Size: pet.Details!.Size,
DominantTraits: pet.Details!.DominantTraits,
RecessiveTraits: pet.Details!.RecessiveTraits
}) - 1
].toJSON<IKubrowPetPrintClient>()
);
};
export const updateSlots = (
inventory: TInventoryDatabaseDocument,
slotName: SlotNames,
@ -1327,7 +1366,7 @@ export const addCustomization = (
customizationName: string,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (!inventory.FlavourItems.find(x => x.ItemType == customizationName)) {
if (!inventory.FlavourItems.some(x => x.ItemType == customizationName)) {
const flavourItemIndex = inventory.FlavourItems.push({ ItemType: customizationName }) - 1;
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
inventoryChanges.FlavourItems ??= [];
@ -1343,7 +1382,7 @@ export const addSkin = (
typeName: string,
inventoryChanges: IInventoryChanges = {}
): IInventoryChanges => {
if (inventory.WeaponSkins.find(x => x.ItemType == typeName)) {
if (inventory.WeaponSkins.some(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;
@ -1783,6 +1822,10 @@ export const addChallenges = (
} else {
inventory.ChallengeProgress.push({ Name, Progress });
}
if (Name.startsWith("Calendar")) {
addString(getCalendarProgress(inventory).SeasonProgress.ActivatedChallenges, Name);
}
});
const affiliationMods: IAffiliationMods[] = [];
@ -1825,12 +1868,24 @@ export const addChallenges = (
return affiliationMods;
};
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes }: IMission): void => {
export const addCalendarProgress = (inventory: TInventoryDatabaseDocument, value: { challenge: string }[]): void => {
const calendarProgress = getCalendarProgress(inventory);
const currentSeason = getWorldState().KnownCalendarSeasons[0];
calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx = currentSeason.Days.findIndex(
day => day.events.length != 0 && day.events[0].challenge == value[value.length - 1].challenge
);
checkCalendarChallengeCompletion(calendarProgress, currentSeason);
};
export const addMissionComplete = (inventory: TInventoryDatabaseDocument, { Tag, Completes, Tier }: IMission): void => {
const { Missions } = inventory;
const itemIndex = Missions.findIndex(item => item.Tag === Tag);
if (itemIndex !== -1) {
Missions[itemIndex].Completes += Completes;
if (Tier) {
Missions[itemIndex].Tier = Tier;
}
} else {
Missions.push({ Tag, Completes });
}
@ -2026,6 +2081,20 @@ export const getCalendarProgress = (inventory: TInventoryDatabaseDocument): ICal
return inventory.CalendarProgress;
};
export const checkCalendarChallengeCompletion = (
calendarProgress: ICalendarProgress,
currentSeason: ICalendarSeason
): void => {
const dayIndex = calendarProgress.SeasonProgress.LastCompletedDayIdx + 1;
if (calendarProgress.SeasonProgress.LastCompletedChallengeDayIdx >= dayIndex) {
const day = currentSeason.Days[dayIndex];
if (day.events.length != 0 && day.events[0].type == "CET_CHALLENGE") {
//logger.debug(`already completed the challenge, skipping ahead`);
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
}
}
};
export const giveNemesisWeaponRecipe = (
inventory: TInventoryDatabaseDocument,
weaponType: string,

View File

@ -144,7 +144,8 @@ export const claimLoginReward = async (
case "RT_STORE_ITEM":
case "RT_RECIPE":
case "RT_RANDOM_RECIPE":
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount)).InventoryChanges;
return (await handleStoreItemAcquisition(reward.StoreItemType, inventory, reward.Amount, undefined, true))
.InventoryChanges;
case "RT_CREDITS":
return updateCurrency(inventory, -reward.Amount, false);

View File

@ -18,6 +18,23 @@ export const isNameTaken = async (name: string): Promise<boolean> => {
return !!(await Account.findOne({ DisplayName: name }));
};
export const createNonce = (): number => {
return Math.round(Math.random() * Number.MAX_SAFE_INTEGER);
};
export const getUsernameFromEmail = async (email: string): Promise<string> => {
const nameFromEmail = email.substring(0, email.indexOf("@"));
let name = nameFromEmail || email.substring(1) || "SpaceNinja";
if (await isNameTaken(name)) {
let suffix = 0;
do {
++suffix;
name = nameFromEmail + suffix;
} while (await isNameTaken(name));
}
return nameFromEmail;
};
export const createAccount = async (accountData: IDatabaseAccountRequiredFields): Promise<IDatabaseAccountJson> => {
const account = new Account(accountData);
try {

View File

@ -2,6 +2,7 @@ import {
ExportEnemies,
ExportFusionBundles,
ExportRegions,
ExportRelics,
ExportRewards,
IMissionReward as IMissionRewardExternal,
IRegion,
@ -13,6 +14,7 @@ import { IRngResult, SRng, getRandomElement, getRandomReward } from "@/src/servi
import { equipmentKeys, IMission, ITypeCount, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
import {
addBooster,
addCalendarProgress,
addChallenges,
addConsumables,
addCrewShipAmmo,
@ -34,7 +36,6 @@ import {
applyClientEquipmentUpdates,
combineInventoryChanges,
generateRewardSeed,
getCalendarProgress,
getDialogue,
giveNemesisPetRecipe,
giveNemesisWeaponRecipe,
@ -44,7 +45,7 @@ import {
import { updateQuestKey } from "@/src/services/questService";
import { Types } from "mongoose";
import { IAffiliationMods, IInventoryChanges } from "@/src/types/purchaseTypes";
import { fromStoreItem, getLevelKeyRewards, toStoreItem } from "@/src/services/itemDataService";
import { fromStoreItem, getLevelKeyRewards, isStoreItem, toStoreItem } from "@/src/services/itemDataService";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { getEntriesUnsafe } from "@/src/utils/ts-utils";
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
@ -66,7 +67,15 @@ import {
} from "@/src/helpers/nemesisHelpers";
import { Loadout } from "../models/inventoryModels/loadoutModel";
import { ILoadoutConfigDatabase } from "../types/saveLoadoutTypes";
import { getLiteSortie, getSortie, idToBountyCycle, idToDay, idToWeek, pushClassicBounties } from "./worldStateService";
import {
getLiteSortie,
getSortie,
getWorldState,
idToBountyCycle,
idToDay,
idToWeek,
pushClassicBounties
} from "./worldStateService";
import { config } from "./configService";
import libraryDailyTasks from "@/static/fixed_responses/libraryDailyTasks.json";
import { ISyndicateMissionInfo } from "../types/worldStateTypes";
@ -78,7 +87,7 @@ const getRotations = (rewardInfo: IRewardInfo, tierOverride?: number): number[]
if (rewardInfo.VaultsCracked) {
const rotations: number[] = [];
for (let i = 0; i != rewardInfo.VaultsCracked; ++i) {
rotations.push(i);
rotations.push(Math.min(i, 2));
}
return rotations;
}
@ -225,7 +234,7 @@ export const addMissionInventoryUpdates = async (
}
for (const [key, value] of getEntriesUnsafe(inventoryUpdates)) {
if (value === undefined) {
logger.error(`Inventory update key ${key} has no value `);
logger.error(`Inventory update key ${key} has no value`);
continue;
}
switch (key) {
@ -258,7 +267,9 @@ export const addMissionInventoryUpdates = async (
addMissionComplete(inventory, value);
break;
case "LastRegionPlayed":
inventory.LastRegionPlayed = value;
if (!(config.unfaithfulBugFixes?.ignore1999LastRegionPlayed && value === "1999MapName")) {
inventory.LastRegionPlayed = value;
}
break;
case "RawUpgrades":
addMods(inventory, value);
@ -470,7 +481,7 @@ export const addMissionInventoryUpdates = async (
msg: "/Lotus/Language/G1Quests/DeathMarkMessage",
icon: "/Lotus/Interface/Icons/Npcs/Stalker_d.png",
highPriority: true,
expiry: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's clear if this is correct.
endDate: new Date(Date.now() + 86400_000) // TOVERIFY: This type of inbox message seems to automatically delete itself. We'll just delete it after 24 hours, but it's not clear if this is correct.
}
]);
}
@ -514,7 +525,6 @@ export const addMissionInventoryUpdates = async (
}
case "KubrowPetEggs": {
for (const egg of value) {
inventory.KubrowPetEggs ??= [];
inventory.KubrowPetEggs.push({
ItemType: egg.ItemType,
_id: new Types.ObjectId()
@ -597,6 +607,47 @@ export const addMissionInventoryUpdates = async (
inventoryChanges.RegularCredits -= value;
break;
}
case "GoalProgress": {
for (const uploadProgress of value) {
const goal = getWorldState().Goals.find(x => x._id.$oid == uploadProgress._id.$oid);
if (goal && goal.Personal) {
inventory.PersonalGoalProgress ??= [];
const goalProgress = inventory.PersonalGoalProgress.find(x => x.goalId.equals(goal._id.$oid));
if (goalProgress) {
goalProgress.Best = Math.max(goalProgress.Best, uploadProgress.Best);
goalProgress.Count += uploadProgress.Count;
} else {
inventory.PersonalGoalProgress.push({
Best: uploadProgress.Best,
Count: uploadProgress.Count,
Tag: goal.Tag,
goalId: new Types.ObjectId(goal._id.$oid)
});
if (
goal.Reward &&
goal.Reward.items &&
goal.MissionKeyName &&
goal.MissionKeyName in goalMessagesByKey
) {
// Send reward via inbox
const info = goalMessagesByKey[goal.MissionKeyName];
await createMessage(inventory.accountOwnerId, [
{
sndr: info.sndr,
msg: info.msg,
att: goal.Reward.items.map(x => (isStoreItem(x) ? fromStoreItem(x) : x)),
sub: info.sub,
icon: info.icon,
highPriority: true
}
]);
}
}
}
}
break;
}
case "InvasionProgress": {
for (const clientProgress of value) {
const dbProgress = inventory.QualifyingInvasions.find(x =>
@ -618,12 +669,7 @@ export const addMissionInventoryUpdates = async (
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);
}
addCalendarProgress(inventory, value);
break;
}
case "duviriCaveOffers": {
@ -915,6 +961,7 @@ const droptableAliases: Record<string, string> = {
//TODO: return type of partial missioninventoryupdate response
export const addMissionRewards = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
{
wagerTier: wagerTier,
@ -950,17 +997,29 @@ export const addMissionRewards = async (
let missionCompletionCredits = 0;
//inventory change is what the client has not rewarded itself, also the client needs to know the credit changes for display
if (rewardInfo.goalId) {
const goal = getWorldState().Goals.find(x => x._id.$oid == rewardInfo.goalId);
if (goal?.MissionKeyName) {
levelKeyName = goal.MissionKeyName;
}
}
if (levelKeyName) {
const fixedLevelRewards = getLevelKeyRewards(levelKeyName);
//logger.debug(`fixedLevelRewards ${fixedLevelRewards}`);
if (fixedLevelRewards.levelKeyRewards) {
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, MissionRewards, rewardInfo);
missionCompletionCredits += addFixedLevelRewards(
fixedLevelRewards.levelKeyRewards,
MissionRewards,
rewardInfo
);
}
if (fixedLevelRewards.levelKeyRewards2) {
for (const reward of fixedLevelRewards.levelKeyRewards2) {
//quest stage completion credit rewards
if (reward.rewardType == "RT_CREDITS") {
missionCompletionCredits += reward.amount; // will be added to inventory in addCredits
missionCompletionCredits += reward.amount;
continue;
}
MissionRewards.push({
@ -989,12 +1048,11 @@ export const addMissionRewards = async (
) {
const levelCreditReward = getLevelCreditRewards(node);
missionCompletionCredits += levelCreditReward;
inventory.RegularCredits += levelCreditReward;
logger.debug(`levelCreditReward ${levelCreditReward}`);
}
if (node.missionReward) {
missionCompletionCredits += addFixedLevelRewards(node.missionReward, inventory, MissionRewards, rewardInfo);
missionCompletionCredits += addFixedLevelRewards(node.missionReward, MissionRewards, rewardInfo);
}
if (rewardInfo.sortieTag == "Mission1") {
@ -1104,7 +1162,9 @@ export const addMissionRewards = async (
combineInventoryChanges(inventoryChanges, inventoryChange.InventoryChanges);
}
const credits = addCredits(inventory, {
inventory.RegularCredits += missionCompletionCredits;
const credits = await addCredits(account, inventory, {
missionCompletionCredits,
missionDropCredits: creditDrops ?? 0,
rngRewardCredits: inventoryChanges.RegularCredits ?? 0
@ -1195,7 +1255,7 @@ export const addMissionRewards = async (
if (rewardInfo.JobStage != undefined && rewardInfo.jobId) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = rewardInfo.jobId.split("_");
const [jobType, unkIndex, hubNode, syndicateMissionId] = rewardInfo.jobId.split("_");
const syndicateMissions: ISyndicateMissionInfo[] = [];
if (syndicateMissionId) {
pushClassicBounties(syndicateMissions, idToBountyCycle(syndicateMissionId));
@ -1204,10 +1264,27 @@ export const addMissionRewards = async (
if (syndicateEntry && syndicateEntry.Jobs) {
let currentJob = syndicateEntry.Jobs[rewardInfo.JobTier!];
if (syndicateEntry.Tag === "EntratiSyndicate") {
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
if (vault) currentJob = vault;
let medallionAmount = Math.floor(currentJob.xpAmounts[rewardInfo.JobStage] / (rewardInfo.Q ? 0.8 : 1));
if (
[
"DeimosRuinsExterminateBounty",
"DeimosRuinsEscortBounty",
"DeimosRuinsMistBounty",
"DeimosRuinsPurifyBounty",
"DeimosRuinsSacBounty",
"VaultBounty"
].some(ending => jobType.endsWith(ending))
) {
const vault = syndicateEntry.Jobs.find(j => j.locationTag == rewardInfo.jobId!.split("_").at(-1));
if (vault) {
currentJob = vault;
if (jobType.endsWith("VaultBounty")) {
currentJob.xpAmounts = [currentJob.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)];
}
}
}
let medallionAmount = Math.floor(
Math.min(rewardInfo.JobStage, currentJob.xpAmounts.length - 1) / (rewardInfo.Q ? 0.8 : 1)
);
if (
["DeimosEndlessAreaDefenseBounty", "DeimosEndlessExcavateBounty", "DeimosEndlessPurifyBounty"].some(
ending => jobType.endsWith(ending)
@ -1310,48 +1387,61 @@ export const addMissionRewards = async (
};
};
//creditBonus is not entirely accurate.
//TODO: consider ActiveBoosters
export const addCredits = (
export const addCredits = async (
account: TAccountDocument,
inventory: TInventoryDatabaseDocument,
{
missionDropCredits,
missionCompletionCredits,
rngRewardCredits
}: { missionDropCredits: number; missionCompletionCredits: number; rngRewardCredits: number }
): IMissionCredits => {
const hasDailyCreditBonus = true;
const totalCredits = missionDropCredits + missionCompletionCredits + rngRewardCredits;
): Promise<IMissionCredits> => {
const finalCredits: IMissionCredits = {
MissionCredits: [missionDropCredits, missionDropCredits],
CreditBonus: [missionCompletionCredits, missionCompletionCredits],
TotalCredits: [totalCredits, totalCredits]
CreditsBonus: [missionCompletionCredits, missionCompletionCredits],
TotalCredits: [0, 0]
};
if (hasDailyCreditBonus) {
const today = Math.trunc(Date.now() / 86400000) * 86400;
if (account.DailyFirstWinDate != today) {
account.DailyFirstWinDate = today;
await account.save();
logger.debug(`daily first win, doubling missionCompletionCredits (${missionCompletionCredits})`);
finalCredits.DailyMissionBonus = true;
inventory.RegularCredits += missionCompletionCredits;
finalCredits.CreditBonus[1] *= 2;
finalCredits.MissionCredits[1] *= 2;
finalCredits.TotalCredits[1] *= 2;
finalCredits.CreditsBonus[1] *= 2;
}
if (!hasDailyCreditBonus) {
return finalCredits;
const totalCredits = finalCredits.MissionCredits[1] + finalCredits.CreditsBonus[1] + rngRewardCredits;
finalCredits.TotalCredits = [totalCredits, totalCredits];
if (config.worldState?.creditBoost) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
return { ...finalCredits, DailyMissionBonus: true };
const now = Math.trunc(Date.now() / 1000); // TOVERIFY: Should we maybe subtract mission time as to apply credit boosters that expired during mission?
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBooster")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
if ((inventory.Boosters.find(x => x.ItemType == "/Lotus/Types/Boosters/CreditBlessing")?.ExpiryDate ?? 0) > now) {
inventory.RegularCredits += finalCredits.TotalCredits[1];
finalCredits.TotalCredits[1] += finalCredits.TotalCredits[1];
}
return finalCredits;
};
export const addFixedLevelRewards = (
rewards: IMissionRewardExternal,
inventory: TInventoryDatabaseDocument,
MissionRewards: IMissionReward[],
rewardInfo?: IRewardInfo
): number => {
let missionBonusCredits = 0;
if (rewards.credits) {
missionBonusCredits += rewards.credits;
inventory.RegularCredits += rewards.credits;
}
if (rewards.items) {
for (const item of rewards.items) {
@ -1364,7 +1454,7 @@ export const addFixedLevelRewards = (
if (rewards.countedItems) {
for (const item of rewards.countedItems) {
MissionRewards.push({
StoreItem: `/Lotus/StoreItems${item.ItemType.substring("Lotus/".length)}`,
StoreItem: toStoreItem(item.ItemType),
ItemCount: item.ItemCount
});
}
@ -1525,6 +1615,27 @@ function getRandomMissionDrops(
? "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoSteelPathRNGRewards"
: "/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriKullervoNormalRNGRewards"
];
} else if (RewardInfo.T == 17) {
if (mission?.Tier == 1) {
logger.warn(`non-steel path duviri murmur tier used on steel path?!`);
}
/*if (operation eight claw is active) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
ItemCount: 10
});
}*/
rewardManifests = ["/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalChestRewards"];
} else if (RewardInfo.T == 19) {
/*if (operation eight claw is active) {
drops.push({
StoreItem: "/Lotus/StoreItems/Types/Gameplay/DuviriMITW/Resources/DuviriMurmurItemEvent",
ItemCount: 15
});
}*/
rewardManifests = [
"/Lotus/Types/Game/MissionDecks/DuviriEncounterRewards/DuviriMurmurFinalSteelChestRewards"
];
} else if (RewardInfo.T == 70) {
// Orowyrm chest, gives 10 Pathos Clamps, or 15 on Steel Path.
drops.push({
@ -1540,7 +1651,7 @@ function getRandomMissionDrops(
if (RewardInfo.jobId) {
if (RewardInfo.JobStage! >= 0) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [jobType, unkIndex, hubNode, syndicateMissionId, locationTag] = RewardInfo.jobId.split("_");
const [jobType, unkIndex, hubNode, syndicateMissionId] = RewardInfo.jobId.split("_");
let isEndlessJob = false;
if (syndicateMissionId) {
const syndicateMissions: ISyndicateMissionInfo[] = [];
@ -1552,19 +1663,30 @@ function getRandomMissionDrops(
let job = syndicateEntry.Jobs[RewardInfo.JobTier!];
if (syndicateEntry.Tag === "EntratiSyndicate") {
const vault = syndicateEntry.Jobs.find(j => j.locationTag === locationTag);
if (vault && locationTag) job = vault;
// if (
// [
// "DeimosRuinsExterminateBounty",
// "DeimosRuinsEscortBounty",
// "DeimosRuinsMistBounty",
// "DeimosRuinsPurifyBounty",
// "DeimosRuinsSacBounty"
// ].some(ending => jobType.endsWith(ending))
// ) {
// job.rewards = "TODO"; // Droptable for Arcana Isolation Vault
// }
if (
[
"DeimosRuinsExterminateBounty",
"DeimosRuinsEscortBounty",
"DeimosRuinsMistBounty",
"DeimosRuinsPurifyBounty",
"DeimosRuinsSacBounty",
"VaultBounty"
].some(ending => jobType.endsWith(ending))
) {
const vault = syndicateEntry.Jobs.find(
j => j.locationTag === RewardInfo.jobId!.split("_").at(-1)
);
if (vault) {
job = vault;
if (jobType.endsWith("VaultBounty")) {
job.rewards = job.rewards.replace(
"/Lotus/Types/Game/MissionDecks/",
"/Supplementals/"
);
job.xpAmounts = [job.xpAmounts.reduce((partialSum, a) => partialSum + a, 0)];
}
}
}
if (
[
"DeimosEndlessAreaDefenseBounty",
@ -1623,20 +1745,28 @@ function getRandomMissionDrops(
}
rewardManifests = [job.rewards];
if (job.xpAmounts.length > 1) {
rotations = [RewardInfo.JobStage! % (job.xpAmounts.length - 1)];
const curentStage = RewardInfo.JobStage! + 1;
const totalStage = job.xpAmounts.length;
let tableIndex = 1; // Stage 2, Stage 3 of 4, and Stage 3 of 5
if (curentStage == 1) {
tableIndex = 0;
} else if (curentStage == totalStage) {
tableIndex = 3;
} else if (totalStage == 5 && curentStage == 4) {
tableIndex = 2;
}
rotations = [tableIndex];
} else {
rotations = [0];
}
if (
RewardInfo.Q &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || job.isVault) &&
(RewardInfo.JobStage === job.xpAmounts.length - 1 || jobType.endsWith("VaultBounty")) &&
!isEndlessJob
) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ExportRewards[job.rewards]) {
rewardManifests.push(job.rewards);
rotations.push(ExportRewards[job.rewards].length - 1);
}
rotations.push(ExportRewards[job.rewards].length - 1);
}
}
}
@ -1801,6 +1931,23 @@ function getRandomMissionDrops(
drops.push({ StoreItem: drop.type, ItemCount: drop.itemCount });
}
}
if (config.missionsCanGiveAllRelics) {
for (const drop of drops) {
const itemType = fromStoreItem(drop.StoreItem);
if (itemType in ExportRelics) {
const relic = ExportRelics[itemType];
const replacement = getRandomElement(
Object.entries(ExportRelics).filter(
arr => arr[1].era == relic.era && arr[1].quality == relic.quality
)
)!;
logger.debug(`replacing ${relic.era} ${relic.category} with ${replacement[1].category}`);
drop.StoreItem = toStoreItem(replacement[0]);
}
}
}
return drops;
}
@ -1913,3 +2060,24 @@ const getHexBounties = (seed: number): { nodes: string[]; buddies: string[] } =>
}
return { nodes, buddies };
};*/
const goalMessagesByKey: Record<string, { sndr: string; msg: string; sub: string; icon: string }> = {
"/Lotus/Types/Keys/GalleonRobberyAlert": {
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgA",
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleA",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
},
"/Lotus/Types/Keys/GalleonRobberyAlertB": {
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgB",
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleB",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
},
"/Lotus/Types/Keys/GalleonRobberyAlertC": {
sndr: "/Lotus/Language/Bosses/BossCouncilorVayHek",
msg: "/Lotus/Language/Messages/GalleonRobbery2025RewardMsgC",
sub: "/Lotus/Language/Messages/GalleonRobbery2025MissionTitleC",
icon: "/Lotus/Interface/Icons/Npcs/VayHekPortrait.png"
}
};

View File

@ -8,12 +8,20 @@ import {
updateCurrency,
updateSlots
} from "@/src/services/inventoryService";
import { getRandomWeightedRewardUc } from "@/src/services/rngService";
import { getRandomReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
import { applyStandingToVendorManifest, getVendorManifestByOid } from "@/src/services/serversideVendorsService";
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
import { IPurchaseRequest, IPurchaseResponse, SlotPurchase, IInventoryChanges } from "@/src/types/purchaseTypes";
import {
IPurchaseRequest,
IPurchaseResponse,
SlotPurchase,
IInventoryChanges,
PurchaseSource,
IPurchaseParams
} from "@/src/types/purchaseTypes";
import { logger } from "@/src/utils/logger";
import worldState from "@/static/fixed_responses/worldState/worldState.json";
import { getWorldState } from "./worldStateService";
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
import {
ExportBoosterPacks,
ExportBoosters,
@ -28,6 +36,8 @@ import {
import { config } from "./configService";
import { TInventoryDatabaseDocument } from "../models/inventoryModels/inventoryModel";
import { fromStoreItem, toStoreItem } from "./itemDataService";
import { DailyDeal } from "../models/worldStateModel";
import { fromMongoDate, toMongoDate } from "../helpers/inventoryHelpers";
export const getStoreItemCategory = (storeItem: string): string => {
const storeItemString = getSubstringFromKeyword(storeItem, "StoreItems/");
@ -44,6 +54,58 @@ export const getStoreItemTypesCategory = (typesItem: string): string => {
return typeElements[1];
};
const tallyVendorPurchase = (
inventory: TInventoryDatabaseDocument,
inventoryChanges: IInventoryChanges,
VendorType: string,
ItemId: string,
numPurchased: number,
Expiry: Date
): void => {
if (!config.noVendorPurchaseLimits) {
inventory.RecentVendorPurchases ??= [];
let vendorPurchases = inventory.RecentVendorPurchases.find(x => x.VendorType == VendorType);
if (!vendorPurchases) {
vendorPurchases =
inventory.RecentVendorPurchases[
inventory.RecentVendorPurchases.push({
VendorType: VendorType,
PurchaseHistory: []
}) - 1
];
}
let historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
if (historyEntry) {
if (Date.now() >= historyEntry.Expiry.getTime()) {
historyEntry.NumPurchased = numPurchased;
historyEntry.Expiry = Expiry;
} else {
historyEntry.NumPurchased += numPurchased;
}
} else {
historyEntry =
vendorPurchases.PurchaseHistory[
vendorPurchases.PurchaseHistory.push({
ItemId: ItemId,
NumPurchased: numPurchased,
Expiry: Expiry
}) - 1
];
}
inventoryChanges.NewVendorPurchase = {
VendorType: VendorType,
PurchaseHistory: [
{
ItemId: ItemId,
NumPurchased: historyEntry.NumPurchased,
Expiry: toMongoDate(Expiry)
}
]
};
inventoryChanges.RecentVendorPurchases = inventoryChanges.NewVendorPurchase;
}
};
export const handlePurchase = async (
purchaseRequest: IPurchaseRequest,
inventory: TInventoryDatabaseDocument
@ -52,7 +114,7 @@ export const handlePurchase = async (
const prePurchaseInventoryChanges: IInventoryChanges = {};
let seed: bigint | undefined;
if (purchaseRequest.PurchaseParams.Source == 7) {
if (purchaseRequest.PurchaseParams.Source == PurchaseSource.Vendor) {
let manifest = getVendorManifestByOid(purchaseRequest.PurchaseParams.SourceId!);
if (manifest) {
manifest = applyStandingToVendorManifest(inventory, manifest);
@ -69,18 +131,12 @@ export const handlePurchase = async (
}
if (!config.dontSubtractPurchaseCreditCost) {
if (offer.RegularPrice) {
combineInventoryChanges(
prePurchaseInventoryChanges,
updateCurrency(inventory, offer.RegularPrice[0], false)
);
updateCurrency(inventory, offer.RegularPrice[0], false, prePurchaseInventoryChanges);
}
}
if (!config.dontSubtractPurchasePlatinumCost) {
if (offer.PremiumPrice) {
combineInventoryChanges(
prePurchaseInventoryChanges,
updateCurrency(inventory, offer.PremiumPrice[0], true)
);
updateCurrency(inventory, offer.PremiumPrice[0], true, prePurchaseInventoryChanges);
}
}
if (!config.dontSubtractPurchaseItemCost) {
@ -96,20 +152,7 @@ export const handlePurchase = async (
if (offer.LocTagRandSeed !== undefined) {
seed = BigInt(offer.LocTagRandSeed);
}
if (!config.noVendorPurchaseLimits && ItemId) {
inventory.RecentVendorPurchases ??= [];
let vendorPurchases = inventory.RecentVendorPurchases.find(
x => x.VendorType == manifest!.VendorInfo.TypeName
);
if (!vendorPurchases) {
vendorPurchases =
inventory.RecentVendorPurchases[
inventory.RecentVendorPurchases.push({
VendorType: manifest.VendorInfo.TypeName,
PurchaseHistory: []
}) - 1
];
}
if (ItemId) {
let expiry = parseInt(offer.Expiry.$date.$numberLong);
if (purchaseRequest.PurchaseParams.IsWeekly) {
const EPOCH = 1734307200 * 1000; // Monday
@ -117,34 +160,14 @@ export const handlePurchase = async (
const weekStart = EPOCH + week * 604800000;
expiry = weekStart + 604800000;
}
const historyEntry = vendorPurchases.PurchaseHistory.find(x => x.ItemId == ItemId);
let numPurchased = purchaseRequest.PurchaseParams.Quantity;
if (historyEntry) {
if (Date.now() >= historyEntry.Expiry.getTime()) {
historyEntry.NumPurchased = numPurchased;
historyEntry.Expiry = new Date(expiry);
} else {
numPurchased += historyEntry.NumPurchased;
historyEntry.NumPurchased += purchaseRequest.PurchaseParams.Quantity;
}
} else {
vendorPurchases.PurchaseHistory.push({
ItemId: ItemId,
NumPurchased: purchaseRequest.PurchaseParams.Quantity,
Expiry: new Date(expiry)
});
}
prePurchaseInventoryChanges.NewVendorPurchase = {
VendorType: manifest.VendorInfo.TypeName,
PurchaseHistory: [
{
ItemId: ItemId,
NumPurchased: numPurchased,
Expiry: { $date: { $numberLong: expiry.toString() } }
}
]
};
prePurchaseInventoryChanges.RecentVendorPurchases = prePurchaseInventoryChanges.NewVendorPurchase;
tallyVendorPurchase(
inventory,
prePurchaseInventoryChanges,
manifest.VendorInfo.TypeName,
ItemId,
purchaseRequest.PurchaseParams.Quantity,
new Date(expiry)
);
}
purchaseRequest.PurchaseParams.Quantity *= offer.QuantityMultiplier;
} else {
@ -166,18 +189,16 @@ export const handlePurchase = async (
);
combineInventoryChanges(purchaseResponse.InventoryChanges, prePurchaseInventoryChanges);
const currencyChanges = updateCurrency(
updateCurrency(
inventory,
purchaseRequest.PurchaseParams.ExpectedPrice,
purchaseRequest.PurchaseParams.UsePremium
purchaseRequest.PurchaseParams.UsePremium,
prePurchaseInventoryChanges
);
purchaseResponse.InventoryChanges = {
...currencyChanges,
...purchaseResponse.InventoryChanges
};
switch (purchaseRequest.PurchaseParams.Source) {
case 1: {
case PurchaseSource.VoidTrader: {
const worldState = getWorldState();
if (purchaseRequest.PurchaseParams.SourceId! != worldState.VoidTraders[0]._id.$oid) {
throw new Error("invalid request source");
}
@ -186,16 +207,13 @@ export const handlePurchase = async (
);
if (offer) {
if (!config.dontSubtractPurchaseCreditCost) {
combineInventoryChanges(
purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.RegularPrice, false)
);
updateCurrency(inventory, offer.RegularPrice, false, purchaseResponse.InventoryChanges);
}
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`vendor purchase should not have an expected price`);
}
if (!config.dontSubtractPurchaseItemCost) {
if (offer.PrimePrice && !config.dontSubtractPurchaseItemCost) {
const invItem: IMiscItem = {
ItemType: "/Lotus/Types/Items/MiscItems/PrimeBucks",
ItemCount: offer.PrimePrice * purchaseRequest.PurchaseParams.Quantity * -1
@ -204,10 +222,21 @@ export const handlePurchase = async (
purchaseResponse.InventoryChanges.MiscItems ??= [];
purchaseResponse.InventoryChanges.MiscItems.push(invItem);
}
if (offer.Limit) {
tallyVendorPurchase(
inventory,
purchaseResponse.InventoryChanges,
"VoidTrader",
offer.ItemType,
purchaseRequest.PurchaseParams.Quantity,
fromMongoDate(worldState.VoidTraders[0].Expiry)
);
}
}
break;
}
case 2:
case PurchaseSource.SyndicateFavor:
{
const syndicateTag = purchaseRequest.PurchaseParams.SyndicateTag!;
if (purchaseRequest.PurchaseParams.UseFreeFavor!) {
@ -244,22 +273,22 @@ export const handlePurchase = async (
}
}
break;
case 7:
case PurchaseSource.DailyDeal:
if (purchaseRequest.PurchaseParams.ExpectedPrice) {
throw new Error(`daily deal purchase should not have an expected price`);
}
await handleDailyDealPurchase(inventory, purchaseRequest.PurchaseParams, purchaseResponse);
break;
case PurchaseSource.Vendor:
if (purchaseRequest.PurchaseParams.SourceId! in ExportVendors) {
const vendor = ExportVendors[purchaseRequest.PurchaseParams.SourceId!];
const offer = vendor.items.find(x => x.storeItem == purchaseRequest.PurchaseParams.StoreItem);
if (offer) {
if (typeof offer.credits == "number" && !config.dontSubtractPurchaseCreditCost) {
combineInventoryChanges(
purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.credits, false)
);
updateCurrency(inventory, offer.credits, false, purchaseResponse.InventoryChanges);
}
if (typeof offer.platinum == "number" && !config.dontSubtractPurchasePlatinumCost) {
combineInventoryChanges(
purchaseResponse.InventoryChanges,
updateCurrency(inventory, offer.platinum, true)
);
updateCurrency(inventory, offer.platinum, true, purchaseResponse.InventoryChanges);
}
if (offer.itemPrices && !config.dontSubtractPurchaseItemCost) {
handleItemPrices(
@ -275,15 +304,15 @@ export const handlePurchase = async (
throw new Error(`vendor purchase should not have an expected price`);
}
break;
case 18: {
if (purchaseRequest.PurchaseParams.SourceId! != worldState.PrimeVaultTraders[0]._id.$oid) {
case PurchaseSource.PrimeVaultTrader: {
if (purchaseRequest.PurchaseParams.SourceId! != staticWorldState.PrimeVaultTraders[0]._id.$oid) {
throw new Error("invalid request source");
}
const offer =
worldState.PrimeVaultTraders[0].Manifest.find(
staticWorldState.PrimeVaultTraders[0].Manifest.find(
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
) ??
worldState.PrimeVaultTraders[0].EvergreenManifest.find(
staticWorldState.PrimeVaultTraders[0].EvergreenManifest.find(
x => x.ItemType == purchaseRequest.PurchaseParams.StoreItem
);
if (offer) {
@ -338,6 +367,25 @@ const handleItemPrices = (
}
};
export const handleDailyDealPurchase = async (
inventory: TInventoryDatabaseDocument,
purchaseParams: IPurchaseParams,
purchaseResponse: IPurchaseResponse
): Promise<void> => {
const dailyDeal = (await DailyDeal.findOne({ StoreItem: purchaseParams.StoreItem }))!;
dailyDeal.AmountSold += 1;
await dailyDeal.save();
if (!config.dontSubtractPurchasePlatinumCost) {
updateCurrency(inventory, dailyDeal.SalePrice, true, purchaseResponse.InventoryChanges);
}
if (!config.noVendorPurchaseLimits) {
inventory.UsedDailyDeals.push(purchaseParams.StoreItem);
purchaseResponse.DailyDealUsed = purchaseParams.StoreItem;
}
};
export const handleBundleAcqusition = async (
storeItemName: string,
inventory: TInventoryDatabaseDocument,
@ -381,18 +429,28 @@ export const handleStoreItemAcquisition = async (
} else {
const storeCategory = getStoreItemCategory(storeItemName);
const internalName = fromStoreItem(storeItemName);
logger.debug(`store category ${storeCategory}`);
if (!ignorePurchaseQuantity) {
if (internalName in ExportGear) {
quantity *= ExportGear[internalName].purchaseQuantity || 1;
logger.debug(`factored quantity is ${quantity}`);
} else if (internalName in ExportResources) {
quantity *= ExportResources[internalName].purchaseQuantity || 1;
logger.debug(`factored quantity is ${quantity}`);
}
}
logger.debug(`store category ${storeCategory}`);
switch (storeCategory) {
default: {
purchaseResponse = {
InventoryChanges: await addItem(inventory, internalName, quantity, premiumPurchase, seed)
InventoryChanges: await addItem(
inventory,
internalName,
quantity,
premiumPurchase,
seed,
undefined,
true
)
};
break;
}
@ -482,12 +540,57 @@ const handleBoosterPackPurchase = async (
"attempt to roll over 100 booster packs in a single go. possible but unlikely to be desirable for the user or the server."
);
}
const specialItemReward = pack.components.find(x => x.PityIncreaseRate);
for (let i = 0; i != quantity; ++i) {
const disallowedItems = new Set();
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
const weights = pack.rarityWeightsPerRoll[roll];
const result = getRandomWeightedRewardUc(pack.components, weights);
if (result) {
if (specialItemReward) {
{
const normalComponents = [];
for (const comp of pack.components) {
if (!comp.PityIncreaseRate) {
const { Probability, ...rest } = comp;
normalComponents.push({
...rest,
probability: Probability!
});
}
}
const result = getRandomReward(normalComponents)!;
logger.debug(`booster pack rolled`, result);
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
combineInventoryChanges(
purchaseResponse.InventoryChanges,
await addItem(inventory, result.Item, result.Amount)
);
}
if (!inventory.WeaponSkins.some(x => x.ItemType == specialItemReward.Item)) {
inventory.SpecialItemRewardAttenuation ??= [];
let atten = inventory.SpecialItemRewardAttenuation.find(x => x.Tag == specialItemReward.Item);
if (!atten) {
atten =
inventory.SpecialItemRewardAttenuation[
inventory.SpecialItemRewardAttenuation.push({
Tag: specialItemReward.Item,
Atten: specialItemReward.Probability!
}) - 1
];
}
if (Math.random() < atten.Atten) {
purchaseResponse.BoosterPackItems += toStoreItem(specialItemReward.Item) + ',{"lvl":0};';
combineInventoryChanges(
purchaseResponse.InventoryChanges,
await addItem(inventory, specialItemReward.Item)
);
// TOVERIFY: Is the SpecialItemRewardAttenuation entry removed now?
} else {
atten.Atten += specialItemReward.PityIncreaseRate!;
}
}
} else {
const disallowedItems = new Set();
for (let roll = 0; roll != pack.rarityWeightsPerRoll.length; ) {
const weights = pack.rarityWeightsPerRoll[roll];
const result = getRandomWeightedRewardUc(pack.components, weights)!;
logger.debug(`booster pack rolled`, result);
if (disallowedItems.has(result.Item)) {
logger.debug(`oops, can't use that one; trying again`);
@ -497,9 +600,12 @@ const handleBoosterPackPurchase = async (
disallowedItems.add(result.Item);
}
purchaseResponse.BoosterPackItems += toStoreItem(result.Item) + ',{"lvl":0};';
combineInventoryChanges(purchaseResponse.InventoryChanges, await addItem(inventory, result.Item, 1));
combineInventoryChanges(
purchaseResponse.InventoryChanges,
await addItem(inventory, result.Item, result.Amount)
);
++roll;
}
++roll;
}
}
return purchaseResponse;
@ -534,7 +640,9 @@ const handleTypesPurchase = async (
logger.debug(`type category ${typeCategory}`);
switch (typeCategory) {
default:
return { InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed) };
return {
InventoryChanges: await addItem(inventory, typesName, quantity, premiumPurchase, seed, undefined, true)
};
case "BoosterPacks":
return handleBoosterPackPurchase(typesName, inventory, quantity);
case "SlotItems":

View File

@ -331,7 +331,7 @@ export const giveKeyChainMissionReward = async (
const fixedLevelRewards = getLevelKeyRewards(missionName);
if (fixedLevelRewards.levelKeyRewards) {
const missionRewards: { StoreItem: string; ItemCount: number }[] = [];
addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, inventory, missionRewards);
inventory.RegularCredits += addFixedLevelRewards(fixedLevelRewards.levelKeyRewards, missionRewards);
for (const reward of missionRewards) {
await addItem(inventory, fromStoreItem(reward.StoreItem), reward.ItemCount);

View File

@ -107,6 +107,16 @@ export class SRng {
return arr[this.randomInt(0, arr.length - 1)];
}
randomElementPop<T>(arr: T[]): T | undefined {
if (arr.length != 0) {
const index = this.randomInt(0, arr.length - 1);
const elm = arr[index];
arr.splice(index, 1);
return elm;
}
return undefined;
}
randomFloat(): number {
this.state = (0x5851f42d4c957f2dn * this.state + 0x14057b7ef767814fn) & 0xffffffffffffffffn;
return (Number(this.state >> 38n) & 0xffffff) * 0.000000059604645;

View File

@ -149,7 +149,8 @@ export const handleInventoryItemConfigChange = async (
} else {
const inventoryItem = inventory.WeaponSkins.id(itemId);
if (!inventoryItem) {
throw new Error(`inventory item WeaponSkins not found with id ${itemId}`);
logger.warn(`inventory item WeaponSkins not found with id ${itemId}`);
continue;
}
if ("Favorite" in itemConfigEntries) {
inventoryItem.Favorite = itemConfigEntries.Favorite;
@ -177,7 +178,8 @@ export const handleInventoryItemConfigChange = async (
const inventoryItem = inventory[equipmentName].id(itemId);
if (!inventoryItem) {
throw new Error(`inventory item ${equipmentName} not found with id ${itemId}`);
logger.warn(`inventory item ${equipmentName} not found with id ${itemId}`);
continue;
}
for (const [configId, config] of Object.entries(itemConfigEntries)) {

View File

@ -1,45 +1,12 @@
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { isDev } from "@/src/helpers/pathHelper";
import { args } from "@/src/helpers/commandLineArguments";
import { catBreadHash } from "@/src/helpers/stringHelpers";
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
import { mixSeeds, SRng } from "@/src/services/rngService";
import { IItemManifest, IVendorInfo, IVendorManifest } from "@/src/types/vendorTypes";
import { logger } from "@/src/utils/logger";
import { ExportVendors, IRange, IVendor, IVendorOffer } from "warframe-public-export-plus";
import DeimosEntratiFragmentVendorProductsManifest from "@/static/fixed_responses/getVendorInfo/DeimosEntratiFragmentVendorProductsManifest.json";
import DeimosHivemindCommisionsManifestFishmonger from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestFishmonger.json";
import DeimosHivemindCommisionsManifestPetVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestPetVendor.json";
import DeimosHivemindCommisionsManifestProspector from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestProspector.json";
import DeimosHivemindCommisionsManifestTokenVendor from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestTokenVendor.json";
import DeimosHivemindCommisionsManifestWeaponsmith from "@/static/fixed_responses/getVendorInfo/DeimosHivemindCommisionsManifestWeaponsmith.json";
import DeimosHivemindTokenVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosHivemindTokenVendorManifest.json";
import DeimosPetVendorManifest from "@/static/fixed_responses/getVendorInfo/DeimosPetVendorManifest.json";
import DuviriAcrithisVendorManifest from "@/static/fixed_responses/getVendorInfo/DuviriAcrithisVendorManifest.json";
import EntratiLabsEntratiLabsCommisionsManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabsCommisionsManifest.json";
import EntratiLabsEntratiLabVendorManifest from "@/static/fixed_responses/getVendorInfo/EntratiLabsEntratiLabVendorManifest.json";
import MaskSalesmanManifest from "@/static/fixed_responses/getVendorInfo/MaskSalesmanManifest.json";
import Nova1999ConquestShopManifest from "@/static/fixed_responses/getVendorInfo/Nova1999ConquestShopManifest.json";
import OstronPetVendorManifest from "@/static/fixed_responses/getVendorInfo/OstronPetVendorManifest.json";
import SolarisDebtTokenVendorRepossessionsManifest from "@/static/fixed_responses/getVendorInfo/SolarisDebtTokenVendorRepossessionsManifest.json";
const rawVendorManifests: IVendorManifest[] = [
DeimosEntratiFragmentVendorProductsManifest,
DeimosHivemindCommisionsManifestFishmonger,
DeimosHivemindCommisionsManifestPetVendor,
DeimosHivemindCommisionsManifestProspector,
DeimosHivemindCommisionsManifestTokenVendor,
DeimosHivemindCommisionsManifestWeaponsmith,
DeimosHivemindTokenVendorManifest,
DeimosPetVendorManifest,
DuviriAcrithisVendorManifest,
EntratiLabsEntratiLabsCommisionsManifest,
EntratiLabsEntratiLabVendorManifest,
MaskSalesmanManifest,
Nova1999ConquestShopManifest,
OstronPetVendorManifest,
SolarisDebtTokenVendorRepossessionsManifest
];
import { config } from "./configService";
interface IGeneratableVendorInfo extends Omit<IVendorInfo, "ItemManifest" | "Expiry"> {
cycleOffset?: number;
@ -93,49 +60,45 @@ const getCycleDuration = (manifest: IVendor): number => {
return dur * unixTimesInMs.hour;
};
export const getVendorManifestByTypeName = (typeName: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo.TypeName == typeName) {
return vendorManifest;
}
}
export const getVendorManifestByTypeName = (typeName: string, fullStock?: boolean): IVendorManifest | undefined => {
for (const vendorInfo of generatableVendors) {
if (vendorInfo.TypeName == typeName) {
return generateVendorManifest(vendorInfo);
return generateVendorManifest(vendorInfo, fullStock ?? config.fullyStockedVendors);
}
}
if (typeName in ExportVendors) {
const manifest = ExportVendors[typeName];
return generateVendorManifest({
_id: { $oid: getVendorOid(typeName) },
TypeName: typeName,
RandomSeedType: manifest.randomSeedType,
cycleDuration: getCycleDuration(manifest)
});
return generateVendorManifest(
{
_id: { $oid: getVendorOid(typeName) },
TypeName: typeName,
RandomSeedType: manifest.randomSeedType,
cycleDuration: getCycleDuration(manifest)
},
fullStock ?? config.fullyStockedVendors
);
}
return undefined;
};
export const getVendorManifestByOid = (oid: string): IVendorManifest | undefined => {
for (const vendorManifest of rawVendorManifests) {
if (vendorManifest.VendorInfo._id.$oid == oid) {
return vendorManifest;
}
}
for (const vendorInfo of generatableVendors) {
if (vendorInfo._id.$oid == oid) {
return generateVendorManifest(vendorInfo);
return generateVendorManifest(vendorInfo, config.fullyStockedVendors);
}
}
for (const [typeName, manifest] of Object.entries(ExportVendors)) {
const typeNameOid = getVendorOid(typeName);
if (typeNameOid == oid) {
return generateVendorManifest({
_id: { $oid: typeNameOid },
TypeName: typeName,
RandomSeedType: manifest.randomSeedType,
cycleDuration: getCycleDuration(manifest)
});
return generateVendorManifest(
{
_id: { $oid: typeNameOid },
TypeName: typeName,
RandomSeedType: manifest.randomSeedType,
cycleDuration: getCycleDuration(manifest)
},
config.fullyStockedVendors
);
}
}
return undefined;
@ -213,9 +176,26 @@ const getOfferId = (offer: IVendorOffer | IItemManifest): TOfferId => {
}
};
let vendorManifestsUsingFullStock = false;
const vendorManifestCache: Record<string, IVendorManifest> = {};
const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorManifest => {
const clearVendorCache = (): void => {
for (const k of Object.keys(vendorManifestCache)) {
delete vendorManifestCache[k];
}
};
const generateVendorManifest = (
vendorInfo: IGeneratableVendorInfo,
fullStock: boolean | undefined
): IVendorManifest => {
fullStock ??= config.fullyStockedVendors;
fullStock ??= false;
if (vendorManifestsUsingFullStock != fullStock) {
vendorManifestsUsingFullStock = fullStock;
clearVendorCache();
}
if (!(vendorInfo.TypeName in vendorManifestCache)) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { cycleOffset, cycleDuration, ...clientVendorInfo } = vendorInfo;
@ -252,7 +232,20 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
const cycleIndex = Math.trunc((now - cycleOffset) / cycleDuration);
const rng = new SRng(mixSeeds(vendorSeed, cycleIndex));
const offersToAdd: IVendorOffer[] = [];
if (!manifest.isOneBinPerCycle) {
if (manifest.isOneBinPerCycle) {
if (fullStock) {
for (const rawItem of manifest.items) {
offersToAdd.push(rawItem);
}
} else {
const binThisCycle = cycleIndex % 2; // Note: May want to check the actual number of bins, but this is only used for coda weapons right now.
for (const rawItem of manifest.items) {
if (rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem);
}
}
}
} else {
// Compute vendor requirements, subtracting existing offers
const remainingItemCapacity: Record<TOfferId, number> = {};
const missingItemsPerBin: Record<number, number> = {};
@ -277,6 +270,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
// Add permanent offers
let numUncountedOffers = 0;
let numCountedOffers = 0;
let offset = 0;
for (const item of manifest.items) {
if (item.alwaysOffered || item.rotatedWeekly) {
@ -287,22 +281,36 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
offersToAdd.push(item);
++offset;
}
if (missingItemsPerBin[item.bin]) {
missingItemsPerBin[item.bin] -= 1;
numOffersThatNeedToMatchABin -= 1;
}
} else {
numCountedOffers += 1 + item.duplicates;
}
}
// Add counted offers
const useRng = manifest.numItems && manifest.numItems.minValue != manifest.numItems.maxValue;
const numItemsTarget = manifest.numItems
? numUncountedOffers +
(useRng
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
: manifest.numItems.minValue)
: manifest.items.length;
const useRng =
manifest.numItems &&
(manifest.numItems.minValue != manifest.numItems.maxValue ||
manifest.numItems.minValue != numCountedOffers);
const numItemsTarget = fullStock
? numUncountedOffers + numCountedOffers
: manifest.numItems
? numUncountedOffers +
(useRng
? rng.randomInt(manifest.numItems.minValue, manifest.numItems.maxValue)
: manifest.numItems.minValue)
: manifest.items.length;
let i = 0;
const rollableOffers = manifest.items.filter(x => x.probability !== undefined) as (Omit<
IVendorOffer,
"probability"
> & { probability: number })[];
while (info.ItemManifest.length + offersToAdd.length < numItemsTarget) {
const item = useRng ? rng.randomElement(manifest.items)! : manifest.items[i++];
const item = useRng ? rng.randomReward(rollableOffers)! : rollableOffers[i++];
if (
!item.alwaysOffered &&
remainingItemCapacity[getOfferId(item)] != 0 &&
(numOffersThatNeedToMatchABin == 0 || missingItemsPerBin[item.bin])
) {
@ -313,17 +321,10 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
}
offersToAdd.splice(offset, 0, item);
}
if (i == manifest.items.length) {
if (i == rollableOffers.length) {
i = 0;
}
}
} else {
const binThisCycle = cycleIndex % 2; // Note: May want to auto-compute the bin size, but this is only used for coda weapons right now.
for (const rawItem of manifest.items) {
if (rawItem.bin == binThisCycle) {
offersToAdd.push(rawItem);
}
}
}
const cycleStart = cycleOffset + cycleIndex * cycleDuration;
for (const rawItem of offersToAdd) {
@ -351,7 +352,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
}
};
if (rawItem.numRandomItemPrices) {
item.ItemPrices = [];
item.ItemPrices ??= [];
for (let i = 0; i != rawItem.numRandomItemPrices; ++i) {
let itemPrice: { type: string; count: IRange };
do {
@ -391,11 +392,13 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
info.ItemManifest.push(item);
}
info.ItemManifest.sort((a, b) => {
const aBin = parseInt(a.Bin.substring(4));
const bBin = parseInt(b.Bin.substring(4));
return aBin == bBin ? 0 : aBin < bBin ? +1 : -1;
});
if (manifest.numItemsPerBin) {
info.ItemManifest.sort((a, b) => {
const aBin = parseInt(a.Bin.substring(4));
const bBin = parseInt(b.Bin.substring(4));
return aBin == bBin ? 0 : aBin < bBin ? +1 : -1;
});
}
// Update vendor expiry
let soonestOfferExpiry: number = Number.MAX_SAFE_INTEGER;
@ -412,7 +415,7 @@ const generateVendorManifest = (vendorInfo: IGeneratableVendorInfo): IVendorMani
return cacheEntry;
};
if (isDev) {
if (args.dev) {
if (
getCycleDuration(ExportVendors["/Lotus/Types/Game/VendorManifests/Hubs/TeshinHardModeVendorManifest"]) !=
unixTimesInMs.week
@ -420,34 +423,44 @@ if (isDev) {
logger.warn(`getCycleDuration self test failed`);
}
const ads = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest")!
.VendorInfo.ItemManifest;
if (
ads.length != 5 ||
ads[0].Bin != "BIN_4" ||
ads[1].Bin != "BIN_3" ||
ads[2].Bin != "BIN_2" ||
ads[3].Bin != "BIN_1" ||
ads[4].Bin != "BIN_0"
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
for (let i = 0; i != 2; ++i) {
const fullStock = !!i;
const ads = getVendorManifestByTypeName(
"/Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest",
fullStock
)!.VendorInfo.ItemManifest;
if (
ads.length != 5 ||
ads[0].Bin != "BIN_4" ||
ads[1].Bin != "BIN_3" ||
ads[2].Bin != "BIN_2" ||
ads[3].Bin != "BIN_1" ||
ads[4].Bin != "BIN_0"
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/GuildAdvertisementVendorManifest`);
}
const pall = getVendorManifestByTypeName(
"/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest",
fullStock
)!.VendorInfo.ItemManifest;
if (
pall.length != 5 ||
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
}
}
const pall = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest")!
.VendorInfo.ItemManifest;
if (
pall.length != 5 ||
pall[0].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/HarrowQuestKeyOrnament" ||
pall[1].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack" ||
pall[2].StoreItem != "/Lotus/StoreItems/Types/StoreItems/CreditBundles/150000Credits" ||
pall[3].StoreItem != "/Lotus/StoreItems/Types/Items/MiscItems/Kuva" ||
pall[4].StoreItem != "/Lotus/StoreItems/Types/BoosterPacks/RivenModPack"
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/IronwakeDondaVendorManifest`);
}
const cms = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest")!
.VendorInfo.ItemManifest;
const cms = getVendorManifestByTypeName(
"/Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest",
false
)!.VendorInfo.ItemManifest;
if (
cms.length != 9 ||
cms[0].Bin != "BIN_2" ||
@ -459,9 +472,27 @@ if (isDev) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Hubs/RailjackCrewMemberVendorManifest`);
}
const temple = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest")!
.VendorInfo.ItemManifest;
const temple = getVendorManifestByTypeName(
"/Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest",
false
)!.VendorInfo.ItemManifest;
if (!temple.find(x => x.StoreItem == "/Lotus/StoreItems/Types/Items/MiscItems/Kuva")) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/TheHex/Temple1999VendorManifest`);
}
const nakak = getVendorManifestByTypeName("/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest", false)!
.VendorInfo.ItemManifest;
if (
nakak.length != 10 ||
nakak[0].StoreItem != "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask" ||
nakak[1].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper" ||
nakak[1].ItemPrices?.length != 4 ||
nakak[2].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium" ||
nakak[2].ItemPrices?.length != 4 ||
nakak[3].StoreItem != "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge" ||
nakak[3].ItemPrices?.length != 4
// The remaining offers should be computed by weighted RNG.
) {
logger.warn(`self test failed for /Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest`);
}
}

View File

@ -4,15 +4,18 @@ import {
ISetShipCustomizationsRequest,
IShipDecorationsRequest,
IShipDecorationsResponse,
ISetPlacedDecoInfoRequest
ISetPlacedDecoInfoRequest,
TBootLocation
} from "@/src/types/shipTypes";
import { logger } from "@/src/utils/logger";
import { Types } from "mongoose";
import { addShipDecorations, getInventory } from "./inventoryService";
import { addFusionTreasures, addShipDecorations, getInventory } from "./inventoryService";
import { config } from "./configService";
import { Guild } from "../models/guildModel";
import { hasGuildPermission } from "./guildService";
import { GuildPermission } from "../types/guildTypes";
import { ExportResources } from "warframe-public-export-plus";
import { RoomsType, TPersonalRoomsDatabaseDocument } from "../types/personalRoomsTypes";
export const setShipCustomizations = async (
accountId: string,
@ -58,7 +61,12 @@ export const handleSetShipDecorations = async (
const roomToPlaceIn = rooms.find(room => room.Name === placedDecoration.Room);
if (!roomToPlaceIn) {
throw new Error("room not found");
throw new Error(`unknown room: ${placedDecoration.Room}`);
}
const [itemType, meta] = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)!;
if (!itemType || meta.capacityCost === undefined) {
throw new Error(`unknown deco type: ${placedDecoration.Type}`);
}
if (placedDecoration.MoveId) {
@ -82,7 +90,7 @@ export const handleSetShipDecorations = async (
OldRoom: placedDecoration.OldRoom,
NewRoom: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal
MaxCapacityIncrease: 0
};
}
@ -95,33 +103,44 @@ export const handleSetShipDecorations = async (
}
oldRoom.PlacedDecos.pull({ _id: placedDecoration.MoveId });
oldRoom.MaxCapacity += meta.capacityCost;
const newDecoration = {
Type: placedDecoration.Type,
Pos: placedDecoration.Pos,
Rot: placedDecoration.Rot,
Scale: placedDecoration.Scale,
Sockets: placedDecoration.Sockets,
_id: placedDecoration.MoveId
};
//the new room is still roomToPlaceIn
roomToPlaceIn.PlacedDecos.push(newDecoration);
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
await personalRooms.save();
return {
OldRoom: placedDecoration.OldRoom,
NewRoom: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0 // TODO: calculate capacity change upon removal
MaxCapacityIncrease: -meta.capacityCost
};
}
if (placedDecoration.RemoveId) {
roomToPlaceIn.PlacedDecos.pull({ _id: placedDecoration.RemoveId });
const decoIndex = roomToPlaceIn.PlacedDecos.findIndex(x => x._id.equals(placedDecoration.RemoveId));
const deco = roomToPlaceIn.PlacedDecos[decoIndex];
roomToPlaceIn.PlacedDecos.splice(decoIndex, 1);
roomToPlaceIn.MaxCapacity += meta.capacityCost;
await personalRooms.save();
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: 1 }]);
if (deco.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: deco.Sockets, ItemCount: 1 }]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: 1 }]);
}
await inventory.save();
}
@ -129,17 +148,20 @@ export const handleSetShipDecorations = async (
DecoId: placedDecoration.RemoveId,
Room: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: 0
MaxCapacityIncrease: 0 // Client already implies the capacity being refunded.
};
} else {
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
addShipDecorations(inventory, [{ ItemType: placedDecoration.Type, ItemCount: -1 }]);
await inventory.save();
}
}
// TODO: handle capacity
if (!config.unlockAllShipDecorations) {
const inventory = await getInventory(accountId);
const itemType = Object.entries(ExportResources).find(arr => arr[1].deco == placedDecoration.Type)![0];
if (placedDecoration.Sockets !== undefined) {
addFusionTreasures(inventory, [{ ItemType: itemType, Sockets: placedDecoration.Sockets, ItemCount: -1 }]);
} else {
addShipDecorations(inventory, [{ ItemType: itemType, ItemCount: -1 }]);
}
await inventory.save();
}
//place decoration
const decoId = new Types.ObjectId();
@ -148,12 +170,32 @@ export const handleSetShipDecorations = async (
Pos: placedDecoration.Pos,
Rot: placedDecoration.Rot,
Scale: placedDecoration.Scale,
Sockets: placedDecoration.Sockets,
_id: decoId
});
roomToPlaceIn.MaxCapacity -= meta.capacityCost;
await personalRooms.save();
return { DecoId: decoId.toString(), Room: placedDecoration.Room, IsApartment: placedDecoration.IsApartment };
return {
DecoId: decoId.toString(),
Room: placedDecoration.Room,
IsApartment: placedDecoration.IsApartment,
MaxCapacityIncrease: -meta.capacityCost
};
};
const getRoomsForBootLocation = (
personalRooms: TPersonalRoomsDatabaseDocument,
bootLocation: TBootLocation | undefined
): RoomsType[] => {
if (bootLocation == "SHOP") {
return personalRooms.TailorShop.Rooms;
}
if (bootLocation == "APARTMENT") {
return personalRooms.Apartment.Rooms;
}
return personalRooms.Ship.Rooms;
};
export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlacedDecoInfoRequest): Promise<void> => {
@ -170,14 +212,14 @@ export const handleSetPlacedDecoInfo = async (accountId: string, req: ISetPlaced
const personalRooms = await getPersonalRooms(accountId);
const room = personalRooms.Ship.Rooms.find(room => room.Name === req.Room);
const room = getRoomsForBootLocation(personalRooms, req.BootLocation).find(room => room.Name === req.Room);
if (!room) {
throw new Error("room not found");
throw new Error(`unknown room: ${req.Room}`);
}
const placedDeco = room.PlacedDecos.id(req.DecoId);
if (!placedDeco) {
throw new Error("deco not found");
throw new Error(`unknown deco id: ${req.DecoId}`);
}
placedDeco.PictureFrameInfo = req.PictureFrameInfo;

View File

@ -5,9 +5,17 @@ import { config } from "./configService";
import { logger } from "../utils/logger";
import { app } from "../app";
import { AddressInfo } from "node:net";
import ws from "ws";
import { Account } from "../models/loginModel";
import { createAccount, createNonce, getUsernameFromEmail, isCorrectPassword } from "./loginService";
import { IDatabaseAccountJson } from "../types/loginTypes";
import { HydratedDocument } from "mongoose";
import { Agent, WebSocket as UnidiciWebSocket } from "undici";
let httpServer: http.Server | undefined;
let httpsServer: https.Server | undefined;
let wsServer: ws.Server | undefined;
let wssServer: ws.Server | undefined;
const tlsOptions = {
key: fs.readFileSync("static/certs/key.pem"),
@ -21,19 +29,65 @@ export const startWebServer = (): void => {
// eslint-disable-next-line @typescript-eslint/no-misused-promises
httpServer = http.createServer(app);
httpServer.listen(httpPort, () => {
wsServer = new ws.Server({ server: httpServer });
wsServer.on("connection", wsOnConnect);
logger.info("HTTP server started on port " + httpPort);
// eslint-disable-next-line @typescript-eslint/no-misused-promises
httpsServer = https.createServer(tlsOptions, app);
httpsServer.listen(httpsPort, () => {
wssServer = new ws.Server({ server: httpsServer });
wssServer.on("connection", wsOnConnect);
logger.info("HTTPS server started on port " + httpsPort);
logger.info(
"Access the WebUI in your browser at http://localhost" + (httpPort == 80 ? "" : ":" + httpPort)
);
void runWsSelfTest("wss", httpsPort).then(ok => {
if (!ok) {
logger.warn(`WSS self-test failed. The server may not actually be reachable at port ${httpsPort}.`);
if (process.platform == "win32") {
logger.warn(
`You can check who actually has that port via powershell: Get-Process -Id (Get-NetTCPConnection -LocalPort ${httpsPort}).OwningProcess`
);
}
}
});
});
});
};
const runWsSelfTest = (protocol: "ws" | "wss", port: number): Promise<boolean> => {
return new Promise(resolve => {
// https://github.com/oven-sh/bun/issues/20547
if (process.versions.bun) {
const client = new WebSocket(`${protocol}://localhost:${port}/custom/selftest`, {
tls: { rejectUnauthorized: false }
} as unknown as string);
client.onmessage = (e): void => {
resolve(e.data == "SpaceNinjaServer");
};
client.onerror = client.onclose = (): void => {
resolve(false);
};
} else {
const agent = new Agent({ connect: { rejectUnauthorized: false } });
const client = new UnidiciWebSocket(`${protocol}://localhost:${port}/custom/selftest`, {
dispatcher: agent
});
client.onmessage = (e): void => {
resolve(e.data == "SpaceNinjaServer");
};
client.onerror = client.onclose = (): void => {
resolve(false);
};
}
});
};
export const getWebPorts = (): Record<"http" | "https", number | undefined> => {
return {
http: (httpServer?.address() as AddressInfo | undefined)?.port,
@ -61,5 +115,182 @@ export const stopWebServer = async (): Promise<void> => {
})
);
}
if (wsServer) {
promises.push(
new Promise(resolve => {
wsServer!.close(() => {
resolve();
});
})
);
}
if (wssServer) {
promises.push(
new Promise(resolve => {
wssServer!.close(() => {
resolve();
});
})
);
}
await Promise.all(promises);
};
let lastWsid: number = 0;
interface IWsCustomData extends ws {
id?: number;
accountId?: string;
}
interface IWsMsgFromClient {
auth?: {
email: string;
password: string;
isRegister: boolean;
};
logout?: boolean;
}
interface IWsMsgToClient {
//wsid?: number;
reload?: boolean;
ports?: {
http: number | undefined;
https: number | undefined;
};
config_reloaded?: boolean;
auth_succ?: {
id: string;
DisplayName: string;
Nonce: number;
};
auth_fail?: {
isRegister: boolean;
};
logged_out?: boolean;
update_inventory?: boolean;
}
const wsOnConnect = (ws: ws, req: http.IncomingMessage): void => {
if (req.url == "/custom/selftest") {
ws.send("SpaceNinjaServer");
ws.close();
return;
}
(ws as IWsCustomData).id = ++lastWsid;
ws.send(JSON.stringify({ wsid: lastWsid }));
// eslint-disable-next-line @typescript-eslint/no-misused-promises
ws.on("message", async msg => {
const data = JSON.parse(String(msg)) as IWsMsgFromClient;
if (data.auth) {
let account: IDatabaseAccountJson | null = await Account.findOne({ email: data.auth.email });
if (account) {
if (isCorrectPassword(data.auth.password, account.password)) {
if (!account.Nonce) {
account.ClientType = "webui";
account.Nonce = createNonce();
await (account as HydratedDocument<IDatabaseAccountJson>).save();
}
} else {
account = null;
}
} else if (data.auth.isRegister) {
const name = await getUsernameFromEmail(data.auth.email);
account = await createAccount({
email: data.auth.email,
password: data.auth.password,
ClientType: "webui",
LastLogin: new Date(),
DisplayName: name,
Nonce: createNonce()
});
}
if (account) {
(ws as IWsCustomData).accountId = account.id;
ws.send(
JSON.stringify({
auth_succ: {
id: account.id,
DisplayName: account.DisplayName,
Nonce: account.Nonce
}
} satisfies IWsMsgToClient)
);
} else {
ws.send(
JSON.stringify({
auth_fail: {
isRegister: data.auth.isRegister
}
} satisfies IWsMsgToClient)
);
}
}
if (data.logout) {
const accountId = (ws as IWsCustomData).accountId;
(ws as IWsCustomData).accountId = undefined;
await Account.updateOne(
{
_id: accountId,
ClientType: "webui"
},
{
Nonce: 0
}
);
}
});
};
export const sendWsBroadcast = (data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
client.send(msg);
}
}
if (wssServer) {
for (const client of wssServer.clients) {
client.send(msg);
}
}
};
export const sendWsBroadcastTo = (accountId: string, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).accountId == accountId) {
client.send(msg);
}
}
}
};
export const sendWsBroadcastExcept = (wsid: number | undefined, data: IWsMsgToClient): void => {
const msg = JSON.stringify(data);
if (wsServer) {
for (const client of wsServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
if (wssServer) {
for (const client of wssServer.clients) {
if ((client as IWsCustomData).id != wsid) {
client.send(msg);
}
}
}
};

View File

@ -1,12 +1,15 @@
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
import baro from "@/static/fixed_responses/worldState/baro.json";
import fissureMissions from "@/static/fixed_responses/worldState/fissureMissions.json";
import sortieTilesets from "@/static/fixed_responses/worldState/sortieTilesets.json";
import sortieTilesetMissions from "@/static/fixed_responses/worldState/sortieTilesetMissions.json";
import syndicateMissions from "@/static/fixed_responses/worldState/syndicateMissions.json";
import darvoDeals from "@/static/fixed_responses/worldState/darvoDeals.json";
import { buildConfig } from "@/src/services/buildConfigService";
import { unixTimesInMs } from "@/src/constants/timeConstants";
import { config } from "@/src/services/configService";
import { SRng } from "@/src/services/rngService";
import { ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
import { getRandomElement, getRandomInt, SRng } from "@/src/services/rngService";
import { eMissionType, ExportRegions, ExportSyndicates, IRegion } from "warframe-public-export-plus";
import {
ICalendarDay,
ICalendarEvent,
@ -18,11 +21,14 @@ import {
ISyndicateMissionInfo,
ITmp,
IVoidStorm,
IVoidTrader,
IVoidTraderOffer,
IWorldState,
TCircuitGameMode
} from "../types/worldStateTypes";
import { version_compare } from "../helpers/inventoryHelpers";
import { toMongoDate, toOid, version_compare } from "../helpers/inventoryHelpers";
import { logger } from "../utils/logger";
import { DailyDeal, Fissure } from "../models/worldStateModel";
const sortieBosses = [
"SORTIE_BOSS_HYENA",
@ -96,7 +102,7 @@ const sortieBossNode: Record<Exclude<TSortieBoss, "SORTIE_BOSS_CORRUPTED_VOR">,
SORTIE_BOSS_VOR: "SolNode108"
};
const eidolonJobs = [
const eidolonJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AssassinateBountyCap",
"/Lotus/Types/Gameplay/Eidolon/Jobs/AttritionBountySab",
@ -112,14 +118,14 @@ const eidolonJobs = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/RescueBountyResc"
];
const eidolonNarmerJobs = [
const eidolonNarmerJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AssassinateBountyAss",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyExt",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/ReclamationBountyTheft",
"/Lotus/Types/Gameplay/Eidolon/Jobs/Narmer/AttritionBountyLib"
];
const venusJobs = [
const venusJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobAmbush",
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobExcavation",
"/Lotus/Types/Gameplay/Venus/Jobs/VenusArtifactJobRecovery",
@ -145,14 +151,14 @@ const venusJobs = [
"/Lotus/Types/Gameplay/Venus/Jobs/VenusWetworkJobSpy"
];
const venusNarmerJobs = [
const venusNarmerJobs: readonly string[] = [
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobAssassinate",
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusCullJobExterminate",
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusPreservationJobDefense",
"/Lotus/Types/Gameplay/Venus/Jobs/Narmer/NarmerVenusTheftJobExcavation"
];
const microplanetJobs = [
const microplanetJobs: readonly string[] = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAreaDefenseBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosAssassinateBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosCrpSurvivorBounty",
@ -162,7 +168,7 @@ const microplanetJobs = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosPurifyBounty"
];
const microplanetEndlessJobs = [
const microplanetEndlessJobs: readonly string[] = [
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessAreaDefenseBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessExcavateBounty",
"/Lotus/Types/Gameplay/InfestedMicroplanet/Jobs/DeimosEndlessPurifyBounty"
@ -367,7 +373,7 @@ const getSeasonChallengePools = (syndicateTag: string): IRotatingSeasonChallenge
hardWeekly: syndicate.weeklyChallenges!.filter(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
),
hasWeeklyPermanent: !!syndicate.weeklyChallenges!.find(x =>
hasWeeklyPermanent: syndicate.weeklyChallenges!.some(x =>
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
)
};
@ -493,6 +499,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
{
const rng = new SRng(seed);
const pool = [...eidolonJobs];
syndicateMissions.push({
_id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000008"
@ -504,7 +511,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
Nodes: [],
Jobs: [
{
jobType: rng.randomElement(eidolonJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierATable${table}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
@ -512,7 +519,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
},
{
jobType: rng.randomElement(eidolonJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierBTable${table}Rewards`,
masteryReq: 1,
minEnemyLevel: 10,
@ -520,7 +527,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
},
{
jobType: rng.randomElement(eidolonJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierCTable${table}Rewards`,
masteryReq: 2,
minEnemyLevel: 20,
@ -528,7 +535,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
},
{
jobType: rng.randomElement(eidolonJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierDTable${table}Rewards`,
masteryReq: 3,
minEnemyLevel: 30,
@ -536,7 +543,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
},
{
jobType: rng.randomElement(eidolonJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
@ -544,7 +551,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
},
{
jobType: rng.randomElement(eidolonJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/EidolonJobMissionRewards/TierETable${table}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
@ -565,6 +572,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
{
const rng = new SRng(seed);
const pool = [...venusJobs];
syndicateMissions.push({
_id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000025"
@ -576,7 +584,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
Nodes: [],
Jobs: [
{
jobType: rng.randomElement(venusJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierATable${table}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
@ -584,7 +592,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1000, 1500)
},
{
jobType: rng.randomElement(venusJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierBTable${table}Rewards`,
masteryReq: 1,
minEnemyLevel: 10,
@ -592,7 +600,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 1750, 2250)
},
{
jobType: rng.randomElement(venusJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierCTable${table}Rewards`,
masteryReq: 2,
minEnemyLevel: 20,
@ -600,7 +608,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 4, 2500, 3000)
},
{
jobType: rng.randomElement(venusJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierDTable${table}Rewards`,
masteryReq: 3,
minEnemyLevel: 30,
@ -608,7 +616,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 3250, 3750)
},
{
jobType: rng.randomElement(venusJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
masteryReq: 5,
minEnemyLevel: 40,
@ -616,7 +624,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 4000, 4500)
},
{
jobType: rng.randomElement(venusJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/VenusJobMissionRewards/VenusTierETable${table}Rewards`,
masteryReq: 10,
minEnemyLevel: 100,
@ -637,6 +645,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
{
const rng = new SRng(seed);
const pool = [...microplanetJobs];
syndicateMissions.push({
_id: {
$oid: ((bountyCycleStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "0000000000000002"
@ -648,7 +657,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
Nodes: [],
Jobs: [
{
jobType: rng.randomElement(microplanetJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierATable${table}Rewards`,
masteryReq: 0,
minEnemyLevel: 5,
@ -656,7 +665,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 3, 12, 18)
},
{
jobType: rng.randomElement(microplanetJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierCTable${table}Rewards`,
masteryReq: 1,
minEnemyLevel: 15,
@ -673,7 +682,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: [14, 14, 14]
},
{
jobType: rng.randomElement(microplanetJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierDTable${deimosDTable}Rewards`,
masteryReq: 2,
minEnemyLevel: 30,
@ -681,7 +690,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 4, 72, 88)
},
{
jobType: rng.randomElement(microplanetJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
masteryReq: 3,
minEnemyLevel: 40,
@ -689,7 +698,7 @@ export const pushClassicBounties = (syndicateMissions: ISyndicateMissionInfo[],
xpAmounts: generateXpAmounts(rng, 5, 115, 135)
},
{
jobType: rng.randomElement(microplanetJobs),
jobType: rng.randomElementPop(pool),
rewards: `/Lotus/Types/Game/MissionDecks/DeimosMissionRewards/TierETableARewards`,
masteryReq: 10,
minEnemyLevel: 100,
@ -1110,21 +1119,20 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Alerts: [],
Sorties: [],
LiteSorties: [],
ActiveMissions: [],
GlobalUpgrades: [],
VoidTraders: [],
VoidStorms: [],
DailyDeals: [],
EndlessXpChoices: [],
KnownCalendarSeasons: [],
...staticWorldState,
SyndicateMissions: [...staticWorldState.SyndicateMissions]
};
// Omit void fissures for versions prior to Dante Unbound to avoid script errors.
if (buildLabel && version_compare(buildLabel, "2024.03.24.20.00") < 0) {
worldState.ActiveMissions = [];
if (version_compare(buildLabel, "2017.10.12.17.04") < 0) {
// Old versions seem to really get hung up on not being able to load these.
worldState.PVPChallengeInstances = [];
}
// Old versions seem to really get hung up on not being able to load these.
if (buildLabel && version_compare(buildLabel, "2017.10.12.17.04") < 0) {
worldState.PVPChallengeInstances = [];
}
if (config.worldState?.starDays) {
@ -1143,6 +1151,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
Node: "SolarisUnitedHub1"
});
}
// The client gets kinda confused when multiple goals have the same tag, so considering these mutually exclusive.
if (config.worldState?.galleonOfGhouls == 1) {
worldState.Goals.push({
_id: { $oid: "6814ddf00000000000000000" },
Activation: { $date: { $numberLong: "1746198000000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Count: 0,
Goal: 1,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode19",
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlert",
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
Tag: "GalleonRobbery",
Reward: {
items: [
"/Lotus/StoreItems/Types/Recipes/Weapons/GrnChainSawTonfaBlueprint",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
}
});
} else if (config.worldState?.galleonOfGhouls == 2) {
worldState.Goals.push({
_id: { $oid: "681e18700000000000000000" },
Activation: { $date: { $numberLong: "1746802800000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Count: 0,
Goal: 1,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode28",
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertB",
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
Tag: "GalleonRobbery",
Reward: {
items: [
"/Lotus/StoreItems/Types/Recipes/Weapons/MortiforShieldAndSwordBlueprint",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
}
});
} else if (config.worldState?.galleonOfGhouls == 3) {
worldState.Goals.push({
_id: { $oid: "682752f00000000000000000" },
Activation: { $date: { $numberLong: "1747407600000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Count: 0,
Goal: 1,
Success: 0,
Personal: true,
Bounty: true,
ClampNodeScores: true,
Node: "EventNode19",
MissionKeyName: "/Lotus/Types/Keys/GalleonRobberyAlertC",
Desc: "/Lotus/Language/Events/GalleonRobberyEventMissionTitle",
Icon: "/Lotus/Interface/Icons/Player/GalleonRobberiesEvent.png",
Tag: "GalleonRobbery",
Reward: {
items: [
"/Lotus/Types/StoreItems/Packages/EventCatalystReactorBundle",
"/Lotus/StoreItems/Upgrades/Skins/Clan/BountyHunterBadgeItem"
]
}
});
}
// Nightwave Challenges
const nightwaveSyndicateTag = getNightwaveSyndicateTag(buildLabel);
@ -1243,6 +1322,77 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
});
}
// Baro
{
const baroIndex = Math.trunc((Date.now() - 910800000) / (unixTimesInMs.day * 14));
const baroStart = baroIndex * (unixTimesInMs.day * 14) + 910800000;
const baroActualStart = baroStart + unixTimesInMs.day * (config.baroAlwaysAvailable ? 0 : 12);
const baroEnd = baroStart + unixTimesInMs.day * 14;
const baroNode = ["EarthHUB", "MercuryHUB", "SaturnHUB", "PlutoHUB"][baroIndex % 4];
const vt: IVoidTrader = {
_id: { $oid: ((baroStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "493c96d6067610bc" },
Activation: { $date: { $numberLong: baroActualStart.toString() } },
Expiry: { $date: { $numberLong: baroEnd.toString() } },
Character: "Baro'Ki Teel",
Node: baroNode,
Manifest: []
};
worldState.VoidTraders.push(vt);
if (isBeforeNextExpectedWorldStateRefresh(timeMs, baroActualStart)) {
vt.Manifest = [];
if (config.baroFullyStocked) {
for (const armorSet of baro.armorSets) {
if (Array.isArray(armorSet[0])) {
for (const set of armorSet as IVoidTraderOffer[][]) {
for (const item of set) {
vt.Manifest.push(item);
}
}
} else {
for (const item of armorSet as IVoidTraderOffer[]) {
vt.Manifest.push(item);
}
}
}
for (const item of baro.rest) {
vt.Manifest.push(item);
}
} else {
const rng = new SRng(new SRng(baroIndex).randomInt(0, 100_000));
// TOVERIFY: Constraint for upgrades amount?
// TOVERIFY: Constraint for weapon amount?
// TOVERIFY: Constraint for relics amount?
let armorSet = rng.randomElement(baro.armorSets)!;
if (Array.isArray(armorSet[0])) {
armorSet = rng.randomElement(baro.armorSets)!;
}
while (vt.Manifest.length + armorSet.length < 31) {
const item = rng.randomElement(baro.rest)!;
if (vt.Manifest.indexOf(item) == -1) {
const set = baro.allIfAny.find(set => set.indexOf(item.ItemType) != -1);
if (set) {
for (const itemType of set) {
vt.Manifest.push(baro.rest.find(x => x.ItemType == itemType)!);
}
} else {
vt.Manifest.push(item);
}
}
}
const overflow = 31 - (vt.Manifest.length + armorSet.length);
if (overflow > 0) {
vt.Manifest.splice(0, overflow);
}
for (const armor of armorSet) {
vt.Manifest.push(armor as IVoidTraderOffer);
}
}
for (const item of baro.evergreen) {
vt.Manifest.push(item);
}
}
}
// Sortie & syndicate missions cycling every day (at 16:00 or 17:00 UTC depending on if London, OT is observing DST)
{
const rollover = getSortieTime(day);
@ -1326,6 +1476,17 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
const cheeseInterval = hourInSeconds * 8;
const cheeseDuration = hourInSeconds * 2;
const cheeseIndex = Math.trunc(timeSecs / cheeseInterval);
let cheeseStart = cheeseIndex * cheeseInterval;
let cheeseEnd = cheeseStart + cheeseDuration;
let cheeseNext = (cheeseIndex + 1) * cheeseInterval;
// Live servers only update the start time once it happens, which makes the
// client show a negative countdown during off-hours. Optionally adjust the
// times so the next activation is always in the future.
if (config.unfaithfulBugFixes?.fixXtraCheeseTimer && timeSecs >= cheeseEnd) {
cheeseStart = cheeseNext;
cheeseEnd = cheeseStart + cheeseDuration;
cheeseNext += cheeseInterval;
}
const tmp: ITmp = {
cavabegin: "1690761600",
PurchasePlatformLockEnabled: true,
@ -1350,9 +1511,9 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
ennnd: true,
mbrt: true,
fbst: {
a: cheeseIndex * cheeseInterval, // This has a bug where the client shows a negative time for "Xtra cheese starts in ..." until it refreshes the world state. This is because we're only providing the new activation as soon as that time/date is reached. However, this is 100% faithful to live.
e: cheeseIndex * cheeseInterval + cheeseDuration,
n: (cheeseIndex + 1) * cheeseInterval
a: cheeseStart,
e: cheeseEnd,
n: cheeseNext
},
sfn: [550, 553, 554, 555][halfHour % 4]
};
@ -1364,6 +1525,62 @@ export const getWorldState = (buildLabel?: string): IWorldState => {
return worldState;
};
export const populateFissures = async (worldState: IWorldState): Promise<void> => {
if (config.worldState?.allTheFissures) {
let i = 0;
for (const [tier, nodes] of Object.entries(fissureMissions)) {
for (const node of nodes) {
const meta = ExportRegions[node];
worldState.ActiveMissions.push({
_id: { $oid: (i++).toString().padStart(8, "0") + "8e0c70ba050f1eb7" },
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: { $date: { $numberLong: "1000000000000" } },
Expiry: { $date: { $numberLong: "2000000000000" } },
Node: node,
MissionType: eMissionType[meta.missionIndex].tag,
Modifier: tier,
Hard: config.worldState.allTheFissures == "hard"
});
}
}
} else {
const fissures = await Fissure.find({});
for (const fissure of fissures) {
const meta = ExportRegions[fissure.Node];
worldState.ActiveMissions.push({
_id: toOid(fissure._id),
Region: meta.systemIndex + 1,
Seed: 1337,
Activation: toMongoDate(fissure.Activation),
Expiry: toMongoDate(fissure.Expiry),
Node: fissure.Node,
MissionType: eMissionType[meta.missionIndex].tag,
Modifier: fissure.Modifier,
Hard: fissure.Hard
});
}
}
};
export const populateDailyDeal = async (worldState: IWorldState): Promise<void> => {
const dailyDeals = await DailyDeal.find({});
for (const dailyDeal of dailyDeals) {
if (dailyDeal.Expiry.getTime() > Date.now()) {
worldState.DailyDeals.push({
StoreItem: dailyDeal.StoreItem,
Activation: toMongoDate(dailyDeal.Activation),
Expiry: toMongoDate(dailyDeal.Expiry),
Discount: dailyDeal.Discount,
OriginalPrice: dailyDeal.OriginalPrice,
SalePrice: dailyDeal.SalePrice,
AmountTotal: Math.round(dailyDeal.AmountTotal * (config.worldState?.darvoStockMultiplier ?? 1)),
AmountSold: dailyDeal.AmountSold
});
}
}
};
export const idToBountyCycle = (id: string): number => {
return Math.trunc((parseInt(id.substring(0, 8), 16) * 1000) / 9000_000);
};
@ -1491,3 +1708,92 @@ const nightwaveTagToSeason: Record<string, number> = {
RadioLegionIntermissionSyndicate: 1, // Intermission I
RadioLegionSyndicate: 0 // The Wolf of Saturn Six
};
const updateFissures = async (): Promise<void> => {
const fissures = await Fissure.find();
const activeNodes = new Set<string>();
const tierToFurthestExpiry: Record<string, number> = {
VoidT1: 0,
VoidT2: 0,
VoidT3: 0,
VoidT4: 0,
VoidT5: 0,
VoidT6: 0,
VoidT1Hard: 0,
VoidT2Hard: 0,
VoidT3Hard: 0,
VoidT4Hard: 0,
VoidT5Hard: 0,
VoidT6Hard: 0
};
for (const fissure of fissures) {
activeNodes.add(fissure.Node);
const key = fissure.Modifier + (fissure.Hard ? "Hard" : "");
tierToFurthestExpiry[key] = Math.max(tierToFurthestExpiry[key], fissure.Expiry.getTime());
}
const deadline = Date.now() - 6 * unixTimesInMs.minute;
for (const [tier, expiry] of Object.entries(tierToFurthestExpiry)) {
if (expiry < deadline) {
const numFissures = getRandomInt(1, 3);
for (let i = 0; i != numFissures; ++i) {
const modifier = tier.replace("Hard", "") as
| "VoidT1"
| "VoidT2"
| "VoidT3"
| "VoidT4"
| "VoidT5"
| "VoidT6";
let node: string;
do {
node = getRandomElement(fissureMissions[modifier])!;
} while (activeNodes.has(node));
activeNodes.add(node);
await Fissure.insertOne({
Activation: new Date(),
Expiry: new Date(Date.now() + getRandomInt(60, 120) * unixTimesInMs.minute),
Node: node,
Modifier: modifier,
Hard: tier.indexOf("Hard") != -1 ? true : undefined
});
}
}
}
};
const updateDailyDeal = async (): Promise<void> => {
let darvoIndex = Math.trunc((Date.now() - 25200000) / (26 * unixTimesInMs.hour));
let darvoEnd;
do {
const darvoStart = darvoIndex * (26 * unixTimesInMs.hour) + 25200000;
darvoEnd = darvoStart + 26 * unixTimesInMs.hour;
const darvoOid = ((darvoStart / 1000) & 0xffffffff).toString(16).padStart(8, "0") + "adc51a72f7324d95";
if (!(await DailyDeal.findById(darvoOid))) {
const seed = new SRng(darvoIndex).randomInt(0, 100_000);
const rng = new SRng(seed);
let deal;
do {
deal = rng.randomReward(darvoDeals)!; // Using an actual sampling collected over roughly a year because I can't extrapolate an algorithm from it with enough certainty.
//const [storeItem, meta] = rng.randomElement(Object.entries(darvoDeals))!;
//const discount = Math.min(rng.randomInt(1, 9) * 10, (meta as { MaxDiscount?: number }).MaxDiscount ?? 1);
} while (await DailyDeal.exists({ StoreItem: deal.StoreItem }));
await DailyDeal.insertOne({
_id: darvoOid,
StoreItem: deal.StoreItem,
Activation: new Date(darvoStart),
Expiry: new Date(darvoEnd),
Discount: deal.Discount,
OriginalPrice: deal.OriginalPrice,
SalePrice: deal.SalePrice, //Math.trunc(deal.OriginalPrice * (1 - discount))
AmountTotal: deal.AmountTotal,
AmountSold: 0
});
}
} while (darvoEnd < Date.now() + 6 * unixTimesInMs.minute && ++darvoIndex);
};
export const updateWorldStateCollections = async (): Promise<void> => {
await Promise.all([updateFissures(), updateDailyDeal()]);
};

View File

@ -40,6 +40,7 @@ export interface IInventoryDatabase
| "InfestedFoundry"
| "DialogueHistory"
| "KubrowPetEggs"
| "KubrowPetPrints"
| "PendingCoupon"
| "Drones"
| "RecentVendorPurchases"
@ -56,6 +57,7 @@ export interface IInventoryDatabase
| "QualifyingInvasions"
| "LastInventorySync"
| "EndlessXP"
| "PersonalGoalProgress"
| TEquipmentKey
>,
InventoryDatabaseEquipment {
@ -63,7 +65,7 @@ export interface IInventoryDatabase
Created: Date;
TrainingDate: Date;
LoadOutPresets: Types.ObjectId; // LoadOutPresets changed from ILoadOutPresets to Types.ObjectId for population
Mailbox?: IMailboxDatabase;
//Mailbox?: IMailboxDatabase;
GuildId?: Types.ObjectId;
PendingRecipes: IPendingRecipeDatabase[];
QuestKeys: IQuestKeyDatabase[];
@ -78,7 +80,8 @@ export interface IInventoryDatabase
KahlLoadOuts: IOperatorConfigDatabase[];
InfestedFoundry?: IInfestedFoundryDatabase;
DialogueHistory?: IDialogueHistoryDatabase;
KubrowPetEggs?: IKubrowPetEggDatabase[];
KubrowPetEggs: IKubrowPetEggDatabase[];
KubrowPetPrints: IKubrowPetPrintDatabase[];
PendingCoupon?: IPendingCouponDatabase;
Drones: IDroneDatabase[];
RecentVendorPurchases?: IRecentVendorPurchaseDatabase[];
@ -95,6 +98,7 @@ export interface IInventoryDatabase
QualifyingInvasions: IInvasionProgressDatabase[];
LastInventorySync?: Types.ObjectId;
EndlessXP?: IEndlessXpProgressDatabase[];
PersonalGoalProgress?: IPersonalGoalProgressDatabase[];
}
export interface IQuestKeyDatabase {
@ -150,9 +154,9 @@ export interface IMailboxClient {
LastInboxId: IOid;
}
export interface IMailboxDatabase {
/*export interface IMailboxDatabase {
LastInboxId: Types.ObjectId;
}
}*/
export type TSolarMapRegion =
| "Earth"
@ -285,6 +289,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
ArchwingEnabled?: boolean;
PendingSpectreLoadouts?: ISpectreLoadout[];
SpectreLoadouts?: ISpectreLoadout[];
UsedDailyDeals: string[];
EmailItems: ITypeCount[];
CompletedSyndicates: string[];
FocusXP?: IFocusXP;
@ -293,7 +298,7 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
CompletedSorties: string[];
LastSortieReward?: ILastSortieRewardClient[];
LastLiteSortieReward?: ILastSortieRewardClient[];
SortieRewardAttenuation?: ISortieRewardAttenuation[];
SortieRewardAttenuation?: IRewardAttenuation[];
Drones: IDroneClient[];
StepSequencers: IStepSequencer[];
ActiveAvatarImageType?: string;
@ -304,9 +309,9 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
FocusUpgrades: IFocusUpgrade[];
HasContributedToDojo?: boolean;
HWIDProtectEnabled?: boolean;
//KubrowPetPrints: IKubrowPetPrint[];
KubrowPetPrints: IKubrowPetPrintClient[];
AlignmentReplay?: IAlignment;
//PersonalGoalProgress: IPersonalGoalProgress[];
PersonalGoalProgress?: IPersonalGoalProgressClient[];
ThemeStyle: string;
ThemeBackground: string;
ThemeSounds: string;
@ -349,7 +354,6 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
//LeagueTickets: any[];
//Quests: any[];
//Robotics: any[];
//UsedDailyDeals: any[];
LibraryPersonalTarget?: string;
LibraryPersonalProgress: ILibraryPersonalProgress[];
CollectibleSeries?: ICollectibleEntry[];
@ -378,6 +382,8 @@ export interface IInventoryClient extends IDailyAffiliations, InventoryClientEqu
LockedWeaponGroup?: ILockedWeaponGroupClient;
HubNpcCustomizations?: IHubNpcCustomization[];
Ship?: IOrbiter; // U22 and below, response only
ClaimedJunctionChallengeRewards?: string[]; // U39
SpecialItemRewardAttenuation?: IRewardAttenuation[]; // Baro's Void Surplus
}
export interface IAffiliation {
@ -446,8 +452,9 @@ export interface IVendorPurchaseHistoryEntryDatabase {
export interface IChallengeProgress {
Progress: number;
Name: string;
Completed?: string[];
ReceivedJunctionReward?: boolean; // U39
Name: string;
}
export interface ICollectibleEntry {
@ -718,8 +725,8 @@ export interface IKubrowPetEggDatabase {
_id: Types.ObjectId;
}
export interface IKubrowPetPrint {
ItemType: KubrowPetPrintItemType;
export interface IKubrowPetPrintClient {
ItemType: "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint";
Name: string;
IsMale: boolean;
Size: number; // seems to be 0.7 to 1.0
@ -729,6 +736,10 @@ export interface IKubrowPetPrint {
InheritedModularParts?: any[];
}
export interface IKubrowPetPrintDatabase extends Omit<IKubrowPetPrintClient, "ItemId" | "InheritedModularParts"> {
_id: Types.ObjectId;
}
export interface ITraits {
BaseColor: string;
SecondaryColor: string;
@ -742,15 +753,11 @@ export interface ITraits {
Tail?: string;
}
export enum KubrowPetPrintItemType {
LotusTypesGameKubrowPetImprintedTraitPrint = "/Lotus/Types/Game/KubrowPet/ImprintedTraitPrint"
}
export interface IKubrowPetDetailsDatabase {
Name?: string;
IsPuppy?: boolean;
HasCollar: boolean;
PrintsRemaining?: number;
PrintsRemaining: number;
Status: Status;
HatchDate?: Date;
DominantTraits: ITraits;
@ -779,7 +786,7 @@ export interface ILastSortieRewardDatabase extends Omit<ILastSortieRewardClient,
SortieId: Types.ObjectId;
}
export interface ISortieRewardAttenuation {
export interface IRewardAttenuation {
Tag: string;
Atten: number;
}
@ -1015,13 +1022,17 @@ export interface IPeriodicMissionCompletionResponse extends Omit<IPeriodicMissio
date: IMongoDate;
}
export interface IPersonalGoalProgress {
export interface IPersonalGoalProgressClient {
Best: number;
Count: number;
Tag: string;
Best?: number;
_id: IOid;
ReceivedClanReward0?: boolean;
ReceivedClanReward1?: boolean;
//ReceivedClanReward0?: boolean;
//ReceivedClanReward1?: boolean;
}
export interface IPersonalGoalProgressDatabase extends Omit<IPersonalGoalProgressClient, "_id"> {
goalId: Types.ObjectId;
}
export interface IPersonalTechProjectDatabase {

View File

@ -2,7 +2,7 @@ import { Types } from "mongoose";
export interface IAccountAndLoginResponseCommons {
DisplayName: string;
CountryCode: string;
CountryCode?: string;
ClientType?: string;
CrossPlatformAllowed?: boolean;
ForceLogoutVersion?: number;
@ -25,6 +25,7 @@ export interface IDatabaseAccount extends IDatabaseAccountRequiredFields {
LatestEventMessageDate: Date;
LastLoginRewardDate: number;
LoginDays: number;
DailyFirstWinDate: number;
}
// Includes virtual ID

View File

@ -17,9 +17,9 @@ export interface IMissionReward {
}
export interface IMissionCredits {
MissionCredits: number[];
CreditBonus: number[];
TotalCredits: number[];
MissionCredits: [number, number];
CreditsBonus: [number, number]; // "Credit Reward"; `CreditsBonus[1]` is `CreditsBonus[0] * 2` if DailyMissionBonus
TotalCredits: [number, number];
DailyMissionBonus?: boolean;
}

View File

@ -7,17 +7,46 @@ import {
ITypeCount,
IRecentVendorPurchaseClient,
TEquipmentKey,
ICrewMemberClient
ICrewMemberClient,
IKubrowPetPrintClient
} from "./inventoryTypes/inventoryTypes";
export enum PurchaseSource {
Market = 0,
VoidTrader = 1,
SyndicateFavor = 2,
DailyDeal = 3,
Arsenal = 4,
Profile = 5,
Hub = 6,
Vendor = 7,
AppearancePreview = 8,
Museum = 9,
Operator = 10,
PlayerShip = 11,
Crewship = 12,
MenuStyle = 13,
MenuHud = 14,
Chat = 15,
Inventory = 16,
StarChart = 17,
PrimeVaultTrader = 18,
Incubator = 19,
Prompt = 20,
Kaithe = 21,
DuviriWeapon = 22,
UpdateScreen = 23,
Motorcycle = 24
}
export interface IPurchaseRequest {
PurchaseParams: IPurchaseParams;
buildLabel: string;
}
export interface IPurchaseParams {
Source: number;
SourceId?: string; // for Source 1, 7 & 18
Source: PurchaseSource;
SourceId?: string; // VoidTrader, Vendor, PrimeVaultTrader
StoreItem: string;
StorePage: string;
SearchTerm: string;
@ -25,10 +54,10 @@ export interface IPurchaseParams {
Quantity: number;
UsePremium: boolean;
ExpectedPrice: number;
SyndicateTag?: string; // for Source 2
UseFreeFavor?: boolean; // for Source 2
ExtraPurchaseInfoJson?: string; // for Source 7
IsWeekly?: boolean; // for Source 7
SyndicateTag?: string; // SyndicateFavor
UseFreeFavor?: boolean; // SyndicateFavor
ExtraPurchaseInfoJson?: string; // Vendor
IsWeekly?: boolean; // Vendor
}
export type IInventoryChanges = {
@ -50,6 +79,7 @@ export type IInventoryChanges = {
NewVendorPurchase?: IRecentVendorPurchaseClient; // >= 38.5.0
RecentVendorPurchases?: IRecentVendorPurchaseClient; // < 38.5.0
CrewMembers?: ICrewMemberClient[];
KubrowPetPrints?: IKubrowPetPrintClient[];
} & Record<
Exclude<
string,
@ -77,6 +107,7 @@ export interface IPurchaseResponse {
Standing?: IAffiliationMods[];
FreeFavorsUsed?: IAffiliationMods[];
BoosterPackItems?: string;
DailyDealUsed?: string;
}
export type IBinChanges = {

View File

@ -139,6 +139,14 @@ export type IMissionInventoryUpdateRequest = {
};
wagerTier?: number; // the index
creditsFee?: number; // the index
GoalProgress?: {
_id: IOid;
Count: number;
Best: number;
Tag: string;
IsMultiProgress: boolean;
MultiProgress: unknown[];
}[];
InvasionProgress?: IInvasionProgressClient[];
ConquestMissionsCompleted?: number;
duviriSuitSelection?: string;
@ -156,6 +164,8 @@ export type IMissionInventoryUpdateRequest = {
export interface IRewardInfo {
node: string;
goalId?: string;
goalManifest?: string;
invasionId?: string;
invasionAllyFaction?: "FC_GRINEER" | "FC_CORPUS";
sortieId?: string;

View File

@ -96,6 +96,7 @@ export interface IPlacedDecosDatabase {
Pos: [number, number, number];
Rot: [number, number, number];
Scale?: number;
Sockets?: number;
PictureFrameInfo?: IPictureFrameInfo;
_id: Types.ObjectId;
}
@ -136,6 +137,7 @@ export interface IShipDecorationsRequest {
MoveId?: string;
OldRoom?: string;
Scale?: number;
Sockets?: number;
}
export interface IShipDecorationsResponse {
@ -152,7 +154,7 @@ export interface ISetPlacedDecoInfoRequest {
DecoId: string;
Room: string;
PictureFrameInfo: IPictureFrameInfo;
BootLocation?: string;
BootLocation?: TBootLocation;
ComponentId?: string;
GuildId?: string;
}

View File

@ -1,3 +1,4 @@
import { IMissionReward } from "warframe-public-export-plus";
import { IMongoDate, IOid } from "./commonTypes";
export interface IWorldState {
@ -9,10 +10,12 @@ export interface IWorldState {
Sorties: ISortie[];
LiteSorties: ILiteSortie[];
SyndicateMissions: ISyndicateMissionInfo[];
GlobalUpgrades: IGlobalUpgrade[];
ActiveMissions: IFissure[];
GlobalUpgrades: IGlobalUpgrade[];
NodeOverrides: INodeOverride[];
VoidTraders: IVoidTrader[];
VoidStorms: IVoidStorm[];
DailyDeals: IDailyDeal[];
PVPChallengeInstances: IPVPChallengeInstance[];
EndlessXpChoices: IEndlessXpChoice[];
SeasonInfo?: {
@ -36,11 +39,15 @@ export interface IGoal {
Goal: number;
Success: number;
Personal: boolean;
Bounty?: boolean;
ClampNodeScores?: boolean;
Desc: string;
ToolTip: string;
ToolTip?: string;
Icon: string;
Tag: string;
Node: string;
MissionKeyName?: string;
Reward?: IMissionReward;
}
export interface ISyndicateMissionInfo {
@ -86,6 +93,14 @@ export interface IFissure {
Hard?: boolean;
}
export interface IFissureDatabase {
Activation: Date;
Expiry: Date;
Node: string;
Modifier: "VoidT1" | "VoidT2" | "VoidT3" | "VoidT4" | "VoidT5" | "VoidT6";
Hard?: boolean;
}
export interface INodeOverride {
_id: IOid;
Activation?: IMongoDate;
@ -132,6 +147,22 @@ export interface ILiteSortie {
}[];
}
export interface IVoidTrader {
_id: IOid;
Activation: IMongoDate;
Expiry: IMongoDate;
Character: string;
Node: string;
Manifest: IVoidTraderOffer[];
}
export interface IVoidTraderOffer {
ItemType: string;
PrimePrice: number;
RegularPrice: number;
Limit?: number;
}
export interface IVoidStorm {
_id: IOid;
Node: string;
@ -140,6 +171,28 @@ export interface IVoidStorm {
ActiveMissionTier: string;
}
export interface IDailyDeal {
StoreItem: string;
Activation: IMongoDate;
Expiry: IMongoDate;
Discount: number;
OriginalPrice: number;
SalePrice: number;
AmountTotal: number;
AmountSold: number;
}
export interface IDailyDealDatabase {
StoreItem: string;
Activation: Date;
Expiry: Date;
Discount: number;
OriginalPrice: number;
SalePrice: number;
AmountTotal: number;
AmountSold: number;
}
export interface IPVPChallengeInstance {
_id: IOid;
challengeTypeRefID: string;

View File

@ -20,6 +20,10 @@
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyGoldARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophyPlatinumARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DojoRemasterTrophySilverARecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventClayTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventGoldTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/DuviriMurmurEventSilverTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBaseTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventBronzeTrophyRecipe",
"/Lotus/Levels/ClanDojo/ComponentPropRecipes/EntratiEventGoldTrophyRecipe",

View File

@ -45,5 +45,6 @@
"/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
"/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
"/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
"/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon",
"/Lotus/Weapons/Tenno/Zariman/Melee/HeavyScythe/ZarimanHeavyScythe/ZarimanHeavyScytheWeapon"
]

View File

@ -1,12 +0,0 @@
{
"Messages": [
{
"sub": "Welcome to Space Ninja Server",
"sndr": "/Lotus/Language/Bosses/Ordis",
"msg": "Enjoy your Space Ninja Experience",
"icon": "/Lotus/Interface/Icons/Npcs/Ordis.png",
"eventMessageDate": "2025-01-30T13:00:00.000Z",
"r": false
}
]
}

View File

@ -1,300 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e01c96976e97d6b802e"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/EntratiFragmentVendorProductsManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/SeriglassShard",
"ItemPrices": [
{
"ItemCount": 20,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8390"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosBouncy",
"ItemPrices": [
{
"ItemCount": 50,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8391"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosBreakthrough",
"ItemPrices": [
{
"ItemCount": 50,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8392"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosCatacombs",
"ItemPrices": [
{
"ItemCount": 50,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8393"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosDownfall",
"ItemPrices": [
{
"ItemCount": 50,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8394"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosObsession",
"ItemPrices": [
{
"ItemCount": 50,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8395"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDeimosTunnels",
"ItemPrices": [
{
"ItemCount": 50,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e8396"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/FatherTokenShipDeco",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA",
"ProductCategory": "MiscItems"
},
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e83f1"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/LisetPropEntratiLamp",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB",
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e83f2"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/LisetPropInfestedCrate",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonA",
"ItemCount": 11,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e83f3"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/LisetPropInfestedCystC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e83f4"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/RequiemRisGlyph",
"ItemPrices": [
{
"ItemCount": 15,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b10ba592c4c95e83f5"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Deimos/MotherTokenShipDeco",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB",
"ProductCategory": "MiscItems"
},
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentRareA",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e83f6"
}
}
],
"PropertyTextHash": "DB953EE163A65B3BCC0552902321D791",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,241 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e01c96976e97d6b8009"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestFishmonger",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishFPartItem",
"ItemCount": 6,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 16,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 63978959,
"Id": {
"$oid": "66fd60b10ba592c4c95e82cc"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishDPartItem",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3808064409,
"Id": {
"$oid": "66fd60b10ba592c4c95e82cd"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishEPartItem",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3849710569,
"Id": {
"$oid": "66fd60b10ba592c4c95e82d0"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1687111317,
"Id": {
"$oid": "66fd60b10ba592c4c95e82d1"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem",
"ItemCount": 4,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2267414276,
"Id": {
"$oid": "66fd60b10ba592c4c95e82d2"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem",
"ItemCount": 6,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem",
"ItemCount": 6,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1497494256,
"Id": {
"$oid": "66fd60b10ba592c4c95e82d3"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2883527039,
"Id": {
"$oid": "66fd60b10ba592c4c95e82d4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Daughter/DaughterTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishGPartItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 4116691539,
"Id": {
"$oid": "66fd60b10ba592c4c95e82d5"
}
}
],
"PropertyTextHash": "54B6992C6314367F8EEA74B7F1A1C352",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,287 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e03c96976e97d6b80a3"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestPetVendor",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotRare",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2707699975,
"Id": {
"$oid": "66fd60b10ba592c4c95e8897"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3610714639,
"Id": {
"$oid": "66fd60b10ba592c4c95e8898"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotRare",
"ItemCount": 1,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1782149988,
"Id": {
"$oid": "66fd60b10ba592c4c95e8899"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooCommon",
"ItemCount": 3,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2149416825,
"Id": {
"$oid": "66fd60b10ba592c4c95e889a"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterRare",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveUncommon",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 890863265,
"Id": {
"$oid": "66fd60b10ba592c4c95e889b"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterCommon",
"ItemCount": 4,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2507606934,
"Id": {
"$oid": "66fd60b10ba592c4c95e889c"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorRare",
"ItemCount": 5,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1037784729,
"Id": {
"$oid": "66fd60b10ba592c4c95e889e"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMaggotCommon",
"ItemCount": 4,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedCritterCommon",
"ItemCount": 4,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2048707501,
"Id": {
"$oid": "66fd60b10ba592c4c95e889f"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedKdriveRare",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 4038149313,
"Id": {
"$oid": "66fd60b10ba592c4c95e88a0"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Son/SonTaskD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedMergooCommon",
"ItemCount": 3,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/AnimalTagInfestedPredatorRare",
"ItemCount": 3,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2155290001,
"Id": {
"$oid": "66fd60b10ba592c4c95e88a1"
}
}
],
"PropertyTextHash": "61E66B4E9E5A121DD06A476AE2A81B24",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,312 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e01c96976e97d6b7ff1"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestProspector",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemBItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1370972414,
"Id": {
"$oid": "66fd60b20ba592c4c95e8ef8"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreBItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonOreAItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2361790143,
"Id": {
"$oid": "66fd60b20ba592c4c95e8ef9"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem",
"ItemCount": 6,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 472210739,
"Id": {
"$oid": "66fd60b20ba592c4c95e8efb"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreBItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 15,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3072462886,
"Id": {
"$oid": "66fd60b20ba592c4c95e8efd"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 767765909,
"Id": {
"$oid": "66fd60b20ba592c4c95e8efe"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosEidolonGemAItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosEidolonGemBItem",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosRareOreAItem",
"ItemCount": 22,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 512512880,
"Id": {
"$oid": "66fd60b20ba592c4c95e8eff"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem",
"ItemCount": 6,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2037734419,
"Id": {
"$oid": "66fd60b20ba592c4c95e8f00"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonOreAItem",
"ItemCount": 13,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1433662587,
"Id": {
"$oid": "66fd60b20ba592c4c95e8f01"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonOreAItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1618492734,
"Id": {
"$oid": "66fd60b20ba592c4c95e8f02"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Otak/OtakTaskD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem",
"ItemCount": 7,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemBItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosRareOreAItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 4032699594,
"Id": {
"$oid": "66fd60b20ba592c4c95e8f03"
}
}
],
"PropertyTextHash": "0AC3C284471037011B36EC51238D13A9",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,223 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e03c96976e97d6b80d2"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestTokenVendor",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonC",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonA",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1415858946,
"Id": {
"$oid": "670a47b1872b2325705e746c"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonA",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3178511462,
"Id": {
"$oid": "670a47b1872b2325705e746e"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonA",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonC",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3313207881,
"Id": {
"$oid": "670a47b1872b2325705e7471"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2241288767,
"Id": {
"$oid": "670a47b1872b2325705e7472"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3395082536,
"Id": {
"$oid": "670a47b1872b2325705e7473"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonC",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 493457277,
"Id": {
"$oid": "670a47b1872b2325705e7474"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Grandmother/GrandmotherTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentUncommonB",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Deimos/EntratiFragmentCommonB",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 4225814786,
"Id": {
"$oid": "670a47b1872b2325705e7475"
}
}
],
"PropertyTextHash": "58884EC7ECE7D22AD4BD9E9B436C37A8",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,254 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e02c96976e97d6b8049"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindCommisionsManifestWeaponsmith",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 16,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2438288725,
"Id": {
"$oid": "66fd60b00ba592c4c95e7caf"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Game/FishBait/Infested/OrokinFishBaitA",
"ItemCount": 6,
"ProductCategory": "Consumables"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem",
"ItemCount": 21,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Game/FishBait/Infested/InfestedFishBaitA",
"ItemCount": 6,
"ProductCategory": "Consumables"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2431016296,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb2"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem",
"ItemCount": 16,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 18484856,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb3"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem",
"ItemCount": 14,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2278976516,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Game/FishBait/Infested/OrokinFishBaitA",
"ItemCount": 7,
"ProductCategory": "Consumables"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem",
"ItemCount": 25,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Game/FishBait/Infested/InfestedFishBaitA",
"ItemCount": 6,
"ProductCategory": "Consumables"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3150323898,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb5"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfMapricoFruitItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 3971758486,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb6"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 17,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem",
"ItemCount": 18,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2512835718,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb7"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 825411410,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb8"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Father/FatherTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 22,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2383349671,
"Id": {
"$oid": "66fd60b00ba592c4c95e7cb9"
}
}
],
"PropertyTextHash": "CE9413585756FA39B793A9814E74E49F",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,286 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5fb70313c96976e97d6be6fe"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/HivemindTokenVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedySonB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"ItemCount": 36,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishEPartItem",
"ItemCount": 36,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 379215713,
"Id": {
"$oid": "66fd60b20ba592c4c95e9308"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyMotherB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericInfestedFishPartItem",
"ItemCount": 80,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 32,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 28,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2534781881,
"Id": {
"$oid": "66fd60b20ba592c4c95e9309"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyDaughterB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem",
"ItemCount": 28,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 32,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishAPartItem",
"ItemCount": 32,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 1507786123,
"Id": {
"$oid": "66fd60b20ba592c4c95e930a"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedySonA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosRareGemAItem",
"ItemCount": 15,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem",
"ItemCount": 30,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 21,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 616241643,
"Id": {
"$oid": "66fd60b20ba592c4c95e930b"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyOtakA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"ItemCount": 21,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishCPartItem",
"ItemCount": 27,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishEPartItem",
"ItemCount": 27,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2888479655,
"Id": {
"$oid": "66fd60b20ba592c4c95e930c"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyGrandmotherA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemBItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"ItemCount": 28,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 24,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosCommonGemAItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2874726481,
"Id": {
"$oid": "66fd60b20ba592c4c95e930d"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyFatherA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem",
"ItemCount": 75,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Deimos/DeimosUncommonGemAItem",
"ItemCount": 27,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/OrbStoneItem",
"ItemCount": 30,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 274676857,
"Id": {
"$oid": "66fd60b20ba592c4c95e930e"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/Deimos/Remedies/RemedyDaughterA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/InfestedMicroplanet/Resources/InfGorgaricusSeedItem",
"ItemCount": 24,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosInfestedFishBPartItem",
"ItemCount": 30,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Deimos/FishParts/DeimosGenericSharedFishPartItem",
"ItemCount": 51,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"LocTagRandSeed": 2487943761,
"Id": {
"$oid": "66fd60b20ba592c4c95e930f"
}
}
],
"PropertyTextHash": "C34BF0BEDEAF7CBB0EEBFFECDFD6646D",
"RandomSeedType": "VRST_FLAVOUR_TEXT",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,136 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5f456e02c96976e97d6b8080"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Deimos/PetVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedNexiferaRare",
"PremiumPrice": [35, 35],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89f6"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedNexiferaUncommon",
"PremiumPrice": [22, 22],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89f7"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedMergooUncommon",
"PremiumPrice": [28, 28],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89f8"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedKdriveUncommon",
"PremiumPrice": [25, 25],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89f9"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedZongroCommon",
"PremiumPrice": [14, 14],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89fa"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedPredatorCommon",
"PremiumPrice": [12, 12],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89fb"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedMergooCommon",
"PremiumPrice": [13, 13],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89fc"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/Deimos/AnimalTagInfestedKdriveCommon",
"PremiumPrice": [14, 14],
"Bin": "BIN_0",
"QuantityMultiplier": 5,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b10ba592c4c95e89fd"
}
}
],
"PropertyTextHash": "F14C6B6A61D7585A10537995661F5220",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,321 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "64493ca759e9b164c86a2e14"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Duviri/AcrithisVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/DangerRoom/DangerRoomTileDuviriDragonArena",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_5",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b00ba592c4c95e7d88"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/Components/FormaBlueprint",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b00ba592c4c95e7deb"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/UtilityUnlocker",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b00ba592c4c95e7dec"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 5000,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b00ba592c4c95e7ded"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/WeaponUtilityUnlocker",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b00ba592c4c95e7dee"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Kuva",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 5000,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "670c5e12576f461f1e5e739c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Plastids",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemD",
"ItemCount": 40,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"Id": {
"$oid": "6710c312fa0b2c5cd85e73c3"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Duviri/DUVxPlanterHangingPot",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemE",
"ItemCount": 51,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6710c312fa0b2c5cd85e73c6"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Duviri/DUVxPlanterPotB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriRockItem",
"ItemCount": 44,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6710c312fa0b2c5cd85e73c7"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/NeuralSensor",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemA",
"ItemCount": 52,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 3,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"Id": {
"$oid": "6710c312fa0b2c5cd85e73c8"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/ControlModule",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemF",
"ItemCount": 42,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 3,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"Id": {
"$oid": "6710c312fa0b2c5cd85e73c9"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileDuviriArenaOpera",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriProcessedItem",
"ItemCount": 240,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b00ba592c4c95e7ddd"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Utility/HealthWhileUsingChanneledAbilities",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriDragonDropItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b00ba592c4c95e7e01"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Boons/DuviriVendorBoonItem",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItemG",
"ItemCount": 50,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_4",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": true,
"Id": {
"$oid": "6711a412ba1ba01e405e739c"
}
}
],
"PropertyTextHash": "9EE40048EB685549ACA3D01AB1F65BF2",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,245 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "6579d82b553a20c6fc0067ca"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalAmar",
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"RotatedWeekly": true,
"Affiliation": "EntratiLabSyndicate",
"MinAffiliationRank": 5,
"ReductionPerPositiveRank": 0,
"IncreasePerNegativeRank": 0,
"StandingCost": 30000,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e920d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalBoreal",
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"RotatedWeekly": true,
"Affiliation": "EntratiLabSyndicate",
"MinAffiliationRank": 5,
"ReductionPerPositiveRank": 0,
"IncreasePerNegativeRank": 0,
"StandingCost": 30000,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e920e"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Gameplay/NarmerSorties/ArchonCrystalNira",
"Bin": "BIN_3",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"RotatedWeekly": true,
"Affiliation": "EntratiLabSyndicate",
"MinAffiliationRank": 5,
"ReductionPerPositiveRank": 0,
"IncreasePerNegativeRank": 0,
"StandingCost": 30000,
"AllowMultipurchase": false,
"Id": {
"$oid": "66fd60b20ba592c4c95e920f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabStool",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagUncommon",
"ItemCount": 2,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA",
"ItemCount": 22,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 18,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9270"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabChairA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 15,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA",
"ItemCount": 19,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemB",
"ItemCount": 19,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9271"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightWallCandleA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 12,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagCommon",
"ItemCount": 3,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9272"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightChandelierD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemB",
"ItemCount": 8,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagCommon",
"ItemCount": 3,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9273"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightChandelierB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA",
"ItemCount": 12,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabDogTagCommon",
"ItemCount": 2,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9274"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/EntratiLabs/ORKxLabLightChandelierA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 15,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA",
"ItemCount": 13,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9275"
}
}
],
"PropertyTextHash": "44DA3839E6F7BDB32ACED53F2B0BE14E",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,97 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "6579d82b553a20c6fc0067ae"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/EntratiLabs/EntratiLabsCommisionsManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/EntratiLabs/LoidTaskC",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemB",
"ItemCount": 17,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 30,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_2",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "670a2b928ac7854ac55e73d3"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/EntratiLabs/LoidTaskB",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/EntratiLabMiscItemA",
"ItemCount": 228,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "670a2b928ac7854ac55e73d4"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/Tasks/EntratiLabs/LoidTaskA",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/EntratiLab/Resources/MurmurItem",
"ItemCount": 15,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/Zariman/Resources/ZarimanMiscItemB",
"ItemCount": 1,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "670a2b928ac7854ac55e73d5"
}
}
],
"PropertyTextHash": "60C4D85A8DE5E6538AD23CDDFEEF0422",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,301 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "598a090d9a4a313746fd1f24"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/MaskSalesmanManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/RevenantMask",
"ItemPrices": [
{
"ItemCount": 1,
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "63ed01ef4c37f93d0b797674"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumper",
"ItemPrices": [
{
"ItemCount": 2,
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem",
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem",
"ItemCount": 32,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "63ed01ef4c37f93d0b797675"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperMedium",
"ItemPrices": [
{
"ItemCount": 4,
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem",
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemBCutAItem",
"ItemCount": 24,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem",
"ItemCount": 18,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem",
"ItemCount": 27,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "63ed01ef4c37f93d0b797676"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyThumperLarge",
"ItemPrices": [
{
"ItemCount": 6,
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/CetusWispItem",
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonGemACutAItem",
"ItemCount": 35,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishAPartItem",
"ItemCount": 95,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/NistlebrushItem",
"ItemCount": 60,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "63ed01ef4c37f93d0b797677"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareBlueBlueprint",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishBPartItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishCPartItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6651291214e90115b91b50a1"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Recipes/SynthicatorRecipes/FlareRedBlueprint",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem",
"ItemCount": 37,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothUncommonFishAPartItem",
"ItemCount": 7,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6651291214e90115b91b50a2"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/VoltMask",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreBAlloyBItem",
"ItemCount": 34,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem",
"ItemCount": 17,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6651291214e90115b91b50a3"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/MagMask",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem",
"ItemCount": 16,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/ForestRodentPartItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6651291214e90115b91b50a4"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/ExcaliburMask",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/BirdOfPreyPartItem",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Gameplay/Eidolon/Resources/GrokdrulItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6651291214e90115b91b50a5"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/Skins/Ostron/GrineerMask",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem",
"ItemCount": 20,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreAAlloyAItem",
"ItemCount": 31,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6651291214e90115b91b50a6"
}
}
],
"PropertyTextHash": "6AACA376DA34B35B5C16F1B40DBC017D",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,188 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "67dadc30e4b6e0e5979c8d6a"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/TheHex/Nova1999ConquestShopManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedea",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67db32b983b2ad79a9c1c18c"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFree",
"ItemPrices": [
{
"ItemCount": 1,
"ItemType": "/Lotus/Types/Items/MiscItems/1999FreeStickersPack",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67db32b983b2ad79a9c1c18d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/BoosterPacks/1999StickersPackEchoesArchimedeaFixed",
"ItemPrices": [
{
"ItemCount": 1,
"ItemType": "/Lotus/Types/Items/MiscItems/1999FixedStickersPack",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67db32b983b2ad79a9c1c18e"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/SyndicateVosforPack",
"ItemPrices": [
{
"ItemCount": 6,
"ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67db32b983b2ad79a9c1c18f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/StickerPictureFrame",
"ItemPrices": [
{
"ItemCount": 10,
"ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "67db32b983b2ad79a9c1c190"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Utility/AbilityRadiationProcsCreateUniversalOrbsOnKill",
"ItemPrices": [
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "67db32b983b2ad79a9c1c191"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Offensive/AbilityHeatProcsGiveCritChance",
"ItemPrices": [
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "67db32b983b2ad79a9c1c192"
}
},
{
"StoreItem": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Defensive/InvulnerabilityOnDeathOnMercyKill",
"ItemPrices": [
{
"ItemCount": 5,
"ItemType": "/Lotus/Types/Items/MiscItems/1999ConquestBucks",
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
},
"PurchaseQuantityLimit": 1,
"AllowMultipurchase": false,
"Id": {
"$oid": "67db32b983b2ad79a9c1c193"
}
}
],
"PropertyTextHash": "CB7D0E807FD5E2BCD059195201D963B9",
"RequiredGoalTag": "",
"Expiry": {
"$date": {
"$numberLong": "2051240400000"
}
}
}
}

View File

@ -1,94 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5991d5e6bcc718474ee90c15"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Ostron/PetVendorManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropOstBirdCage",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/UncommonOreAAlloyAItem",
"ItemCount": 10,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayUncommonFishBPartItem",
"ItemCount": 8,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9a8e"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/KubrowColorPackDrahk",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/DayCommonFishBPartItem",
"ItemCount": 14,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishBPartItem",
"ItemCount": 13,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9a8f"
}
},
{
"StoreItem": "/Lotus/Types/StoreItems/Packages/KubrowColorPackFeral",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Fish/Eidolon/FishParts/BothCommonFishAPartItem",
"ItemCount": 19,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Gems/Eidolon/CommonOreBAlloyBItem",
"ItemCount": 34,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "66fd60b20ba592c4c95e9a90"
}
}
],
"PropertyTextHash": "3D85F1A0A2B62734AE90370DEC214C26",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -1,126 +0,0 @@
{
"VendorInfo": {
"_id": {
"$oid": "5be4a159b144f3cdf1c22edf"
},
"TypeName": "/Lotus/Types/Game/VendorManifests/Solaris/DebtTokenVendorRepossessionsManifest",
"ItemManifest": [
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUToolBox",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Solaris/DebtTokenB",
"ItemCount": 6,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6711a412ba1ba01e405e739d"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUBookAOpen",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Solaris/DebtTokenC",
"ItemCount": 6,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6711a412ba1ba01e405e739e"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUFoodCans",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Solaris/DebtTokenC",
"ItemCount": 7,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6711a412ba1ba01e405e739f"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUTechToolD",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Solaris/DebtTokenD",
"ItemCount": 5,
"ProductCategory": "MiscItems"
},
{
"ItemType": "/Lotus/Types/Items/Solaris/DebtTokenA",
"ItemCount": 15,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_1",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6711a412ba1ba01e405e73a0"
}
},
{
"StoreItem": "/Lotus/StoreItems/Types/Items/ShipDecos/Venus/SUContainerCrate",
"ItemPrices": [
{
"ItemType": "/Lotus/Types/Items/Solaris/DebtTokenA",
"ItemCount": 9,
"ProductCategory": "MiscItems"
}
],
"Bin": "BIN_0",
"QuantityMultiplier": 1,
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
},
"AllowMultipurchase": true,
"Id": {
"$oid": "6711a412ba1ba01e405e73a1"
}
}
],
"PropertyTextHash": "E0E83157D73468DC578403CB9EBA9DA6",
"Expiry": {
"$date": {
"$numberLong": "9999999000000"
}
}
}
}

View File

@ -0,0 +1,414 @@
{
"evergreen": [
{ "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Types/BoosterPacks/BaroTreasureBox", "PrimePrice": 0, "RegularPrice": 50000, "Limit": 1 }
],
"armorSets": [
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourA", "PrimePrice": 350, "RegularPrice": 110000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourC", "PrimePrice": 150, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmour/BaroArmourL", "PrimePrice": 300, "RegularPrice": 150000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoA", "PrimePrice": 310, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoC", "PrimePrice": 175, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourTwo/BaroArmourTwoL", "PrimePrice": 225, "RegularPrice": 150000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeA", "PrimePrice": 400, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeC", "PrimePrice": 350, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/BaroArmourThree/BaroArmourThreeL", "PrimePrice": 400, "RegularPrice": 350000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisAArmor", "PrimePrice": 300, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisCArmor", "PrimePrice": 250, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/FurisArmor/PrismaFurisLArmor", "PrimePrice": 225, "RegularPrice": 175000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesArmArmor", "PrimePrice": 350, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesChestArmor", "PrimePrice": 300, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/GrineerTurbines/WraithTurbinesLegArmor", "PrimePrice": 350, "RegularPrice": 150000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorA", "PrimePrice": 315, "RegularPrice": 215000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorC", "PrimePrice": 325, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/NecraArmor/NecraArmorL", "PrimePrice": 300, "RegularPrice": 200000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeArmLeftArmor", "PrimePrice": 65, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeArmRightArmor", "PrimePrice": 65, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeChestArmor", "PrimePrice": 150, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeLegLeftArmor", "PrimePrice": 65, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetThreeWinged/VTSetThreeLegRightArmor", "PrimePrice": 65, "RegularPrice": 75000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoArmLeftArmor", "PrimePrice": 100, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoArmRightArmor", "PrimePrice": 100, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoChestArmor", "PrimePrice": 225, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoLegLeftArmor", "PrimePrice": 100, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/SetTwoSamurai/VTSetTwoLegRightArmor", "PrimePrice": 100, "RegularPrice": 55000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronArmArmorElixis", "PrimePrice": 325, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronArmArmorPrisma", "PrimePrice": 325, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronChestArmorElixis", "PrimePrice": 275, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronChestArmorPrisma", "PrimePrice": 275, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronLegArmorElixis", "PrimePrice": 300, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnLatronArmor/TnLatronLegArmorPrisma", "PrimePrice": 300, "RegularPrice": 175000 }
],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorA", "PrimePrice": 315, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorC", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/TnShinaiArmor/TnShinaiArmorL", "PrimePrice": 275, "RegularPrice": 115000 }
],
[
[{ "ItemType": "/Lotus/Types/StoreItems/Packages/VTEosArmourBundle", "PrimePrice": 285, "RegularPrice": 260000 }],
[
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosALArmor", "PrimePrice": 50, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosARArmor", "PrimePrice": 50, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosChestArmor", "PrimePrice": 125, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosLLArmor", "PrimePrice": 65, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/VTEos/VTEosLRArmor", "PrimePrice": 65, "RegularPrice": 50000 }
]
]
],
"rest": [
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Armor/Halloween2014Wings/PrismaNaberusArmArmor", "PrimePrice": 220, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/TennoCon2024GlyphAlt", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/Tennocon2024EmoteAlt", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/HeartOfDeimosAlbumCoverPoster", "PrimePrice": 80, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConC", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConJ", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConH", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T3VoidProjectionVoltOdonataPrimeBronze", "PrimePrice": 125, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionVoltOdonataPrimeBronze", "PrimePrice": 125, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionMagNovaVaultBBronze", "PrimePrice": 125, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/SolsticeNelumboCape", "PrimePrice": 325, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerSolsticeTwinGrakatas", "PrimePrice": 300, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/Staff/TnRibbonStaffSkin", "PrimePrice": 350, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GunBlade/GrnGunBlade/GrnGunblade", "PrimePrice": 550, "RegularPrice": 325000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpBFG/Vandal/VandalCrpBFG", "PrimePrice": 650, "RegularPrice": 550000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Event/AmbulasEvent/Expert/SecondaryExplosionRadiusModExpert", "PrimePrice": 350, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/Dragon2024BadgeItem", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/PrimedArchwingDamageOnReloadMod", "PrimePrice": 375, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/PrimedArchwingRifleFireIterationsMod", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaruukDoanStyle", "PrimePrice": 75, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OctaviaBobbleHead", "PrimePrice": 50, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Skins/GaussSentinelSkin", "PrimePrice": 500, "RegularPrice": 425000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusVinesSigil", "PrimePrice": 55, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageExcaliburActionProto", "PrimePrice": 75, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageIvaraAction", "PrimePrice": 75, "RegularPrice": 60000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/HornSkullScarf", "PrimePrice": 325, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/RhinoDeluxeSigil", "PrimePrice": 45, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Events/InfQuantaInfestedAladV", "PrimePrice": 325, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterD", "PrimePrice": 90, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterB", "PrimePrice": 90, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterC", "PrimePrice": 90, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/PrimedWeaponElectricityDamageMod", "PrimePrice": 350, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarShieldMaxModExpert", "PrimePrice": 350, "RegularPrice": 225000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/JavisExperimentsPosterA", "PrimePrice": 90, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/EventSigilScarletSpear", "PrimePrice": 45, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnOrokinRifle/GrnOrokinRifleWeapon", "PrimePrice": 675, "RegularPrice": 625000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisNikana", "PrimePrice": 375, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/GaussSentinelWings", "PrimePrice": 400, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/GaussSentinelTail", "PrimePrice": 400, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/GaussSentinelMask", "PrimePrice": 450, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerCutter", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/CNY2023EmblemItem", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/WeGameNewYearFreeTigerSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/CNY2022EmblemItem", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Leverian/IvaraLeverianPovisRecordsDecoration", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodDuviriOperator", "PrimePrice": 550, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Melee/CrpTonfa/CrpPrismaTonfa", "PrimePrice": 450, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneDuviri", "PrimePrice": 800, "RegularPrice": 650000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/AshLevarianTiara", "PrimePrice": 550, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/BaroEphemeraB", "PrimePrice": 250, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Promo/Warframe/PromoParis", "PrimePrice": 315, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/ThraxSigil", "PrimePrice": 50, "RegularPrice": 55000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/PrismaLenz/PrismaLenzWeapon", "PrimePrice": 575, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Vignettes/Warframes/ArchwingAFItem", "PrimePrice": 100, "RegularPrice": 330000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/QuartersWallpapers/LavosAlchemistWallpaper", "PrimePrice": 275, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GrendelOrokinDishSet", "PrimePrice": 110, "RegularPrice": 130000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerKiteerItemB", "PrimePrice": 200, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/NezhaEtchingsTablets", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GaussTowerOfAltraDeco", "PrimePrice": 110, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroPlanter", "PrimePrice": 125, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroPedestal", "PrimePrice": 150, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Leggings/LeggingsNovaEngineer", "PrimePrice": 300, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/BodySuits/BodySuitNovaEngineer", "PrimePrice": 300, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Sleeves/SleevesNovaEngineer", "PrimePrice": 300, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodNovaEngineer", "PrimePrice": 350, "RegularPrice": 375000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BekranZaftBucketBroom", "PrimePrice": 100, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/Warfan/TnMoonWarfan/MoonWarfanWeapon", "PrimePrice": 410, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/MoonWarfanSugatraMeleeDangle", "PrimePrice": 250, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OstronHeadStatue", "PrimePrice": 125, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/DomsFinalDrink", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Wisp/WispAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Pacifist/BaruukImmortalSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ErraBobbleHead", "PrimePrice": 75, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OwlOrdisStatue", "PrimePrice": 350, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TNWVesoBobbleHead", "PrimePrice": 75, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TNWTeshinBobbleHead", "PrimePrice": 75, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/CosmeticEnhancers/Peculiars/EvilSpiritMod", "PrimePrice": 250, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape3Scarf", "PrimePrice": 500, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisTiberon", "PrimePrice": 315, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/LotusFlowers", "PrimePrice": 250, "RegularPrice": 450000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/UmbraPedestal", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Dragon/ChromaAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerBaroB", "PrimePrice": 75, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisLatronPistol", "PrimePrice": 400, "RegularPrice": 215000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveOnKillBuffSecondary", "PrimePrice": 300, "RegularPrice": 115000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConA", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveSecondaryHeadshotKillMod", "PrimePrice": 300, "RegularPrice": 115000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConD", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConB", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponIncreaseRadialExplosionModExpert", "PrimePrice": 350, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Archwing/Primary/ArchwingHeavyPistols/Prisma/PrismaArchHeavyPistols", "PrimePrice": 525, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/TwinSnakesGlyph", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConF", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConE", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponGlaiveOnSixKillsBuffSecondary", "PrimePrice": 300, "RegularPrice": 115000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/WeGameNewYearOxSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConG", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponFreezeDamageModExpert", "PrimePrice": 350, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/GarvLatroxPoster", "PrimePrice": 80, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrnBoomerang/HalikarWraithWeapon", "PrimePrice": 450, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/TarotCardTennoConI", "PrimePrice": 75, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponReloadSpeedModExpert", "PrimePrice": 300, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/PrismaMachete", "PrimePrice": 400, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MoaPet/BaroMoaPetSkin", "PrimePrice": 500, "RegularPrice": 325000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Deimos/PlushySunMonsterCommon", "PrimePrice": 150, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/Deimos/PlushyMoonMonsterCommon", "PrimePrice": 150, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponClipMaxModExpert", "PrimePrice": 280, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponClipMaxModExpert", "PrimePrice": 280, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/TnShinaiSword/TnShinaiSwordSkin", "PrimePrice": 375, "RegularPrice": 280000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Weapons/DualSword/DualRibbonKamasSkin", "PrimePrice": 350, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Infestation/NidusAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/ActionFigureDioramas/EmpyreanRegionADiorama", "PrimePrice": 155, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerFlak", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropGrineerTaktis", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/AshLeverianLiosPistol", "PrimePrice": 400, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Glass/GaraAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponSnipersConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/EraHypnosisPoster", "PrimePrice": 100, "RegularPrice": 110000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/NezhaLeverianCape", "PrimePrice": 400, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Leverian/NezhaLeverian/NezhaLeverianPolearm", "PrimePrice": 350, "RegularPrice": 325000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BoredTennoPoster", "PrimePrice": 90, "RegularPrice": 120000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusBasilisk", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusWeaver", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCorpusHarpi", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Archwing/GrendelArchwingSkin", "PrimePrice": 400, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/JaviExecutionHood", "PrimePrice": 450, "RegularPrice": 450000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/ElectEventMeleeMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/DualStat/FireEventMeleeMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/ClawCmbTwoMeleeTree", "PrimePrice": 385, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/FireEventRifleMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/AxeCmbThreeMeleeTree", "PrimePrice": 385, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/BowMultiShotOnHitMod", "PrimePrice": 300, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/ElectEventShotgunMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/FireEventPistolMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/DualStat/FireEventShotgunMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/WeaponEventPistolImpactDamageMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/PrimedWeaponCritDamageMod", "PrimePrice": 400, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageInfestedExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageGrineerExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageCorruptedExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeFactionDamageCorpusExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponClipMaxModExpert", "PrimePrice": 280, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Rifle/Expert/ArchwingRifleDamageAmountModExpert", "PrimePrice": 350, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponRifleConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Types/Sentinels/SentinelPrecepts/PrimedRegen", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeRangeIncModExpert", "PrimePrice": 300, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponCritDamageModExpert", "PrimePrice": 280, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponReloadSpeedModExpert", "PrimePrice": 375, "RegularPrice": 120000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponMeleeDamageModExpert", "PrimePrice": 385, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponDamageAmountModExpert", "PrimePrice": 300, "RegularPrice": 110000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponCritChanceModBeginnerExpert", "PrimePrice": 400, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolConvertAmmoModExpert", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Sentinel/Kubrow/Expert/KubrowPackLeaderExpertMod", "PrimePrice": 300, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Archwing/Expert/ArchwingSuitAbilityStrengthModExpert", "PrimePrice": 350, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponImpactDamageModExpert", "PrimePrice": 350, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponFireDamageModExpert", "PrimePrice": 350, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarPowerMaxModExpert", "PrimePrice": 350, "RegularPrice": 110000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/Expert/WeaponToxinDamageModExpert", "PrimePrice": 350, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponReloadSpeedModExpert", "PrimePrice": 375, "RegularPrice": 120000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageInfestedExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageGrineerExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageCorruptedExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/Expert/WeaponPistolFactionDamageCorpusExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/WeaponFreezeDamageModExpert", "PrimePrice": 350, "RegularPrice": 110000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Warframe/Expert/AvatarAbilityDurationModExpert", "PrimePrice": 350, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageInfestedExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageGrineerExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageCorruptedExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponShotgunFactionDamageCorpusExpert", "PrimePrice": 350, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/Expert/WeaponElectricityDamageModExpert", "PrimePrice": 350, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageInfested", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageGrineer", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageCorrupted", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/Expert/PrimedWeaponFactionDamageCorpus", "PrimePrice": 400, "RegularPrice": 140000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Sentinel/SentinelLootRadarEnemyRadarExpertMod", "PrimePrice": 300, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Melee/MeleeTrees/GlaiveCmbTwoMeleeTree", "PrimePrice": 385, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Melee/WeaponEventMeleeImpactDamageMod", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/WeaponEventRifleImpactDamageMod", "PrimePrice": 330, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Shotgun/WeaponEventShotgunImpactDamageMod", "PrimePrice": 365, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/DualStat/ElectEventRifleMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/DualStat/ElectEventPistolMod", "PrimePrice": 300, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Pistol/WeaponEventSlashDamageMod", "PrimePrice": 375, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/VTDetron", "PrimePrice": 500, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpFreezeRay/Vandal/CrpFreezeRayVandalRifle", "PrimePrice": 475, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/ClanTech/Chemical/FlameThrowerWraith", "PrimePrice": 550, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/WraithMacheteWeapon", "PrimePrice": 410, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CrpHandRL/PrismaAngstrum", "PrimePrice": 475, "RegularPrice": 210000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/PrismaDualCleavers", "PrimePrice": 490, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/VoidTraderGorgon/VTGorgon", "PrimePrice": 600, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/PrismaGrakata", "PrimePrice": 610, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrineerLeverActionRifle/PrismaGrinlokWeapon", "PrimePrice": 500, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/PrismaObex", "PrimePrice": 500, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/VoidTrader/PrismaSkana", "PrimePrice": 510, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CorpusUMP/PrismaCorpusUMP", "PrimePrice": 400, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Pistols/GrineerBulbousSMG/Prisma/PrismaTwinGremlinsWeapon", "PrimePrice": 500, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Archwing/Melee/VoidTraderArchsword/VTArchSwordWeapon", "PrimePrice": 550, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/ClanTech/Energy/VandalElectroProd", "PrimePrice": 410, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/CrpShockRifle/QuantaVandal", "PrimePrice": 450, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Corpus/LongGuns/Machinegun/SupraVandal", "PrimePrice": 500, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/Pistols/WraithSingleViper/WraithSingleViper", "PrimePrice": 400, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrineerSniperRifle/VulkarWraith", "PrimePrice": 450, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Weapons/Tenno/Pistols/ConclaveLeverPistol/ConclaveLeverPistol", "PrimePrice": 500, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/FireMeleeDangle", "PrimePrice": 100, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroInarosPolearmSkin", "PrimePrice": 325, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/BaroInarosMeleeDangle", "PrimePrice": 250, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/InfestedMeleeDangle", "PrimePrice": 250, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTHalloweenDarkSword", "PrimePrice": 320, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerSolsticeGorgon", "PrimePrice": 300, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/SummerSolstice/SummerIgnisSkin", "PrimePrice": 300, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroArrow", "PrimePrice": 375, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/MeleeDangles/BaroMeleeDangle", "PrimePrice": 250, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/BaroScytheMacheteSkin", "PrimePrice": 375, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisOdonataSkin", "PrimePrice": 350, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisBallasSword", "PrimePrice": 350, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/PrismaArrow", "PrimePrice": 350, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTRedeemerSkin", "PrimePrice": 325, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisSonicor", "PrimePrice": 380, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisTigris", "PrimePrice": 300, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/VTQuanta", "PrimePrice": 300, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/VoidTrader/ElixisOpticor", "PrimePrice": 325, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Halloween/HalloweenDread", "PrimePrice": 300, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/ImageBaroKiteer", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKavat", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/AvatarImageGlyphCookieKubrow", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/LisetScarf", "PrimePrice": 600, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerTwitchBItemA", "PrimePrice": 220, "RegularPrice": 220000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/FootstepsMaple", "PrimePrice": 15, "RegularPrice": 1000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKavatBadgeItem", "PrimePrice": 50, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKavatSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/WraithTurbinesScarf", "PrimePrice": 400, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Pirate/HydroidAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/Seasonal/Halloween2019GrendelTreat", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/SuitCustomizations/ColourPickerKiteerItemA", "PrimePrice": 150, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/KazBaroCape", "PrimePrice": 325, "RegularPrice": 450000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Effects/BaroEphemeraA", "PrimePrice": 100, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape2Scarf", "PrimePrice": 400, "RegularPrice": 350000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroQuantumBadgeItem", "PrimePrice": 400, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/SolsticeBaroCape", "PrimePrice": 425, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/BaroCape", "PrimePrice": 500, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaroIcon", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Magician/LimboImmortalSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Cowgirl/MesaImmortallSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Harlequin/MirageAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/BaroKubrowBadgeItem", "PrimePrice": 50, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/BaroKubrowSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/VTHornSkullScarf", "PrimePrice": 250, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Clan/PrismaLotusEmblem", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageBaroTwoIcon", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrismaLotusSigil", "PrimePrice": 55, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sigils/PrimeTraderSigil", "PrimePrice": 50, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/PrismaRazorScarf", "PrimePrice": 350, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Scarves/VTDinoSpikeScarf", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageLowPolyKavat", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/StoreItems/AvatarImages/AvatarImageLowPolyKubrow", "PrimePrice": 80, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Tengu/ZephyrAlternateSkin", "PrimePrice": 550, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/KubrowPet/Patterns/KubrowPetPatternPrimeTraderA", "PrimePrice": 150, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Camo/DesertDirigaSkin", "PrimePrice": 225, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/KavatPetMask", "PrimePrice": 500, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/KavatPetTail", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/KavatPetWings", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Catbrows/Armor/CatbrowArmorVoidTraderA", "PrimePrice": 500, "RegularPrice": 275000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Kubrows/Armor/KubrowArmorBaro", "PrimePrice": 500, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/BaroPetMask", "PrimePrice": 500, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/BaroPetTail", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/BaroPetWings", "PrimePrice": 400, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/Types/StoreItems/Packages/KavatColorPackNexus", "PrimePrice": 200, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Wings/PrismaJetWings", "PrimePrice": 300, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Tails/PrismaFishTail", "PrimePrice": 200, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Masks/PrismaMechHeadMask", "PrimePrice": 175, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Kubrows/Armor/KubrowArmorPrisma", "PrimePrice": 400, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Sentinels/SentinelPowersuits/PrismaShadePowerSuit", "PrimePrice": 500, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Sentinels/Skins/DesertTaxonSkin", "PrimePrice": 200, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Catbrows/Armor/CatbrowArmorHalloweenA", "PrimePrice": 400, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/Types/StoreItems/Boosters/AffinityBooster3DayStoreItem", "PrimePrice": 450, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/Types/StoreItems/Boosters/CreditBooster3DayStoreItem", "PrimePrice": 350, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/Types/StoreItems/Boosters/ModDropChanceBooster3DayStoreItem", "PrimePrice": 500, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/Types/StoreItems/Boosters/ResourceAmount3DayStoreItem", "PrimePrice": 400, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/Projections/T4VoidProjectionPBronze", "PrimePrice": 50, "RegularPrice": 45000 },
{ "ItemType": "/Lotus/StoreItems/Types/Recipes/Components/CorruptedBombardBallBlueprint", "PrimePrice": 100, "RegularPrice": 50000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/CorruptedHeavyGunnerBall", "PrimePrice": 100, "RegularPrice": 40000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/OrbiterPictureFrameBaro", "PrimePrice": 100, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitC", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileInarosTomb", "PrimePrice": 325, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/BaroFireWorksCrate", "PrimePrice": 50, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/MiscItems/PhotoboothTileOrokinExtraction", "PrimePrice": 325, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Types/Keys/MummyQuestKeyBlueprint", "PrimePrice": 100, "RegularPrice": 25000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBait", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Restoratives/Consumable/AssassinBaitB", "PrimePrice": 200, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationB", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationE", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropCleaningDroneBaro", "PrimePrice": 700, "RegularPrice": 500000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerBobbleHead", "PrimePrice": 70, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Hoverboard/HoverboardStickerBaroA", "PrimePrice": 75, "RegularPrice": 75000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KavatBust", "PrimePrice": 220, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KubrowBust", "PrimePrice": 220, "RegularPrice": 250000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/Plushies/PlushyDesertSkate", "PrimePrice": 125, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationD", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ExcaliburArchwingBobbleHead", "PrimePrice": 90, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroTiara", "PrimePrice": 525, "RegularPrice": 375000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroC", "PrimePrice": 500, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroMouthPieceA", "PrimePrice": 500, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroVisor", "PrimePrice": 525, "RegularPrice": 375000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/BaroHorn", "PrimePrice": 525, "RegularPrice": 375000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroA", "PrimePrice": 500, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Operator/Accessories/EarpieceBaroB", "PrimePrice": 250, "RegularPrice": 200000 },
{ "ItemType": "/Lotus/StoreItems/Types/Game/QuartersWallpapers/BaroWallpaper", "PrimePrice": 250, "RegularPrice": 175000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/InarosLisetSkin", "PrimePrice": 400, "RegularPrice": 300000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationA", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetInsectSkinInaros", "PrimePrice": 425, "RegularPrice": 320000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetInsectSkinPrimeTrader", "PrimePrice": 230, "RegularPrice": 375000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/ParazonPoster", "PrimePrice": 100, "RegularPrice": 125000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/KubrowKavatLowPolyPoster", "PrimePrice": 90, "RegularPrice": 110000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetSkinVoidTrader", "PrimePrice": 120, "RegularPrice": 150000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetBlueSkySkinPrimeTrader", "PrimePrice": 210, "RegularPrice": 450000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationF", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/LisetBlueSkySkinInaros", "PrimePrice": 375, "RegularPrice": 340000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationG", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/LisetPropOstRugBaro", "PrimePrice": 225, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationH", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Skins/Liset/Gyroscope/LisetGyroscopeSkinPrimeTrader", "PrimePrice": 220, "RegularPrice": 400000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/BaroKiTeerDecorationC", "PrimePrice": 100, "RegularPrice": 100000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/ShipDecos/PedistalPrime", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Types/Items/Emotes/BaroEmote", "PrimePrice": 0, "RegularPrice": 1000000 },
{ "ItemType": "/Lotus/StoreItems/Upgrades/Mods/Rifle/EventSniperReloadDamageMod", "PrimePrice": 2995, "RegularPrice": 1000000 }
],
"allIfAny": [
[
"/Lotus/StoreItems/Upgrades/Skins/Operator/Leggings/LeggingsNovaEngineer",
"/Lotus/StoreItems/Upgrades/Skins/Operator/BodySuits/BodySuitNovaEngineer",
"/Lotus/StoreItems/Upgrades/Skins/Operator/Sleeves/SleevesNovaEngineer",
"/Lotus/StoreItems/Upgrades/Skins/Operator/Hoods/HoodNovaEngineer"
]
]
}

View File

@ -0,0 +1,158 @@
[
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Archwing/DemolitionJetPack/DemolitionJetPack", "Discount": 60, "OriginalPrice": 275, "SalePrice": 110, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Bard/Bard", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Ember/Ember", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Ember/Ember", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 20, "OriginalPrice": 200, "SalePrice": 160, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 30, "OriginalPrice": 200, "SalePrice": 140, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 40, "OriginalPrice": 200, "SalePrice": 120, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Magician/Magician", "Discount": 50, "OriginalPrice": 200, "SalePrice": 100, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Sandman/Sandman", "Discount": 20, "OriginalPrice": 225, "SalePrice": 180, "AmountTotal": 100, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Sandman/Sandman", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Trapper/Trapper", "Discount": 40, "OriginalPrice": 300, "SalePrice": 180, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Powersuits/Trapper/Trapper", "Discount": 50, "OriginalPrice": 300, "SalePrice": 150, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/CatbrowPet/CatbrowGeneticSignature", "Discount": 20, "OriginalPrice": 5, "SalePrice": 4, "AmountTotal": 500, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/CatbrowPet/CatbrowGeneticSignature", "Discount": 30, "OriginalPrice": 5, "SalePrice": 3, "AmountTotal": 415, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/KubrowPet/Eggs/KubrowEgg", "Discount": 50, "OriginalPrice": 10, "SalePrice": 5, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Game/KubrowPet/Eggs/KubrowEgg", "Discount": 60, "OriginalPrice": 10, "SalePrice": 4, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", "Discount": 30, "OriginalPrice": 20, "SalePrice": 14, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/Forma", "Discount": 45, "OriginalPrice": 20, "SalePrice": 11, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/MiscItems/OrokinReactor", "Discount": 40, "OriginalPrice": 20, "SalePrice": 12, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/BioComponent", "Discount": 40, "OriginalPrice": 10, "SalePrice": 6, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/ChemComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 10, "OriginalPrice": 10, "SalePrice": 9, "AmountTotal": 200, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 20, "OriginalPrice": 10, "SalePrice": 8, "AmountTotal": 165, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 30, "OriginalPrice": 10, "SalePrice": 7, "AmountTotal": 135, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Types/Items/Research/EnergyComponent", "Discount": 40, "OriginalPrice": 10, "SalePrice": 6, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 20, "OriginalPrice": 40, "SalePrice": 32, "AmountTotal": 125, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/AttackLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/DefenseLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/PowerLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 10, "OriginalPrice": 40, "SalePrice": 36, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 20, "OriginalPrice": 40, "SalePrice": 32, "AmountTotal": 125, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 30, "OriginalPrice": 40, "SalePrice": 28, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/TacticLensGreater", "Discount": 50, "OriginalPrice": 40, "SalePrice": 20, "AmountTotal": 50, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Upgrades/Focus/WardLensGreater", "Discount": 40, "OriginalPrice": 40, "SalePrice": 24, "AmountTotal": 75, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 20, "OriginalPrice": 235, "SalePrice": 188, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 30, "OriginalPrice": 235, "SalePrice": 164, "AmountTotal": 250, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 50, "OriginalPrice": 235, "SalePrice": 117, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Bow/Longbow/CrpBow", "Discount": 60, "OriginalPrice": 235, "SalePrice": 94, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 30, "OriginalPrice": 125, "SalePrice": 87, "AmountTotal": 90, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Melee/KickAndPunch/KickPunchWeapon", "Discount": 60, "OriginalPrice": 125, "SalePrice": 50, "AmountTotal": 65, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 90, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 50, "OriginalPrice": 175, "SalePrice": 87, "AmountTotal": 80, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 60, "OriginalPrice": 175, "SalePrice": 70, "AmountTotal": 70, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Corpus/Pistols/CorpusMinigun/CorpusMinigun", "Discount": 70, "OriginalPrice": 175, "SalePrice": 52, "AmountTotal": 60, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 10, "OriginalPrice": 75, "SalePrice": 67, "AmountTotal": 100, "probability": 6 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 20, "OriginalPrice": 75, "SalePrice": 60, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/GrineerPistol/GrineerLightPistol", "Discount": 30, "OriginalPrice": 75, "SalePrice": 52, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 500, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 500, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/BurstRifle/GrnBurstRifle", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 500, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 20, "OriginalPrice": 150, "SalePrice": 120, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 250, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/LongGuns/GrnSpark/GrnSparkRifle", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 150, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 30, "OriginalPrice": 225, "SalePrice": 157, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 175, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 150, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 60, "OriginalPrice": 225, "SalePrice": 90, "AmountTotal": 125, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Grineer/Melee/GrineerMachetteAndCleaver/DualCleaverWeapon", "Discount": 70, "OriginalPrice": 225, "SalePrice": 67, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 10, "OriginalPrice": 150, "SalePrice": 135, "AmountTotal": 300, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 20, "OriginalPrice": 150, "SalePrice": 120, "AmountTotal": 270, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 240, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 40, "OriginalPrice": 150, "SalePrice": 90, "AmountTotal": 205, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 60, "OriginalPrice": 150, "SalePrice": 60, "AmountTotal": 145, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Infested/Melee/Swords/Mire/MireSword", "Discount": 80, "OriginalPrice": 150, "SalePrice": 30, "AmountTotal": 80, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 20, "OriginalPrice": 225, "SalePrice": 180, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 40, "OriginalPrice": 225, "SalePrice": 135, "AmountTotal": 165, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Akimbo/AkimboShotGun", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 150, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 30, "OriginalPrice": 75, "SalePrice": 52, "AmountTotal": 350, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 40, "OriginalPrice": 75, "SalePrice": 45, "AmountTotal": 300, "probability": 7 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 50, "OriginalPrice": 75, "SalePrice": 37, "AmountTotal": 250, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Dagger/Dagger", "Discount": 60, "OriginalPrice": 75, "SalePrice": 30, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/DualShortSword/DualHeatSwords", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/DualShortSword/DualHeatSwords", "Discount": 70, "OriginalPrice": 175, "SalePrice": 52, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Fist/Fist", "Discount": 10, "OriginalPrice": 125, "SalePrice": 112, "AmountTotal": 500, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Fist/Fist", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 250, "probability": 6 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 20, "OriginalPrice": 125, "SalePrice": 100, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 30, "OriginalPrice": 125, "SalePrice": 87, "AmountTotal": 125, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Gauntlet/Gauntlet", "Discount": 40, "OriginalPrice": 125, "SalePrice": 75, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 30, "OriginalPrice": 150, "SalePrice": 105, "AmountTotal": 300, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 40, "OriginalPrice": 150, "SalePrice": 90, "AmountTotal": 250, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Glaives/Boomerang/BoomerangWeapon", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 200, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 20, "OriginalPrice": 165, "SalePrice": 132, "AmountTotal": 300, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 30, "OriginalPrice": 165, "SalePrice": 115, "AmountTotal": 250, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 40, "OriginalPrice": 165, "SalePrice": 99, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 50, "OriginalPrice": 165, "SalePrice": 82, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Hammer/IceHammer/IceHammer", "Discount": 60, "OriginalPrice": 165, "SalePrice": 66, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 50, "OriginalPrice": 150, "SalePrice": 75, "AmountTotal": 300, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 60, "OriginalPrice": 150, "SalePrice": 60, "AmountTotal": 265, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 70, "OriginalPrice": 150, "SalePrice": 45, "AmountTotal": 225, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/LongSword/LongSword", "Discount": 90, "OriginalPrice": 150, "SalePrice": 15, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Scythe/EtherScytheWeapon", "Discount": 40, "OriginalPrice": 230, "SalePrice": 138, "AmountTotal": 250, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Scythe/EtherScytheWeapon", "Discount": 60, "OriginalPrice": 230, "SalePrice": 92, "AmountTotal": 150, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 20, "OriginalPrice": 175, "SalePrice": 140, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 100, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/Swords/GreatSword/TennoGreatSword", "Discount": 90, "OriginalPrice": 175, "SalePrice": 17, "AmountTotal": 100, "probability": 1 },
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield",
"Discount": 30,
"OriginalPrice": 150,
"SalePrice": 105,
"AmountTotal": 100,
"probability": 1
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield",
"Discount": 70,
"OriginalPrice": 150,
"SalePrice": 45,
"AmountTotal": 100,
"probability": 1
},
{
"StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Melee/SwordsAndBoards/MeleeContestWinnerOne/TennoSwordShield",
"Discount": 90,
"OriginalPrice": 150,
"SalePrice": 15,
"AmountTotal": 100,
"probability": 1
},
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 300, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 250, "probability": 6 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 50, "OriginalPrice": 175, "SalePrice": 87, "AmountTotal": 200, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/CrossBow", "Discount": 60, "OriginalPrice": 175, "SalePrice": 70, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 300, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/HandShotGun", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 100, "probability": 5 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 200, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 150, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistol/RevolverPistol", "Discount": 50, "OriginalPrice": 190, "SalePrice": 95, "AmountTotal": 50, "probability": 4 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 20, "OriginalPrice": 190, "SalePrice": 152, "AmountTotal": 300, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 30, "OriginalPrice": 190, "SalePrice": 133, "AmountTotal": 250, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 40, "OriginalPrice": 190, "SalePrice": 114, "AmountTotal": 200, "probability": 2 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 50, "OriginalPrice": 190, "SalePrice": 95, "AmountTotal": 150, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Pistols/TnBardPistol/TnBardPistolGun", "Discount": 60, "OriginalPrice": 190, "SalePrice": 76, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 10, "OriginalPrice": 250, "SalePrice": 225, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 30, "OriginalPrice": 250, "SalePrice": 175, "AmountTotal": 100, "probability": 3 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Rifle/TennoSniperRifle", "Discount": 50, "OriginalPrice": 250, "SalePrice": 125, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Shotgun/QuadShotgun", "Discount": 50, "OriginalPrice": 225, "SalePrice": 112, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/Shotgun/QuadShotgun", "Discount": 70, "OriginalPrice": 225, "SalePrice": 67, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 10, "OriginalPrice": 175, "SalePrice": 157, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 20, "OriginalPrice": 175, "SalePrice": 140, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 30, "OriginalPrice": 175, "SalePrice": 122, "AmountTotal": 100, "probability": 1 },
{ "StoreItem": "/Lotus/StoreItems/Weapons/Tenno/ThrowingWeapons/Kunai", "Discount": 40, "OriginalPrice": 175, "SalePrice": 105, "AmountTotal": 100, "probability": 2 }
]

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -13,37 +13,38 @@
<span class="navbar-toggler-icon"></span>
</button>
<a class="navbar-brand">OpenWF WebUI</a>
<ul class="navbar-nav ms-auto mb-0">
<li class="nav-item dropdown">
<button id="active-lang-name" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item active" href="#" data-lang="en" onclick="event.preventDefault();setLanguage('en');">English</a></li>
<li><a class="dropdown-item" href="#" data-lang="de" onclick="event.preventDefault();setLanguage('de');">Deutsch</a></li>
<li><a class="dropdown-item" href="#" data-lang="es" onclick="event.preventDefault();setLanguage('es');">Español</a></li>
<li><a class="dropdown-item" href="#" data-lang="fr" onclick="event.preventDefault();setLanguage('fr');">Français</a></li>
<li><a class="dropdown-item" href="#" data-lang="it" onclick="event.preventDefault();setLanguage('it');">Italiano</a></li>
<li><a class="dropdown-item" href="#" data-lang="ja" onclick="event.preventDefault();setLanguage('ja');">日本語</a></li>
<li><a class="dropdown-item" href="#" data-lang="ko" onclick="event.preventDefault();setLanguage('ko');">한국어</a></li>
<li><a class="dropdown-item" href="#" data-lang="pl" onclick="event.preventDefault();setLanguage('pl');">Polski</a></li>
<li><a class="dropdown-item" href="#" data-lang="pt" onclick="event.preventDefault();setLanguage('pt');">Português</a></li>
<li><a class="dropdown-item" href="#" data-lang="ru" onclick="event.preventDefault();setLanguage('ru');">Русский</a></li>
<li><a class="dropdown-item" href="#" data-lang="tr" onclick="event.preventDefault();setLanguage('tr');">Türkçe</a></li>
<li><a class="dropdown-item" href="#" data-lang="uk" onclick="event.preventDefault();setLanguage('uk');">Українська</a></li>
<li><a class="dropdown-item" href="#" data-lang="zh" onclick="event.preventDefault();setLanguage('zh');">简体中文</a></li>
<li><a class="dropdown-item" href="#" data-lang="tc" onclick="event.preventDefault();setLanguage('tc');">繁體中文</a></li>
<li><a class="dropdown-item" href="#" data-lang="th" onclick="event.preventDefault();setLanguage('th');">แบบไทย</a></li>
</ul>
</li>
<li class="nav-item dropdown user-dropdown">
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/webui/" onclick="logout();" data-loc="navbar_logout"></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();renameAccount();" data-loc="navbar_renameAccount"></a></li>
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();deleteAccount();" data-loc="navbar_deleteAccount"></a></li>
</ul>
</li>
</ul>
<div class="ms-auto nav-item dropdown">
<button id="active-lang-name" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item active" href="#" data-lang="en" onclick="event.preventDefault();setLanguage('en');">English</a></li>
<li><a class="dropdown-item" href="#" data-lang="de" onclick="event.preventDefault();setLanguage('de');">Deutsch</a></li>
<li><a class="dropdown-item" href="#" data-lang="es" onclick="event.preventDefault();setLanguage('es');">Español</a></li>
<li><a class="dropdown-item" href="#" data-lang="fr" onclick="event.preventDefault();setLanguage('fr');">Français</a></li>
<li><a class="dropdown-item" href="#" data-lang="it" onclick="event.preventDefault();setLanguage('it');">Italiano</a></li>
<li><a class="dropdown-item" href="#" data-lang="ja" onclick="event.preventDefault();setLanguage('ja');">日本語</a></li>
<li><a class="dropdown-item" href="#" data-lang="ko" onclick="event.preventDefault();setLanguage('ko');">한국어</a></li>
<li><a class="dropdown-item" href="#" data-lang="pl" onclick="event.preventDefault();setLanguage('pl');">Polski</a></li>
<li><a class="dropdown-item" href="#" data-lang="pt" onclick="event.preventDefault();setLanguage('pt');">Português</a></li>
<li><a class="dropdown-item" href="#" data-lang="ru" onclick="event.preventDefault();setLanguage('ru');">Русский</a></li>
<li><a class="dropdown-item" href="#" data-lang="tr" onclick="event.preventDefault();setLanguage('tr');">Türkçe</a></li>
<li><a class="dropdown-item" href="#" data-lang="uk" onclick="event.preventDefault();setLanguage('uk');">Українська</a></li>
<li><a class="dropdown-item" href="#" data-lang="zh" onclick="event.preventDefault();setLanguage('zh');">简体中文</a></li>
<li><a class="dropdown-item" href="#" data-lang="tc" onclick="event.preventDefault();setLanguage('tc');">繁體中文</a></li>
<li><a class="dropdown-item" href="#" data-lang="th" onclick="event.preventDefault();setLanguage('th');">แบบไทย</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item active" href="#" data-loc="theme_dark" data-theme="dark" onclick="event.preventDefault();setTheme('dark');"></a></li>
<li><a class="dropdown-item" href="#" data-loc="theme_light" data-theme="light" onclick="event.preventDefault();setTheme('light');"></a></li>
</ul>
</div>
<div class="nav-item dropdown user-dropdown">
<button class="nav-link dropdown-toggle displayname" data-bs-toggle="dropdown" aria-expanded="false"></button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/webui/" onclick="doLogout();" data-loc="navbar_logout"></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();renameAccount();" data-loc="navbar_renameAccount"></a></li>
<li><a class="dropdown-item" href="#" onclick="event.preventDefault();deleteAccount();" data-loc="navbar_deleteAccount"></a></li>
</ul>
</div>
</div>
</nav>
<div class="container pt-3 pb-3" id="main-view">
@ -98,9 +99,9 @@
<button class="btn btn-primary" type="submit" data-loc="general_addButton"></button>
</form>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_RegularCredits"></h5>
<div class="card-body">
<p class="card-text" id="RegularCredits-owned"></p>
@ -112,7 +113,7 @@
</div>
</div>
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_PremiumCredits"></h5>
<div class="card-body">
<p class="card-text" id="PremiumCredits-owned"></p>
@ -124,7 +125,7 @@
</div>
</div>
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_FusionPoints"></h5>
<div class="card-body">
<p class="card-text" id="FusionPoints-owned"></p>
@ -136,7 +137,7 @@
</div>
</div>
<div class="col-md-3">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="currency_PrimeTokens"></h5>
<div class="card-body">
<p class="card-text" id="PrimeTokens-owned"></p>
@ -148,9 +149,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_suits"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Suits');return false;">
@ -164,7 +165,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_longGuns"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('LongGuns');return false;">
@ -183,9 +184,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_pistols"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('Pistols');return false;">
@ -204,7 +205,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_melee"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('Melee');return false;">
@ -223,9 +224,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceSuits"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceSuits');return false;">
@ -239,7 +240,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceGuns"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceGuns');return false;">
@ -253,9 +254,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_spaceMelee"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SpaceMelee');return false;">
@ -269,7 +270,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_mechSuits"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('MechSuits');return false;">
@ -283,9 +284,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinels"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('Sentinels');return false;">
@ -299,7 +300,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_moaPets"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('MoaPets');return false;">
@ -325,9 +326,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" 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;">
@ -349,7 +350,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_sentinelWeapons"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('SentinelWeapons');return false;">
@ -363,9 +364,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_operatorAmps"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="handleModularSelection('OperatorAmps');return false;">
@ -384,7 +385,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_hoverboards"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireModularEquipment('HoverBoards');return false;">
@ -401,9 +402,9 @@
</div>
</div>
</div>
<div class="row g-3">
<div class="row g-3 mb-3">
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" 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;">
@ -417,7 +418,7 @@
</div>
</div>
<div class="col-lg-6">
<div class="card mb-3" style="height: 400px;">
<div class="card" style="height: 400px;">
<h5 class="card-header" data-loc="inventory_Boosters"></h5>
<div class="card-body overflow-auto">
<form class="input-group mb-3" onsubmit="doAcquireBoosters();return false;">
@ -431,7 +432,7 @@
</div>
</div>
</div>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
@ -455,15 +456,15 @@
</div>
</div>
</div>
<div id="powersuit-route" data-route="~ /webui/powersuit/(.+)" data-title="Inventory | OpenWF WebUI">
<div id="detailedView-route" data-route="/webui/detailedView" data-title="Inventory | OpenWF WebUI">
<h3 class="mb-0"></h3>
<p class="text-body-secondary"></p>
<div class="card mb-3">
<h5 class="card-header" data-loc="powersuit_archonShardsLabel"></h5>
<div id="archonShards-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_archonShardsLabel"></h5>
<div class="card-body">
<p>
<span data-loc="powersuit_archonShardsDescription"></span>
<span data-loc="powersuit_archonShardsDescription2"></span>
<span data-loc="detailedView_archonShardsDescription"></span>
<span data-loc="detailedView_archonShardsDescription2"></span>
</p>
<form class="input-group mb-3" onsubmit="doPushArchonCrystalUpgrade();return false;">
<input type="number" id="archon-crystal-add-count" min="1" max="10000" value="1" class="form-control" style="max-width:100px" />
@ -476,6 +477,18 @@
</table>
</div>
</div>
<div id="valenceBonus-card" class="card mb-3 d-none">
<h5 class="card-header" data-loc="detailedView_valenceBonusLabel"></h5>
<div class="card-body">
<p data-loc="detailedView_valenceBonusDescription"></p>
<form class="input-group mb-3" onsubmit="handleValenceBonusChange(event)">
<select class="form-control" id="valenceBonus-innateDamage"></select>
<input type="number" id="valenceBonus-procent" min="25" max="60" step="0.1" class="form-control" style="max-width:100px" />
<button class="btn btn-primary" type="submit" value="set" data-loc="general_setButton"></button>
<button class="btn btn-danger" type="submit" value="remove" data-loc="general_removeButton"></button>
</form>
</div>
</div>
</div>
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
<p class="mb-3" data-loc="general_inventoryUpdateNote"></p>
@ -498,7 +511,7 @@
<a href="riven-tool/" target="_blank" data-loc="mods_fingerprintHelp"></a>
</form>
</div>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="mods_rivens"></h5>
<div class="card-body">
<table class="table table-hover w-100">
@ -521,7 +534,7 @@
</table>
</div>
</div>
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="doAddAllMods();" data-loc="mods_addMissingUnrankedMods"></button>
@ -535,7 +548,7 @@
<div data-route="/webui/quests" data-title="Quests | OpenWF WebUI">
<div class="row g-3">
<div class="col-md-6">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="quests_list"></h5>
<div class="card-body">
<form class="input-group mb-3" onsubmit="doAcquireEquipment('QuestKeys');return false;">
@ -549,7 +562,7 @@
</div>
</div>
<div class="col-md-6">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="general_bulkActions"></h5>
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
@ -565,13 +578,13 @@
<div data-route="/webui/cheats, /webui/settings" data-title="Cheats | OpenWF WebUI">
<div class="row g-3">
<div class="col-md-6">
<div class="card mb-3">
<div class="card">
<h5 class="card-header" data-loc="cheats_server"></h5>
<div class="card-body">
<div id="server-settings-no-perms" class="d-none">
<div class="d-none config-admin-hide">
<p class="card-text" data-loc="cheats_administratorRequirement"></p>
</div>
<div id="server-settings" class="d-none">
<div class="d-none config-admin-show config-form">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="skipTutorial" />
<label class="form-check-label" for="skipTutorial" data-loc="cheats_skipTutorial"></label>
@ -584,10 +597,6 @@
<input class="form-check-input" type="checkbox" id="unlockAllScans" />
<label class="form-check-label" for="unlockAllScans" data-loc="cheats_unlockAllScans"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllMissions" />
<label class="form-check-label" for="unlockAllMissions" data-loc="cheats_unlockAllMissions"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="infiniteCredits" />
<label class="form-check-label" for="infiniteCredits" data-loc="cheats_infiniteCredits"></label>
@ -704,10 +713,26 @@
<input class="form-check-input" type="checkbox" id="noKimCooldowns" />
<label class="form-check-label" for="noKimCooldowns" data-loc="cheats_noKimCooldowns"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="fullyStockedVendors" />
<label class="form-check-label" for="fullyStockedVendors" data-loc="cheats_fullyStockedVendors"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="baroAlwaysAvailable" />
<label class="form-check-label" for="baroAlwaysAvailable" data-loc="cheats_baroAlwaysAvailable"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="baroFullyStocked" />
<label class="form-check-label" for="baroFullyStocked" data-loc="cheats_baroFullyStocked"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="syndicateMissionsRepeatable" />
<label class="form-check-label" for="syndicateMissionsRepeatable" data-loc="cheats_syndicateMissionsRepeatable"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllProfitTakerStages" />
<label class="form-check-label" for="unlockAllProfitTakerStages" data-loc="cheats_unlockAllProfitTakerStages"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="instantFinishRivenChallenge" />
<label class="form-check-label" for="instantFinishRivenChallenge" data-loc="cheats_instantFinishRivenChallenge"></label>
@ -748,14 +773,26 @@
<input class="form-check-input" type="checkbox" id="fastClanAscension" />
<label class="form-check-label" for="fastClanAscension" data-loc="cheats_fastClanAscension"></label>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfig('spoofMasteryRank'); return false;">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="missionsCanGiveAllRelics" />
<label class="form-check-label" for="missionsCanGiveAllRelics" data-loc="cheats_missionsCanGiveAllRelics"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="unlockAllSimarisResearchEntries" />
<label class="form-check-label" for="unlockAllSimarisResearchEntries" data-loc="cheats_unlockAllSimarisResearchEntries"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="disableDailyTribute" />
<label class="form-check-label" for="disableDailyTribute" data-loc="cheats_disableDailyTribute"></label>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfigInt('spoofMasteryRank'); return false;">
<label class="form-label" for="spoofMasteryRank" data-loc="cheats_spoofMasteryRank"></label>
<div class="input-group">
<input class="form-control" id="spoofMasteryRank" type="number" min="-1" max="65535" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfig('nightwaveStandingMultiplier'); return false;">
<form class="form-group mt-2" onsubmit="doSaveConfigInt('nightwaveStandingMultiplier'); return false;">
<label class="form-label" for="nightwaveStandingMultiplier" data-loc="cheats_nightwaveStandingMultiplier"></label>
<div class="input-group">
<input class="form-control" id="nightwaveStandingMultiplier" type="number" min="1" max="1000000" value="1" />
@ -771,9 +808,12 @@
<h5 class="card-header" data-loc="cheats_account"></h5>
<div class="card-body">
<div class="mb-2 d-flex flex-wrap gap-2">
<button class="btn btn-primary" onclick="debounce(doUnlockAllMissions);" data-loc="cheats_unlockAllMissions"></button>
<button class="btn btn-primary" onclick="doUnlockAllFocusSchools();" data-loc="cheats_unlockAllFocusSchools"></button>
<button class="btn btn-primary" onclick="doHelminthUnlockAll();" data-loc="cheats_helminthUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(addMissingHelminthRecipes);" data-loc="cheats_addMissingSubsumedAbilities"></button>
<button class="btn btn-primary" onclick="doIntrinsicsUnlockAll();" data-loc="cheats_intrinsicsUnlockAll"></button>
<button class="btn btn-primary" onclick="debounce(doMaxPlexus);" data-loc="inventory_maxPlexus"></button>
</div>
<form class="mt-2" onsubmit="doChangeSupportedSyndicate(); return false;">
<label class="form-label" for="changeSyndicate" data-loc="cheats_changeSupportedSyndicate"></label>
@ -784,13 +824,118 @@
</form>
</div>
</div>
<div class="card d-none config-admin-show config-form">
<h5 class="card-header" data-loc="worldState"></h5>
<div class="card-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.creditBoost" />
<label class="form-check-label" for="worldState.creditBoost" data-loc="worldState_creditBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.affinityBoost" />
<label class="form-check-label" for="worldState.affinityBoost" data-loc="worldState_affinityBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.resourceBoost" />
<label class="form-check-label" for="worldState.resourceBoost" data-loc="worldState_resourceBoost"></label>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="worldState.starDays" />
<label class="form-check-label" for="worldState.starDays" data-loc="worldState_starDays"></label>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_galleonOfGhouls"></label>
<select class="form-control" id="worldState.galleonOfGhouls">
<option value="0" data-loc="disabled"></option>
<option value="1" data-loc="worldState_we1"></option>
<option value="2" data-loc="worldState_we2"></option>
<option value="3" data-loc="worldState_we3"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_eidolonOverride"></label>
<select class="form-control" id="worldState.eidolonOverride">
<option value="" data-loc="disabled"></option>
<option value="day" data-loc="worldState_day"></option>
<option value="night" data-loc="worldState_night"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_vallisOverride"></label>
<select class="form-control" id="worldState.vallisOverride">
<option value="" data-loc="disabled"></option>
<option value="warm" data-loc="worldState_warm"></option>
<option value="cold" data-loc="worldState_cold"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_duviriOverride"></label>
<select class="form-control" id="worldState.duviriOverride">
<option value="" data-loc="disabled"></option>
<option value="joy" data-loc="worldState_joy"></option>
<option value="anger" data-loc="worldState_anger"></option>
<option value="envy" data-loc="worldState_envy"></option>
<option value="sorrow" data-loc="worldState_sorrow"></option>
<option value="fear" data-loc="worldState_fear"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_nightwaveOverride"></label>
<select class="form-control" id="worldState.nightwaveOverride">
<option value="" data-loc="disabled"></option>
<option value="RadioLegionIntermission13Syndicate" data-loc="worldState_RadioLegionIntermission13Syndicate"></option>
<option value="RadioLegionIntermission12Syndicate" data-loc="worldState_RadioLegionIntermission12Syndicate"></option>
<option value="RadioLegionIntermission11Syndicate" data-loc="worldState_RadioLegionIntermission11Syndicate"></option>
<option value="RadioLegionIntermission10Syndicate" data-loc="worldState_RadioLegionIntermission10Syndicate"></option>
<option value="RadioLegionIntermission9Syndicate" data-loc="worldState_RadioLegionIntermission9Syndicate"></option>
<option value="RadioLegionIntermission8Syndicate" data-loc="worldState_RadioLegionIntermission8Syndicate"></option>
<option value="RadioLegionIntermission7Syndicate" data-loc="worldState_RadioLegionIntermission7Syndicate"></option>
<option value="RadioLegionIntermission6Syndicate" data-loc="worldState_RadioLegionIntermission6Syndicate"></option>
<option value="RadioLegionIntermission5Syndicate" data-loc="worldState_RadioLegionIntermission5Syndicate"></option>
<option value="RadioLegionIntermission4Syndicate" data-loc="worldState_RadioLegionIntermission4Syndicate"></option>
<option value="RadioLegionIntermission3Syndicate" data-loc="worldState_RadioLegionIntermission3Syndicate"></option>
<option value="RadioLegion3Syndicate" data-loc="worldState_RadioLegion3Syndicate"></option>
<option value="RadioLegionIntermission2Syndicate" data-loc="worldState_RadioLegionIntermission2Syndicate"></option>
<option value="RadioLegion2Syndicate" data-loc="worldState_RadioLegion2Syndicate"></option>
<option value="RadioLegionIntermissionSyndicate" data-loc="worldState_RadioLegionIntermissionSyndicate"></option>
<option value="RadioLegionSyndicate" data-loc="worldState_RadioLegionSyndicate"></option>
</select>
</div>
<div class="form-group mt-2">
<label class="form-label" for="changeSyndicate" data-loc="worldState_fissures"></label>
<select class="form-control" id="worldState.allTheFissures">
<option value="" data-loc="normal"></option>
<option value="normal" data-loc="worldState_allAtOnceNormal"></option>
<option value="hard" data-loc="worldState_allAtOnceSteelPath"></option>
</select>
</div>
<form class="form-group mt-2" onsubmit="doSaveConfigStringArray('worldState.circuitGameModes'); return false;">
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_theCircuitOverride"></label>
<div class="input-group">
<input id="worldState.circuitGameModes" type="text" class="form-control tags-input" list="datalist-circuitGameModes" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
<form class="form-group mt-2" onsubmit="doSaveConfigFloat('worldState.darvoStockMultiplier'); return false;">
<label class="form-label" for="worldState.circuitGameModes" data-loc="worldState_darvoStockMultiplier"></label>
<div class="input-group">
<input id="worldState.darvoStockMultiplier" class="form-control" type="number" step="0.01" placeholder="1" />
<button class="btn btn-primary" type="submit" data-loc="cheats_save"></button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<div data-route="/webui/import" data-title="Import | OpenWF WebUI">
<p data-loc="import_importNote"></p>
<textarea class="form-control" id="import-inventory"></textarea>
<textarea class="form-control" id="import-inventory" style="height: calc(100vh - 300px)"></textarea>
<button class="btn btn-primary mt-3" onclick="doImport();" data-loc="import_submit"></button>
<p class="mt-3 mb-1" data-loc="import_samples"></p>
<ul>
<li><a href="#" onclick="event.preventDefault();setImportSample('maxFocus');" data-loc="import_samples_maxFocus"></a></li>
</ul>
</div>
</div>
<div class="toast-container position-fixed bottom-0 end-0 p-3"></div>
@ -809,10 +954,7 @@
<datalist id="datalist-KubrowPets"></datalist>
<datalist id="datalist-QuestKeys"></datalist>
<datalist id="datalist-miscitems"></datalist>
<datalist id="datalist-mods">
<option data-key="/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser" value="Legendary Core"></option>
<option data-key="/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod" value="Traumatic Peculiar"></option>
</datalist>
<datalist id="datalist-mods"></datalist>
<datalist id="datalist-archonCrystalUpgrades"></datalist>
<datalist id="datalist-OperatorAmps"></datalist>
<datalist id="datalist-EvolutionProgress"></datalist>
@ -844,6 +986,15 @@
<datalist id="datalist-ModularParts-KUBROW_ANTIGEN"></datalist>
<datalist id="datalist-ModularParts-KUBROW_MUTAGEN"></datalist>
<datalist id="datalist-Boosters"></datalist>
<datalist id="datalist-circuitGameModes">
<option>Survival</option>
<option>VoidFlood</option>
<option>Excavation</option>
<option>Defense</option>
<option>Exterminate</option>
<option>Assassination</option>
<option>Alchemy</option>
</datalist>
<script src="/webui/libs/jquery-3.6.0.min.js"></script>
<script src="/webui/libs/whirlpool-js.min.js"></script>
<script src="/webui/libs/single.js"></script>

Some files were not shown because too many files have changed in this diff Show More