forked from OpenWF/SpaceNinjaServer
Compare commits
353 Commits
Author | SHA1 | Date | |
---|---|---|---|
ba6cd47432 | |||
92d34fd69e | |||
09b9683fa1 | |||
a47eccdec8 | |||
ffd410537e | |||
79eab71aaf | |||
21164554a3 | |||
45c0da6ed8 | |||
727f6837ba | |||
77a3b64f49 | |||
ce59086f7d | |||
9b0989f1df | |||
b01376f703 | |||
870ff2dd2c | |||
1ac71a9b28 | |||
a622787500 | |||
52c8802d57 | |||
daf721f7cd | |||
f724073d93 | |||
8fb676c906 | |||
2ce5cc4562 | |||
099f12a197 | |||
bfe2e93c76 | |||
8b97bb4b0a | |||
85a45a04ea | |||
2a40449604 | |||
382f8c55ce | |||
77513190e4 | |||
5e8ce934c9 | |||
6de81c2b41 | |||
4c5ac4f03a | |||
d6f4c1a035 | |||
c58c70c4ce | |||
3e1e19d6c5 | |||
2521733e55 | |||
d5297d3547 | |||
5bc39aac8a | |||
b201508fa1 | |||
5f9ae2aef6 | |||
b451c73598 | |||
9d4bce852e | |||
c83e732b88 | |||
3d13ec311e | |||
31043b55de | |||
ab32728c47 | |||
3fc2dccf81 | |||
1084932afb | |||
f9b3fecc10 | |||
7a51fab5d3 | |||
0e255067a8 | |||
8d788d38a5 | |||
bdd5ade2eb | |||
3ff7e4264c | |||
29b54b52dd | |||
dab8c6c8ba | |||
bdb4c8fd7c | |||
3bcac1459b | |||
b987e01811 | |||
d831732513 | |||
42c63ecbe8 | |||
0319031e13 | |||
1f3bb88910 | |||
6171b36479 | |||
c56507e12d | |||
e545ecf767 | |||
58549c1488 | |||
bb606f3a95 | |||
4b12fe12cb | |||
4f28688837 | |||
203b3e20d9 | |||
005690daa4 | |||
5fefd189af | |||
f6cbc02c47 | |||
da6d75c748 | |||
460deed3ed | |||
4e57bcd1ae | |||
e2fe406017 | |||
c53dc2fd02 | |||
cfa3586f64 | |||
238af294fe | |||
1914fd8f10 | |||
ec4af075b5 | |||
355a70d366 | |||
cad82cf7de | |||
ff3a9b382c | |||
18fbd51efb | |||
f7b4b4f089 | |||
83743831c9 | |||
8520650018 | |||
b958c108f9 | |||
8ae5fcfad0 | |||
468efed71c | |||
4926b2f2be | |||
562ddd513f | |||
35d5c01203 | |||
8f8bc5b364 | |||
a8227ce54c | |||
ec9dc2aa5f | |||
f7906c91e3 | |||
159598979d | |||
4983417201 | |||
9b652f5c3c | |||
19b04533df | |||
cddd4bdf5c | |||
12b6e5d16e | |||
8eefd67d79 | |||
c4b2248df5 | |||
2c3043f40e | |||
7d02906656 | |||
ed54e00a03 | |||
3d6c880c96 | |||
660768b53b | |||
3de68e51d5 | |||
c06abded11 | |||
9468768947 | |||
0af7f41201 | |||
de1e2a25f2 | |||
1cf7b41d3f | |||
ab9cc685eb | |||
743b784754 | |||
5df533a7fb | |||
9417aa3c84 | |||
a1872e2b07 | |||
9042e85355 | |||
66ee550ccd | |||
7a295a86ec | |||
88d00eaaa1 | |||
1e8f2fc766 | |||
0d842ade90 | |||
4e3a2e17ee | |||
61864b2be1 | |||
45748fa8be | |||
afec59e8a6 | |||
ee1a49f5f2 | |||
9e94083875 | |||
db0e0d80dd | |||
5cda2e2d08 | |||
e23d865044 | |||
c7658b5b20 | |||
9993500eca | |||
267357871b | |||
cf5ed0442d | |||
de36e2ee8d | |||
ca1b6c31b6 | |||
d66c474bfc | |||
781f01520f | |||
ac37702468 | |||
75c011e3cb | |||
4d4f885c8e | |||
66d1a65e63 | |||
48eefd8db1 | |||
4a6a5ea9cc | |||
95c0ad7892 | |||
a90d3a5156 | |||
d0c9409a2d | |||
bbde7b2141 | |||
5271123090 | |||
f3e56480e5 | |||
6f46ace40c | |||
883426e429 | |||
13432bf034 | |||
a1267e5f64 | |||
2058207b6a | |||
c7c416c100 | |||
90e97d7888 | |||
3f6734ac1c | |||
6f64690b91 | |||
fb5a7320bb | |||
143b358a03 | |||
0b75757277 | |||
fd7f4c9e92 | |||
fa6fac494b | |||
6b3f524574 | |||
506365f97e | |||
70646160c3 | |||
3ffa4a7fd3 | |||
826a09a473 | |||
100aefcee4 | |||
409c089d11 | |||
8c32dc2670 | |||
a67f99b665 | |||
756a01d270 | |||
efc7467a99 | |||
99e1a66da8 | |||
370f8c1008 | |||
f039998d71 | |||
eb594af9d8 | |||
bb8596fa87 | |||
a85539a686 | |||
ada6a4bad0 | |||
948104a9a6 | |||
7a8b12b372 | |||
26d644a982 | |||
d6750cd84b | |||
f3601ec43e | |||
15aaa28a4f | |||
ce5b0fc9e2 | |||
64290b72c0 | |||
570c6fe0d1 | |||
146dbd1b89 | |||
e17d43dcb6 | |||
daacbf6f7b | |||
32bb6d4ccb | |||
23dafb53d1 | |||
3aa853f953 | |||
409f41d3bf | |||
c4b8a71c5a | |||
3b20a109f6 | |||
6d93ae9f2d | |||
ad2f143f15 | |||
03590c7360 | |||
e3fca682d6 | |||
c94bc3ef90 | |||
731be0d5e3 | |||
a49edefbd1 | |||
e3a34399e5 | |||
ec6729db4d | |||
72b28f1d75 | |||
bdf0ac722b | |||
98aebba677 | |||
9912a623b1 | |||
98975edca1 | |||
218df461e1 | |||
86d871537b | |||
11f2ffe64d | |||
8fd7152c41 | |||
0f3d9f6c2c | |||
c2a633b549 | |||
7040d422a2 | |||
ba1380ec4c | |||
26f37f58e5 | |||
e59bdcdfbc | |||
c1ca303310 | |||
8afb515231 | |||
5eecf11b1a | |||
37ac10acd2 | |||
8b0ba0b84a | |||
cdead6fdf8 | |||
da6067ec43 | |||
a98e18d511 | |||
6394adb0f0 | |||
bc5dc02fc9 | |||
de5fd5fce0 | |||
a6d4fab595 | |||
f549b042d6 | |||
0c34c87d75 | |||
3baf6ad015 | |||
196182f9a8 | |||
379f57be2c | |||
0d8f5ee66c | |||
79492efbb4 | |||
decbbdc81b | |||
41d976d362 | |||
f94ecbfbfc | |||
f4f1e11b31 | |||
e38d52fb1b | |||
419096f603 | |||
76a53bb1f6 | |||
435aafeaae | |||
8a1603a661 | |||
66e34b7be9 | |||
9940024a01 | |||
ed217bae33 | |||
16e850e7ee | |||
850a073594 | |||
66dae6d3f8 | |||
9a50c05205 | |||
379e83a764 | |||
44da0eb50a | |||
deb652ab37 | |||
0ea67ea89a | |||
51b82df5fd | |||
3d1b009bdb | |||
46aef2c00e | |||
7d607b7348 | |||
4cb1ea94e5 | |||
729061951f | |||
38502f10bf | |||
a738dbfa9a | |||
95562a97ad | |||
c13615c4df | |||
eb6b1c1f57 | |||
64fbdf6064 | |||
47551e93b3 | |||
7a53363b1b | |||
ea0ca8c88b | |||
a10c3b061a | |||
3165d9f459 | |||
28d7ca8ca0 | |||
3f0a2bec48 | |||
d28437b658 | |||
a6d2c8b18a | |||
0c884576bd | |||
380f0662a4 | |||
bd83738168 | |||
fa68a1357d | |||
43f3917b09 | |||
8ebb749732 | |||
827ea47468 | |||
c64d466ce1 | |||
c8ae3d688f | |||
4a971841a1 | |||
9472f855b6 | |||
7736a2bf65 | |||
bef3aeed72 | |||
aacd089123 | |||
d281e929ae | |||
729ea0abff | |||
a75e0c59af | |||
b429eed46c | |||
92d2616dda | |||
37ccd33d5c | |||
20326fdaa0 | |||
6a97a0c7c8 | |||
0928b842ad | |||
e0200b2111 | |||
18a13911ba | |||
2c53d17489 | |||
2eb28c4e89 | |||
2187d9cd7e | |||
e5e6f7963b | |||
900c6e9a26 | |||
f0ee1e8aad | |||
5c6b4b5779 | |||
9b330ffd3e | |||
97d27e8110 | |||
525e3067c9 | |||
0c1fa05e9c | |||
946f3129b8 | |||
355de3fa04 | |||
61e168e444 | |||
70fa48ab07 | |||
dde95c2b61 | |||
2ca79ef898 | |||
63e3c96671 | |||
f0351489be | |||
5149d0e382 | |||
ec8982a921 | |||
cc338c2173 | |||
85b5bb438e | |||
9f727789ca | |||
b308b91f44 | |||
fc3ef3a126 | |||
3f47f89b56 | |||
e784b2dfb8 | |||
c0947b8822 | |||
a0b61bec12 | |||
d3620c00e2 | |||
0ffcee5faf | |||
c2ed8b40f0 | |||
feb1dd4715 | |||
540961ff9e | |||
5692a6201e |
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,4 +1,4 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
* text=auto eol=lf
|
||||
|
||||
static/webui/libs/ linguist-vendored
|
||||
|
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@ -15,11 +15,12 @@ jobs:
|
||||
- run: npm run verify
|
||||
- run: npm run lint:ci
|
||||
- run: npm run prettier
|
||||
- run: npm run update-translations
|
||||
- name: Fail if there are uncommitted changes
|
||||
run: |
|
||||
if [[ -n "$(git status --porcelain)" ]]; then
|
||||
echo "Uncommitted changes detected:"
|
||||
git status
|
||||
git diff
|
||||
git --no-pager diff
|
||||
exit 1
|
||||
fi
|
||||
|
49
Dockerfile
49
Dockerfile
@ -5,17 +5,44 @@ ENV APP_MY_ADDRESS=localhost
|
||||
ENV APP_HTTP_PORT=80
|
||||
ENV APP_HTTPS_PORT=443
|
||||
ENV APP_AUTO_CREATE_ACCOUNT=true
|
||||
ENV APP_SKIP_STORY_MODE_CHOICE=true
|
||||
ENV APP_SKIP_TUTORIAL=true
|
||||
ENV APP_SKIP_ALL_DIALOGUE=true
|
||||
ENV APP_UNLOCK_ALL_SCANS=true
|
||||
ENV APP_UNLOCK_ALL_MISSIONS=true
|
||||
ENV APP_INFINITE_RESOURCES=true
|
||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=true
|
||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=true
|
||||
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=true
|
||||
ENV APP_UNLOCK_ALL_SKINS=true
|
||||
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=true
|
||||
ENV APP_SKIP_TUTORIAL=false
|
||||
ENV APP_SKIP_ALL_DIALOGUE=false
|
||||
ENV APP_UNLOCK_ALL_SCANS=false
|
||||
ENV APP_UNLOCK_ALL_MISSIONS=false
|
||||
ENV APP_INFINITE_CREDITS=false
|
||||
ENV APP_INFINITE_PLATINUM=false
|
||||
ENV APP_INFINITE_ENDO=false
|
||||
ENV APP_INFINITE_REGAL_AYA=false
|
||||
ENV APP_INFINITE_HELMINTH_MATERIALS=false
|
||||
ENV APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS=false
|
||||
ENV APP_DONT_SUBTRACT_VOIDTRACES=false
|
||||
ENV APP_DONT_SUBTRACT_CONSUMABLES=false
|
||||
ENV APP_UNLOCK_ALL_SHIP_FEATURES=false
|
||||
ENV APP_UNLOCK_ALL_SHIP_DECORATIONS=false
|
||||
ENV APP_UNLOCK_ALL_FLAVOUR_ITEMS=false
|
||||
ENV APP_UNLOCK_ALL_SKINS=false
|
||||
ENV APP_UNLOCK_ALL_CAPTURA_SCENES=false
|
||||
ENV APP_UNIVERSAL_POLARITY_EVERYWHERE=false
|
||||
ENV APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE=false
|
||||
ENV APP_UNLOCK_EXILUS_EVERYWHERE=false
|
||||
ENV APP_UNLOCK_ARCANES_EVERYWHERE=false
|
||||
ENV APP_NO_DAILY_FOCUS_LIMIT=false
|
||||
ENV APP_NO_ARGON_CRYSTAL_DECAY=false
|
||||
ENV APP_NO_MASTERY_RANK_UP_COOLDOWN=false
|
||||
ENV APP_NO_VENDOR_PURCHASE_LIMITS=true
|
||||
ENV APP_NO_DEATH_MARKS=false
|
||||
ENV APP_NO_KIM_COOLDOWNS=false
|
||||
ENV APP_SYNDICATE_MISSIONS_REPEATABLE=false
|
||||
ENV APP_INSTANT_FINISH_RIVEN_CHALLENGE=false
|
||||
ENV APP_INSTANT_RESOURCE_EXTRACTOR_DRONES=false
|
||||
ENV APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE=false
|
||||
ENV APP_SKIP_CLAN_KEY_CRAFTING=false
|
||||
ENV APP_NO_DOJO_ROOM_BUILD_STAGE=false
|
||||
ENV APP_NO_DECO_BUILD_STAGE=false
|
||||
ENV APP_FAST_DOJO_ROOM_DESTRUCTION=false
|
||||
ENV APP_NO_DOJO_RESEARCH_COSTS=false
|
||||
ENV APP_NO_DOJO_RESEARCH_TIME=false
|
||||
ENV APP_FAST_CLAN_ASCENSION=false
|
||||
ENV APP_SPOOF_MASTERY_RANK=-1
|
||||
|
||||
RUN apk add --no-cache bash sed wget jq
|
||||
|
@ -10,6 +10,8 @@ To get an idea of what functionality you can expect to be missing [have a look t
|
||||
|
||||
## config.json
|
||||
|
||||
SpaceNinjaServer requires a `config.json`. To set it up, you can copy the [config.json.example](config.json.example), which has most cheats disabled.
|
||||
|
||||
- `logger.level` can be `fatal`, `error`, `warn`, `info`, `http`, `debug`, or `trace`.
|
||||
- `myIrcAddresses` can be used to point to an IRC server. If not provided, defaults to `[ myAddress ]`.
|
||||
- `worldState.lockTime` will lock the time provided in worldState if nonzero, e.g. `1743202800` for night in POE.
|
||||
|
@ -1,7 +1,6 @@
|
||||
@echo off
|
||||
|
||||
echo Updating SpaceNinjaServer...
|
||||
git config remote.origin.url https://openwf.io/SpaceNinjaServer.git
|
||||
git fetch --prune
|
||||
git stash
|
||||
git reset --hard origin/main
|
||||
|
@ -19,6 +19,9 @@
|
||||
"infiniteEndo": false,
|
||||
"infiniteRegalAya": false,
|
||||
"infiniteHelminthMaterials": false,
|
||||
"claimingBlueprintRefundsIngredients": false,
|
||||
"dontSubtractVoidTraces": false,
|
||||
"dontSubtractConsumables": false,
|
||||
"unlockAllShipFeatures": false,
|
||||
"unlockAllShipDecorations": false,
|
||||
"unlockAllFlavourItems": false,
|
||||
@ -29,11 +32,19 @@
|
||||
"unlockExilusEverywhere": false,
|
||||
"unlockArcanesEverywhere": false,
|
||||
"noDailyStandingLimits": false,
|
||||
"noDailyFocusLimit": false,
|
||||
"noArgonCrystalDecay": false,
|
||||
"noMasteryRankUpCooldown": false,
|
||||
"noVendorPurchaseLimits": true,
|
||||
"noDeathMarks": false,
|
||||
"noKimCooldowns": false,
|
||||
"syndicateMissionsRepeatable": false,
|
||||
"instantFinishRivenChallenge": false,
|
||||
"instantResourceExtractorDrones": false,
|
||||
"noResourceExtractorDronesDamage": false,
|
||||
"skipClanKeyCrafting": false,
|
||||
"noDojoRoomBuildStage": false,
|
||||
"noDecoBuildStage": false,
|
||||
"fastDojoRoomDestruction": false,
|
||||
"noDojoResearchCosts": false,
|
||||
"noDojoResearchTime": false,
|
||||
|
@ -12,19 +12,44 @@ services:
|
||||
# APP_HTTP_PORT: 80
|
||||
# APP_HTTPS_PORT: 443
|
||||
# APP_AUTO_CREATE_ACCOUNT: true
|
||||
# APP_SKIP_STORY_MODE_CHOICE: true
|
||||
# APP_SKIP_TUTORIAL: true
|
||||
# APP_SKIP_ALL_DIALOGUE: true
|
||||
# APP_UNLOCK_ALL_SCANS: true
|
||||
# APP_UNLOCK_ALL_MISSIONS: true
|
||||
# APP_UNLOCK_ALL_QUESTS: true
|
||||
# APP_COMPLETE_ALL_QUESTS: true
|
||||
# APP_INFINITE_RESOURCES: true
|
||||
# APP_UNLOCK_ALL_SHIP_FEATURES: true
|
||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: true
|
||||
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: true
|
||||
# APP_UNLOCK_ALL_SKINS: true
|
||||
# APP_UNIVERSAL_POLARITY_EVERYWHERE: true
|
||||
# APP_SKIP_TUTORIAL: false
|
||||
# APP_SKIP_ALL_DIALOGUE: false
|
||||
# APP_UNLOCK_ALL_SCANS: false
|
||||
# APP_UNLOCK_ALL_MISSIONS: false
|
||||
# APP_INFINITE_CREDITS: false
|
||||
# APP_INFINITE_PLATINUM: false
|
||||
# APP_INFINITE_ENDO: false
|
||||
# APP_INFINITE_REGAL_AYA: false
|
||||
# APP_INFINITE_HELMINTH_MATERIALS: false
|
||||
# APP_CLAIMING_BLUEPRINT_REFUNDS_INGREDIENTS: false
|
||||
# APP_DONT_SUBTRACT_VOIDTRACES: false
|
||||
# APP_DONT_SUBTRACT_CONSUMABLES: false
|
||||
# APP_UNLOCK_ALL_SHIP_FEATURES: false
|
||||
# APP_UNLOCK_ALL_SHIP_DECORATIONS: false
|
||||
# APP_UNLOCK_ALL_FLAVOUR_ITEMS: false
|
||||
# APP_UNLOCK_ALL_SKINS: false
|
||||
# APP_UNLOCK_ALL_CAPTURA_SCENES: false
|
||||
# APP_UNIVERSAL_POLARITY_EVERYWHERE: false
|
||||
# APP_UNLOCK_DOUBLE_CAPACITY_POTATOES_EVERYWHERE: false
|
||||
# APP_UNLOCK_EXILUS_EVERYWHERE: false
|
||||
# APP_UNLOCK_ARCANES_EVERYWHERE: false
|
||||
# APP_NO_DAILY_FOCUS_LIMIT: false
|
||||
# APP_NO_ARGON_CRYSTAL_DECAY: false
|
||||
# APP_NO_MASTERY_RANK_UP_COOLDOWN: false
|
||||
# APP_NO_VENDOR_PURCHASE_LIMITS: true
|
||||
# APP_NO_DEATH_MARKS: false
|
||||
# APP_NO_KIM_COOLDOWNS: false
|
||||
# APP_SYNDICATE_MISSIONS_REPEATABLE: false
|
||||
# APP_INSTANT_FINISH_RIVEN_CHALLENGE: false
|
||||
# APP_INSTANT_RESOURCE_EXTRACTOR_DRONES: false
|
||||
# APP_NO_RESOURCE_EXTRACTOR_DRONES_DAMAGE: false
|
||||
# APP_SKIP_CLAN_KEY_CRAFTING: false
|
||||
# APP_NO_DOJO_ROOM_BUILD_STAGE: false
|
||||
# APP_NO_DECO_BUILD_STAGE: false
|
||||
# APP_FAST_DOJO_ROOM_DESTRUCTION: false
|
||||
# APP_NO_DOJO_RESEARCH_COSTS: false
|
||||
# APP_NO_DOJO_RESEARCH_TIME: false
|
||||
# APP_FAST_CLAN_ASCENSION: false
|
||||
# APP_SPOOF_MASTERY_RANK: -1
|
||||
volumes:
|
||||
- ./docker-data/static:/app/static/data
|
||||
|
@ -19,5 +19,6 @@ do
|
||||
mv config.tmp config.json
|
||||
done
|
||||
|
||||
npm install
|
||||
exec npm run dev
|
||||
npm i --omit=dev
|
||||
npm run build
|
||||
exec npm run start
|
||||
|
506
package-lock.json
generated
506
package-lock.json
generated
@ -13,12 +13,12 @@
|
||||
"@types/morgan": "^1.9.9",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.2.2",
|
||||
"json-with-bigint": "^3.4.4",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
"typescript": ">=5.5 <5.6.0",
|
||||
"warframe-public-export-plus": "^0.5.52",
|
||||
"typescript": "^5.5",
|
||||
"warframe-public-export-plus": "^0.5.64",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
@ -72,9 +72,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint-community/eslint-utils": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.0.tgz",
|
||||
"integrity": "sha512-RoV8Xs9eNwiDvhv7M+xcL4PWyRyIXRY/FLp3buU4h1EYfdF7unWUy3dOjPqb3C7rMUewIcqwW850PgS8h1o1yg==",
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
|
||||
"integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -249,9 +249,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz",
|
||||
"integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.2.tgz",
|
||||
"integrity": "sha512-EB0O3SCSNRUFk66iRCpI+cXzIjdswfCs7F6nOC3RAGJ7xr5YhaicvsRwJ9eyzYvYRlCSDUO/c7g4yNulxKC1WA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"sparse-bitfield": "^3.0.3"
|
||||
@ -296,22 +296,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.0.tgz",
|
||||
"integrity": "sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==",
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.4.tgz",
|
||||
"integrity": "sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
"url": "https://opencollective.com/pkgr"
|
||||
}
|
||||
},
|
||||
"node_modules/@rxliuli/tsgo": {
|
||||
"version": "2025.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@rxliuli/tsgo/-/tsgo-2025.3.31.tgz",
|
||||
"integrity": "sha512-jEistRy/+Mu79rDv/Q8xn2yIM56WF3rfQOkwrbtivumij5HBVTfY4W3EYNL3N7rop7yg9Trew3joDohDoxQ2Ow==",
|
||||
"version": "2025.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@rxliuli/tsgo/-/tsgo-2025.5.8.tgz",
|
||||
"integrity": "sha512-P3/qxcUgiWz6nSJslJ5mMeAEqacK8LQSoOhdvHxI1/d0Xqxt2Qp6/nmhWuOlyqnCyAaIoXgoiUshiXWBGr2jaw==",
|
||||
"cpu": [
|
||||
"x64",
|
||||
"ia32",
|
||||
@ -382,14 +382,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
|
||||
"integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz",
|
||||
"integrity": "sha512-UZUw8vjpWFXuDnjFTh7/5c2TWDlQqeXHi6hcN7F2XSVT5P+WmUnnbFS3KA6Jnc6IsEqI2qCVu2bK0R0J4A8ZQQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/body-parser": "*",
|
||||
"@types/express-serve-static-core": "^5.0.0",
|
||||
"@types/qs": "*",
|
||||
"@types/serve-static": "*"
|
||||
}
|
||||
},
|
||||
@ -427,12 +426,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
|
||||
"integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
|
||||
"version": "22.15.16",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.16.tgz",
|
||||
"integrity": "sha512-3pr+KjwpVujqWqOKT8mNR+rd09FqhBLwg+5L/4t0cNYBzm/yEiYGCxWttjaPBsLtAo+WFNoXzGJfolM1JuRXoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
@ -504,21 +503,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.28.0.tgz",
|
||||
"integrity": "sha512-lvFK3TCGAHsItNdWZ/1FkvpzCxTHUVuFrdnOGLMa0GGCFIbCgQWVk3CzCGdA7kM3qGVc+dfW9tr0Z/sHnGDFyg==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.0.tgz",
|
||||
"integrity": "sha512-/jU9ettcntkBFmWUzzGgsClEi2ZFiikMX5eEQsmxIAWMOn4H3D4rvHssstmAHGVvrYnaMqdWWWg0b5M6IN/MTQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.28.0",
|
||||
"@typescript-eslint/type-utils": "8.28.0",
|
||||
"@typescript-eslint/utils": "8.28.0",
|
||||
"@typescript-eslint/visitor-keys": "8.28.0",
|
||||
"@typescript-eslint/scope-manager": "8.32.0",
|
||||
"@typescript-eslint/type-utils": "8.32.0",
|
||||
"@typescript-eslint/utils": "8.32.0",
|
||||
"@typescript-eslint/visitor-keys": "8.32.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.0.1"
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -534,16 +533,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.28.0.tgz",
|
||||
"integrity": "sha512-LPcw1yHD3ToaDEoljFEfQ9j2xShY367h7FZ1sq5NJT9I3yj4LHer1Xd1yRSOdYy9BpsrxU7R+eoDokChYM53lQ==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.32.0.tgz",
|
||||
"integrity": "sha512-B2MdzyWxCE2+SqiZHAjPphft+/2x2FlO9YBx7eKE1BCb+rqBlQdhtAEhzIEdozHd55DXPmxBdpMygFJjfjjA9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.28.0",
|
||||
"@typescript-eslint/types": "8.28.0",
|
||||
"@typescript-eslint/typescript-estree": "8.28.0",
|
||||
"@typescript-eslint/visitor-keys": "8.28.0",
|
||||
"@typescript-eslint/scope-manager": "8.32.0",
|
||||
"@typescript-eslint/types": "8.32.0",
|
||||
"@typescript-eslint/typescript-estree": "8.32.0",
|
||||
"@typescript-eslint/visitor-keys": "8.32.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -559,14 +558,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.28.0.tgz",
|
||||
"integrity": "sha512-u2oITX3BJwzWCapoZ/pXw6BCOl8rJP4Ij/3wPoGvY8XwvXflOzd1kLrDUUUAIEdJSFh+ASwdTHqtan9xSg8buw==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.0.tgz",
|
||||
"integrity": "sha512-jc/4IxGNedXkmG4mx4nJTILb6TMjL66D41vyeaPWvDUmeYQzF3lKtN15WsAeTr65ce4mPxwopPSo1yUUAWw0hQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.28.0",
|
||||
"@typescript-eslint/visitor-keys": "8.28.0"
|
||||
"@typescript-eslint/types": "8.32.0",
|
||||
"@typescript-eslint/visitor-keys": "8.32.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -577,16 +576,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.28.0.tgz",
|
||||
"integrity": "sha512-oRoXu2v0Rsy/VoOGhtWrOKDiIehvI+YNrDk5Oqj40Mwm0Yt01FC/Q7nFqg088d3yAsR1ZcZFVfPCTTFCe/KPwg==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.32.0.tgz",
|
||||
"integrity": "sha512-t2vouuYQKEKSLtJaa5bB4jHeha2HJczQ6E5IXPDPgIty9EqcJxpr1QHQ86YyIPwDwxvUmLfP2YADQ5ZY4qddZg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.28.0",
|
||||
"@typescript-eslint/utils": "8.28.0",
|
||||
"@typescript-eslint/typescript-estree": "8.32.0",
|
||||
"@typescript-eslint/utils": "8.32.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^2.0.1"
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -601,9 +600,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.28.0.tgz",
|
||||
"integrity": "sha512-bn4WS1bkKEjx7HqiwG2JNB3YJdC1q6Ue7GyGlwPHyt0TnVq6TtD/hiOdTZt71sq0s7UzqBFXD8t8o2e63tXgwA==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.0.tgz",
|
||||
"integrity": "sha512-O5Id6tGadAZEMThM6L9HmVf5hQUXNSxLVKeGJYWNhhVseps/0LddMkp7//VDkzwJ69lPL0UmZdcZwggj9akJaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -615,20 +614,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.28.0.tgz",
|
||||
"integrity": "sha512-H74nHEeBGeklctAVUvmDkxB1mk+PAZ9FiOMPFncdqeRBXxk1lWSYraHw8V12b7aa6Sg9HOBNbGdSHobBPuQSuA==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.0.tgz",
|
||||
"integrity": "sha512-pU9VD7anSCOIoBFnhTGfOzlVFQIA1XXiQpH/CezqOBaDppRwTglJzCC6fUQGpfwey4T183NKhF1/mfatYmjRqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.28.0",
|
||||
"@typescript-eslint/visitor-keys": "8.28.0",
|
||||
"@typescript-eslint/types": "8.32.0",
|
||||
"@typescript-eslint/visitor-keys": "8.32.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
"minimatch": "^9.0.4",
|
||||
"semver": "^7.6.0",
|
||||
"ts-api-utils": "^2.0.1"
|
||||
"ts-api-utils": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -642,16 +641,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.28.0.tgz",
|
||||
"integrity": "sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.0.tgz",
|
||||
"integrity": "sha512-8S9hXau6nQ/sYVtC3D6ISIDoJzS1NsCK+gluVhLN2YkBPX+/1wkwyUiDKnxRh15579WoOIyVWnoyIf3yGI9REw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.28.0",
|
||||
"@typescript-eslint/types": "8.28.0",
|
||||
"@typescript-eslint/typescript-estree": "8.28.0"
|
||||
"@eslint-community/eslint-utils": "^4.7.0",
|
||||
"@typescript-eslint/scope-manager": "8.32.0",
|
||||
"@typescript-eslint/types": "8.32.0",
|
||||
"@typescript-eslint/typescript-estree": "8.32.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -666,13 +665,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.28.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.28.0.tgz",
|
||||
"integrity": "sha512-hbn8SZ8w4u2pRwgQ1GlUrPKE+t2XvcCW5tTRF7j6SMYIuYG37XuzIW44JCZPa36evi0Oy2SnM664BlIaAuQcvg==",
|
||||
"version": "8.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.0.tgz",
|
||||
"integrity": "sha512-1rYQTCLFFzOI5Nl0c8LUpJT8HxpwVRn9E4CkMsYfuN6ctmQqExjSTzzSk0Tz2apmXy7WU6/6fyaZVVA/thPN+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.28.0",
|
||||
"@typescript-eslint/types": "8.32.0",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -868,16 +867,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.1.0.tgz",
|
||||
"integrity": "sha512-/hPxh61E+ll0Ujp24Ilm64cykicul1ypfwjVttduAiEdtnJFvLePSrIPk+HMImtNv5270wOGCb1Tns2rybMkoQ==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "^3.1.2",
|
||||
"content-type": "^1.0.5",
|
||||
"debug": "^4.4.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"iconv-lite": "^0.5.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"qs": "^6.14.0",
|
||||
"raw-body": "^3.0.0",
|
||||
@ -887,21 +886,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser/node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
@ -1137,9 +1121,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
|
||||
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@ -1221,16 +1205,6 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
@ -1406,14 +1380,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz",
|
||||
"integrity": "sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.0.tgz",
|
||||
"integrity": "sha512-BvQOvUhkVQM1i63iMETK9Hjud9QhqBnbtT1Zc642p9ynzBuCe5pybkOnvqZIBypXmMlsGcnU4HZ8sCTPfpAexA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"prettier-linter-helpers": "^1.0.0",
|
||||
"synckit": "^0.10.2"
|
||||
"synckit": "^0.11.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
@ -1564,71 +1538,47 @@
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.0.1.tgz",
|
||||
"integrity": "sha512-ORF7g6qGnD+YtUG9yx4DFoqCShNMmUKiXuT5oWMHiOvt/4WFbHC6yCwQMTSBMno7AqntNCAzzcnnjowRkTL9eQ==",
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz",
|
||||
"integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "^2.0.0",
|
||||
"body-parser": "^2.0.1",
|
||||
"body-parser": "^2.2.0",
|
||||
"content-disposition": "^1.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.7.1",
|
||||
"content-type": "^1.0.5",
|
||||
"cookie": "^0.7.1",
|
||||
"cookie-signature": "^1.2.1",
|
||||
"debug": "4.3.6",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "^2.0.0",
|
||||
"fresh": "2.0.0",
|
||||
"http-errors": "2.0.0",
|
||||
"debug": "^4.4.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"finalhandler": "^2.1.0",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"merge-descriptors": "^2.0.0",
|
||||
"methods": "~1.1.2",
|
||||
"mime-types": "^3.0.0",
|
||||
"on-finished": "2.4.1",
|
||||
"once": "1.4.0",
|
||||
"parseurl": "~1.3.3",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "6.13.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"router": "^2.0.0",
|
||||
"safe-buffer": "5.2.1",
|
||||
"on-finished": "^2.4.1",
|
||||
"once": "^1.4.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"proxy-addr": "^2.0.7",
|
||||
"qs": "^6.14.0",
|
||||
"range-parser": "^1.2.1",
|
||||
"router": "^2.2.0",
|
||||
"send": "^1.1.0",
|
||||
"serve-static": "^2.1.0",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "2.0.1",
|
||||
"type-is": "^2.0.0",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
"serve-static": "^2.2.0",
|
||||
"statuses": "^2.0.1",
|
||||
"type-is": "^2.0.1",
|
||||
"vary": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/debug": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
|
||||
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/express/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@ -2031,12 +1981,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
|
||||
"integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -2244,9 +2194,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-with-bigint": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.2.2.tgz",
|
||||
"integrity": "sha512-zbaZ+MZ2PEcAD0yINpxvlLMKzoC1GPqy5p8/ZgzRJRoB+NCczGrTX9x2ashSvkfYTitQKbV5aYQCJCiHxrzF2w==",
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.4.4.tgz",
|
||||
"integrity": "sha512-AhpYAAaZsPjU7smaBomDt1SOQshi9rEm6BlTbfVwsG1vNmeHKtEedJi62sHZzJTyKNtwzmNnrsd55kjwJ7054A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json5": {
|
||||
@ -2394,15 +2344,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
@ -2418,21 +2359,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.53.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz",
|
||||
"integrity": "sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==",
|
||||
"version": "1.54.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
|
||||
"integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.0.tgz",
|
||||
"integrity": "sha512-XqoSHeCGjVClAmoGFG3lVFqQFRIrTVw2OH3axRqAcfaw+gHWIfnASS92AV+Rl/mk0MupgZTRHQOjxY6YVnzK5w==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz",
|
||||
"integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "^1.53.0"
|
||||
"mime-db": "^1.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
@ -2487,9 +2428,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz",
|
||||
"integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==",
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.16.0.tgz",
|
||||
"integrity": "sha512-D1PNcdT0y4Grhou5Zi/qgipZOYeWrhLEpk33n3nm6LGtz61jvO88WlrWCK/bigMjpnOdAUKKQwsGIl0NtWMyYw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@mongodb-js/saslprep": "^1.1.9",
|
||||
@ -2543,14 +2484,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mongoose": {
|
||||
"version": "8.12.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz",
|
||||
"integrity": "sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==",
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.14.1.tgz",
|
||||
"integrity": "sha512-ijd12vjqUBr5Btqqflu0c/o8Oed5JpdaE0AKO9TjGxCgywYwnzt6ynR1ySjhgxGxrYVeXC0t1P11f1zlRiE93Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bson": "^6.10.3",
|
||||
"kareem": "2.6.3",
|
||||
"mongodb": "~6.14.0",
|
||||
"mongodb": "~6.16.0",
|
||||
"mpath": "0.9.0",
|
||||
"mquery": "5.0.0",
|
||||
"ms": "2.1.3",
|
||||
@ -2922,12 +2863,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.13.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
|
||||
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
@ -2981,16 +2922,18 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
@ -3066,11 +3009,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.1.0.tgz",
|
||||
"integrity": "sha512-/m/NSLxeYEgWNtyC+WtNHCF7jbGxOibVWKnn+1Psff4dJGOfoXP+MuC/f2CwSmyiHdOIzYnYFp4W6GxWfekaLA==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
"integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.4.0",
|
||||
"depd": "^2.0.0",
|
||||
"is-promise": "^4.0.0",
|
||||
"parseurl": "^1.3.3",
|
||||
"path-to-regexp": "^8.0.0"
|
||||
@ -3152,19 +3097,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz",
|
||||
"integrity": "sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz",
|
||||
"integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.5",
|
||||
"destroy": "^1.2.0",
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"etag": "^1.8.1",
|
||||
"fresh": "^0.5.2",
|
||||
"fresh": "^2.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"mime-types": "^2.1.35",
|
||||
"mime-types": "^3.0.1",
|
||||
"ms": "^2.1.3",
|
||||
"on-finished": "^2.4.1",
|
||||
"range-parser": "^1.2.1",
|
||||
@ -3174,46 +3118,16 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.1.0.tgz",
|
||||
"integrity": "sha512-A3We5UfEjG8Z7VkDv6uItWw6HY2bBSBJT1KtVESn6EOoOr2jAxNhxWCLY3jDE2WcuHXByWju74ck3ZgLwL8xmA==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz",
|
||||
"integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "^2.0.0",
|
||||
"escape-html": "^1.0.3",
|
||||
"parseurl": "^1.3.3",
|
||||
"send": "^1.0.0"
|
||||
"send": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
@ -3383,6 +3297,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
@ -3446,20 +3369,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/synckit": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.10.3.tgz",
|
||||
"integrity": "sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==",
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.4.tgz",
|
||||
"integrity": "sha512-Q/XQKRaJiLiFIBNN+mndW7S/RHxvwzuZS6ZwmRzUBqJBv/5QIKCEwkBC8GBf8EQJKYnaFs0wOZbKTXBPj8L9oQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pkgr/core": "^0.2.0",
|
||||
"@pkgr/core": "^0.2.3",
|
||||
"tslib": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/unts"
|
||||
"url": "https://opencollective.com/synckit"
|
||||
}
|
||||
},
|
||||
"node_modules/text-hex": {
|
||||
@ -3498,9 +3421,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
|
||||
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
|
||||
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"punycode": "^2.3.1"
|
||||
@ -3706,9 +3629,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.0.tgz",
|
||||
"integrity": "sha512-gd0sGezQYCbWSbkZr75mln4YBidWUN60+devscpLF5mtRDUpiaTvKpBNrdaCvel1NdR2k6vclXybU5fBd2i+nw==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
|
||||
"integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"content-type": "^1.0.5",
|
||||
@ -3720,9 +3643,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.5.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
|
||||
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@ -3733,9 +3656,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.20.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
|
||||
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
@ -3763,15 +3686,6 @@
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
@ -3789,9 +3703,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/warframe-public-export-plus": {
|
||||
"version": "0.5.52",
|
||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.52.tgz",
|
||||
"integrity": "sha512-mJyQbTFMDwgBSkhUYJzcfJg9qrMTrL1pyZuAxV/Dov68xUikK5zigQSYM3ZkKYbhwBtg0Bx/+7q9GAmPzGaRhA=="
|
||||
"version": "0.5.64",
|
||||
"resolved": "https://registry.npmjs.org/warframe-public-export-plus/-/warframe-public-export-plus-0.5.64.tgz",
|
||||
"integrity": "sha512-JyHRtYumfwQ1Iog2unzlBWfQHJlZER+iUISquyFFv0Qqtv2QsNzFv2AbV7sCaqgDcE8tw6e5/YqGgfI0m403/g=="
|
||||
},
|
||||
"node_modules/warframe-riven-info": {
|
||||
"version": "0.1.2",
|
||||
@ -3808,12 +3722,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "14.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz",
|
||||
"integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==",
|
||||
"version": "14.2.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
|
||||
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "^5.0.0",
|
||||
"tr46": "^5.1.0",
|
||||
"webidl-conversions": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -3890,52 +3804,6 @@
|
||||
"node": ">= 12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/winston-transport/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/winston-transport/node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/winston/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/winston/node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"description": "WF Emulator",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"start": "node --import ./build/src/pathman.js build/src/index.js",
|
||||
"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",
|
||||
"verify": "tsgo --noEmit",
|
||||
@ -20,12 +20,12 @@
|
||||
"@types/morgan": "^1.9.9",
|
||||
"crc-32": "^1.2.2",
|
||||
"express": "^5",
|
||||
"json-with-bigint": "^3.2.2",
|
||||
"json-with-bigint": "^3.4.4",
|
||||
"mongoose": "^8.11.0",
|
||||
"morgan": "^1.10.0",
|
||||
"ncp": "^2.0.0",
|
||||
"typescript": ">=5.5 <5.6.0",
|
||||
"warframe-public-export-plus": "^0.5.52",
|
||||
"typescript": "^5.5",
|
||||
"warframe-public-export-plus": "^0.5.64",
|
||||
"warframe-riven-info": "^0.1.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0"
|
||||
|
@ -4,7 +4,7 @@
|
||||
const fs = require("fs");
|
||||
|
||||
function extractStrings(content) {
|
||||
const regex = /([a-zA-Z_]+): `([^`]*)`,/g;
|
||||
const regex = /([a-zA-Z0-9_]+): `([^`]*)`,/g;
|
||||
let matches;
|
||||
const strings = {};
|
||||
while ((matches = regex.exec(content)) !== null) {
|
||||
@ -15,7 +15,7 @@ function extractStrings(content) {
|
||||
|
||||
const source = fs.readFileSync("../static/webui/translations/en.js", "utf8");
|
||||
const sourceStrings = extractStrings(source);
|
||||
const sourceLines = source.split("\n");
|
||||
const sourceLines = source.substring(0, source.length - 1).split("\n");
|
||||
|
||||
fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||
if (fs.lstatSync(`../static/webui/translations/${file}`).isFile() && file !== "en.js") {
|
||||
@ -36,7 +36,7 @@ fs.readdirSync("../static/webui/translations").forEach(file => {
|
||||
fs.writeSync(fileHandle, ` ${key}: \`[UNTRANSLATED] ${value}\`,\n`);
|
||||
}
|
||||
});
|
||||
} else if (line.length) {
|
||||
} else {
|
||||
fs.writeSync(fileHandle, line + "\n");
|
||||
}
|
||||
});
|
||||
|
13
src/app.ts
13
src/app.ts
@ -16,17 +16,24 @@ import { webuiRouter } from "@/src/routes/webui";
|
||||
const app = express();
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
// 38.5.0 introduced "ezip" for encrypted body blobs.
|
||||
// 38.5.0 introduced "ezip" for encrypted body blobs and "e" for request verification only (encrypted body blobs with no application data).
|
||||
// The bootstrapper decrypts it for us but having an unsupported Content-Encoding here would still be an issue for Express, so removing it.
|
||||
if (req.headers["content-encoding"] == "ezip") {
|
||||
if (req.headers["content-encoding"] == "ezip" || req.headers["content-encoding"] == "e") {
|
||||
req.headers["content-encoding"] = undefined;
|
||||
}
|
||||
|
||||
// U18 uses application/x-www-form-urlencoded even tho the data is JSON which Express doesn't like.
|
||||
// U17 sets no Content-Type at all, which Express also doesn't like.
|
||||
if (!req.headers["content-type"] || req.headers["content-type"] == "application/x-www-form-urlencoded") {
|
||||
req.headers["content-type"] = "application/octet-stream";
|
||||
}
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(bodyParser.raw());
|
||||
app.use(express.json({ limit: "4mb" }));
|
||||
app.use(bodyParser.text());
|
||||
app.use(bodyParser.text({ limit: "4mb" }));
|
||||
app.use(requestLogger);
|
||||
|
||||
app.use("/api", apiRouter);
|
||||
|
@ -2,15 +2,18 @@ const millisecondsPerSecond = 1000;
|
||||
const secondsPerMinute = 60;
|
||||
const minutesPerHour = 60;
|
||||
const hoursPerDay = 24;
|
||||
const daysPerWeek = 7;
|
||||
|
||||
const unixSecond = millisecondsPerSecond;
|
||||
const unixMinute = secondsPerMinute * millisecondsPerSecond;
|
||||
const unixHour = unixMinute * minutesPerHour;
|
||||
const unixDay = hoursPerDay * unixHour;
|
||||
const unixWeek = daysPerWeek * unixDay;
|
||||
|
||||
export const unixTimesInMs = {
|
||||
second: unixSecond,
|
||||
minute: unixMinute,
|
||||
hour: unixHour,
|
||||
day: unixDay
|
||||
day: unixDay,
|
||||
week: unixWeek
|
||||
};
|
||||
|
@ -1,11 +1,16 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||
import {
|
||||
createVeiledRivenFingerprint,
|
||||
createUnveiledRivenFingerprint,
|
||||
rivenRawToRealWeighted
|
||||
} from "@/src/helpers/rivenHelper";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addMods, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomElement } from "@/src/services/rngService";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportUpgrades } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
|
||||
export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -17,8 +22,10 @@ export const activateRandomModController: RequestHandler = async (req, res) => {
|
||||
ItemCount: -1
|
||||
}
|
||||
]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType]);
|
||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[request.ItemType])!;
|
||||
const fingerprint = config.instantFinishRivenChallenge
|
||||
? createUnveiledRivenFingerprint(ExportUpgrades[rivenType])
|
||||
: createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
const upgradeIndex =
|
||||
inventory.Upgrades.push({
|
||||
ItemType: rivenType,
|
||||
|
60
src/controllers/api/addFriendController.ts
Normal file
60
src/controllers/api/addFriendController.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const addFriendController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const payload = getJSONfromString<IAddFriendRequest>(String(req.body));
|
||||
const promises: Promise<void>[] = [];
|
||||
const newFriends: IFriendInfo[] = [];
|
||||
if (payload.friend == "all") {
|
||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||
Friendship.find({ owner: accountId }, "friend"),
|
||||
Friendship.find({ friend: accountId }, "owner")
|
||||
]);
|
||||
for (const externalFriendship of externalFriendships) {
|
||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||
promises.push(
|
||||
Friendship.insertOne({
|
||||
owner: accountId,
|
||||
friend: externalFriendship.owner,
|
||||
Note: externalFriendship.Note // TOVERIFY: Should the note be copied when accepting a friend request?
|
||||
}) as unknown as Promise<void>
|
||||
);
|
||||
newFriends.push({
|
||||
_id: toOid(externalFriendship.owner)
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const externalFriendship = await Friendship.findOne({ owner: payload.friend, friend: accountId }, "Note");
|
||||
if (externalFriendship) {
|
||||
promises.push(
|
||||
Friendship.insertOne({
|
||||
owner: accountId,
|
||||
friend: payload.friend,
|
||||
Note: externalFriendship.Note
|
||||
}) as unknown as Promise<void>
|
||||
);
|
||||
newFriends.push({
|
||||
_id: { $oid: payload.friend }
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const newFriend of newFriends) {
|
||||
promises.push(addAccountDataToFriendInfo(newFriend));
|
||||
promises.push(addInventoryDataToFriendInfo(newFriend));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
res.json({
|
||||
Friends: newFriends
|
||||
});
|
||||
};
|
||||
|
||||
interface IAddFriendRequest {
|
||||
friend: string; // oid or "all" in which case all=1 is also a query parameter
|
||||
}
|
30
src/controllers/api/addIgnoredUserController.ts
Normal file
30
src/controllers/api/addIgnoredUserController.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Account, Ignore } from "@/src/models/loginModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const addIgnoredUserController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString<IAddIgnoredUserRequest>(String(req.body));
|
||||
const ignoreeAccount = await Account.findOne(
|
||||
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
|
||||
"_id"
|
||||
);
|
||||
if (ignoreeAccount) {
|
||||
await Ignore.create({ ignorer: accountId, ignoree: ignoreeAccount._id });
|
||||
res.json({
|
||||
Ignored: {
|
||||
_id: toOid(ignoreeAccount._id),
|
||||
DisplayName: data.playerName
|
||||
} satisfies IFriendInfo
|
||||
});
|
||||
} else {
|
||||
res.status(400).end();
|
||||
}
|
||||
};
|
||||
|
||||
interface IAddIgnoredUserRequest {
|
||||
playerName: string;
|
||||
}
|
52
src/controllers/api/addPendingFriendController.ts
Normal file
52
src/controllers/api/addPendingFriendController.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { addInventoryDataToFriendInfo, areFriendsOfFriends } from "@/src/services/friendService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const addPendingFriendController: RequestHandler = async (req, res) => {
|
||||
const payload = getJSONfromString<IAddPendingFriendRequest>(String(req.body));
|
||||
|
||||
const account = await Account.findOne({ DisplayName: payload.friend });
|
||||
if (!account) {
|
||||
res.status(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||
if (
|
||||
inventory.Settings?.FriendInvRestriction == "GIFT_MODE_NONE" ||
|
||||
(inventory.Settings?.FriendInvRestriction == "GIFT_MODE_FRIENDS" &&
|
||||
!(await areFriendsOfFriends(account._id, accountId)))
|
||||
) {
|
||||
res.status(400).send("Friend Invite Restriction");
|
||||
return;
|
||||
}
|
||||
|
||||
await Friendship.insertOne({
|
||||
owner: accountId,
|
||||
friend: account._id,
|
||||
Note: payload.message
|
||||
});
|
||||
|
||||
const friendInfo: IFriendInfo = {
|
||||
_id: toOid(account._id),
|
||||
DisplayName: account.DisplayName,
|
||||
LastLogin: toMongoDate(account.LastLogin),
|
||||
Note: payload.message
|
||||
};
|
||||
await addInventoryDataToFriendInfo(friendInfo);
|
||||
res.json({
|
||||
Friend: friendInfo
|
||||
});
|
||||
};
|
||||
|
||||
interface IAddPendingFriendRequest {
|
||||
friend: string;
|
||||
message: string;
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { getJSONfromString, regexEscape } from "@/src/helpers/stringHelpers";
|
||||
import { Alliance, AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
@ -75,7 +75,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
|
||||
const invitedClanOwnerMember = (await GuildMember.findOne({ guildId: guilds[0]._id, rank: 0 }))!;
|
||||
const senderInventory = await getInventory(account._id.toString(), "ActiveAvatarImageType");
|
||||
const senderGuild = (await Guild.findById(allianceMember.guildId, "Name"))!;
|
||||
const alliance = (await Alliance.findById(req.query.allianceId, "Name"))!;
|
||||
const alliance = (await Alliance.findById(req.query.allianceId as string, "Name"))!;
|
||||
await createMessage(invitedClanOwnerMember.accountId, [
|
||||
{
|
||||
sndr: getSuffixedName(account),
|
||||
@ -95,7 +95,7 @@ export const addToAllianceController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/Mailbox_AllianceInvite_Title",
|
||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||
contextInfo: alliance._id.toString(),
|
||||
highPriority: true,
|
||||
acceptAction: "ALLIANCE_INVITE",
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { fillInInventoryDataForGuildMember, hasGuildPermission } from "@/src/services/guildService";
|
||||
import { addInventoryDataToFriendInfo, areFriends } from "@/src/services/friendService";
|
||||
import { hasGuildPermission } from "@/src/services/guildService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getEffectiveAvatarImageType, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { GuildPermission, IGuildMemberClient } from "@/src/types/guildTypes";
|
||||
@ -22,15 +24,18 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const senderAccount = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "Settings");
|
||||
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||
if (inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE") {
|
||||
if (
|
||||
inventory.Settings?.GuildInvRestriction == "GIFT_MODE_NONE" ||
|
||||
(inventory.Settings?.GuildInvRestriction == "GIFT_MODE_FRIENDS" &&
|
||||
!(await areFriends(account._id, senderAccount._id)))
|
||||
) {
|
||||
res.status(400).json("Invite restricted");
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = (await Guild.findById(payload.GuildId.$oid, "Name Ranks"))!;
|
||||
const senderAccount = await getAccountForRequest(req);
|
||||
if (!(await hasGuildPermission(guild, senderAccount._id.toString(), GuildPermission.Recruiter))) {
|
||||
res.status(400).json("Invalid permission");
|
||||
}
|
||||
@ -59,7 +64,7 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/Mailbox_ClanInvite_Title",
|
||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||
contextInfo: payload.GuildId.$oid,
|
||||
highPriority: true,
|
||||
acceptAction: "GUILD_INVITE",
|
||||
@ -71,10 +76,11 @@ export const addToGuildController: RequestHandler = async (req, res) => {
|
||||
const member: IGuildMemberClient = {
|
||||
_id: { $oid: account._id.toString() },
|
||||
DisplayName: account.DisplayName,
|
||||
LastLogin: toMongoDate(account.LastLogin),
|
||||
Rank: 7,
|
||||
Status: 2
|
||||
};
|
||||
await fillInInventoryDataForGuildMember(member);
|
||||
await addInventoryDataToFriendInfo(member);
|
||||
res.json({ NewMember: member });
|
||||
} else if ("RequestMsg" in payload) {
|
||||
// Player applying to join a clan
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { fromOid, toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { createVeiledRivenFingerprint, rivenRawToRealWeighted } from "@/src/helpers/rivenHelper";
|
||||
import { addMiscItems, addMods, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getRandomElement, getRandomWeightedReward, getRandomWeightedRewardUc } from "@/src/services/rngService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IUpgradeFromClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportBoosterPacks, ExportUpgrades, TRarity } from "warframe-public-export-plus";
|
||||
|
||||
@ -24,11 +24,11 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
]);
|
||||
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
inventory.Upgrades.pull({ _id: upgrade.ItemId.$oid });
|
||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||
});
|
||||
|
||||
const rawRivenType = getRandomRawRivenType();
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType]);
|
||||
const rivenType = getRandomElement(rivenRawToRealWeighted[rawRivenType])!;
|
||||
const fingerprint = createVeiledRivenFingerprint(ExportUpgrades[rivenType]);
|
||||
|
||||
const upgradeIndex =
|
||||
@ -57,12 +57,16 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
payload.Consumed.forEach(upgrade => {
|
||||
const meta = ExportUpgrades[upgrade.ItemType];
|
||||
counts[meta.rarity] += upgrade.ItemCount;
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: upgrade.ItemType,
|
||||
ItemCount: upgrade.ItemCount * -1
|
||||
}
|
||||
]);
|
||||
if (fromOid(upgrade.ItemId) != "000000000000000000000000") {
|
||||
inventory.Upgrades.pull({ _id: fromOid(upgrade.ItemId) });
|
||||
} else {
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: upgrade.ItemType,
|
||||
ItemCount: upgrade.ItemCount * -1
|
||||
}
|
||||
]);
|
||||
}
|
||||
if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/AttackTransmuteCore") {
|
||||
forcedPolarity = "AP_ATTACK";
|
||||
} else if (upgrade.ItemType == "/Lotus/Upgrades/Mods/TransmuteCores/DefenseTransmuteCore") {
|
||||
@ -72,22 +76,33 @@ export const artifactTransmutationController: RequestHandler = async (req, res)
|
||||
}
|
||||
});
|
||||
|
||||
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
||||
const weights: Record<TRarity, number> = {
|
||||
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
||||
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
||||
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
||||
LEGENDARY: 0
|
||||
};
|
||||
|
||||
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
||||
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||
let newModType: string | undefined;
|
||||
for (const specialModSet of specialModSets) {
|
||||
if (specialModSet.indexOf(payload.Consumed[0].ItemType) != -1) {
|
||||
newModType = getRandomElement(specialModSet);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!newModType) {
|
||||
// Based on the table on https://wiki.warframe.com/w/Transmutation
|
||||
const weights: Record<TRarity, number> = {
|
||||
COMMON: counts.COMMON * 95 + counts.UNCOMMON * 15 + counts.RARE * 4,
|
||||
UNCOMMON: counts.COMMON * 4 + counts.UNCOMMON * 80 + counts.RARE * 10,
|
||||
RARE: counts.COMMON * 1 + counts.UNCOMMON * 5 + counts.RARE * 50,
|
||||
LEGENDARY: 0
|
||||
};
|
||||
|
||||
const options: { uniqueName: string; rarity: TRarity }[] = [];
|
||||
Object.entries(ExportUpgrades).forEach(([uniqueName, upgrade]) => {
|
||||
if (upgrade.canBeTransmutation && (!forcedPolarity || upgrade.polarity == forcedPolarity)) {
|
||||
options.push({ uniqueName, rarity: upgrade.rarity });
|
||||
}
|
||||
});
|
||||
|
||||
newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||
}
|
||||
|
||||
const newModType = getRandomWeightedReward(options, weights)!.uniqueName;
|
||||
addMods(inventory, [
|
||||
{
|
||||
ItemType: newModType,
|
||||
@ -113,20 +128,41 @@ const getRandomRawRivenType = (): string => {
|
||||
};
|
||||
|
||||
interface IArtifactTransmutationRequest {
|
||||
Upgrade: IAgnosticUpgradeClient;
|
||||
Upgrade: IUpgradeFromClient;
|
||||
LevelDiff: number;
|
||||
Consumed: IAgnosticUpgradeClient[];
|
||||
Consumed: IUpgradeFromClient[];
|
||||
Cost: number;
|
||||
FusionPointCost: number;
|
||||
RivenTransmute?: boolean;
|
||||
}
|
||||
|
||||
interface IAgnosticUpgradeClient {
|
||||
ItemType: string;
|
||||
ItemId: IOid;
|
||||
FromSKU: boolean;
|
||||
UpgradeFingerprint: string;
|
||||
PendingRerollFingerprint: string;
|
||||
ItemCount: number;
|
||||
LastAdded: IOid;
|
||||
}
|
||||
const specialModSets: string[][] = [
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalOneMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalTwoMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalThreeMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalFourMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalFiveMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSixMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalSevenMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalEightMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/ImmortalWildcardMod"
|
||||
],
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusOneMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusTwoMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusThreeMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusFourMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusFiveMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusSixMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusSevenMod",
|
||||
"/Lotus/Upgrades/Mods/Immortal/AntivirusEightMod"
|
||||
],
|
||||
[
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod",
|
||||
"/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod"
|
||||
]
|
||||
];
|
||||
|
@ -15,6 +15,12 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Example POST body: {"pivot":[0, 0, -64],"components":"{\"670429301ca0a63848ccc467\":{\"R\":[0,0,0],\"P\":[0,3,32]},\"6704254a1ca0a63848ccb33c\":{\"R\":[0,0,0],\"P\":[0,9.25,-32]},\"670429461ca0a63848ccc731\":{\"R\":[-90,0,0],\"P\":[-47.999992370605,3,16]}}"}
|
||||
if (req.body) {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error("dojo reparent operation should not need deco repositioning"); // because we always provide SortId
|
||||
}
|
||||
|
||||
const idToNode: Record<string, INode> = {};
|
||||
guild.DojoComponents.forEach(x => {
|
||||
idToNode[x._id.toString()] = {
|
||||
@ -43,23 +49,13 @@ export const changeDojoRootController: RequestHandler = async (req, res) => {
|
||||
newRoot.component.pp = undefined;
|
||||
newRoot.parent = undefined;
|
||||
|
||||
// Don't even ask me why this is needed because I don't know either
|
||||
// Set/update SortId in top-to-bottom order
|
||||
const stack: INode[] = [newRoot];
|
||||
let i = 0;
|
||||
const idMap: Record<string, Types.ObjectId> = {};
|
||||
while (stack.length != 0) {
|
||||
const top = stack.shift()!;
|
||||
idMap[top.component._id.toString()] = new Types.ObjectId(
|
||||
(++i).toString(16).padStart(8, "0") + top.component._id.toString().substr(8)
|
||||
);
|
||||
top.component.SortId = new Types.ObjectId();
|
||||
top.children.forEach(x => stack.push(x));
|
||||
}
|
||||
guild.DojoComponents.forEach(x => {
|
||||
x._id = idMap[x._id.toString()];
|
||||
if (x.pi) {
|
||||
x.pi = idMap[x.pi.toString()];
|
||||
}
|
||||
});
|
||||
|
||||
logger.debug("New tree:\n" + treeToString(newRoot));
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { getRecipe } from "@/src/services/itemDataService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
getInventory,
|
||||
updateCurrency,
|
||||
@ -17,7 +17,11 @@ import {
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { InventorySlot, IPendingRecipeDatabase } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { toOid2 } from "@/src/helpers/inventoryHelpers";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { IRecipe } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
|
||||
interface IClaimCompletedRecipeRequest {
|
||||
RecipeIds: IOid[];
|
||||
@ -25,10 +29,8 @@ interface IClaimCompletedRecipeRequest {
|
||||
|
||||
export const claimCompletedRecipeController: RequestHandler = async (req, res) => {
|
||||
const claimCompletedRecipeRequest = getJSONfromString<IClaimCompletedRecipeRequest>(String(req.body));
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
if (!accountId) throw new Error("no account id");
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const pendingRecipe = inventory.PendingRecipes.id(claimCompletedRecipeRequest.RecipeIds[0].$oid);
|
||||
if (!pendingRecipe) {
|
||||
throw new Error(`no pending recipe found with id ${claimCompletedRecipeRequest.RecipeIds[0].$oid}`);
|
||||
@ -47,39 +49,14 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
}
|
||||
|
||||
if (req.query.cancel) {
|
||||
const inventoryChanges: IInventoryChanges = {
|
||||
...updateCurrency(inventory, recipe.buildPrice * -1, false)
|
||||
};
|
||||
|
||||
const equipmentIngredients = new Set();
|
||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||
if (pendingRecipe[category]) {
|
||||
pendingRecipe[category].forEach(item => {
|
||||
const index = inventory[category].push(item) - 1;
|
||||
inventoryChanges[category] ??= [];
|
||||
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||
equipmentIngredients.add(item.ItemType);
|
||||
|
||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||
inventoryChanges.WeaponBin.Slots -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
await refundRecipeIngredients(inventory, inventoryChanges, recipe, pendingRecipe);
|
||||
await inventory.save();
|
||||
res.json(inventoryChanges); // Not a bug: In the specific case of cancelling a recipe, InventoryChanges are expected to be the root.
|
||||
} else {
|
||||
logger.debug("Claiming Recipe", { recipe, pendingRecipe });
|
||||
|
||||
let BrandedSuits: undefined | IOidWithLegacySupport[];
|
||||
if (recipe.secretIngredientAction == "SIA_SPECTRE_LOADOUT_COPY") {
|
||||
inventory.PendingSpectreLoadouts ??= [];
|
||||
inventory.SpectreLoadouts ??= [];
|
||||
@ -104,9 +81,10 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
inventory.BrandedSuits!.findIndex(x => x.equals(pendingRecipe.SuitToUnbrand)),
|
||||
1
|
||||
);
|
||||
BrandedSuits = [toOid2(pendingRecipe.SuitToUnbrand!, account.BuildLabel)];
|
||||
}
|
||||
|
||||
let InventoryChanges = {};
|
||||
let InventoryChanges: IInventoryChanges = {};
|
||||
if (recipe.consumeOnUse) {
|
||||
addRecipes(inventory, [
|
||||
{
|
||||
@ -130,10 +108,53 @@ export const claimCompletedRecipeController: RequestHandler = async (req, res) =
|
||||
if (recipe.secretIngredientAction != "SIA_UNBRAND") {
|
||||
InventoryChanges = {
|
||||
...InventoryChanges,
|
||||
...(await addItem(inventory, recipe.resultType, recipe.num, false))
|
||||
...(await addItem(
|
||||
inventory,
|
||||
recipe.resultType,
|
||||
recipe.num,
|
||||
false,
|
||||
undefined,
|
||||
pendingRecipe.TargetFingerprint
|
||||
))
|
||||
};
|
||||
}
|
||||
if (config.claimingBlueprintRefundsIngredients) {
|
||||
await refundRecipeIngredients(inventory, InventoryChanges, recipe, pendingRecipe);
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({ InventoryChanges });
|
||||
res.json({ InventoryChanges, BrandedSuits });
|
||||
}
|
||||
};
|
||||
|
||||
const refundRecipeIngredients = async (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
inventoryChanges: IInventoryChanges,
|
||||
recipe: IRecipe,
|
||||
pendingRecipe: IPendingRecipeDatabase
|
||||
): Promise<void> => {
|
||||
updateCurrency(inventory, recipe.buildPrice * -1, false, inventoryChanges);
|
||||
|
||||
const equipmentIngredients = new Set();
|
||||
for (const category of ["LongGuns", "Pistols", "Melee"] as const) {
|
||||
if (pendingRecipe[category]) {
|
||||
pendingRecipe[category].forEach(item => {
|
||||
const index = inventory[category].push(item) - 1;
|
||||
inventoryChanges[category] ??= [];
|
||||
inventoryChanges[category].push(inventory[category][index].toJSON<IEquipmentClient>());
|
||||
equipmentIngredients.add(item.ItemType);
|
||||
|
||||
occupySlot(inventory, InventorySlot.WEAPONS, false);
|
||||
inventoryChanges.WeaponBin ??= { Slots: 0 };
|
||||
inventoryChanges.WeaponBin.Slots -= 1;
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const ingredient of recipe.ingredients) {
|
||||
if (!equipmentIngredients.has(ingredient.ItemType)) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
await addItem(inventory, ingredient.ItemType, ingredient.ItemCount)
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
@ -17,7 +17,7 @@ export const claimLibraryDailyTaskRewardController: RequestHandler = async (req,
|
||||
}
|
||||
syndicate.Standing += rewardStanding;
|
||||
|
||||
inventory.FusionPoints += 80 * rewardQuantity;
|
||||
addFusionPoints(inventory, 80 * rewardQuantity);
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
|
41
src/controllers/api/completeCalendarEventController.ts
Normal file
41
src/controllers/api/completeCalendarEventController.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { getCalendarProgress, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { getWorldState } from "@/src/services/worldStateService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
// GET request; query parameters: CompletedEventIdx=0&Iteration=4&Version=19&Season=CST_SUMMER
|
||||
export const completeCalendarEventController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const calendarProgress = getCalendarProgress(inventory);
|
||||
const currentSeason = getWorldState().KnownCalendarSeasons[0];
|
||||
let inventoryChanges: IInventoryChanges = {};
|
||||
let dayIndex = 0;
|
||||
for (const day of currentSeason.Days) {
|
||||
if (day.events.length == 0 || day.events[0].type != "CET_CHALLENGE") {
|
||||
if (dayIndex == calendarProgress.SeasonProgress.LastCompletedDayIdx) {
|
||||
if (day.events.length != 0) {
|
||||
const selection = day.events[parseInt(req.query.CompletedEventIdx as string)];
|
||||
if (selection.type == "CET_REWARD") {
|
||||
inventoryChanges = (await handleStoreItemAcquisition(selection.reward!, inventory))
|
||||
.InventoryChanges;
|
||||
} else if (selection.type == "CET_UPGRADE") {
|
||||
calendarProgress.YearProgress.Upgrades.push(selection.upgrade!);
|
||||
} else if (selection.type != "CET_PLOT") {
|
||||
throw new Error(`unexpected selection type: ${selection.type}`);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
++dayIndex;
|
||||
}
|
||||
}
|
||||
calendarProgress.SeasonProgress.LastCompletedDayIdx++;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
CalendarProgress: inventory.CalendarProgress
|
||||
});
|
||||
};
|
@ -1,8 +1,14 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { deleteGuild, getGuildClient, hasGuildPermission, removeDojoKeyItems } from "@/src/services/guildService";
|
||||
import { addRecipes, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||
import {
|
||||
deleteGuild,
|
||||
getGuildClient,
|
||||
giveClanKey,
|
||||
hasGuildPermission,
|
||||
removeDojoKeyItems
|
||||
} from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getAccountIdForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
@ -41,14 +47,7 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
|
||||
// Update inventory of new member
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||
const recipeChanges = [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||
ItemCount: 1
|
||||
}
|
||||
];
|
||||
addRecipes(inventory, recipeChanges);
|
||||
combineInventoryChanges(inventoryChanges, { Recipes: recipeChanges });
|
||||
giveClanKey(inventory, inventoryChanges);
|
||||
await inventory.save();
|
||||
|
||||
const guild = (await Guild.findById(req.query.clanId as string))!;
|
||||
@ -63,7 +62,7 @@ export const confirmGuildInvitationGetController: RequestHandler = async (req, r
|
||||
await guild.save();
|
||||
|
||||
res.json({
|
||||
...(await getGuildClient(guild, account._id.toString())),
|
||||
...(await getGuildClient(guild, account)),
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
} else {
|
||||
@ -96,14 +95,9 @@ export const confirmGuildInvitationPostController: RequestHandler = async (req,
|
||||
await GuildMember.deleteMany({ accountId: guildMember.accountId, status: 1 });
|
||||
|
||||
// Update inventory of new member
|
||||
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId Recipes");
|
||||
const inventory = await getInventory(guildMember.accountId.toString(), "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = new Types.ObjectId(req.query.clanId as string);
|
||||
addRecipes(inventory, [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||
ItemCount: 1
|
||||
}
|
||||
]);
|
||||
giveClanKey(inventory);
|
||||
await inventory.save();
|
||||
|
||||
// Add join to clan log
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { checkClanAscensionHasRequiredContributors } from "@/src/services/guildService";
|
||||
import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
@ -31,49 +30,13 @@ export const contributeGuildClassController: RequestHandler = async (req, res) =
|
||||
|
||||
guild.CeremonyContributors.push(new Types.ObjectId(accountId));
|
||||
|
||||
// Once required contributor count is hit, the class is committed and there's 72 hours to claim endo.
|
||||
if (guild.CeremonyContributors.length == payload.RequiredContributors) {
|
||||
guild.Class = guild.CeremonyClass!;
|
||||
guild.CeremonyClass = undefined;
|
||||
guild.CeremonyResetDate = new Date(Date.now() + (config.fastClanAscension ? 5_000 : 72 * 3600_000));
|
||||
if (!config.fastClanAscension) {
|
||||
// Send message to all active guild members
|
||||
const members = await GuildMember.find({ guildId: payload.GuildId, status: 0 }, "accountId");
|
||||
for (const member of members) {
|
||||
// somewhat unfaithful as on live the "msg" is not a loctag, but since we don't have the string, we'll let the client fill it in with "arg".
|
||||
await createMessage(member.accountId, [
|
||||
{
|
||||
sndr: guild.Name,
|
||||
msg: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgressDetails",
|
||||
arg: [
|
||||
{
|
||||
Key: "RESETDATE",
|
||||
Tag:
|
||||
guild.CeremonyResetDate.getUTCMonth() +
|
||||
"/" +
|
||||
guild.CeremonyResetDate.getUTCDate() +
|
||||
"/" +
|
||||
(guild.CeremonyResetDate.getUTCFullYear() % 100) +
|
||||
" " +
|
||||
guild.CeremonyResetDate.getUTCHours().toString().padStart(2, "0") +
|
||||
":" +
|
||||
guild.CeremonyResetDate.getUTCMinutes().toString().padStart(2, "0")
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Clan/Clan_AscensionCeremonyInProgress",
|
||||
icon: "/Lotus/Interface/Graphics/ClanTileImages/ClanEnterDojo.png",
|
||||
highPriority: true
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
await checkClanAscensionHasRequiredContributors(guild);
|
||||
|
||||
await guild.save();
|
||||
|
||||
// Either way, endo is given to the contributor.
|
||||
const inventory = await getInventory(accountId, "FusionPoints");
|
||||
inventory.FusionPoints += guild.CeremonyEndo!;
|
||||
addFusionPoints(inventory, guild.CeremonyEndo!);
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
|
@ -1,16 +1,17 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||
import { addRecipes, getInventory } from "@/src/services/inventoryService";
|
||||
import { createUniqueClanName, getGuildClient, giveClanKey } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
|
||||
export const createGuildController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await getAccountForRequest(req);
|
||||
const payload = getJSONfromString<ICreateGuildRequest>(String(req.body));
|
||||
|
||||
// Remove pending applications for this account
|
||||
await GuildMember.deleteMany({ accountId, status: 1 });
|
||||
await GuildMember.deleteMany({ accountId: account._id, status: 1 });
|
||||
|
||||
// Create guild on database
|
||||
const guild = new Guild({
|
||||
@ -20,32 +21,21 @@ export const createGuildController: RequestHandler = async (req, res) => {
|
||||
|
||||
// Create guild member on database
|
||||
await GuildMember.insertOne({
|
||||
accountId: accountId,
|
||||
accountId: account._id,
|
||||
guildId: guild._id,
|
||||
status: 0,
|
||||
rank: 0
|
||||
});
|
||||
|
||||
const inventory = await getInventory(accountId, "GuildId Recipes");
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys Recipes");
|
||||
inventory.GuildId = guild._id;
|
||||
addRecipes(inventory, [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||
ItemCount: 1
|
||||
}
|
||||
]);
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
giveClanKey(inventory, inventoryChanges);
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
...(await getGuildClient(guild, accountId)),
|
||||
InventoryChanges: {
|
||||
Recipes: [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Keys/DojoKeyBlueprint",
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
...(await getGuildClient(guild, account)),
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
|
54
src/controllers/api/crewMembersController.ts
Normal file
54
src/controllers/api/crewMembersController.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ICrewMemberClient } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export const crewMembersController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "CrewMembers NemesisHistory");
|
||||
const data = getJSONfromString<ICrewMembersRequest>(String(req.body));
|
||||
if (data.crewMember.SecondInCommand) {
|
||||
clearOnCall(inventory);
|
||||
}
|
||||
if (data.crewMember.ItemId.$oid == "000000000000000000000000") {
|
||||
const convertedNemesis = inventory.NemesisHistory!.find(x => x.fp == data.crewMember.NemesisFingerprint)!;
|
||||
convertedNemesis.SecondInCommand = data.crewMember.SecondInCommand;
|
||||
} else {
|
||||
const dbCrewMember = inventory.CrewMembers.id(data.crewMember.ItemId.$oid)!;
|
||||
dbCrewMember.AssignedRole = data.crewMember.AssignedRole;
|
||||
dbCrewMember.SkillEfficiency = data.crewMember.SkillEfficiency;
|
||||
dbCrewMember.WeaponConfigIdx = data.crewMember.WeaponConfigIdx;
|
||||
dbCrewMember.WeaponId = new Types.ObjectId(data.crewMember.WeaponId.$oid);
|
||||
dbCrewMember.Configs = data.crewMember.Configs;
|
||||
dbCrewMember.SecondInCommand = data.crewMember.SecondInCommand;
|
||||
}
|
||||
await inventory.save();
|
||||
res.json({
|
||||
crewMemberId: data.crewMember.ItemId.$oid,
|
||||
NemesisFingerprint: data.crewMember.NemesisFingerprint
|
||||
});
|
||||
};
|
||||
|
||||
interface ICrewMembersRequest {
|
||||
crewMember: ICrewMemberClient;
|
||||
}
|
||||
|
||||
const clearOnCall = (inventory: TInventoryDatabaseDocument): void => {
|
||||
for (const cm of inventory.CrewMembers) {
|
||||
if (cm.SecondInCommand) {
|
||||
cm.SecondInCommand = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (inventory.NemesisHistory) {
|
||||
for (const cm of inventory.NemesisHistory) {
|
||||
if (cm.SecondInCommand) {
|
||||
cm.SecondInCommand = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
84
src/controllers/api/crewShipIdentifySalvageController.ts
Normal file
84
src/controllers/api/crewShipIdentifySalvageController.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {
|
||||
addCrewShipSalvagedWeaponSkin,
|
||||
addCrewShipRawSalvage,
|
||||
getInventory,
|
||||
addEquipment
|
||||
} from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
import { ICrewShipComponentFingerprint, IInnateDamageFingerprint } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ExportCustoms, ExportRailjackWeapons, ExportUpgrades } from "warframe-public-export-plus";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { getRandomInt } from "@/src/services/rngService";
|
||||
import { IFingerprintStat } from "@/src/helpers/rivenHelper";
|
||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
|
||||
export const crewShipIdentifySalvageController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(
|
||||
accountId,
|
||||
"CrewShipSalvagedWeaponSkins CrewShipSalvagedWeapons CrewShipRawSalvage"
|
||||
);
|
||||
const payload = getJSONfromString<ICrewShipIdentifySalvageRequest>(String(req.body));
|
||||
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
if (payload.ItemType in ExportCustoms) {
|
||||
const meta = ExportCustoms[payload.ItemType];
|
||||
let upgradeFingerprint: ICrewShipComponentFingerprint = { compat: payload.ItemType, buffs: [] };
|
||||
if (meta.subroutines) {
|
||||
upgradeFingerprint = {
|
||||
SubroutineIndex: getRandomInt(0, meta.subroutines.length - 1),
|
||||
...upgradeFingerprint
|
||||
};
|
||||
}
|
||||
for (const upgrade of meta.randomisedUpgrades!) {
|
||||
upgradeFingerprint.buffs.push({ Tag: upgrade.tag, Value: Math.trunc(Math.random() * 0x40000000) });
|
||||
}
|
||||
addCrewShipSalvagedWeaponSkin(
|
||||
inventory,
|
||||
payload.ItemType,
|
||||
JSON.stringify(upgradeFingerprint),
|
||||
inventoryChanges
|
||||
);
|
||||
} else {
|
||||
const meta = ExportRailjackWeapons[payload.ItemType];
|
||||
let defaultOverwrites: Partial<IEquipmentDatabase> | undefined;
|
||||
if (meta.defaultUpgrades?.[0]) {
|
||||
const upgradeType = meta.defaultUpgrades[0].ItemType;
|
||||
const upgradeMeta = ExportUpgrades[upgradeType];
|
||||
const buffs: IFingerprintStat[] = [];
|
||||
for (const buff of upgradeMeta.upgradeEntries!) {
|
||||
buffs.push({
|
||||
Tag: buff.tag,
|
||||
Value: Math.trunc(Math.random() * 0x40000000)
|
||||
});
|
||||
}
|
||||
defaultOverwrites = {
|
||||
UpgradeType: upgradeType,
|
||||
UpgradeFingerprint: JSON.stringify({
|
||||
compat: payload.ItemType,
|
||||
buffs
|
||||
} satisfies IInnateDamageFingerprint)
|
||||
};
|
||||
}
|
||||
addEquipment(inventory, "CrewShipSalvagedWeapons", payload.ItemType, defaultOverwrites, inventoryChanges);
|
||||
}
|
||||
|
||||
inventoryChanges.CrewShipRawSalvage = [
|
||||
{
|
||||
ItemType: payload.ItemType,
|
||||
ItemCount: -1
|
||||
}
|
||||
];
|
||||
addCrewShipRawSalvage(inventory, inventoryChanges.CrewShipRawSalvage);
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
interface ICrewShipIdentifySalvageRequest {
|
||||
ItemType: string;
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
// Arbiter Dojo endpoints, not really used by us as we don't provide a ContentURL.
|
||||
|
||||
export const dojoController: RequestHandler = (_req, res) => {
|
||||
res.json("-1"); // Tell client to use authorised request.
|
||||
};
|
||||
|
||||
export const setDojoURLController: RequestHandler = (_req, res) => {
|
||||
res.end();
|
||||
};
|
||||
|
@ -55,7 +55,7 @@ export const dronesController: RequestHandler = async (req, res) => {
|
||||
? new Date()
|
||||
: new Date(Date.now() + getRandomInt(3 * 3600 * 1000, 4 * 3600 * 1000));
|
||||
drone.PendingDamage =
|
||||
Math.random() < system.damageChance
|
||||
!config.noResourceExtractorDronesDamage && Math.random() < system.damageChance
|
||||
? getRandomInt(system.droneDamage.minValue, system.droneDamage.maxValue)
|
||||
: 0;
|
||||
const resource = getRandomWeightedRewardUc(system.resources, droneMeta.probabilities)!;
|
||||
|
@ -1,60 +1,529 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IEndlessXpReward, IInventoryClient, TEndlessXpCategory } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ExportRewards, ICountedStoreItem } from "warframe-public-export-plus";
|
||||
import { getRandomElement } from "@/src/services/rngService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
|
||||
export const endlessXpController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const payload = getJSONfromString<IEndlessXpRequest>(String(req.body));
|
||||
|
||||
inventory.EndlessXP ??= [];
|
||||
const entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||
if (entry) {
|
||||
entry.Choices = payload.Choices;
|
||||
} else {
|
||||
inventory.EndlessXP.push({
|
||||
Category: payload.Category,
|
||||
Choices: payload.Choices
|
||||
});
|
||||
}
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
NewProgress: {
|
||||
Category: payload.Category,
|
||||
Earn: 0,
|
||||
Claim: 0,
|
||||
BonusAvailable: {
|
||||
$date: {
|
||||
$numberLong: "9999999999999"
|
||||
}
|
||||
},
|
||||
Expiry: {
|
||||
$date: {
|
||||
$numberLong: "9999999999999"
|
||||
}
|
||||
},
|
||||
Choices: payload.Choices,
|
||||
PendingRewards: [
|
||||
{
|
||||
RequiredTotalXp: 190,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: "/Lotus/StoreItems/Upgrades/Mods/Aura/PlayerHealthAuraMod",
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
// ...
|
||||
]
|
||||
if (payload.Mode == "r") {
|
||||
const inventory = await getInventory(accountId, "EndlessXP");
|
||||
inventory.EndlessXP ??= [];
|
||||
let entry = inventory.EndlessXP.find(x => x.Category == payload.Category);
|
||||
if (!entry) {
|
||||
entry = {
|
||||
Category: payload.Category,
|
||||
Earn: 0,
|
||||
Claim: 0,
|
||||
Choices: payload.Choices,
|
||||
PendingRewards: []
|
||||
};
|
||||
inventory.EndlessXP.push(entry);
|
||||
}
|
||||
});
|
||||
|
||||
const weekStart = 1734307200_000 + Math.trunc((Date.now() - 1734307200_000) / 604800000) * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
|
||||
entry.Earn = 0;
|
||||
entry.Claim = 0;
|
||||
entry.BonusAvailable = new Date(weekStart);
|
||||
entry.Expiry = new Date(weekEnd);
|
||||
entry.Choices = payload.Choices;
|
||||
entry.PendingRewards =
|
||||
payload.Category == "EXC_HARD"
|
||||
? generateHardModeRewards(payload.Choices)
|
||||
: generateNormalModeRewards(payload.Choices);
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
NewProgress: inventory.toJSON<IInventoryClient>().EndlessXP!.find(x => x.Category == payload.Category)!
|
||||
});
|
||||
} else if (payload.Mode == "c") {
|
||||
const inventory = await getInventory(accountId);
|
||||
const entry = inventory.EndlessXP!.find(x => x.Category == payload.Category)!;
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
for (const reward of entry.PendingRewards) {
|
||||
if (entry.Claim < reward.RequiredTotalXp && reward.RequiredTotalXp <= entry.Earn) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
(
|
||||
await handleStoreItemAcquisition(
|
||||
reward.Rewards[0].StoreItem,
|
||||
inventory,
|
||||
reward.Rewards[0].ItemCount
|
||||
)
|
||||
).InventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
entry.Claim = entry.Earn;
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
ClaimedXp: entry.Claim
|
||||
});
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unexpected endlessXp mode: ${payload.Mode}`);
|
||||
}
|
||||
};
|
||||
|
||||
interface IEndlessXpRequest {
|
||||
Mode: string; // "r"
|
||||
Category: TEndlessXpCategory;
|
||||
Choices: string[];
|
||||
}
|
||||
type IEndlessXpRequest =
|
||||
| {
|
||||
Mode: "r";
|
||||
Category: TEndlessXpCategory;
|
||||
Choices: string[];
|
||||
}
|
||||
| {
|
||||
Mode: "c" | "something else";
|
||||
Category: TEndlessXpCategory;
|
||||
};
|
||||
|
||||
const generateRandomRewards = (deckName: string): ICountedStoreItem[] => {
|
||||
const reward = getRandomElement(ExportRewards[deckName][0])!;
|
||||
return [
|
||||
{
|
||||
StoreItem: reward.type,
|
||||
ItemCount: reward.itemCount
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const normalModeChosenRewards: Record<string, string[]> = {
|
||||
Excalibur: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Excalibur/RadialJavelinAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ExcaliburBlueprint"
|
||||
],
|
||||
Trinity: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Trinity/EnergyVampireAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinitySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrinityBlueprint"
|
||||
],
|
||||
Ember: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ember/WorldOnFireAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/EmberBlueprint"
|
||||
],
|
||||
Loki: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Loki/InvisibilityAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKISystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/LOKIBlueprint"
|
||||
],
|
||||
Mag: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Mag/CrushAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagBlueprint"
|
||||
],
|
||||
Rhino: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Rhino/RhinoChargeAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RhinoBlueprint"
|
||||
],
|
||||
Ash: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ninja/GlaiveAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/AshBlueprint"
|
||||
],
|
||||
Frost: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Frost/IceShieldAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FrostBlueprint"
|
||||
],
|
||||
Nyx: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Jade/SelfBulletAttractorAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NyxBlueprint"
|
||||
],
|
||||
Saryn: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Saryn/PoisonAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/SarynBlueprint"
|
||||
],
|
||||
Vauban: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Trapper/LevTrapAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/TrapperBlueprint"
|
||||
],
|
||||
Nova: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/AntiMatter/MolecularPrimeAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NovaBlueprint"
|
||||
],
|
||||
Nekros: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Necro/CloneTheDeadAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NecroBlueprint"
|
||||
],
|
||||
Valkyr: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Berserker/IntimidateAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BerserkerBlueprint"
|
||||
],
|
||||
Oberon: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Paladin/RegenerationAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PaladinBlueprint"
|
||||
],
|
||||
Hydroid: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Pirate/CannonBarrageAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HydroidBlueprint"
|
||||
],
|
||||
Mirage: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Harlequin/LightAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/HarlequinBlueprint"
|
||||
],
|
||||
Limbo: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Magician/TearInSpaceAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MagicianBlueprint"
|
||||
],
|
||||
Mesa: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Cowgirl/GunFuPvPAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GunslingerBlueprint"
|
||||
],
|
||||
Chroma: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Dragon/DragonLuckAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/ChromaBlueprint"
|
||||
],
|
||||
Atlas: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Brawler/BrawlerPassiveAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/BrawlerBlueprint"
|
||||
],
|
||||
Ivara: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Ranger/RangerStealAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RangerBlueprint"
|
||||
],
|
||||
Inaros: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Sandman/SandmanSwarmAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/MummyBlueprint"
|
||||
],
|
||||
Titania: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Fairy/FairyFlightAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairySystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/FairyBlueprint"
|
||||
],
|
||||
Nidus: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Infestation/InfestPodsAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/NidusBlueprint"
|
||||
],
|
||||
Octavia: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Bard/BardCharmAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/OctaviaBlueprint"
|
||||
],
|
||||
Harrow: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Priest/PriestPactAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PriestBlueprint"
|
||||
],
|
||||
Gara: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Glass/GlassFragmentAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GlassBlueprint"
|
||||
],
|
||||
Khora: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Khora/KhoraCrackAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/KhoraBlueprint"
|
||||
],
|
||||
Revenant: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Revenant/RevenantMarkAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/RevenantBlueprint"
|
||||
],
|
||||
Garuda: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Garuda/GarudaUnstoppableAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/GarudaBlueprint"
|
||||
],
|
||||
Baruuk: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/Pacifist/PacifistFistAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/PacifistBlueprint"
|
||||
],
|
||||
Hildryn: [
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeHelmetBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeChassisBlueprint",
|
||||
"/Lotus/StoreItems/Powersuits/IronFrame/IronFrameStripAugmentCard",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeSystemsBlueprint",
|
||||
"/Lotus/StoreItems/Types/Recipes/WarframeRecipes/IronframeBlueprint"
|
||||
]
|
||||
};
|
||||
|
||||
const generateNormalModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||
const choiceRewards = normalModeChosenRewards[choices[0]];
|
||||
return [
|
||||
{
|
||||
RequiredTotalXp: 190,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 400,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[0],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 630,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 890,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalMODRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1190,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[1],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1540,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1950,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[2],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2430,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[3],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2990,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessNormalArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 3640,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: choiceRewards[4],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const hardModeChosenRewards: Record<string, string> = {
|
||||
Braton: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BratonIncarnonUnlocker",
|
||||
Lato: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LatoIncarnonUnlocker",
|
||||
Skana: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SkanaIncarnonUnlocker",
|
||||
Paris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ParisIncarnonUnlocker",
|
||||
Kunai: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/KunaiIncarnonUnlocker",
|
||||
Boar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoarIncarnonUnlocker",
|
||||
Gammacor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/GammacorIncarnonUnlocker",
|
||||
Anku: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AnkuIncarnonUnlocker",
|
||||
Gorgon: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/GorgonIncarnonUnlocker",
|
||||
Angstrum: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AngstrumIncarnonUnlocker",
|
||||
Bo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/BoIncarnonUnlocker",
|
||||
Latron: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/LatronIncarnonUnlocker",
|
||||
Furis: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/FurisIncarnonUnlocker",
|
||||
Furax: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/FuraxIncarnonUnlocker",
|
||||
Strun: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/StrunIncarnonUnlocker",
|
||||
Lex: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/LexIncarnonUnlocker",
|
||||
Magistar: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/MagistarIncarnonUnlocker",
|
||||
Boltor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BoltorIncarnonUnlocker",
|
||||
Bronco: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/BroncoIncarnonUnlocker",
|
||||
CeramicDagger: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/CeramicDaggerIncarnonUnlocker",
|
||||
Torid: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/ToridIncarnonUnlocker",
|
||||
DualToxocyst: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DualToxocystIncarnonUnlocker",
|
||||
DualIchor: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/DualIchorIncarnonUnlocker",
|
||||
Miter: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/MiterIncarnonUnlocker",
|
||||
Atomos: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/AtomosIncarnonUnlocker",
|
||||
AckAndBrunt: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/AckAndBruntIncarnonUnlocker",
|
||||
Soma: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SomaIncarnonUnlocker",
|
||||
Vasto: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/VastoIncarnonUnlocker",
|
||||
NamiSolo: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/NamiSoloIncarnonUnlocker",
|
||||
Burston: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/BurstonIncarnonUnlocker",
|
||||
Zylok: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/ZylokIncarnonUnlocker",
|
||||
Sibear: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/SibearIncarnonUnlocker",
|
||||
Dread: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DreadIncarnonUnlocker",
|
||||
Despair: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/DespairIncarnonUnlocker",
|
||||
Hate: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/HateIncarnonUnlocker",
|
||||
Dera: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/DeraIncarnonUnlocker",
|
||||
Cestra: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/CestraIncarnonUnlocker",
|
||||
Okina: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Melee/OkinaIncarnonUnlocker",
|
||||
Sybaris: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Primary/SybarisIncarnonUnlocker",
|
||||
Sicarus: "/Lotus/StoreItems/Types/Items/MiscItems/IncarnonAdapters/Secondary/SicarusIncarnonUnlocker",
|
||||
RivenPrimary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawRifleRandomMod",
|
||||
RivenSecondary: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawPistolRandomMod",
|
||||
RivenMelee: "/Lotus/StoreItems/Upgrades/Mods/Randomized/RawMeleeRandomMod",
|
||||
Kuva: "/Lotus/Types/Game/DuviriEndless/CircuitSteelPathBIGKuvaReward"
|
||||
};
|
||||
|
||||
const generateHardModeRewards = (choices: string[]): IEndlessXpReward[] => {
|
||||
return [
|
||||
{
|
||||
RequiredTotalXp: 285,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 600,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 945,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1335,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSilverRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 1785,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: hardModeChosenRewards[choices[0]],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2310,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 2925,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathGoldRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 3645,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathArcaneRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 4485,
|
||||
Rewards: generateRandomRewards(
|
||||
"/Lotus/Types/Game/MissionDecks/DuviriEndlessCircuitRewards/DuviriEndlessSteelPathSteelEssenceRewards"
|
||||
)
|
||||
},
|
||||
{
|
||||
RequiredTotalXp: 5460,
|
||||
Rewards: [
|
||||
{
|
||||
StoreItem: hardModeChosenRewards[choices[1]],
|
||||
ItemCount: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
@ -21,10 +21,12 @@ export const entratiLabConquestModeController: RequestHandler = async (req, res)
|
||||
inventory.EntratiVaultCountResetDate = new Date(weekEnd);
|
||||
if (inventory.EntratiLabConquestUnlocked) {
|
||||
inventory.EntratiLabConquestUnlocked = 0;
|
||||
inventory.EntratiLabConquestCacheScoreMission = 0;
|
||||
inventory.EntratiLabConquestActiveFrameVariants = [];
|
||||
}
|
||||
if (inventory.EchoesHexConquestUnlocked) {
|
||||
inventory.EchoesHexConquestUnlocked = 0;
|
||||
inventory.EchoesHexConquestCacheScoreMission = 0;
|
||||
inventory.EchoesHexConquestActiveFrameVariants = [];
|
||||
inventory.EchoesHexConquestActiveStickers = [];
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||
import { addMiscItems, getInventory, getStandingLimit, updateStandingLimit } from "@/src/services/inventoryService";
|
||||
import { addMiscItems, addStanding, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportResources, ExportSyndicates } from "warframe-public-export-plus";
|
||||
import { ExportResources } from "warframe-public-export-plus";
|
||||
|
||||
export const fishmongerController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -31,32 +30,15 @@ export const fishmongerController: RequestHandler = async (req, res) => {
|
||||
miscItemChanges.push({ ItemType: fish.ItemType, ItemCount: fish.ItemCount * -1 });
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
if (gainedStanding && syndicateTag) {
|
||||
let syndicate = inventory.Affiliations.find(x => x.Tag == syndicateTag);
|
||||
if (!syndicate) {
|
||||
syndicate = inventory.Affiliations[inventory.Affiliations.push({ Tag: syndicateTag, Standing: 0 }) - 1];
|
||||
}
|
||||
const syndicateMeta = ExportSyndicates[syndicateTag];
|
||||
|
||||
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||
if (syndicate.Standing + gainedStanding > max) {
|
||||
gainedStanding = max - syndicate.Standing;
|
||||
}
|
||||
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
||||
}
|
||||
|
||||
syndicate.Standing += gainedStanding;
|
||||
|
||||
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
|
||||
}
|
||||
let affiliationMod;
|
||||
if (gainedStanding && syndicateTag) affiliationMod = addStanding(inventory, syndicateTag, gainedStanding);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: {
|
||||
MiscItems: miscItemChanges
|
||||
},
|
||||
SyndicateTag: syndicateTag,
|
||||
StandingChange: gainedStanding
|
||||
StandingChange: affiliationMod?.Standing || 0
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -64,7 +64,9 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
);
|
||||
|
||||
res.end();
|
||||
res.json({
|
||||
FocusUpgrade: { ItemType: focusType }
|
||||
});
|
||||
break;
|
||||
}
|
||||
case FocusOperation.UnlockUpgrade: {
|
||||
@ -104,13 +106,14 @@ export const focusController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
case FocusOperation.SentTrainingAmplifier: {
|
||||
const request = JSON.parse(String(req.body)) as ISentTrainingAmplifierRequest;
|
||||
const parts: string[] = [
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||
];
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, parts);
|
||||
const inventoryChanges = addEquipment(inventory, "OperatorAmps", request.StartingWeaponType, {
|
||||
ModularParts: [
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingGrip",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingChassis",
|
||||
"/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/SentAmpTrainingBarrel"
|
||||
]
|
||||
});
|
||||
occupySlot(inventory, InventorySlot.AMPS, false);
|
||||
await inventory.save();
|
||||
res.json((inventoryChanges.OperatorAmps as IEquipmentClient[])[0]);
|
||||
|
84
src/controllers/api/gardeningController.ts
Normal file
84
src/controllers/api/gardeningController.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addMiscItem, getInventory } from "@/src/services/inventoryService";
|
||||
import { toStoreItem } from "@/src/services/itemDataService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { IMissionReward } from "@/src/types/missionTypes";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { IGardeningClient } from "@/src/types/shipTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { dict_en, ExportResources } from "warframe-public-export-plus";
|
||||
|
||||
export const gardeningController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<IGardeningRequest>(String(req.body));
|
||||
if (data.Mode != "HarvestAll") {
|
||||
throw new Error(`unexpected gardening mode: ${data.Mode}`);
|
||||
}
|
||||
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const [inventory, personalRooms] = await Promise.all([
|
||||
getInventory(accountId, "MiscItems"),
|
||||
getPersonalRooms(accountId, "Apartment")
|
||||
]);
|
||||
|
||||
// Harvest plants
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
const rewards: Record<string, IMissionReward[][]> = {};
|
||||
for (const planter of personalRooms.Apartment.Gardening.Planters) {
|
||||
rewards[planter.Name] = [];
|
||||
for (const plant of planter.Plants) {
|
||||
const itemType =
|
||||
"/Lotus/Types/Gameplay/Duviri/Resource/DuviriPlantItem" +
|
||||
plant.PlantType.substring(plant.PlantType.length - 1);
|
||||
const itemCount = Math.random() < 0.775 ? 2 : 4;
|
||||
|
||||
addMiscItem(inventory, itemType, itemCount, inventoryChanges);
|
||||
|
||||
rewards[planter.Name].push([
|
||||
{
|
||||
StoreItem: toStoreItem(itemType),
|
||||
TypeName: itemType,
|
||||
ItemCount: itemCount,
|
||||
DailyCooldown: false,
|
||||
Rarity: itemCount == 2 ? 0.7743589743589744 : 0.22564102564102564,
|
||||
TweetText: `${itemCount}x ${dict_en[ExportResources[itemType].name]} (Resource)`,
|
||||
ProductCategory: "MiscItems"
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh garden
|
||||
personalRooms.Apartment.Gardening = createGarden();
|
||||
|
||||
await Promise.all([inventory.save(), personalRooms.save()]);
|
||||
|
||||
const planter = personalRooms.Apartment.Gardening.Planters[personalRooms.Apartment.Gardening.Planters.length - 1];
|
||||
const plant = planter.Plants[planter.Plants.length - 1];
|
||||
res.json({
|
||||
GardenTagName: planter.Name,
|
||||
PlantType: plant.PlantType,
|
||||
PlotIndex: plant.PlotIndex,
|
||||
EndTime: toMongoDate(plant.EndTime),
|
||||
InventoryChanges: inventoryChanges,
|
||||
Gardening: personalRooms.toJSON<IPersonalRoomsClient>().Apartment.Gardening,
|
||||
Rewards: rewards
|
||||
} satisfies IGardeningResponse);
|
||||
};
|
||||
|
||||
interface IGardeningRequest {
|
||||
Mode: string;
|
||||
}
|
||||
|
||||
interface IGardeningResponse {
|
||||
GardenTagName: string;
|
||||
PlantType: string;
|
||||
PlotIndex: number;
|
||||
EndTime: IMongoDate;
|
||||
InventoryChanges: IInventoryChanges;
|
||||
Gardening: IGardeningClient;
|
||||
Rewards: Record<string, IMissionReward[][]>;
|
||||
}
|
@ -18,6 +18,7 @@ export const getAllianceController: RequestHandler = async (req, res) => {
|
||||
res.end();
|
||||
};
|
||||
|
||||
// POST request since U27
|
||||
/*interface IGetAllianceRequest {
|
||||
memberCount: number;
|
||||
clanLeaderName: string;
|
||||
|
@ -1,15 +1,54 @@
|
||||
import { Request, Response } from "express";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { addAccountDataToFriendInfo, addInventoryDataToFriendInfo } from "@/src/services/friendService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||
import { Request, RequestHandler, Response } from "express";
|
||||
|
||||
// POST with {} instead of GET as of 38.5.0
|
||||
const getFriendsController = (_request: Request, response: Response): void => {
|
||||
response.writeHead(200, {
|
||||
//Connection: "keep-alive",
|
||||
//"Content-Encoding": "gzip",
|
||||
"Content-Type": "text/html",
|
||||
// charset: "UTF - 8",
|
||||
"Content-Length": "3"
|
||||
});
|
||||
response.end(Buffer.from([0x7b, 0x7d, 0x0a]));
|
||||
export const getFriendsController: RequestHandler = async (req: Request, res: Response) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const response: IGetFriendsResponse = {
|
||||
Current: [],
|
||||
IncomingFriendRequests: [],
|
||||
OutgoingFriendRequests: []
|
||||
};
|
||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||
Friendship.find({ owner: accountId }),
|
||||
Friendship.find({ friend: accountId }, "owner Note")
|
||||
]);
|
||||
for (const externalFriendship of externalFriendships) {
|
||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||
response.IncomingFriendRequests.push({
|
||||
_id: toOid(externalFriendship.owner),
|
||||
Note: externalFriendship.Note
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const internalFriendship of internalFriendships) {
|
||||
const friendInfo: IFriendInfo = {
|
||||
_id: toOid(internalFriendship.friend)
|
||||
};
|
||||
if (externalFriendships.find(x => x.owner.equals(internalFriendship.friend))) {
|
||||
response.Current.push(friendInfo);
|
||||
} else {
|
||||
response.OutgoingFriendRequests.push(friendInfo);
|
||||
}
|
||||
}
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const arr of Object.values(response)) {
|
||||
for (const friendInfo of arr) {
|
||||
promises.push(addAccountDataToFriendInfo(friendInfo));
|
||||
promises.push(addInventoryDataToFriendInfo(friendInfo));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
res.json(response);
|
||||
};
|
||||
|
||||
export { getFriendsController };
|
||||
// interface IGetFriendsResponse {
|
||||
// Current: IFriendInfo[];
|
||||
// IncomingFriendRequests: IFriendInfo[];
|
||||
// OutgoingFriendRequests: IFriendInfo[];
|
||||
// }
|
||||
type IGetFriendsResponse = Record<"Current" | "IncomingFriendRequests" | "OutgoingFriendRequests", IFriendInfo[]>;
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { createUniqueClanName, getGuildClient } from "@/src/services/guildService";
|
||||
|
||||
export const getGuildController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
const guild = await Guild.findById(inventory.GuildId);
|
||||
if (guild) {
|
||||
@ -24,7 +24,7 @@ export const getGuildController: RequestHandler = async (req, res) => {
|
||||
guild.CeremonyResetDate = undefined;
|
||||
await guild.save();
|
||||
}
|
||||
res.json(await getGuildClient(guild, accountId));
|
||||
res.json(await getGuildClient(guild, account));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { Guild } from "@/src/models/guildModel";
|
||||
import { getDojoClient } from "@/src/services/guildService";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
|
||||
export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||
const guildId = req.query.guildId as string;
|
||||
@ -25,7 +26,8 @@ export const getGuildDojoController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
const payload: IGetGuildDojoRequest = req.body ? (JSON.parse(String(req.body)) as IGetGuildDojoRequest) : {};
|
||||
res.json(await getDojoClient(guild, 0, payload.ComponentId));
|
||||
const account = await Account.findById(req.query.accountId as string);
|
||||
res.json(await getDojoClient(guild, 0, payload.ComponentId, account?.BuildLabel));
|
||||
};
|
||||
|
||||
interface IGetGuildDojoRequest {
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { Account, Ignore } from "@/src/models/loginModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IFriendInfo } from "@/src/types/friendTypes";
|
||||
import { parallelForeach } from "@/src/utils/async-utils";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
const getIgnoredUsersController: RequestHandler = (_req, res) => {
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/html",
|
||||
"Content-Length": "3"
|
||||
export const getIgnoredUsersController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const ignores = await Ignore.find({ ignorer: accountId });
|
||||
const ignoredUsers: IFriendInfo[] = [];
|
||||
await parallelForeach(ignores, async ignore => {
|
||||
const ignoreeAccount = (await Account.findById(ignore.ignoree, "DisplayName"))!;
|
||||
ignoredUsers.push({
|
||||
_id: toOid(ignore.ignoree),
|
||||
DisplayName: ignoreeAccount.DisplayName + ""
|
||||
});
|
||||
});
|
||||
res.end(
|
||||
Buffer.from([
|
||||
0x7b, 0x22, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0x3a, 0x38, 0x33, 0x30, 0x34, 0x30, 0x37, 0x37, 0x32, 0x32,
|
||||
0x34, 0x30, 0x32, 0x32, 0x32, 0x36, 0x31, 0x35, 0x30, 0x31, 0x7d
|
||||
])
|
||||
);
|
||||
res.json({ IgnoredUsers: ignoredUsers });
|
||||
};
|
||||
|
||||
export { getIgnoredUsersController };
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { generateRewardSeed } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getNewRewardSeedController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
const rewardSeed = generateRewardSeed();
|
||||
logger.debug(`generated new reward seed: ${rewardSeed}`);
|
||||
await Inventory.updateOne(
|
||||
{
|
||||
accountOwnerId: accountId
|
||||
|
@ -2,19 +2,24 @@ import { RequestHandler } from "express";
|
||||
import { config } from "@/src/services/configService";
|
||||
import allShipFeatures from "@/static/fixed_responses/allShipFeatures.json";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { getShip } from "@/src/services/shipService";
|
||||
import { createGarden, getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { IGetShipResponse } from "@/src/types/shipTypes";
|
||||
import { IPersonalRooms } from "@/src/types/personalRoomsTypes";
|
||||
import { IPersonalRoomsClient } from "@/src/types/personalRoomsTypes";
|
||||
import { getLoadout } from "@/src/services/loadoutService";
|
||||
|
||||
export const getShipController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const personalRoomsDb = await getPersonalRooms(accountId);
|
||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRooms>();
|
||||
|
||||
// Setup gardening if it's missing. Maybe should be done as part of some quest completion in the future.
|
||||
if (personalRoomsDb.Apartment.Gardening.Planters.length == 0) {
|
||||
personalRoomsDb.Apartment.Gardening = createGarden();
|
||||
await personalRoomsDb.save();
|
||||
}
|
||||
|
||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||
const loadout = await getLoadout(accountId);
|
||||
const ship = await getShip(personalRoomsDb.activeShipId, "ShipAttachments SkinFlavourItem");
|
||||
|
||||
const getShipResponse: IGetShipResponse = {
|
||||
ShipOwnerId: accountId,
|
||||
@ -24,9 +29,12 @@ export const getShipController: RequestHandler = async (req, res) => {
|
||||
ShipId: toOid(personalRoomsDb.activeShipId),
|
||||
ShipInterior: {
|
||||
Colors: personalRooms.ShipInteriorColors,
|
||||
ShipAttachments: ship.ShipAttachments,
|
||||
SkinFlavourItem: ship.SkinFlavourItem
|
||||
}
|
||||
ShipAttachments: { HOOD_ORNAMENT: "" },
|
||||
SkinFlavourItem: ""
|
||||
},
|
||||
FavouriteLoadoutId: personalRooms.Ship.FavouriteLoadoutId
|
||||
? toOid(personalRooms.Ship.FavouriteLoadoutId)
|
||||
: undefined
|
||||
},
|
||||
Apartment: personalRooms.Apartment,
|
||||
TailorShop: personalRooms.TailorShop
|
||||
|
@ -1,14 +1,20 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||
import { applyStandingToVendorManifest, getVendorManifestByTypeName } from "@/src/services/serversideVendorsService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
|
||||
export const getVendorInfoController: RequestHandler = (req, res) => {
|
||||
if (typeof req.query.vendor == "string") {
|
||||
const manifest = getVendorManifestByTypeName(req.query.vendor);
|
||||
if (!manifest) {
|
||||
throw new Error(`Unknown vendor: ${req.query.vendor}`);
|
||||
}
|
||||
res.json(manifest);
|
||||
} else {
|
||||
res.status(400).end();
|
||||
export const getVendorInfoController: RequestHandler = async (req, res) => {
|
||||
let manifest = getVendorManifestByTypeName(req.query.vendor as string);
|
||||
if (!manifest) {
|
||||
throw new Error(`Unknown vendor: ${req.query.vendor as string}`);
|
||||
}
|
||||
|
||||
// For testing purposes, authenticating with this endpoint is optional here, but would be required on live.
|
||||
if (req.query.accountId) {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
manifest = applyStandingToVendorManifest(inventory, manifest);
|
||||
}
|
||||
|
||||
res.json(manifest);
|
||||
};
|
||||
|
@ -1,12 +1,19 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { areFriends } from "@/src/services/friendService";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import {
|
||||
combineInventoryChanges,
|
||||
getEffectiveAvatarImageType,
|
||||
getInventory,
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, getSuffixedName } from "@/src/services/loginService";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { IInventoryChanges, IPurchaseParams } from "@/src/types/purchaseTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
import { ExportBundles, ExportFlavour } from "warframe-public-export-plus";
|
||||
|
||||
export const giftingController: RequestHandler = async (req, res) => {
|
||||
const data = getJSONfromString<IGiftingRequest>(String(req.body));
|
||||
@ -30,8 +37,11 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
// Cannot gift to players who have gifting disabled.
|
||||
// TODO: Also consider GIFT_MODE_FRIENDS once friends are implemented
|
||||
if (inventory.Settings?.GiftMode == "GIFT_MODE_NONE") {
|
||||
const senderAccount = await getAccountForRequest(req);
|
||||
if (
|
||||
inventory.Settings?.GiftMode == "GIFT_MODE_NONE" ||
|
||||
(inventory.Settings?.GiftMode == "GIFT_MODE_FRIENDS" && !(await areFriends(account._id, senderAccount._id)))
|
||||
) {
|
||||
res.status(400).send("17").end();
|
||||
return;
|
||||
}
|
||||
@ -40,11 +50,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
// TODO: Cannot gift archwing items to players that have not completed the archwing quest. (Code 7)
|
||||
// TODO: Cannot gift necramechs to players that have not completed heart of deimos. (Code 20)
|
||||
|
||||
const senderAccount = await getAccountForRequest(req);
|
||||
const senderInventory = await getInventory(
|
||||
senderAccount._id.toString(),
|
||||
"PremiumCredits PremiumCreditsFree ActiveAvatarImageType GiftsRemaining"
|
||||
);
|
||||
const senderInventory = await getInventory(senderAccount._id.toString());
|
||||
|
||||
if (senderInventory.GiftsRemaining == 0) {
|
||||
res.status(400).send("10").end();
|
||||
@ -52,7 +58,20 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
senderInventory.GiftsRemaining -= 1;
|
||||
|
||||
updateCurrency(senderInventory, data.PurchaseParams.ExpectedPrice, true);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(
|
||||
senderInventory,
|
||||
data.PurchaseParams.ExpectedPrice,
|
||||
true
|
||||
);
|
||||
if (data.PurchaseParams.StoreItem in ExportBundles) {
|
||||
const bundle = ExportBundles[data.PurchaseParams.StoreItem];
|
||||
if (bundle.giftingBonus) {
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
(await handleStoreItemAcquisition(bundle.giftingBonus, senderInventory)).InventoryChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
await senderInventory.save();
|
||||
|
||||
const senderName = getSuffixedName(senderAccount);
|
||||
@ -71,7 +90,7 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/GiftReceivedSubject",
|
||||
icon: ExportFlavour[senderInventory.ActiveAvatarImageType].icon,
|
||||
icon: ExportFlavour[getEffectiveAvatarImageType(senderInventory)].icon,
|
||||
gifts: [
|
||||
{
|
||||
GiftType: data.PurchaseParams.StoreItem
|
||||
@ -80,7 +99,9 @@ export const giftingController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
]);
|
||||
|
||||
res.end();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
interface IGiftingRequest {
|
||||
|
@ -2,36 +2,25 @@ import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { WeaponTypeInternal } from "@/src/services/itemDataService";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ArtifactPolarity, EquipmentFeatures, IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { ExportRecipes } from "warframe-public-export-plus";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
|
||||
const modularWeaponCategory: (WeaponTypeInternal | "Hoverboards")[] = [
|
||||
"LongGuns",
|
||||
"Pistols",
|
||||
"Melee",
|
||||
"OperatorAmps",
|
||||
"Hoverboards"
|
||||
];
|
||||
|
||||
interface IGildWeaponRequest {
|
||||
ItemName: string;
|
||||
Recipe: string; // e.g. /Lotus/Weapons/SolarisUnited/LotusGildKitgunBlueprint
|
||||
PolarizeSlot?: number;
|
||||
PolarizeValue?: ArtifactPolarity;
|
||||
ItemId: string;
|
||||
Category: WeaponTypeInternal | "Hoverboards";
|
||||
Category: TEquipmentKey;
|
||||
}
|
||||
|
||||
export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString<IGildWeaponRequest>(String(req.body));
|
||||
data.ItemId = String(req.query.ItemId);
|
||||
if (!modularWeaponCategory.includes(req.query.Category as WeaponTypeInternal | "Hoverboards")) {
|
||||
throw new Error(`Unknown modular weapon Category: ${String(req.query.Category)}`);
|
||||
}
|
||||
data.Category = req.query.Category as WeaponTypeInternal | "Hoverboards";
|
||||
data.Category = req.query.Category as TEquipmentKey;
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
const weaponIndex = inventory[data.Category].findIndex(x => String(x._id) === data.ItemId);
|
||||
@ -42,8 +31,10 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
const weapon = inventory[data.Category][weaponIndex];
|
||||
weapon.Features ??= 0;
|
||||
weapon.Features |= EquipmentFeatures.GILDED;
|
||||
weapon.ItemName = data.ItemName;
|
||||
weapon.XP = 0;
|
||||
if (data.Recipe != "webui") {
|
||||
weapon.ItemName = data.ItemName;
|
||||
weapon.XP = 0;
|
||||
}
|
||||
if (data.Category != "OperatorAmps" && data.PolarizeSlot && data.PolarizeValue) {
|
||||
weapon.Polarity = [
|
||||
{
|
||||
@ -56,21 +47,24 @@ export const gildWeaponController: RequestHandler = async (req, res) => {
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
inventoryChanges[data.Category] = [weapon.toJSON<IEquipmentClient>()];
|
||||
|
||||
const recipe = ExportRecipes[data.Recipe];
|
||||
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
||||
ItemType: ingredient.ItemType,
|
||||
ItemCount: ingredient.ItemCount * -1
|
||||
}));
|
||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||
|
||||
const affiliationMods = [];
|
||||
if (recipe.syndicateStandingChange) {
|
||||
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
|
||||
affiliation.Standing += recipe.syndicateStandingChange.value;
|
||||
affiliationMods.push({
|
||||
Tag: recipe.syndicateStandingChange.tag,
|
||||
Standing: recipe.syndicateStandingChange.value
|
||||
});
|
||||
|
||||
if (data.Recipe != "webui") {
|
||||
const recipe = ExportRecipes[data.Recipe];
|
||||
inventoryChanges.MiscItems = recipe.secretIngredients!.map(ingredient => ({
|
||||
ItemType: ingredient.ItemType,
|
||||
ItemCount: ingredient.ItemCount * -1
|
||||
}));
|
||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||
|
||||
if (recipe.syndicateStandingChange) {
|
||||
const affiliation = inventory.Affiliations.find(x => x.Tag == recipe.syndicateStandingChange!.tag)!;
|
||||
affiliation.Standing += recipe.syndicateStandingChange.value;
|
||||
affiliationMods.push({
|
||||
Tag: recipe.syndicateStandingChange.tag,
|
||||
Standing: recipe.syndicateStandingChange.value
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
|
@ -16,7 +16,7 @@ export const giveQuestKeyRewardController: RequestHandler = async (req, res) =>
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventoryChanges = await addItem(inventory, reward.ItemType, reward.Amount);
|
||||
await inventory.save();
|
||||
res.json(inventoryChanges.InventoryChanges);
|
||||
res.json(inventoryChanges);
|
||||
//TODO: consider whishlist changes
|
||||
};
|
||||
|
20
src/controllers/api/giveShipDecoAndLoreFragmentController.ts
Normal file
20
src/controllers/api/giveShipDecoAndLoreFragmentController.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { addLoreFragmentScans, addShipDecorations, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ILoreFragmentScan, ITypeCount } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const giveShipDecoAndLoreFragmentController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "LoreFragmentScans ShipDecorations");
|
||||
const data = getJSONfromString<IGiveShipDecoAndLoreFragmentRequest>(String(req.body));
|
||||
addLoreFragmentScans(inventory, data.LoreFragmentScans);
|
||||
addShipDecorations(inventory, data.ShipDecorations);
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IGiveShipDecoAndLoreFragmentRequest {
|
||||
LoreFragmentScans: ILoreFragmentScan[];
|
||||
ShipDecorations: ITypeCount[];
|
||||
}
|
@ -14,29 +14,33 @@ import {
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
addCrewShipWeaponSkin,
|
||||
addEquipment,
|
||||
addItem,
|
||||
addMiscItems,
|
||||
addRecipes,
|
||||
combineInventoryChanges,
|
||||
getInventory,
|
||||
occupySlot,
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { IMiscItem } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { GuildPermission, ITechProjectClient } from "@/src/types/guildTypes";
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { toMongoDate, toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
|
||||
export const guildTechController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const data = JSON.parse(String(req.body)) as TGuildTechRequest;
|
||||
if (data.Action == "Sync") {
|
||||
let needSave = false;
|
||||
const techProjects: ITechProjectClient[] = [];
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (guild.TechProjects) {
|
||||
for (const project of guild.TechProjects) {
|
||||
const techProject: ITechProjectClient = {
|
||||
@ -59,137 +63,224 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
res.json({ TechProjects: techProjects });
|
||||
} else if (data.Action == "Start") {
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
guild.TechProjects ??= [];
|
||||
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||
const techProject =
|
||||
guild.TechProjects[
|
||||
guild.TechProjects.push({
|
||||
ItemType: data.RecipeType,
|
||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||
ReqItems: recipe.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||
})),
|
||||
State: 0
|
||||
}) - 1
|
||||
];
|
||||
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||
if (config.noDojoResearchCosts) {
|
||||
processFundedGuildTechProject(guild, techProject, recipe);
|
||||
} else {
|
||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
if (data.Mode == "Guild") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
guild.TechProjects ??= [];
|
||||
if (!guild.TechProjects.find(x => x.ItemType == data.RecipeType)) {
|
||||
const techProject =
|
||||
guild.TechProjects[
|
||||
guild.TechProjects.push({
|
||||
ItemType: data.RecipeType,
|
||||
ReqCredits: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, recipe.price),
|
||||
ReqItems: recipe.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: config.noDojoResearchCosts ? 0 : scaleRequiredCount(guild.Tier, x.ItemCount)
|
||||
})),
|
||||
State: 0
|
||||
}) - 1
|
||||
];
|
||||
setGuildTechLogState(guild, techProject.ItemType, 5);
|
||||
if (config.noDojoResearchCosts) {
|
||||
processFundedGuildTechProject(guild, techProject, recipe);
|
||||
} else {
|
||||
if (data.RecipeType.substring(0, 39) == "/Lotus/Types/Items/Research/DojoColors/") {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await guild.save();
|
||||
res.end();
|
||||
} else if (data.Action == "Contribute") {
|
||||
if (!hasAccessToDojo(inventory)) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
|
||||
const guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed"
|
||||
))!;
|
||||
|
||||
const contributions = data;
|
||||
const techProject = guild.TechProjects!.find(x => x.ItemType == contributions.RecipeType)!;
|
||||
|
||||
if (contributions.VaultCredits) {
|
||||
if (contributions.VaultCredits > techProject.ReqCredits) {
|
||||
contributions.VaultCredits = techProject.ReqCredits;
|
||||
await guild.save();
|
||||
res.end();
|
||||
} else {
|
||||
const recipe = ExportDojoRecipes.research[data.RecipeType];
|
||||
if (data.TechProductCategory) {
|
||||
if (
|
||||
data.TechProductCategory != "CrewShipWeapons" &&
|
||||
data.TechProductCategory != "CrewShipWeaponSkins"
|
||||
) {
|
||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||
}
|
||||
if (!inventory[getSalvageCategory(data.TechProductCategory)].id(data.CategoryItemId!)) {
|
||||
throw new Error(
|
||||
`no item with id ${data.CategoryItemId} in ${getSalvageCategory(data.TechProductCategory)} array`
|
||||
);
|
||||
}
|
||||
}
|
||||
techProject.ReqCredits -= contributions.VaultCredits;
|
||||
guild.VaultRegularCredits! -= contributions.VaultCredits;
|
||||
const techProject =
|
||||
inventory.PersonalTechProjects[
|
||||
inventory.PersonalTechProjects.push({
|
||||
State: 0,
|
||||
ReqCredits: recipe.price,
|
||||
ItemType: data.RecipeType,
|
||||
ProductCategory: data.TechProductCategory,
|
||||
CategoryItemId: data.CategoryItemId,
|
||||
ReqItems: recipe.ingredients
|
||||
}) - 1
|
||||
];
|
||||
await inventory.save();
|
||||
res.json({
|
||||
isPersonal: true,
|
||||
action: "Start",
|
||||
personalTech: techProject.toJSON()
|
||||
});
|
||||
}
|
||||
} else if (data.Action == "Contribute") {
|
||||
if ((req.query.guildId as string) == "000000000000000000000000") {
|
||||
const techProject = inventory.PersonalTechProjects.id(data.ResearchId)!;
|
||||
|
||||
if (contributions.RegularCredits > techProject.ReqCredits) {
|
||||
contributions.RegularCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= contributions.RegularCredits;
|
||||
techProject.ReqCredits -= data.RegularCredits;
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += contributions.RegularCredits;
|
||||
|
||||
if (contributions.VaultMiscItems.length) {
|
||||
for (const miscItem of contributions.VaultMiscItems) {
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of data.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
|
||||
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
||||
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
||||
miscItemChanges.push({
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of contributions.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
miscItemChanges.push({
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
techProject.HasContributions = true;
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||
if (techProject.ReqCredits == 0 && !techProject.ReqItems.find(x => x.ItemCount > 0)) {
|
||||
techProject.State = 1;
|
||||
const recipe = ExportDojoRecipes.research[techProject.ItemType];
|
||||
techProject.CompletionDate = new Date(Date.now() + recipe.time * 1000);
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
PersonalResearch: { $oid: data.ResearchId },
|
||||
PersonalResearchDate: techProject.CompletionDate ? toMongoDate(techProject.CompletionDate) : undefined
|
||||
});
|
||||
} else {
|
||||
if (!hasAccessToDojo(inventory)) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
const guildMember = (await GuildMember.findOne(
|
||||
{ accountId, guildId: guild._id },
|
||||
"RegularCreditsContributed MiscItemsContributed"
|
||||
))!;
|
||||
|
||||
const techProject = guild.TechProjects!.find(x => x.ItemType == data.RecipeType)!;
|
||||
|
||||
if (data.VaultCredits) {
|
||||
if (data.VaultCredits > techProject.ReqCredits) {
|
||||
data.VaultCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= data.VaultCredits;
|
||||
guild.VaultRegularCredits! -= data.VaultCredits;
|
||||
}
|
||||
|
||||
if (data.RegularCredits > techProject.ReqCredits) {
|
||||
data.RegularCredits = techProject.ReqCredits;
|
||||
}
|
||||
techProject.ReqCredits -= data.RegularCredits;
|
||||
|
||||
guildMember.RegularCreditsContributed ??= 0;
|
||||
guildMember.RegularCreditsContributed += data.RegularCredits;
|
||||
|
||||
if (data.VaultMiscItems.length) {
|
||||
for (const miscItem of data.VaultMiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
|
||||
const vaultMiscItem = guild.VaultMiscItems!.find(x => x.ItemType == miscItem.ItemType)!;
|
||||
vaultMiscItem.ItemCount -= miscItem.ItemCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const miscItemChanges = [];
|
||||
for (const miscItem of data.MiscItems) {
|
||||
const reqItem = techProject.ReqItems.find(x => x.ItemType == miscItem.ItemType);
|
||||
if (reqItem) {
|
||||
if (miscItem.ItemCount > reqItem.ItemCount) {
|
||||
miscItem.ItemCount = reqItem.ItemCount;
|
||||
}
|
||||
reqItem.ItemCount -= miscItem.ItemCount;
|
||||
miscItemChanges.push({
|
||||
ItemType: miscItem.ItemType,
|
||||
ItemCount: miscItem.ItemCount * -1
|
||||
});
|
||||
|
||||
addGuildMemberMiscItemContribution(guildMember, miscItem);
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, data.RegularCredits, false);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
// Check if research is fully funded now.
|
||||
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
Vault: getGuildVault(guild)
|
||||
});
|
||||
}
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
const inventoryChanges: IInventoryChanges = updateCurrency(inventory, contributions.RegularCredits, false);
|
||||
inventoryChanges.MiscItems = miscItemChanges;
|
||||
|
||||
// Check if research is fully funded now.
|
||||
await processGuildTechProjectContributionsUpdate(guild, techProject);
|
||||
|
||||
await Promise.all([guild.save(), inventory.save(), guildMember.save()]);
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
Vault: getGuildVault(guild)
|
||||
});
|
||||
} else if (data.Action.split(",")[0] == "Buy") {
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
const purchase = data as IGuildTechBuyRequest;
|
||||
const quantity = parseInt(data.Action.split(",")[1]);
|
||||
const recipeChanges = [
|
||||
{
|
||||
ItemType: purchase.RecipeType,
|
||||
ItemCount: quantity
|
||||
if (purchase.Mode == "Guild") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (
|
||||
!hasAccessToDojo(inventory) ||
|
||||
!(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))
|
||||
) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
}
|
||||
];
|
||||
addRecipes(inventory, recipeChanges);
|
||||
const currencyChanges = updateCurrency(
|
||||
inventory,
|
||||
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
||||
false
|
||||
);
|
||||
await inventory.save();
|
||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||
res.json({
|
||||
inventoryChanges: {
|
||||
...currencyChanges,
|
||||
Recipes: recipeChanges
|
||||
}
|
||||
});
|
||||
const quantity = parseInt(data.Action.split(",")[1]);
|
||||
const recipeChanges = [
|
||||
{
|
||||
ItemType: purchase.RecipeType,
|
||||
ItemCount: quantity
|
||||
}
|
||||
];
|
||||
addRecipes(inventory, recipeChanges);
|
||||
const currencyChanges = updateCurrency(
|
||||
inventory,
|
||||
ExportDojoRecipes.research[purchase.RecipeType].replicatePrice,
|
||||
false
|
||||
);
|
||||
await inventory.save();
|
||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||
res.json({
|
||||
inventoryChanges: {
|
||||
...currencyChanges,
|
||||
Recipes: recipeChanges
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const inventoryChanges = claimSalvagedComponent(inventory, purchase.CategoryItemId!);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
inventoryChanges: inventoryChanges
|
||||
});
|
||||
}
|
||||
} else if (data.Action == "Fabricate") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Fabricator))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -206,6 +297,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
// Not a mistake: This response uses `inventoryChanges` instead of `InventoryChanges`.
|
||||
res.json({ inventoryChanges: inventoryChanges });
|
||||
} else if (data.Action == "Pause") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -217,6 +309,7 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
await removePigmentsFromGuildMembers(guild._id);
|
||||
res.end();
|
||||
} else if (data.Action == "Unpause") {
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Tech))) {
|
||||
res.status(400).send("-1").end();
|
||||
return;
|
||||
@ -226,9 +319,66 @@ export const guildTechController: RequestHandler = async (req, res) => {
|
||||
guild.ActiveDojoColorResearch = data.RecipeType;
|
||||
await guild.save();
|
||||
res.end();
|
||||
} else if (data.Action == "Cancel" && data.CategoryItemId) {
|
||||
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x =>
|
||||
x.CategoryItemId?.equals(data.CategoryItemId)
|
||||
);
|
||||
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
|
||||
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
||||
|
||||
const meta = ExportDojoRecipes.research[personalTechProject.ItemType];
|
||||
const contributedCredits = meta.price - personalTechProject.ReqCredits;
|
||||
const inventoryChanges = updateCurrency(inventory, contributedCredits * -1, false);
|
||||
inventoryChanges.MiscItems = [];
|
||||
for (const ingredient of meta.ingredients) {
|
||||
const reqItem = personalTechProject.ReqItems.find(x => x.ItemType == ingredient.ItemType);
|
||||
if (reqItem) {
|
||||
const contributedItems = ingredient.ItemCount - reqItem.ItemCount;
|
||||
inventoryChanges.MiscItems.push({
|
||||
ItemType: ingredient.ItemType,
|
||||
ItemCount: contributedItems
|
||||
});
|
||||
}
|
||||
}
|
||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||
|
||||
await inventory.save();
|
||||
res.json({
|
||||
action: "Cancel",
|
||||
isPersonal: true,
|
||||
inventoryChanges: inventoryChanges,
|
||||
personalTech: {
|
||||
ItemId: toOid(personalTechProject._id)
|
||||
}
|
||||
});
|
||||
} else if (data.Action == "Rush" && data.CategoryItemId) {
|
||||
const inventoryChanges: IInventoryChanges = {
|
||||
...updateCurrency(inventory, 20, true),
|
||||
...claimSalvagedComponent(inventory, data.CategoryItemId)
|
||||
};
|
||||
await inventory.save();
|
||||
res.json({
|
||||
inventoryChanges: inventoryChanges
|
||||
});
|
||||
} else if (data.Action == "InstantFinish") {
|
||||
if (data.TechProductCategory != "CrewShipWeapons" && data.TechProductCategory != "CrewShipWeaponSkins") {
|
||||
throw new Error(`unexpected TechProductCategory: ${data.TechProductCategory}`);
|
||||
}
|
||||
const inventoryChanges = finishComponentRepair(inventory, data.TechProductCategory, data.CategoryItemId!);
|
||||
inventoryChanges.MiscItems = [
|
||||
{
|
||||
ItemType: "/Lotus/Types/Items/MiscItems/InstantSalvageRepairItem",
|
||||
ItemCount: -1
|
||||
}
|
||||
];
|
||||
addMiscItems(inventory, inventoryChanges.MiscItems);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
inventoryChanges: inventoryChanges
|
||||
});
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unknown guildTech action: ${data.Action}`);
|
||||
throw new Error(`unhandled guildTech request`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -238,23 +388,70 @@ type TGuildTechRequest =
|
||||
| IGuildTechContributeRequest;
|
||||
|
||||
interface IGuildTechBasicRequest {
|
||||
Action: "Start" | "Fabricate" | "Pause" | "Unpause";
|
||||
Mode: "Guild";
|
||||
Action: "Start" | "Fabricate" | "Pause" | "Unpause" | "Cancel" | "Rush" | "InstantFinish";
|
||||
Mode: "Guild" | "Personal";
|
||||
RecipeType: string;
|
||||
TechProductCategory?: string;
|
||||
CategoryItemId?: string;
|
||||
}
|
||||
|
||||
interface IGuildTechBuyRequest {
|
||||
interface IGuildTechBuyRequest extends Omit<IGuildTechBasicRequest, "Action"> {
|
||||
Action: string;
|
||||
Mode: "Guild";
|
||||
RecipeType: string;
|
||||
}
|
||||
|
||||
interface IGuildTechContributeRequest {
|
||||
Action: "Contribute";
|
||||
ResearchId: "";
|
||||
ResearchId: string;
|
||||
RecipeType: string;
|
||||
RegularCredits: number;
|
||||
MiscItems: IMiscItem[];
|
||||
VaultCredits: number;
|
||||
VaultMiscItems: IMiscItem[];
|
||||
}
|
||||
|
||||
const getSalvageCategory = (
|
||||
category: "CrewShipWeapons" | "CrewShipWeaponSkins"
|
||||
): "CrewShipSalvagedWeapons" | "CrewShipSalvagedWeaponSkins" => {
|
||||
return category == "CrewShipWeapons" ? "CrewShipSalvagedWeapons" : "CrewShipSalvagedWeaponSkins";
|
||||
};
|
||||
|
||||
const claimSalvagedComponent = (inventory: TInventoryDatabaseDocument, itemId: string): IInventoryChanges => {
|
||||
// delete personal tech project
|
||||
const personalTechProjectIndex = inventory.PersonalTechProjects.findIndex(x => x.CategoryItemId?.equals(itemId));
|
||||
const personalTechProject = inventory.PersonalTechProjects[personalTechProjectIndex];
|
||||
inventory.PersonalTechProjects.splice(personalTechProjectIndex, 1);
|
||||
|
||||
const category = personalTechProject.ProductCategory! as "CrewShipWeapons" | "CrewShipWeaponSkins";
|
||||
return finishComponentRepair(inventory, category, itemId);
|
||||
};
|
||||
|
||||
const finishComponentRepair = (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
category: "CrewShipWeapons" | "CrewShipWeaponSkins",
|
||||
itemId: string
|
||||
): IInventoryChanges => {
|
||||
const salvageCategory = getSalvageCategory(category);
|
||||
|
||||
// find salved part & delete it
|
||||
const salvageIndex = inventory[salvageCategory].findIndex(x => x._id.equals(itemId));
|
||||
const salvageItem = inventory[salvageCategory][salvageIndex];
|
||||
inventory[salvageCategory].splice(salvageIndex, 1);
|
||||
|
||||
// add final item
|
||||
const inventoryChanges = {
|
||||
...(category == "CrewShipWeaponSkins"
|
||||
? addCrewShipWeaponSkin(inventory, salvageItem.ItemType, salvageItem.UpgradeFingerprint)
|
||||
: addEquipment(inventory, category, salvageItem.ItemType, {
|
||||
UpgradeFingerprint: salvageItem.UpgradeFingerprint
|
||||
})),
|
||||
...occupySlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS, false)
|
||||
};
|
||||
|
||||
inventoryChanges.RemovedIdItems = [
|
||||
{
|
||||
ItemId: { $oid: itemId }
|
||||
}
|
||||
];
|
||||
|
||||
return inventoryChanges;
|
||||
};
|
||||
|
@ -1,17 +1,24 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { createNewSession } from "@/src/managers/sessionManager";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ISession } from "@/src/types/session";
|
||||
import { JSONParse } from "json-with-bigint";
|
||||
import { toOid2, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
|
||||
const hostSessionController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const hostSessionRequest = JSON.parse(req.body as string) as ISession;
|
||||
const account = await getAccountForRequest(req);
|
||||
const hostSessionRequest = JSONParse(String(req.body)) as ISession;
|
||||
logger.debug("HostSession Request", { hostSessionRequest });
|
||||
const session = createNewSession(hostSessionRequest, accountId);
|
||||
const session = createNewSession(hostSessionRequest, account._id);
|
||||
logger.debug(`New Session Created`, { session });
|
||||
|
||||
res.json({ sessionId: { $oid: session.sessionId }, rewardSeed: 99999999 });
|
||||
if (account.BuildLabel && version_compare(account.BuildLabel, "2015.03.21.08.17") < 0) {
|
||||
// U15 or below
|
||||
res.send(session.sessionId.toString());
|
||||
} else {
|
||||
res.json({ sessionId: toOid2(session.sessionId, account.BuildLabel), rewardSeed: 99999999 });
|
||||
}
|
||||
};
|
||||
|
||||
export { hostSessionController };
|
||||
|
@ -9,10 +9,17 @@ import {
|
||||
getMessage
|
||||
} from "@/src/services/inboxService";
|
||||
import { getAccountForRequest, getAccountFromSuffixedName, getSuffixedName } from "@/src/services/loginService";
|
||||
import { addItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||
import {
|
||||
addItems,
|
||||
combineInventoryChanges,
|
||||
getEffectiveAvatarImageType,
|
||||
getInventory
|
||||
} from "@/src/services/inventoryService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { ExportFlavour, ExportGear } from "warframe-public-export-plus";
|
||||
import { ExportFlavour } from "warframe-public-export-plus";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
|
||||
export const inboxController: RequestHandler = async (req, res) => {
|
||||
const { deleteId, lastMessage: latestClientMessageId, messageId } = req.query;
|
||||
@ -27,10 +34,10 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
await deleteMessageRead(deleteId as string);
|
||||
await deleteMessageRead(parseOid(deleteId as string));
|
||||
res.status(200).end();
|
||||
} else if (messageId) {
|
||||
const message = await getMessage(messageId as string);
|
||||
const message = await getMessage(parseOid(messageId as string));
|
||||
message.r = true;
|
||||
await message.save();
|
||||
|
||||
@ -48,8 +55,8 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
await addItems(
|
||||
inventory,
|
||||
attachmentItems.map(attItem => ({
|
||||
ItemType: attItem,
|
||||
ItemCount: attItem in ExportGear ? (ExportGear[attItem].purchaseQuantity ?? 1) : 1
|
||||
ItemType: isStoreItem(attItem) ? fromStoreItem(attItem) : attItem,
|
||||
ItemCount: 1
|
||||
})),
|
||||
inventoryChanges
|
||||
);
|
||||
@ -86,7 +93,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
],
|
||||
sub: "/Lotus/Language/Menu/GiftReceivedConfirmationSubject",
|
||||
icon: ExportFlavour[inventory.ActiveAvatarImageType].icon,
|
||||
icon: ExportFlavour[getEffectiveAvatarImageType(inventory)].icon,
|
||||
highPriority: true
|
||||
}
|
||||
]);
|
||||
@ -99,7 +106,7 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
await createNewEventMessages(req);
|
||||
const messages = await Inbox.find({ ownerId: accountId }).sort({ date: 1 });
|
||||
|
||||
const latestClientMessage = messages.find(m => m._id.toString() === latestClientMessageId);
|
||||
const latestClientMessage = messages.find(m => m._id.toString() === parseOid(latestClientMessageId as string));
|
||||
|
||||
if (!latestClientMessage) {
|
||||
logger.debug(`this should only happen after DeleteAllRead `);
|
||||
@ -122,3 +129,11 @@ export const inboxController: RequestHandler = async (req, res) => {
|
||||
res.json({ Inbox: inbox });
|
||||
}
|
||||
};
|
||||
|
||||
// 33.6.0 has query arguments like lastMessage={"$oid":"68112baebf192e786d1502bb"} instead of lastMessage=68112baebf192e786d1502bb
|
||||
const parseOid = (oid: string): string => {
|
||||
if (oid[0] == "{") {
|
||||
return (JSON.parse(oid) as IOid).$oid;
|
||||
}
|
||||
return oid;
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory, addMiscItems, updateCurrency, addRecipes, freeUpSlot } from "@/src/services/inventoryService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
@ -12,7 +12,7 @@ import {
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ExportMisc } from "warframe-public-export-plus";
|
||||
import { getRecipe } from "@/src/services/itemDataService";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { toMongoDate, version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { colorToShard } from "@/src/helpers/shardHelper";
|
||||
import { config } from "@/src/services/configService";
|
||||
@ -23,12 +23,12 @@ import {
|
||||
} from "@/src/services/infestedFoundryService";
|
||||
|
||||
export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await getAccountForRequest(req);
|
||||
switch (req.query.mode) {
|
||||
case "s": {
|
||||
// shard installation
|
||||
const request = getJSONfromString<IShardInstallRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||
if (!suit.ArchonCrystalUpgrades || suit.ArchonCrystalUpgrades.length != 5) {
|
||||
suit.ArchonCrystalUpgrades = [{}, {}, {}, {}, {}];
|
||||
@ -56,7 +56,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
case "x": {
|
||||
// shard removal
|
||||
const request = getJSONfromString<IShardUninstallRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||
|
||||
const miscItemChanges: IMiscItem[] = [];
|
||||
@ -70,19 +70,30 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
ItemCount: 1
|
||||
});
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
|
||||
// consume resources
|
||||
if (!config.infiniteHelminthMaterials) {
|
||||
let type: string;
|
||||
let count: number;
|
||||
if (account.BuildLabel && version_compare(account.BuildLabel, "2025.05.20.10.18") < 0) {
|
||||
// < 38.6.0
|
||||
type = "/Lotus/Types/Items/InfestedFoundry/HelminthBile";
|
||||
count = 300;
|
||||
} else {
|
||||
// >= 38.6.0
|
||||
type =
|
||||
archonCrystalRemovalResource[
|
||||
suit.ArchonCrystalUpgrades![request.Slot].Color!.replace("_MYTHIC", "")
|
||||
];
|
||||
count = suit.ArchonCrystalUpgrades![request.Slot].Color!.indexOf("_MYTHIC") != -1 ? 300 : 150;
|
||||
}
|
||||
inventory.InfestedFoundry!.Resources!.find(x => x.ItemType == type)!.Count -= count;
|
||||
}
|
||||
}
|
||||
|
||||
// remove from suit
|
||||
suit.ArchonCrystalUpgrades![request.Slot] = {};
|
||||
|
||||
if (!config.infiniteHelminthMaterials) {
|
||||
// remove bile
|
||||
const bile = inventory.InfestedFoundry!.Resources!.find(
|
||||
x => x.ItemType == "/Lotus/Types/Items/InfestedFoundry/HelminthBile"
|
||||
)!;
|
||||
bile.Count -= 300;
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
|
||||
const infestedFoundry = inventory.toJSON<IInventoryClient>().InfestedFoundry!;
|
||||
@ -99,7 +110,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
case "n": {
|
||||
// name the beast
|
||||
const request = getJSONfromString<IHelminthNameRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
inventory.InfestedFoundry ??= {};
|
||||
inventory.InfestedFoundry.Name = request.newName;
|
||||
await inventory.save();
|
||||
@ -122,7 +133,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
const request = getJSONfromString<IHelminthFeedRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
inventory.InfestedFoundry ??= {};
|
||||
inventory.InfestedFoundry.Resources ??= [];
|
||||
|
||||
@ -218,7 +229,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
case "o": {
|
||||
// offerings update
|
||||
const request = getJSONfromString<IHelminthOfferingsUpdate>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
inventory.InfestedFoundry ??= {};
|
||||
inventory.InfestedFoundry.InvigorationIndex = request.OfferingsIndex;
|
||||
inventory.InfestedFoundry.InvigorationSuitOfferings = request.SuitTypes;
|
||||
@ -239,7 +250,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
case "a": {
|
||||
// subsume warframe
|
||||
const request = getJSONfromString<IHelminthSubsumeRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const recipe = getRecipe(request.Recipe)!;
|
||||
if (!config.infiniteHelminthMaterials) {
|
||||
for (const ingredient of recipe.secretIngredients!) {
|
||||
@ -289,7 +300,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
|
||||
case "r": {
|
||||
// rush subsume
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const currencyChanges = updateCurrency(inventory, 50, true);
|
||||
const recipeChanges = handleSubsumeCompletion(inventory);
|
||||
await inventory.save();
|
||||
@ -307,7 +318,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
|
||||
case "u": {
|
||||
const request = getJSONfromString<IHelminthInvigorationRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const suit = inventory.Suits.id(request.SuitId.$oid)!;
|
||||
const upgradesExpiry = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
||||
suit.OffensiveUpgrade = request.OffensiveUpgradeType;
|
||||
@ -340,7 +351,7 @@ export const infestedFoundryController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
case "custom_unlockall": {
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
inventory.InfestedFoundry ??= {};
|
||||
inventory.InfestedFoundry.XP ??= 0;
|
||||
if (151875_00 > inventory.InfestedFoundry.XP) {
|
||||
@ -439,3 +450,12 @@ const apetiteModel = (x: number): number => {
|
||||
}
|
||||
return 3;
|
||||
};
|
||||
|
||||
const archonCrystalRemovalResource: Record<string, string> = {
|
||||
ACC_RED: "/Lotus/Types/Items/InfestedFoundry/HelminthOxides",
|
||||
ACC_YELLOW: "/Lotus/Types/Items/InfestedFoundry/HelminthBile",
|
||||
ACC_BLUE: "/Lotus/Types/Items/InfestedFoundry/HelminthSynthetics",
|
||||
ACC_GREEN: "/Lotus/Types/Items/InfestedFoundry/HelminthBiotics",
|
||||
ACC_ORANGE: "/Lotus/Types/Items/InfestedFoundry/HelminthPheromones",
|
||||
ACC_PURPLE: "/Lotus/Types/Items/InfestedFoundry/HelminthCalx"
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { Inventory, TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { config } from "@/src/services/configService";
|
||||
import allDialogue from "@/static/fixed_responses/allDialogue.json";
|
||||
@ -14,14 +14,26 @@ import {
|
||||
ExportVirtuals
|
||||
} from "warframe-public-export-plus";
|
||||
import { applyCheatsToInfestedFoundry, handleSubsumeCompletion } from "@/src/services/infestedFoundryService";
|
||||
import { addMiscItems, allDailyAffiliationKeys, createLibraryDailyTask } from "@/src/services/inventoryService";
|
||||
import {
|
||||
addMiscItems,
|
||||
allDailyAffiliationKeys,
|
||||
cleanupInventory,
|
||||
createLibraryDailyTask,
|
||||
generateRewardSeed
|
||||
} from "@/src/services/inventoryService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { 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";
|
||||
|
||||
export const inventoryController: RequestHandler = async (request, response) => {
|
||||
const accountId = await getAccountIdForRequest(request);
|
||||
const account = await getAccountForRequest(request);
|
||||
|
||||
const inventory = await Inventory.findOne({ accountOwnerId: accountId });
|
||||
const inventory = await Inventory.findOne({ accountOwnerId: account._id });
|
||||
|
||||
if (!inventory) {
|
||||
response.status(400).json({ error: "inventory was undefined" });
|
||||
@ -79,8 +91,10 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
}
|
||||
}
|
||||
|
||||
cleanupInventory(inventory);
|
||||
|
||||
inventory.NextRefill = new Date((Math.trunc(Date.now() / 86400000) + 1) * 86400000);
|
||||
await inventory.save();
|
||||
//await inventory.save();
|
||||
}
|
||||
|
||||
if (
|
||||
@ -89,23 +103,43 @@ export const inventoryController: RequestHandler = async (request, response) =>
|
||||
new Date() >= inventory.InfestedFoundry.AbilityOverrideUnlockCooldown
|
||||
) {
|
||||
handleSubsumeCompletion(inventory);
|
||||
await inventory.save();
|
||||
//await inventory.save();
|
||||
}
|
||||
|
||||
response.json(await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query));
|
||||
if (inventory.LastInventorySync) {
|
||||
const lastSyncDuviriMood = Math.trunc(inventory.LastInventorySync.getTimestamp().getTime() / 7200000);
|
||||
const currentDuviriMood = Math.trunc(Date.now() / 7200000);
|
||||
if (lastSyncDuviriMood != currentDuviriMood) {
|
||||
logger.debug(`refreshing duviri seed`);
|
||||
if (!inventory.DuviriInfo) {
|
||||
inventory.DuviriInfo = {
|
||||
Seed: generateRewardSeed(),
|
||||
NumCompletions: 0
|
||||
};
|
||||
} else {
|
||||
inventory.DuviriInfo.Seed = generateRewardSeed();
|
||||
}
|
||||
}
|
||||
}
|
||||
inventory.LastInventorySync = new Types.ObjectId();
|
||||
await inventory.save();
|
||||
|
||||
response.json(
|
||||
await getInventoryResponse(inventory, "xpBasedLevelCapDisabled" in request.query, account.BuildLabel)
|
||||
);
|
||||
};
|
||||
|
||||
export const getInventoryResponse = async (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
xpBasedLevelCapDisabled: boolean
|
||||
xpBasedLevelCapDisabled: boolean,
|
||||
buildLabel: string | undefined
|
||||
): Promise<IInventoryClient> => {
|
||||
const inventoryWithLoadOutPresets = await inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>(
|
||||
"LoadOutPresets"
|
||||
);
|
||||
const inventoryWithLoadOutPresetsAndShips = await inventoryWithLoadOutPresets.populate<{ Ships: IShipInventory }>(
|
||||
"Ships"
|
||||
);
|
||||
const inventoryResponse = inventoryWithLoadOutPresetsAndShips.toJSON<IInventoryClient>();
|
||||
const [inventoryWithLoadOutPresets, ships] = await Promise.all([
|
||||
inventory.populate<{ LoadOutPresets: ILoadoutDatabase }>("LoadOutPresets"),
|
||||
Ship.find({ ShipOwnerId: inventory.accountOwnerId })
|
||||
]);
|
||||
const inventoryResponse = inventoryWithLoadOutPresets.toJSON<IInventoryClient>();
|
||||
inventoryResponse.Ships = ships.map(x => x.toJSON<IShipInventory>());
|
||||
|
||||
if (config.infiniteCredits) {
|
||||
inventoryResponse.RegularCredits = 999999999;
|
||||
@ -149,7 +183,7 @@ export const getInventoryResponse = async (
|
||||
inventoryResponse.ShipDecorations = [];
|
||||
for (const [uniqueName, item] of Object.entries(ExportResources)) {
|
||||
if (item.productCategory == "ShipDecorations") {
|
||||
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 1 });
|
||||
inventoryResponse.ShipDecorations.push({ ItemType: uniqueName, ItemCount: 999_999 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -202,7 +236,8 @@ export const getInventoryResponse = async (
|
||||
|
||||
if (config.universalPolarityEverywhere) {
|
||||
const Polarity: IPolarity[] = [];
|
||||
for (let i = 0; i != 12; ++i) {
|
||||
// 12 is needed for necramechs. 15 is needed for plexus/crewshipharness.
|
||||
for (let i = 0; i != 15; ++i) {
|
||||
Polarity.push({
|
||||
Slot: i,
|
||||
Value: ArtifactPolarity.Any
|
||||
@ -257,21 +292,58 @@ export const getInventoryResponse = async (
|
||||
}
|
||||
}
|
||||
|
||||
if (config.noDailyFocusLimit) {
|
||||
inventoryResponse.DailyFocus = Math.max(999_999, 250000 + inventoryResponse.PlayerLevel * 5000);
|
||||
}
|
||||
|
||||
if (inventoryResponse.InfestedFoundry) {
|
||||
applyCheatsToInfestedFoundry(inventoryResponse.InfestedFoundry);
|
||||
}
|
||||
|
||||
// Omitting this field so opening the navigation resyncs the inventory which is more desirable for typical usage.
|
||||
//inventoryResponse.LastInventorySync = toOid(new Types.ObjectId());
|
||||
inventoryResponse.LastInventorySync = undefined;
|
||||
|
||||
// Set 2FA enabled so trading post can be used
|
||||
inventoryResponse.HWIDProtectEnabled = true;
|
||||
|
||||
if (buildLabel) {
|
||||
// Fix nemesis for older versions
|
||||
if (
|
||||
inventoryResponse.Nemesis &&
|
||||
version_compare(getNemesisManifest(inventoryResponse.Nemesis.manifest).minBuild, buildLabel) < 0
|
||||
) {
|
||||
inventoryResponse.Nemesis = undefined;
|
||||
}
|
||||
|
||||
if (version_compare(buildLabel, "2018.02.22.14.34") < 0) {
|
||||
const personalRoomsDb = await getPersonalRooms(inventory.accountOwnerId.toString());
|
||||
const personalRooms = personalRoomsDb.toJSON<IPersonalRoomsClient>();
|
||||
inventoryResponse.Ship = personalRooms.Ship;
|
||||
|
||||
if (version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
||||
// U19.5 and below use $id instead of $oid
|
||||
for (const category of equipmentKeys) {
|
||||
for (const item of inventoryResponse[category]) {
|
||||
toLegacyOid(item.ItemId);
|
||||
}
|
||||
}
|
||||
for (const upgrade of inventoryResponse.Upgrades) {
|
||||
toLegacyOid(upgrade.ItemId);
|
||||
}
|
||||
if (inventoryResponse.BrandedSuits) {
|
||||
for (const id of inventoryResponse.BrandedSuits) {
|
||||
toLegacyOid(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inventoryResponse;
|
||||
};
|
||||
|
||||
const addString = (arr: string[], str: string): void => {
|
||||
if (!arr.find(x => x == str)) {
|
||||
if (arr.indexOf(str) == -1) {
|
||||
arr.push(str);
|
||||
}
|
||||
};
|
||||
|
@ -7,6 +7,7 @@ import { Account } from "@/src/models/loginModel";
|
||||
import { createAccount, isCorrectPassword, isNameTaken } 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";
|
||||
|
||||
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
|
||||
@ -21,7 +22,11 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
|
||||
const myAddress = request.host.indexOf("warframe.com") == -1 ? request.host : config.myAddress;
|
||||
|
||||
if (!account && config.autoCreateAccount && loginRequest.ClientType != "webui") {
|
||||
if (
|
||||
!account &&
|
||||
((config.autoCreateAccount && loginRequest.ClientType != "webui") ||
|
||||
loginRequest.ClientType == "webui-register")
|
||||
) {
|
||||
try {
|
||||
const nameFromEmail = loginRequest.email.substring(0, loginRequest.email.indexOf("@"));
|
||||
let name = nameFromEmail || loginRequest.email.substring(1) || "SpaceNinja";
|
||||
@ -36,13 +41,15 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
email: loginRequest.email,
|
||||
password: loginRequest.password,
|
||||
DisplayName: name,
|
||||
CountryCode: loginRequest.lang.toUpperCase(),
|
||||
ClientType: loginRequest.ClientType,
|
||||
CountryCode: loginRequest.lang?.toUpperCase() ?? "EN",
|
||||
ClientType: loginRequest.ClientType == "webui-register" ? "webui" : loginRequest.ClientType,
|
||||
CrossPlatformAllowed: true,
|
||||
ForceLogoutVersion: 0,
|
||||
ConsentNeeded: false,
|
||||
TrackedSettings: [],
|
||||
Nonce: nonce
|
||||
Nonce: nonce,
|
||||
BuildLabel: buildLabel,
|
||||
LastLogin: new Date()
|
||||
});
|
||||
logger.debug("created new account");
|
||||
response.json(createLoginResponse(myAddress, newAccount, buildLabel));
|
||||
@ -59,6 +66,11 @@ 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;
|
||||
@ -71,13 +83,18 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
}
|
||||
} else {
|
||||
if (account.Nonce && account.ClientType != "webui" && !account.Dropped && !loginRequest.kick) {
|
||||
response.status(400).json({ error: "nonce still set" });
|
||||
return;
|
||||
// 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();
|
||||
account.CountryCode = loginRequest.lang?.toUpperCase() ?? "EN";
|
||||
account.BuildLabel = buildLabel;
|
||||
account.LastLogin = new Date();
|
||||
}
|
||||
await account.save();
|
||||
|
||||
@ -85,25 +102,48 @@ export const loginController: RequestHandler = async (request, response) => {
|
||||
};
|
||||
|
||||
const createLoginResponse = (myAddress: string, account: IDatabaseAccountJson, buildLabel: string): ILoginResponse => {
|
||||
return {
|
||||
const resp: ILoginResponse = {
|
||||
id: account.id,
|
||||
DisplayName: account.DisplayName,
|
||||
CountryCode: account.CountryCode,
|
||||
ClientType: account.ClientType,
|
||||
CrossPlatformAllowed: account.CrossPlatformAllowed,
|
||||
ForceLogoutVersion: account.ForceLogoutVersion,
|
||||
AmazonAuthToken: account.AmazonAuthToken,
|
||||
AmazonRefreshToken: account.AmazonRefreshToken,
|
||||
ConsentNeeded: account.ConsentNeeded,
|
||||
TrackedSettings: account.TrackedSettings,
|
||||
Nonce: account.Nonce,
|
||||
Groups: [],
|
||||
IRC: config.myIrcAddresses ?? [myAddress],
|
||||
platformCDNs: [`https://${myAddress}/`],
|
||||
HUB: `https://${myAddress}/api/`,
|
||||
NRS: config.NRS,
|
||||
DTLS: 99,
|
||||
BuildLabel: buildLabel,
|
||||
MatchmakingBuildId: buildConfig.matchmakingBuildId
|
||||
BuildLabel: buildLabel
|
||||
};
|
||||
if (version_compare(buildLabel, "2015.02.13.10.41") >= 0) {
|
||||
resp.NRS = config.NRS;
|
||||
}
|
||||
if (version_compare(buildLabel, "2015.05.14.16.29") >= 0) {
|
||||
// U17 and up
|
||||
resp.IRC = config.myIrcAddresses ?? [myAddress];
|
||||
}
|
||||
if (version_compare(buildLabel, "2018.11.08.14.45") >= 0) {
|
||||
// U24 and up
|
||||
resp.ConsentNeeded = account.ConsentNeeded;
|
||||
resp.TrackedSettings = account.TrackedSettings;
|
||||
}
|
||||
if (version_compare(buildLabel, "2019.08.29.20.01") >= 0) {
|
||||
// U25.7 and up
|
||||
resp.ForceLogoutVersion = account.ForceLogoutVersion;
|
||||
}
|
||||
if (version_compare(buildLabel, "2019.10.31.22.42") >= 0) {
|
||||
// U26 and up
|
||||
resp.Groups = [];
|
||||
}
|
||||
if (version_compare(buildLabel, "2021.04.13.19.58") >= 0) {
|
||||
resp.DTLS = 99;
|
||||
}
|
||||
if (version_compare(buildLabel, "2022.04.29.12.53") >= 0) {
|
||||
resp.ClientType = account.ClientType;
|
||||
}
|
||||
if (version_compare(buildLabel, "2022.09.06.19.24") >= 0) {
|
||||
resp.CrossPlatformAllowed = account.CrossPlatformAllowed;
|
||||
resp.HUB = `https://${myAddress}/api/`;
|
||||
resp.MatchmakingBuildId = buildConfig.matchmakingBuildId;
|
||||
}
|
||||
if (version_compare(buildLabel, "2023.04.25.23.40") >= 0) {
|
||||
resp.platformCDNs = [`https://${myAddress}/`];
|
||||
}
|
||||
return resp;
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ export const loginRewardsSelectionController: RequestHandler = async (req, res)
|
||||
StoreItemType: body.ChosenReward
|
||||
};
|
||||
inventoryChanges = (await handleStoreItemAcquisition(body.ChosenReward, inventory)).InventoryChanges;
|
||||
if (!evergreenRewards.find(x => x == body.ChosenReward)) {
|
||||
if (evergreenRewards.indexOf(body.ChosenReward) == -1) {
|
||||
inventory.LoginMilestoneRewards.push(body.ChosenReward);
|
||||
}
|
||||
} else {
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { IMissionInventoryUpdateRequest } from "@/src/types/requestTypes";
|
||||
import { addMissionInventoryUpdates, addMissionRewards } from "@/src/services/missionInventoryUpdateService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { generateRewardSeed, getInventory } from "@/src/services/inventoryService";
|
||||
import { getInventoryResponse } from "./inventoryController";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IMissionInventoryUpdateResponse } from "@/src/types/missionTypes";
|
||||
|
||||
/*
|
||||
**** INPUT ****
|
||||
@ -48,16 +49,29 @@ import { logger } from "@/src/utils/logger";
|
||||
*/
|
||||
//move credit calc in here, return MissionRewards: [] if no reward info
|
||||
export const missionInventoryUpdateController: RequestHandler = async (req, res): Promise<void> => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await getAccountForRequest(req);
|
||||
const missionReport = getJSONfromString<IMissionInventoryUpdateRequest>((req.body as string).toString());
|
||||
logger.debug("mission report:", missionReport);
|
||||
|
||||
const inventory = await getInventory(accountId);
|
||||
const inventoryUpdates = await addMissionInventoryUpdates(inventory, missionReport);
|
||||
const inventory = await getInventory(account._id.toString());
|
||||
const firstCompletion = missionReport.SortieId
|
||||
? inventory.CompletedSorties.indexOf(missionReport.SortieId) == -1
|
||||
: false;
|
||||
const inventoryUpdates = await addMissionInventoryUpdates(account, inventory, missionReport);
|
||||
|
||||
if (missionReport.MissionStatus !== "GS_SUCCESS") {
|
||||
if (
|
||||
missionReport.MissionStatus !== "GS_SUCCESS" &&
|
||||
!(
|
||||
missionReport.RewardInfo?.jobId ||
|
||||
missionReport.RewardInfo?.challengeMissionId ||
|
||||
missionReport.RewardInfo?.T
|
||||
)
|
||||
) {
|
||||
if (missionReport.EndOfMatchUpload) {
|
||||
inventory.RewardSeed = generateRewardSeed();
|
||||
}
|
||||
await inventory.save();
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true);
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
||||
res.json({
|
||||
InventoryJson: JSON.stringify(inventoryResponse),
|
||||
MissionRewards: []
|
||||
@ -65,10 +79,20 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
||||
return;
|
||||
}
|
||||
|
||||
const { MissionRewards, inventoryChanges, credits } = await addMissionRewards(inventory, missionReport);
|
||||
const {
|
||||
MissionRewards,
|
||||
inventoryChanges,
|
||||
credits,
|
||||
AffiliationMods,
|
||||
SyndicateXPItemReward,
|
||||
ConquestCompletedMissionsCount
|
||||
} = await addMissionRewards(inventory, missionReport, firstCompletion);
|
||||
|
||||
if (missionReport.EndOfMatchUpload) {
|
||||
inventory.RewardSeed = generateRewardSeed();
|
||||
}
|
||||
await inventory.save();
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true);
|
||||
const inventoryResponse = await getInventoryResponse(inventory, true, account.BuildLabel);
|
||||
|
||||
//TODO: figure out when to send inventory. it is needed for many cases.
|
||||
res.json({
|
||||
@ -77,8 +101,11 @@ export const missionInventoryUpdateController: RequestHandler = async (req, res)
|
||||
MissionRewards,
|
||||
...credits,
|
||||
...inventoryUpdates,
|
||||
FusionPoints: inventoryChanges?.FusionPoints
|
||||
});
|
||||
//FusionPoints: inventoryChanges?.FusionPoints, // This in combination with InventoryJson or InventoryChanges seems to just double the number of endo shown, so unsure when this is needed.
|
||||
SyndicateXPItemReward,
|
||||
AffiliationMods,
|
||||
ConquestCompletedMissionsCount
|
||||
} satisfies IMissionInventoryUpdateResponse);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -17,12 +17,13 @@ import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { getRandomInt } from "@/src/services/rngService";
|
||||
import { ExportSentinels } from "warframe-public-export-plus";
|
||||
import { ExportSentinels, ExportWeapons, IDefaultUpgrade } from "warframe-public-export-plus";
|
||||
import { Status } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
|
||||
interface IModularCraftRequest {
|
||||
WeaponType: string;
|
||||
Parts: string[];
|
||||
isWebUi?: boolean;
|
||||
}
|
||||
|
||||
export const modularWeaponCraftingController: RequestHandler = async (req, res) => {
|
||||
@ -34,9 +35,9 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
||||
const category = modularWeaponTypes[data.WeaponType];
|
||||
const inventory = await getInventory(accountId);
|
||||
|
||||
const defaultUpgrades = getDefaultUpgrades(data.Parts);
|
||||
let defaultUpgrades: IDefaultUpgrade[] | undefined;
|
||||
const defaultOverwrites: Partial<IEquipmentDatabase> = {
|
||||
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
|
||||
ModularParts: data.Parts
|
||||
};
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
if (category == "KubrowPets") {
|
||||
@ -129,38 +130,63 @@ export const modularWeaponCraftingController: RequestHandler = async (req, res)
|
||||
// Only save mutagen & antigen in the ModularParts.
|
||||
defaultOverwrites.ModularParts = [data.Parts[1], data.Parts[2]];
|
||||
|
||||
for (const specialItem of ExportSentinels[data.WeaponType].exalted!) {
|
||||
const meta = ExportSentinels[data.WeaponType];
|
||||
|
||||
for (const specialItem of meta.exalted!) {
|
||||
addSpecialItem(inventory, specialItem, inventoryChanges);
|
||||
}
|
||||
|
||||
defaultUpgrades = meta.defaultUpgrades;
|
||||
} else {
|
||||
defaultUpgrades = getDefaultUpgrades(data.Parts);
|
||||
}
|
||||
addEquipment(inventory, category, data.WeaponType, data.Parts, inventoryChanges, defaultOverwrites);
|
||||
combineInventoryChanges(inventoryChanges, occupySlot(inventory, productCategoryToInventoryBin(category)!, false));
|
||||
|
||||
if (category == "MoaPets") {
|
||||
const weapon = ExportSentinels[data.WeaponType].defaultWeapon;
|
||||
if (weapon) {
|
||||
const category = ExportWeapons[weapon].productCategory;
|
||||
addEquipment(inventory, category, weapon, undefined, inventoryChanges);
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
|
||||
);
|
||||
}
|
||||
}
|
||||
defaultOverwrites.Configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
||||
addEquipment(inventory, category, data.WeaponType, defaultOverwrites, inventoryChanges);
|
||||
combineInventoryChanges(
|
||||
inventoryChanges,
|
||||
occupySlot(inventory, productCategoryToInventoryBin(category)!, !!data.isWebUi)
|
||||
);
|
||||
if (defaultUpgrades) {
|
||||
inventoryChanges.RawUpgrades = defaultUpgrades.map(x => ({ ItemType: x.ItemType, ItemCount: 1 }));
|
||||
}
|
||||
|
||||
// Remove credits & parts
|
||||
const miscItemChanges = [];
|
||||
for (const part of data.Parts) {
|
||||
miscItemChanges.push({
|
||||
ItemType: part,
|
||||
ItemCount: -1
|
||||
});
|
||||
let currencyChanges = {};
|
||||
if (!data.isWebUi) {
|
||||
for (const part of data.Parts) {
|
||||
miscItemChanges.push({
|
||||
ItemType: part,
|
||||
ItemCount: -1
|
||||
});
|
||||
}
|
||||
currencyChanges = updateCurrency(
|
||||
inventory,
|
||||
category == "Hoverboards" ||
|
||||
category == "MoaPets" ||
|
||||
category == "LongGuns" ||
|
||||
category == "Pistols" ||
|
||||
category == "KubrowPets"
|
||||
? 5000
|
||||
: 4000, // Definitely correct for Melee & OperatorAmps
|
||||
false
|
||||
);
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
}
|
||||
const currencyChanges = updateCurrency(
|
||||
inventory,
|
||||
category == "Hoverboards" ||
|
||||
category == "MoaPets" ||
|
||||
category == "LongGuns" ||
|
||||
category == "Pistols" ||
|
||||
category == "KubrowPets"
|
||||
? 5000
|
||||
: 4000, // Definitely correct for Melee & OperatorAmps
|
||||
false
|
||||
);
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
await inventory.save();
|
||||
|
||||
await inventory.save();
|
||||
// Tell client what we did
|
||||
res.json({
|
||||
InventoryChanges: {
|
||||
|
@ -2,7 +2,7 @@ import { RequestHandler } from "express";
|
||||
import { ExportWeapons } from "warframe-public-export-plus";
|
||||
import { IMongoDate } from "@/src/types/commonTypes";
|
||||
import { toMongoDate } from "@/src/helpers/inventoryHelpers";
|
||||
import { CRng } from "@/src/services/rngService";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { ArtifactPolarity, EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import {
|
||||
@ -21,7 +21,11 @@ import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||
const partTypeToParts: Record<string, string[]> = {};
|
||||
for (const [uniqueName, data] of Object.entries(ExportWeapons)) {
|
||||
if (data.partType && data.premiumPrice) {
|
||||
if (
|
||||
data.partType &&
|
||||
data.premiumPrice &&
|
||||
!data.excludeFromCodex // exclude pvp variants
|
||||
) {
|
||||
partTypeToParts[data.partType] ??= [];
|
||||
partTypeToParts[data.partType].push(uniqueName);
|
||||
}
|
||||
@ -41,24 +45,18 @@ export const modularWeaponSaleController: RequestHandler = async (req, res) => {
|
||||
const defaultUpgrades = getDefaultUpgrades(weaponInfo.ModularParts);
|
||||
const configs = applyDefaultUpgrades(inventory, defaultUpgrades);
|
||||
const inventoryChanges: IInventoryChanges = {
|
||||
...addEquipment(
|
||||
inventory,
|
||||
category,
|
||||
weaponInfo.ItemType,
|
||||
weaponInfo.ModularParts,
|
||||
{},
|
||||
{
|
||||
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
|
||||
ItemName: payload.ItemName,
|
||||
Configs: configs,
|
||||
Polarity: [
|
||||
{
|
||||
Slot: payload.PolarizeSlot,
|
||||
Value: payload.PolarizeValue
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
...addEquipment(inventory, category, weaponInfo.ItemType, {
|
||||
Features: EquipmentFeatures.DOUBLE_CAPACITY | EquipmentFeatures.GILDED,
|
||||
ItemName: payload.ItemName,
|
||||
Configs: configs,
|
||||
ModularParts: weaponInfo.ModularParts,
|
||||
Polarity: [
|
||||
{
|
||||
Slot: payload.PolarizeSlot,
|
||||
Value: payload.PolarizeValue
|
||||
}
|
||||
]
|
||||
}),
|
||||
...occupySlot(inventory, productCategoryToInventoryBin(category)!, true),
|
||||
...updateCurrency(inventory, weaponInfo.PremiumPrice, true)
|
||||
};
|
||||
@ -142,8 +140,8 @@ const getModularWeaponSale = (
|
||||
partTypes: string[],
|
||||
getItemType: (parts: string[]) => string
|
||||
): IModularWeaponSaleInfo => {
|
||||
const rng = new CRng(day);
|
||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType]));
|
||||
const rng = new SRng(day);
|
||||
const parts = partTypes.map(partType => rng.randomElement(partTypeToParts[partType])!);
|
||||
let partsCost = 0;
|
||||
for (const part of parts) {
|
||||
partsCost += ExportWeapons[part].premiumPrice!;
|
||||
|
@ -1,18 +1,40 @@
|
||||
import { getInfNodes, getNemesisPasscode } from "@/src/helpers/nemesisHelpers";
|
||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import {
|
||||
consumeModCharge,
|
||||
encodeNemesisGuess,
|
||||
getInfNodes,
|
||||
getKnifeUpgrade,
|
||||
getNemesisManifest,
|
||||
getNemesisPasscode,
|
||||
getNemesisPasscodeModTypes,
|
||||
IKnifeResponse
|
||||
} from "@/src/helpers/nemesisHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||
import { freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { SRng } from "@/src/services/rngService";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { IInnateDamageFingerprint, InventorySlot, TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import {
|
||||
IInnateDamageFingerprint,
|
||||
IInventoryClient,
|
||||
INemesisClient,
|
||||
InventorySlot,
|
||||
IUpgradeClient,
|
||||
IWeaponSkinClient,
|
||||
LoadoutIndex,
|
||||
TEquipmentKey,
|
||||
TNemesisFaction
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const nemesisController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await getAccountForRequest(req);
|
||||
if ((req.query.mode as string) == "f") {
|
||||
const body = getJSONfromString<IValenceFusionRequest>(String(req.body));
|
||||
const inventory = await getInventory(accountId, body.Category + " WeaponBin");
|
||||
const inventory = await getInventory(account._id.toString(), body.Category + " WeaponBin");
|
||||
const destWeapon = inventory[body.Category].id(body.DestWeapon.$oid)!;
|
||||
const sourceWeapon = inventory[body.Category].id(body.SourceWeapon.$oid)!;
|
||||
const destFingerprint = JSON.parse(destWeapon.UpgradeFingerprint!) as IInnateDamageFingerprint;
|
||||
@ -47,9 +69,9 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
});
|
||||
} else if ((req.query.mode as string) == "p") {
|
||||
const inventory = await getInventory(accountId, "Nemesis");
|
||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||
const body = getJSONfromString<INemesisPrespawnCheckRequest>(String(req.body));
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!.fp, inventory.Nemesis!.Faction);
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||
let guessResult = 0;
|
||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||
for (let i = 0; i != 3; ++i) {
|
||||
@ -66,30 +88,106 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
}
|
||||
res.json({ GuessResult: guessResult });
|
||||
} else if (req.query.mode == "r") {
|
||||
const inventory = await getInventory(
|
||||
account._id.toString(),
|
||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||
);
|
||||
const body = getJSONfromString<INemesisRequiemRequest>(String(req.body));
|
||||
if (inventory.Nemesis!.Faction == "FC_INFESTATION") {
|
||||
const guess: number[] = [body.guess & 0xf, (body.guess >> 4) & 0xf, (body.guess >> 8) & 0xf];
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!)[0];
|
||||
|
||||
// Add to GuessHistory
|
||||
const result1 = passcode == guess[0] ? 0 : 1;
|
||||
const result2 = passcode == guess[1] ? 0 : 1;
|
||||
const result3 = passcode == guess[2] ? 0 : 1;
|
||||
inventory.Nemesis!.GuessHistory.push(
|
||||
encodeNemesisGuess(guess[0], result1, guess[1], result2, guess[2], result3)
|
||||
);
|
||||
|
||||
// Increase antivirus if correct antivirus mod is installed
|
||||
const response: IKnifeResponse = {};
|
||||
if (result1 == 0 || result2 == 0 || result3 == 0) {
|
||||
let antivirusGain = 5;
|
||||
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 (const upgrade of body.knife!.AttachedUpgrades) {
|
||||
switch (upgrade.ItemType) {
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndSpeedOnUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusAndWeaponDamageOnUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusLargeOnSingleUseMod": // Instant Secure
|
||||
antivirusGain += 15;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusOnUseMod": // Immuno Shield
|
||||
antivirusGain += 15;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
case "/Lotus/Upgrades/Mods/DataSpike/Potency/GainAntivirusSmallOnSingleUseMod":
|
||||
antivirusGain += 10;
|
||||
consumeModCharge(response, inventory, upgrade, dataknifeUpgrades);
|
||||
break;
|
||||
}
|
||||
}
|
||||
inventory.Nemesis!.HenchmenKilled += antivirusGain;
|
||||
}
|
||||
|
||||
if (inventory.Nemesis!.HenchmenKilled >= 100) {
|
||||
inventory.Nemesis!.HenchmenKilled = 100;
|
||||
}
|
||||
inventory.Nemesis!.InfNodes = getInfNodes(getNemesisManifest(inventory.Nemesis!.manifest), 0);
|
||||
|
||||
await inventory.save();
|
||||
res.json(response);
|
||||
} else {
|
||||
const passcode = getNemesisPasscode(inventory.Nemesis!);
|
||||
if (passcode[body.position] != body.guess) {
|
||||
res.end();
|
||||
} else {
|
||||
inventory.Nemesis!.Rank += 1;
|
||||
inventory.Nemesis!.InfNodes = getInfNodes(
|
||||
getNemesisManifest(inventory.Nemesis!.manifest),
|
||||
inventory.Nemesis!.Rank
|
||||
);
|
||||
await inventory.save();
|
||||
res.json({ RankIncrease: 1 });
|
||||
}
|
||||
}
|
||||
} else if ((req.query.mode as string) == "rs") {
|
||||
// report spawn; POST but no application data in body
|
||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||
inventory.Nemesis!.LastEnc = inventory.Nemesis!.MissionCount;
|
||||
await inventory.save();
|
||||
res.json({ LastEnc: inventory.Nemesis!.LastEnc });
|
||||
} else if ((req.query.mode as string) == "s") {
|
||||
const inventory = await getInventory(accountId, "Nemesis");
|
||||
const inventory = await getInventory(account._id.toString(), "Nemesis");
|
||||
const body = getJSONfromString<INemesisStartRequest>(String(req.body));
|
||||
body.target.fp = BigInt(body.target.fp);
|
||||
|
||||
const manifest = getNemesisManifest(body.target.manifest);
|
||||
if (account.BuildLabel && version_compare(manifest.minBuild, account.BuildLabel) < 0) {
|
||||
logger.warn(
|
||||
`client on version ${account.BuildLabel} provided nemesis manifest ${body.target.manifest} which was expected to require ${manifest.minBuild} or above. please file a bug report.`
|
||||
);
|
||||
}
|
||||
|
||||
let weaponIdx = -1;
|
||||
if (body.target.Faction != "FC_INFESTATION") {
|
||||
let weapons: readonly string[];
|
||||
if (body.target.manifest == "/Lotus/Types/Game/Nemesis/KuvaLich/KuvaLichManifestVersionSix") {
|
||||
weapons = kuvaLichVersionSixWeapons;
|
||||
} else if (
|
||||
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionFour" ||
|
||||
body.target.manifest == "/Lotus/Types/Enemies/Corpus/Lawyers/LawyerManifestVersionThree"
|
||||
) {
|
||||
weapons = corpusVersionThreeWeapons;
|
||||
} else {
|
||||
throw new Error(`unknown nemesis manifest: ${body.target.manifest}`);
|
||||
}
|
||||
|
||||
const weapons: readonly string[] = manifest.weapons;
|
||||
const initialWeaponIdx = new SRng(body.target.fp).randomInt(0, weapons.length - 1);
|
||||
weaponIdx = initialWeaponIdx;
|
||||
do {
|
||||
const weapon = weapons[weaponIdx];
|
||||
if (!body.target.DisallowedWeapons.find(x => x == weapon)) {
|
||||
if (body.target.DisallowedWeapons.indexOf(weapon) == -1) {
|
||||
break;
|
||||
}
|
||||
weaponIdx = (weaponIdx + 1) % weapons.length;
|
||||
@ -110,7 +208,7 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
k: false,
|
||||
Traded: false,
|
||||
d: new Date(),
|
||||
InfNodes: getInfNodes(body.target.Faction, 0),
|
||||
InfNodes: getInfNodes(manifest, 0),
|
||||
GuessHistory: [],
|
||||
Hints: [],
|
||||
HintProgress: 0,
|
||||
@ -126,6 +224,38 @@ export const nemesisController: RequestHandler = async (req, res) => {
|
||||
res.json({
|
||||
target: inventory.toJSON().Nemesis
|
||||
});
|
||||
} else if ((req.query.mode as string) == "w") {
|
||||
const inventory = await getInventory(
|
||||
account._id.toString(),
|
||||
"Nemesis LoadOutPresets CurrentLoadOutIds DataKnives Upgrades RawUpgrades"
|
||||
);
|
||||
//const body = getJSONfromString<INemesisWeakenRequest>(String(req.body));
|
||||
|
||||
inventory.Nemesis!.InfNodes = [
|
||||
{
|
||||
Node: getNemesisManifest(inventory.Nemesis!.manifest).showdownNode,
|
||||
Influence: 1
|
||||
}
|
||||
];
|
||||
inventory.Nemesis!.Weakened = true;
|
||||
|
||||
const response: IKnifeResponse & { target: INemesisClient } = {
|
||||
target: inventory.toJSON<IInventoryClient>().Nemesis!
|
||||
};
|
||||
|
||||
// Consume charge of the correct requiem mod(s)
|
||||
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);
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json(response);
|
||||
} else {
|
||||
logger.debug(`data provided to ${req.path}: ${String(req.body)}`);
|
||||
throw new Error(`unknown nemesis mode: ${String(req.query.mode)}`);
|
||||
@ -150,7 +280,7 @@ interface INemesisStartRequest {
|
||||
WeaponIdx: number;
|
||||
AgentIdx: number;
|
||||
BirthNode: string;
|
||||
Faction: string;
|
||||
Faction: TNemesisFaction;
|
||||
Rank: number;
|
||||
k: boolean;
|
||||
Traded: boolean;
|
||||
@ -173,38 +303,23 @@ interface INemesisPrespawnCheckRequest {
|
||||
potency?: number[];
|
||||
}
|
||||
|
||||
const kuvaLichVersionSixWeapons = [
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Drakgoon/KuvaDrakgoon",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Karak/KuvaKarak",
|
||||
"/Lotus/Weapons/Grineer/Melee/GrnKuvaLichScythe/GrnKuvaLichScytheWeapon",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Kohm/KuvaKohm",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Ogris/KuvaOgris",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Quartakk/KuvaQuartakk",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Tonkor/KuvaTonkor",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Brakk/KuvaBrakk",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Kraken/KuvaKraken",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Seer/KuvaSeer",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Stubba/KuvaStubba",
|
||||
"/Lotus/Weapons/Grineer/HeavyWeapons/GrnHeavyGrenadeLauncher",
|
||||
"/Lotus/Weapons/Grineer/LongGuns/GrnKuvaLichRifle/GrnKuvaLichRifleWeapon",
|
||||
"/Lotus/Weapons/Grineer/Bows/GrnBow/GrnBowWeapon",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hind/KuvaHind",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/Secondaries/Nukor/KuvaNukor",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Hek/KuvaHekWeapon",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Zarr/KuvaZarr",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/HeavyWeapons/Grattler/KuvaGrattler",
|
||||
"/Lotus/Weapons/Grineer/KuvaLich/LongGuns/Sobek/KuvaSobek"
|
||||
];
|
||||
interface INemesisRequiemRequest {
|
||||
guess: number; // grn/crp: 4 bits | coda: 3x 4 bits
|
||||
position: number; // grn/crp: 0-2 | coda: 0
|
||||
// knife field provided for coda only
|
||||
knife?: IKnife;
|
||||
}
|
||||
|
||||
const corpusVersionThreeWeapons = [
|
||||
"/Lotus/Weapons/Corpus/LongGuns/CrpBriefcaseLauncher/CrpBriefcaseLauncher",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEArcaPlasmor/CrpBEArcaPlasmor",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEFluxRifle/CrpBEFluxRifle",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBETetra/CrpBETetra",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBECycron/CrpBECycron",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEDetron/CrpBEDetron",
|
||||
"/Lotus/Weapons/Corpus/Pistols/CrpIgniterPistol/CrpIgniterPistol",
|
||||
"/Lotus/Weapons/Corpus/Pistols/CrpBriefcaseAkimbo/CrpBriefcaseAkimboPistol",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Secondary/CrpBEPlinx/CrpBEPlinxWeapon",
|
||||
"/Lotus/Weapons/Corpus/BoardExec/Primary/CrpBEGlaxion/CrpBEGlaxion"
|
||||
];
|
||||
// interface INemesisWeakenRequest {
|
||||
// target: INemesisClient;
|
||||
// knife: IKnife;
|
||||
// }
|
||||
|
||||
interface IKnife {
|
||||
Item: IEquipmentClient;
|
||||
Skins: IWeaponSkinClient[];
|
||||
ModSlot: number;
|
||||
CustSlot: number;
|
||||
AttachedUpgrades: IUpgradeClient[];
|
||||
HiddenWhenHolstered: boolean;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { GuildPermission } from "@/src/types/guildTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
import { ExportDojoRecipes, ExportResources } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
|
||||
export const placeDecoInComponentController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -36,6 +37,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
||||
const deco = component.Decos.find(x => x._id.equals(request.MoveId))!;
|
||||
deco.Pos = request.Pos;
|
||||
deco.Rot = request.Rot;
|
||||
deco.Scale = request.Scale;
|
||||
} else {
|
||||
const deco =
|
||||
component.Decos[
|
||||
@ -44,6 +46,7 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
||||
Type: request.Type,
|
||||
Pos: request.Pos,
|
||||
Rot: request.Rot,
|
||||
Scale: request.Scale,
|
||||
Name: request.Name,
|
||||
Sockets: request.Sockets
|
||||
}) - 1
|
||||
@ -62,42 +65,42 @@ export const placeDecoInComponentController: RequestHandler = async (req, res) =
|
||||
guild.VaultShipDecorations!.find(x => x.ItemType == itemType)!.ItemCount -= 1;
|
||||
}
|
||||
}
|
||||
if (!meta || (meta.price == 0 && meta.ingredients.length == 0)) {
|
||||
deco.CompletionTime = new Date();
|
||||
} else if (
|
||||
guild.AutoContributeFromVault &&
|
||||
guild.VaultRegularCredits &&
|
||||
guild.VaultMiscItems &&
|
||||
deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco"
|
||||
) {
|
||||
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
let enoughMiscItems = true;
|
||||
for (const ingredient of meta.ingredients) {
|
||||
if (
|
||||
getVaultMiscItemCount(guild, ingredient.ItemType) <
|
||||
scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||
) {
|
||||
enoughMiscItems = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (enoughMiscItems) {
|
||||
guild.VaultRegularCredits -= scaleRequiredCount(guild.Tier, meta.price);
|
||||
deco.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
||||
|
||||
deco.MiscItems = [];
|
||||
for (const ingredient of meta.ingredients) {
|
||||
guild.VaultMiscItems.find(x => x.ItemType == ingredient.ItemType)!.ItemCount -=
|
||||
scaleRequiredCount(guild.Tier, ingredient.ItemCount);
|
||||
deco.MiscItems.push({
|
||||
ItemType: ingredient.ItemType,
|
||||
ItemCount: scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||
});
|
||||
}
|
||||
|
||||
deco.CompletionTime = new Date(Date.now() + meta.time * 1000);
|
||||
if (deco.Type != "/Lotus/Objects/Tenno/Props/TnoPaintBotDojoDeco") {
|
||||
if (!meta || (meta.price == 0 && meta.ingredients.length == 0) || config.noDojoDecoBuildStage) {
|
||||
deco.CompletionTime = new Date();
|
||||
if (meta) {
|
||||
processDojoBuildMaterialsGathered(guild, meta);
|
||||
}
|
||||
} else if (guild.AutoContributeFromVault && guild.VaultRegularCredits && guild.VaultMiscItems) {
|
||||
if (guild.VaultRegularCredits >= scaleRequiredCount(guild.Tier, meta.price)) {
|
||||
let enoughMiscItems = true;
|
||||
for (const ingredient of meta.ingredients) {
|
||||
if (
|
||||
getVaultMiscItemCount(guild, ingredient.ItemType) <
|
||||
scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||
) {
|
||||
enoughMiscItems = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (enoughMiscItems) {
|
||||
guild.VaultRegularCredits -= scaleRequiredCount(guild.Tier, meta.price);
|
||||
deco.RegularCredits = scaleRequiredCount(guild.Tier, meta.price);
|
||||
|
||||
deco.MiscItems = [];
|
||||
for (const ingredient of meta.ingredients) {
|
||||
guild.VaultMiscItems.find(x => x.ItemType == ingredient.ItemType)!.ItemCount -=
|
||||
scaleRequiredCount(guild.Tier, ingredient.ItemCount);
|
||||
deco.MiscItems.push({
|
||||
ItemType: ingredient.ItemType,
|
||||
ItemCount: scaleRequiredCount(guild.Tier, ingredient.ItemCount)
|
||||
});
|
||||
}
|
||||
|
||||
deco.CompletionTime = new Date(Date.now() + meta.time * 1000);
|
||||
processDojoBuildMaterialsGathered(guild, meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -112,9 +115,9 @@ interface IPlaceDecoInComponentRequest {
|
||||
Type: string;
|
||||
Pos: number[];
|
||||
Rot: number[];
|
||||
Scale?: number;
|
||||
Name?: string;
|
||||
Sockets?: number;
|
||||
Scale?: number; // only provided alongside MoveId and seems to always be 1
|
||||
MoveId?: string;
|
||||
ShipDeco?: boolean;
|
||||
VaultDeco?: boolean;
|
||||
|
9
src/controllers/api/playedParkourTutorialController.ts
Normal file
9
src/controllers/api/playedParkourTutorialController.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const playedParkourTutorialController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
await Inventory.updateOne({ accountOwnerId: accountId }, { PlayedParkourTutorial: true });
|
||||
res.end();
|
||||
};
|
@ -2,13 +2,16 @@ import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { addMiscItems, getInventory } from "@/src/services/inventoryService";
|
||||
import { ExportRelics, IRelic } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
|
||||
export const projectionManagerController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const request = JSON.parse(String(req.body)) as IProjectionUpgradeRequest;
|
||||
const [era, category, currentQuality] = parseProjection(request.projectionType);
|
||||
const upgradeCost = (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||
const upgradeCost = config.dontSubtractVoidTraces
|
||||
? 0
|
||||
: (request.qualityTag - qualityKeywordToNumber[currentQuality]) * 25;
|
||||
const newProjectionType = findProjection(era, category, qualityNumberToKeyword[request.qualityTag]);
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
|
25
src/controllers/api/questControlController.ts
Normal file
25
src/controllers/api/questControlController.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
// Basic shim handling action=sync to login on U21
|
||||
export const questControlController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const quests: IQuestState[] = [];
|
||||
for (const quest of inventory.QuestKeys) {
|
||||
quests.push({
|
||||
quest: quest.ItemType,
|
||||
state: 3 // COMPLETE
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
QuestState: quests
|
||||
});
|
||||
};
|
||||
|
||||
interface IQuestState {
|
||||
quest: string;
|
||||
state: number;
|
||||
task?: string;
|
||||
}
|
@ -8,7 +8,11 @@ export const releasePetController: RequestHandler = async (req, res) => {
|
||||
const inventory = await getInventory(accountId, "RegularCredits KubrowPets");
|
||||
const payload = getJSONfromString<IReleasePetRequest>(String(req.body));
|
||||
|
||||
const inventoryChanges = updateCurrency(inventory, 25000, false);
|
||||
const inventoryChanges = updateCurrency(
|
||||
inventory,
|
||||
payload.recipeName == "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe" ? 25000 : 0,
|
||||
false
|
||||
);
|
||||
|
||||
inventoryChanges.RemovedIdItems = [{ ItemId: { $oid: payload.petId } }];
|
||||
inventory.KubrowPets.pull({ _id: payload.petId });
|
||||
@ -18,6 +22,6 @@ export const releasePetController: RequestHandler = async (req, res) => {
|
||||
};
|
||||
|
||||
interface IReleasePetRequest {
|
||||
recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe";
|
||||
recipeName: "/Lotus/Types/Game/KubrowPet/ReleasePetRecipe" | "webui";
|
||||
petId: string;
|
||||
}
|
||||
|
99
src/controllers/api/removeFriendController.ts
Normal file
99
src/controllers/api/removeFriendController.ts
Normal file
@ -0,0 +1,99 @@
|
||||
import { toOid } from "@/src/helpers/inventoryHelpers";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { parallelForeach } from "@/src/utils/async-utils";
|
||||
import { RequestHandler } from "express";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
export const removeFriendGetController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
if (req.query.all) {
|
||||
const [internalFriendships, externalFriendships] = await Promise.all([
|
||||
Friendship.find({ owner: accountId }, "friend"),
|
||||
Friendship.find({ friend: accountId }, "owner")
|
||||
]);
|
||||
const promises: Promise<void>[] = [];
|
||||
const friends: IOid[] = [];
|
||||
for (const externalFriendship of externalFriendships) {
|
||||
if (!internalFriendships.find(x => x.friend.equals(externalFriendship.owner))) {
|
||||
promises.push(Friendship.deleteOne({ _id: externalFriendship._id }) as unknown as Promise<void>);
|
||||
friends.push(toOid(externalFriendship.owner));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
res.json({
|
||||
Friends: friends
|
||||
} satisfies IRemoveFriendsResponse);
|
||||
} else {
|
||||
const friendId = req.query.friendId as string;
|
||||
await Promise.all([
|
||||
Friendship.deleteOne({ owner: accountId, friend: friendId }),
|
||||
Friendship.deleteOne({ owner: friendId, friend: accountId })
|
||||
]);
|
||||
res.json({
|
||||
Friends: [{ $oid: friendId }]
|
||||
} satisfies IRemoveFriendsResponse);
|
||||
}
|
||||
};
|
||||
|
||||
export const removeFriendPostController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString<IBatchRemoveFriendsRequest>(String(req.body));
|
||||
const friends = new Set((await Friendship.find({ owner: accountId }, "friend")).map(x => x.friend));
|
||||
// TOVERIFY: Should pending friendships also be kept?
|
||||
|
||||
// Keep friends that have been online within threshold
|
||||
await parallelForeach([...friends], async friend => {
|
||||
const account = (await Account.findById(friend, "LastLogin"))!;
|
||||
const daysLoggedOut = (Date.now() - account.LastLogin.getTime()) / 86400_000;
|
||||
if (daysLoggedOut < data.DaysLoggedOut) {
|
||||
friends.delete(friend);
|
||||
}
|
||||
});
|
||||
|
||||
if (data.SkipClanmates) {
|
||||
const inventory = await getInventory(accountId, "GuildId");
|
||||
if (inventory.GuildId) {
|
||||
await parallelForeach([...friends], async friend => {
|
||||
const friendInventory = await getInventory(friend.toString(), "GuildId");
|
||||
if (friendInventory.GuildId?.equals(inventory.GuildId)) {
|
||||
friends.delete(friend);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all remaining friends that aren't in SkipFriendIds & give response.
|
||||
const promises = [];
|
||||
const response: IOid[] = [];
|
||||
for (const friend of friends) {
|
||||
if (!data.SkipFriendIds.find(skipFriendId => checkFriendId(skipFriendId, friend))) {
|
||||
promises.push(Friendship.deleteOne({ owner: accountId, friend: friend }));
|
||||
promises.push(Friendship.deleteOne({ owner: friend, friend: accountId }));
|
||||
response.push(toOid(friend));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
res.json({
|
||||
Friends: response
|
||||
} satisfies IRemoveFriendsResponse);
|
||||
};
|
||||
|
||||
// The friend ids format is a bit weird, e.g. when 6633b81e9dba0b714f28ff02 (A) is friends with 67cdac105ef1f4b49741c267 (B), A's friend id for B is 808000105ef1f40560ca079e and B's friend id for A is 8000b81e9dba0b06408a8075.
|
||||
const checkFriendId = (friendId: string, b: Types.ObjectId): boolean => {
|
||||
return friendId.substring(6, 6 + 8) == b.toString().substring(6, 6 + 8);
|
||||
};
|
||||
|
||||
interface IBatchRemoveFriendsRequest {
|
||||
DaysLoggedOut: number;
|
||||
SkipClanmates: boolean;
|
||||
SkipFriendIds: string[];
|
||||
}
|
||||
|
||||
interface IRemoveFriendsResponse {
|
||||
Friends: IOid[];
|
||||
}
|
21
src/controllers/api/removeIgnoredUserController.ts
Normal file
21
src/controllers/api/removeIgnoredUserController.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Account, Ignore } from "@/src/models/loginModel";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const removeIgnoredUserController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountForRequest(req);
|
||||
const data = getJSONfromString<IRemoveIgnoredUserRequest>(String(req.body));
|
||||
const ignoreeAccount = await Account.findOne(
|
||||
{ DisplayName: data.playerName.substring(0, data.playerName.length - 1) },
|
||||
"_id"
|
||||
);
|
||||
if (ignoreeAccount) {
|
||||
await Ignore.deleteOne({ ignorer: accountId, ignoree: ignoreeAccount._id });
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IRemoveIgnoredUserRequest {
|
||||
playerName: string;
|
||||
}
|
@ -1,51 +1,28 @@
|
||||
import { addEmailItem, getInventory } from "@/src/services/inventoryService";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { addEmailItem, getDialogue, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ICompletedDialogue } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const request = JSON.parse(String(req.body)) as SaveDialogueRequest;
|
||||
if ("YearIteration" in request) {
|
||||
const inventory = await getInventory(accountId);
|
||||
if (inventory.DialogueHistory) {
|
||||
inventory.DialogueHistory.YearIteration = request.YearIteration;
|
||||
} else {
|
||||
inventory.DialogueHistory = { YearIteration: request.YearIteration };
|
||||
}
|
||||
const inventory = await getInventory(accountId, "DialogueHistory");
|
||||
inventory.DialogueHistory ??= {};
|
||||
inventory.DialogueHistory.YearIteration = request.YearIteration;
|
||||
await inventory.save();
|
||||
res.end();
|
||||
} else {
|
||||
const inventory = await getInventory(accountId);
|
||||
if (!inventory.DialogueHistory) {
|
||||
throw new Error("bad inventory state");
|
||||
}
|
||||
if (request.OtherDialogueInfos.length != 0) {
|
||||
logger.error(`saveDialogue request not fully handled: ${String(req.body)}`);
|
||||
}
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
const tomorrowAt0Utc = config.noKimCooldowns
|
||||
? Date.now()
|
||||
: (Math.trunc(Date.now() / 86400_000) + 1) * 86400_000;
|
||||
inventory.DialogueHistory ??= {};
|
||||
inventory.DialogueHistory.Dialogues ??= [];
|
||||
let dialogue = inventory.DialogueHistory.Dialogues.find(x => x.DialogueName == request.DialogueName);
|
||||
if (!dialogue) {
|
||||
dialogue =
|
||||
inventory.DialogueHistory.Dialogues[
|
||||
inventory.DialogueHistory.Dialogues.push({
|
||||
Rank: 0,
|
||||
Chemistry: 0,
|
||||
AvailableDate: new Date(0),
|
||||
AvailableGiftDate: new Date(0),
|
||||
RankUpExpiry: new Date(0),
|
||||
BountyChemExpiry: new Date(0),
|
||||
QueuedDialogues: [],
|
||||
Gifts: [],
|
||||
Booleans: [],
|
||||
Completed: [],
|
||||
DialogueName: request.DialogueName
|
||||
}) - 1
|
||||
];
|
||||
}
|
||||
const dialogue = getDialogue(inventory, request.DialogueName);
|
||||
dialogue.Rank = request.Rank;
|
||||
dialogue.Chemistry = request.Chemistry;
|
||||
dialogue.QueuedDialogues = request.QueuedDialogues;
|
||||
@ -65,14 +42,38 @@ export const saveDialogueController: RequestHandler = async (req, res) => {
|
||||
dialogue.Booleans.splice(index, 1);
|
||||
}
|
||||
}
|
||||
dialogue.Completed.push(request.Data);
|
||||
const tomorrowAt0Utc = (Math.trunc(Date.now() / (86400 * 1000)) + 1) * 86400 * 1000;
|
||||
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
|
||||
});
|
||||
for (const info of request.OtherDialogueInfos) {
|
||||
const otherDialogue = getDialogue(inventory, info.Dialogue);
|
||||
if (info.Tag != "") {
|
||||
otherDialogue.QueuedDialogues.push(info.Tag);
|
||||
}
|
||||
otherDialogue.Chemistry += info.Value; // unsure
|
||||
}
|
||||
if (request.Data) {
|
||||
dialogue.Completed.push(request.Data);
|
||||
dialogue.AvailableDate = new Date(tomorrowAt0Utc);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
AvailableDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
|
||||
});
|
||||
} else if (request.Gift) {
|
||||
const inventoryChanges = updateCurrency(inventory, request.Gift.Cost, false);
|
||||
const gift = dialogue.Gifts.find(x => x.Item == request.Gift!.Item);
|
||||
if (gift) {
|
||||
gift.GiftedQuantity += 1;
|
||||
} else {
|
||||
dialogue.Gifts.push({ Item: request.Gift.Item, GiftedQuantity: 1 });
|
||||
}
|
||||
dialogue.AvailableGiftDate = new Date(tomorrowAt0Utc);
|
||||
await inventory.save();
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
AvailableGiftDate: { $date: { $numberLong: tomorrowAt0Utc.toString() } }
|
||||
});
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -88,10 +89,16 @@ interface SaveCompletedDialogueRequest {
|
||||
Chemistry: number;
|
||||
CompletionType: number;
|
||||
QueuedDialogues: string[];
|
||||
Gift?: {
|
||||
Item: string;
|
||||
GainedChemistry: number;
|
||||
Cost: number;
|
||||
GiftedQuantity: number;
|
||||
};
|
||||
Booleans: string[];
|
||||
ResetBooleans: string[];
|
||||
Data: ICompletedDialogue;
|
||||
OtherDialogueInfos: IOtherDialogueInfo[]; // unsure
|
||||
Data?: ICompletedDialogue;
|
||||
OtherDialogueInfos: IOtherDialogueInfo[];
|
||||
}
|
||||
|
||||
interface IOtherDialogueInfo {
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
|
||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
||||
//validate here
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
try {
|
||||
const body: ISaveLoadoutRequest = JSON.parse(req.body as string) as ISaveLoadoutRequest;
|
||||
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { UpgradeVer, ...equipmentChanges } = body;
|
||||
const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
|
||||
|
||||
//send back new loadout id, if new loadout was added
|
||||
if (newLoadoutId) {
|
||||
res.send(newLoadoutId);
|
||||
}
|
||||
res.status(200).end();
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
logger.error(`error in saveLoadoutController: ${error.message}`);
|
||||
res.status(400).json({ error: error.message });
|
||||
} else {
|
||||
res.status(400).json({ error: "unknown error" });
|
||||
}
|
||||
}
|
||||
};
|
22
src/controllers/api/saveLoadoutController.ts
Normal file
22
src/controllers/api/saveLoadoutController.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { ISaveLoadoutRequest } from "@/src/types/saveLoadoutTypes";
|
||||
import { handleInventoryItemConfigChange } from "@/src/services/saveLoadoutService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
|
||||
export const saveLoadoutController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
|
||||
const body: ISaveLoadoutRequest = getJSONfromString<ISaveLoadoutRequest>(String(req.body));
|
||||
// console.log(util.inspect(body, { showHidden: false, depth: null, colors: true }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { UpgradeVer, ...equipmentChanges } = body;
|
||||
const newLoadoutId = await handleInventoryItemConfigChange(equipmentChanges, accountId);
|
||||
|
||||
//send back new loadout id, if new loadout was added
|
||||
if (newLoadoutId) {
|
||||
res.send(newLoadoutId);
|
||||
}
|
||||
res.end();
|
||||
};
|
@ -6,14 +6,20 @@ import {
|
||||
addRecipes,
|
||||
addMiscItems,
|
||||
addConsumables,
|
||||
freeUpSlot
|
||||
freeUpSlot,
|
||||
combineInventoryChanges,
|
||||
addCrewShipRawSalvage,
|
||||
addFusionPoints
|
||||
} from "@/src/services/inventoryService";
|
||||
import { InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { TInventoryDatabaseDocument } from "@/src/models/inventoryModels/inventoryModel";
|
||||
|
||||
export const sellController: RequestHandler = async (req, res) => {
|
||||
const payload = JSON.parse(String(req.body)) as ISellRequest;
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const requiredFields = new Set();
|
||||
const requiredFields = new Set<keyof TInventoryDatabaseDocument>();
|
||||
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||
requiredFields.add("RegularCredits");
|
||||
} else if (payload.SellCurrency == "SC_FusionPoints") {
|
||||
@ -22,7 +28,7 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
requiredFields.add("MiscItems");
|
||||
}
|
||||
for (const key of Object.keys(payload.Items)) {
|
||||
requiredFields.add(key);
|
||||
requiredFields.add(key as keyof TInventoryDatabaseDocument);
|
||||
}
|
||||
if (requiredFields.has("Upgrades")) {
|
||||
requiredFields.add("RawUpgrades");
|
||||
@ -39,7 +45,10 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
if (payload.Items.SpaceGuns || payload.Items.SpaceMelee) {
|
||||
requiredFields.add(InventorySlot.SPACEWEAPONS);
|
||||
}
|
||||
if (payload.Items.Sentinels || payload.Items.SentinelWeapons) {
|
||||
if (payload.Items.MechSuits) {
|
||||
requiredFields.add(InventorySlot.MECHSUITS);
|
||||
}
|
||||
if (payload.Items.Sentinels || payload.Items.SentinelWeapons || payload.Items.MoaPets) {
|
||||
requiredFields.add(InventorySlot.SENTINELS);
|
||||
}
|
||||
if (payload.Items.OperatorAmps) {
|
||||
@ -48,13 +57,23 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
if (payload.Items.Hoverboards) {
|
||||
requiredFields.add(InventorySlot.SPACESUITS);
|
||||
}
|
||||
if (payload.Items.CrewShipWeapons || payload.Items.CrewShipWeaponSkins) {
|
||||
requiredFields.add(InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||
requiredFields.add("CrewShipRawSalvage");
|
||||
if (payload.Items.CrewShipWeapons) {
|
||||
requiredFields.add("CrewShipSalvagedWeapons");
|
||||
}
|
||||
if (payload.Items.CrewShipWeaponSkins) {
|
||||
requiredFields.add("CrewShipSalvagedWeaponSkins");
|
||||
}
|
||||
}
|
||||
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
|
||||
|
||||
// Give currency
|
||||
if (payload.SellCurrency == "SC_RegularCredits") {
|
||||
inventory.RegularCredits += payload.SellPrice;
|
||||
} else if (payload.SellCurrency == "SC_FusionPoints") {
|
||||
inventory.FusionPoints += payload.SellPrice;
|
||||
addFusionPoints(inventory, payload.SellPrice);
|
||||
} else if (payload.SellCurrency == "SC_PrimeBucks") {
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
@ -69,10 +88,14 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
ItemCount: payload.SellPrice
|
||||
}
|
||||
]);
|
||||
} else if (payload.SellCurrency == "SC_Resources") {
|
||||
// Will add appropriate MiscItems from CrewShipWeapons or CrewShipWeaponSkins
|
||||
} else {
|
||||
throw new Error("Unknown SellCurrency: " + payload.SellCurrency);
|
||||
}
|
||||
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
|
||||
// Remove item(s)
|
||||
if (payload.Items.Suits) {
|
||||
payload.Items.Suits.forEach(sellItem => {
|
||||
@ -116,6 +139,12 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
freeUpSlot(inventory, InventorySlot.SPACEWEAPONS);
|
||||
});
|
||||
}
|
||||
if (payload.Items.MechSuits) {
|
||||
payload.Items.MechSuits.forEach(sellItem => {
|
||||
inventory.MechSuits.pull({ _id: sellItem.String });
|
||||
freeUpSlot(inventory, InventorySlot.MECHSUITS);
|
||||
});
|
||||
}
|
||||
if (payload.Items.Sentinels) {
|
||||
payload.Items.Sentinels.forEach(sellItem => {
|
||||
inventory.Sentinels.pull({ _id: sellItem.String });
|
||||
@ -128,6 +157,12 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
freeUpSlot(inventory, InventorySlot.SENTINELS);
|
||||
});
|
||||
}
|
||||
if (payload.Items.MoaPets) {
|
||||
payload.Items.MoaPets.forEach(sellItem => {
|
||||
inventory.MoaPets.pull({ _id: sellItem.String });
|
||||
freeUpSlot(inventory, InventorySlot.SENTINELS);
|
||||
});
|
||||
}
|
||||
if (payload.Items.OperatorAmps) {
|
||||
payload.Items.OperatorAmps.forEach(sellItem => {
|
||||
inventory.OperatorAmps.pull({ _id: sellItem.String });
|
||||
@ -145,6 +180,56 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
inventory.Drones.pull({ _id: sellItem.String });
|
||||
});
|
||||
}
|
||||
if (payload.Items.CrewShipWeapons) {
|
||||
payload.Items.CrewShipWeapons.forEach(sellItem => {
|
||||
if (sellItem.String[0] == "/") {
|
||||
addCrewShipRawSalvage(inventory, [
|
||||
{
|
||||
ItemType: sellItem.String,
|
||||
ItemCount: sellItem.Count * -1
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
const index = inventory.CrewShipWeapons.findIndex(x => x._id.equals(sellItem.String));
|
||||
if (index != -1) {
|
||||
if (payload.SellCurrency == "SC_Resources") {
|
||||
refundPartialBuildCosts(inventory, inventory.CrewShipWeapons[index].ItemType, inventoryChanges);
|
||||
}
|
||||
inventory.CrewShipWeapons.splice(index, 1);
|
||||
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||
} else {
|
||||
inventory.CrewShipSalvagedWeapons.pull({ _id: sellItem.String });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (payload.Items.CrewShipWeaponSkins) {
|
||||
payload.Items.CrewShipWeaponSkins.forEach(sellItem => {
|
||||
if (sellItem.String[0] == "/") {
|
||||
addCrewShipRawSalvage(inventory, [
|
||||
{
|
||||
ItemType: sellItem.String,
|
||||
ItemCount: sellItem.Count * -1
|
||||
}
|
||||
]);
|
||||
} else {
|
||||
const index = inventory.CrewShipWeaponSkins.findIndex(x => x._id.equals(sellItem.String));
|
||||
if (index != -1) {
|
||||
if (payload.SellCurrency == "SC_Resources") {
|
||||
refundPartialBuildCosts(
|
||||
inventory,
|
||||
inventory.CrewShipWeaponSkins[index].ItemType,
|
||||
inventoryChanges
|
||||
);
|
||||
}
|
||||
inventory.CrewShipWeaponSkins.splice(index, 1);
|
||||
freeUpSlot(inventory, InventorySlot.RJ_COMPONENT_AND_ARMAMENTS);
|
||||
} else {
|
||||
inventory.CrewShipSalvagedWeaponSkins.pull({ _id: sellItem.String });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (payload.Items.Consumables) {
|
||||
const consumablesChanges = [];
|
||||
for (const sellItem of payload.Items.Consumables) {
|
||||
@ -191,7 +276,9 @@ export const sellController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.json({});
|
||||
res.json({
|
||||
inventoryChanges: inventoryChanges // "inventoryChanges" for this response instead of the usual "InventoryChanges"
|
||||
});
|
||||
};
|
||||
|
||||
interface ISellRequest {
|
||||
@ -207,11 +294,15 @@ interface ISellRequest {
|
||||
SpaceSuits?: ISellItem[];
|
||||
SpaceGuns?: ISellItem[];
|
||||
SpaceMelee?: ISellItem[];
|
||||
MechSuits?: ISellItem[];
|
||||
Sentinels?: ISellItem[];
|
||||
SentinelWeapons?: ISellItem[];
|
||||
MoaPets?: ISellItem[];
|
||||
OperatorAmps?: ISellItem[];
|
||||
Hoverboards?: ISellItem[];
|
||||
Drones?: ISellItem[];
|
||||
CrewShipWeapons?: ISellItem[];
|
||||
CrewShipWeaponSkins?: ISellItem[];
|
||||
};
|
||||
SellPrice: number;
|
||||
SellCurrency:
|
||||
@ -228,3 +319,33 @@ interface ISellItem {
|
||||
String: string; // oid or uniqueName
|
||||
Count: number;
|
||||
}
|
||||
|
||||
const refundPartialBuildCosts = (
|
||||
inventory: TInventoryDatabaseDocument,
|
||||
itemType: string,
|
||||
inventoryChanges: IInventoryChanges
|
||||
): void => {
|
||||
// House versions
|
||||
const research = Object.values(ExportDojoRecipes.research).find(x => x.resultType == itemType);
|
||||
if (research) {
|
||||
const miscItemChanges = research.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: Math.trunc(x.ItemCount * 0.8)
|
||||
}));
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
|
||||
return;
|
||||
}
|
||||
|
||||
// Sigma versions
|
||||
const recipe = Object.values(ExportDojoRecipes.fabrications).find(x => x.resultType == itemType);
|
||||
if (recipe) {
|
||||
const miscItemChanges = recipe.ingredients.map(x => ({
|
||||
ItemType: x.ItemType,
|
||||
ItemCount: Math.trunc(x.ItemCount * 0.8)
|
||||
}));
|
||||
addMiscItems(inventory, miscItemChanges);
|
||||
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemChanges });
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
31
src/controllers/api/sendMsgToInBoxController.ts
Normal file
31
src/controllers/api/sendMsgToInBoxController.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { createMessage } from "@/src/services/inboxService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const sendMsgToInBoxController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const data = getJSONfromString<ISendMsgToInBoxRequest>(String(req.body));
|
||||
await createMessage(accountId, [
|
||||
{
|
||||
sub: data.title,
|
||||
msg: data.message,
|
||||
sndr: data.sender ?? "/Lotus/Language/Bosses/Ordis",
|
||||
icon: data.senderIcon,
|
||||
highPriority: data.highPriority,
|
||||
transmission: data.transmission,
|
||||
att: data.attachments
|
||||
}
|
||||
]);
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface ISendMsgToInBoxRequest {
|
||||
title: string;
|
||||
message: string;
|
||||
sender?: string;
|
||||
senderIcon?: string;
|
||||
highPriority?: boolean;
|
||||
transmission?: string;
|
||||
attachments?: string[];
|
||||
}
|
@ -13,7 +13,7 @@ export const setDojoComponentSettingsController: RequestHandler = async (req, re
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
const component = guild.DojoComponents.id(req.query.componentId)!;
|
||||
const component = guild.DojoComponents.id(req.query.componentId as string)!;
|
||||
const data = getJSONfromString<ISetDojoComponentSettingsRequest>(String(req.body));
|
||||
component.Settings = data.Settings;
|
||||
await guild.save();
|
||||
|
30
src/controllers/api/setFriendNoteController.ts
Normal file
30
src/controllers/api/setFriendNoteController.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setFriendNoteController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const payload = getJSONfromString<ISetFriendNoteRequest>(String(req.body));
|
||||
const friendship = await Friendship.findOne({ owner: accountId, friend: payload.FriendId }, "Note Favorite");
|
||||
if (friendship) {
|
||||
if ("Note" in payload) {
|
||||
friendship.Note = payload.Note;
|
||||
} else {
|
||||
friendship.Favorite = payload.Favorite;
|
||||
}
|
||||
await friendship.save();
|
||||
}
|
||||
res.json({
|
||||
Id: payload.FriendId,
|
||||
SetNote: "Note" in payload,
|
||||
Note: friendship?.Note,
|
||||
Favorite: friendship?.Favorite
|
||||
});
|
||||
};
|
||||
|
||||
interface ISetFriendNoteRequest {
|
||||
FriendId: string;
|
||||
Note?: string;
|
||||
Favorite?: boolean;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { version_compare } from "@/src/helpers/inventoryHelpers";
|
||||
import { Alliance, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { hasGuildPermissionEx } from "@/src/services/guildService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
@ -55,5 +56,9 @@ export const setGuildMotdController: RequestHandler = async (req, res) => {
|
||||
await guild.save();
|
||||
}
|
||||
|
||||
res.json({ IsLongMOTD, MOTD });
|
||||
if (!account.BuildLabel || version_compare(account.BuildLabel, "2020.03.24.20.24") > 0) {
|
||||
res.json({ IsLongMOTD, MOTD });
|
||||
} else {
|
||||
res.send(MOTD).end();
|
||||
}
|
||||
};
|
||||
|
21
src/controllers/api/setHubNpcCustomizationsController.ts
Normal file
21
src/controllers/api/setHubNpcCustomizationsController.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IHubNpcCustomization } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setHubNpcCustomizationsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "HubNpcCustomizations");
|
||||
const upload = getJSONfromString<IHubNpcCustomization>(String(req.body));
|
||||
inventory.HubNpcCustomizations ??= [];
|
||||
const cust = inventory.HubNpcCustomizations.find(x => x.Tag == upload.Tag);
|
||||
if (cust) {
|
||||
cust.Colors = upload.Colors;
|
||||
cust.Pattern = upload.Pattern;
|
||||
} else {
|
||||
inventory.HubNpcCustomizations.push(upload);
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
@ -1,5 +1,5 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
|
||||
import { IPictureFrameInfo, ISetPlacedDecoInfoRequest } from "@/src/types/shipTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { handleSetPlacedDecoInfo } from "@/src/services/shipCustomizationsService";
|
||||
|
||||
@ -7,5 +7,17 @@ export const setPlacedDecoInfoController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const payload = JSON.parse(req.body as string) as ISetPlacedDecoInfoRequest;
|
||||
await handleSetPlacedDecoInfo(accountId, payload);
|
||||
res.end();
|
||||
res.json({
|
||||
DecoId: payload.DecoId,
|
||||
IsPicture: true,
|
||||
PictureFrameInfo: payload.PictureFrameInfo,
|
||||
BootLocation: payload.BootLocation
|
||||
} satisfies ISetPlacedDecoInfoResponse);
|
||||
};
|
||||
|
||||
interface ISetPlacedDecoInfoResponse {
|
||||
DecoId: string;
|
||||
IsPicture: boolean;
|
||||
PictureFrameInfo?: IPictureFrameInfo;
|
||||
BootLocation?: string;
|
||||
}
|
||||
|
@ -3,29 +3,40 @@ import { RequestHandler } from "express";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { Types } from "mongoose";
|
||||
import { IFavouriteLoadoutDatabase, TBootLocation } from "@/src/types/shipTypes";
|
||||
|
||||
export const setShipFavouriteLoadoutController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const personalRooms = await getPersonalRooms(accountId);
|
||||
const body = JSON.parse(String(req.body)) as ISetShipFavouriteLoadoutRequest;
|
||||
if (body.BootLocation != "SHOP") {
|
||||
if (body.BootLocation == "LISET") {
|
||||
personalRooms.Ship.FavouriteLoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
|
||||
} else if (body.BootLocation == "APARTMENT") {
|
||||
updateTaggedDisplay(personalRooms.Apartment.FavouriteLoadouts, body);
|
||||
} else if (body.BootLocation == "SHOP") {
|
||||
updateTaggedDisplay(personalRooms.TailorShop.FavouriteLoadouts, body);
|
||||
} else {
|
||||
console.log(body);
|
||||
throw new Error(`unexpected BootLocation: ${body.BootLocation}`);
|
||||
}
|
||||
const display = personalRooms.TailorShop.FavouriteLoadouts.find(x => x.Tag == body.TagName);
|
||||
if (display) {
|
||||
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
|
||||
} else {
|
||||
personalRooms.TailorShop.FavouriteLoadouts.push({
|
||||
Tag: body.TagName,
|
||||
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
|
||||
});
|
||||
}
|
||||
await personalRooms.save();
|
||||
res.json({});
|
||||
res.json(body);
|
||||
};
|
||||
|
||||
interface ISetShipFavouriteLoadoutRequest {
|
||||
BootLocation: string;
|
||||
BootLocation: TBootLocation;
|
||||
FavouriteLoadoutId: IOid;
|
||||
TagName: string;
|
||||
TagName?: string;
|
||||
}
|
||||
|
||||
const updateTaggedDisplay = (arr: IFavouriteLoadoutDatabase[], body: ISetShipFavouriteLoadoutRequest): void => {
|
||||
const display = arr.find(x => x.Tag == body.TagName!);
|
||||
if (display) {
|
||||
display.LoadoutId = new Types.ObjectId(body.FavouriteLoadoutId.$oid);
|
||||
} else {
|
||||
arr.push({
|
||||
Tag: body.TagName!,
|
||||
LoadoutId: new Types.ObjectId(body.FavouriteLoadoutId.$oid)
|
||||
});
|
||||
}
|
||||
};
|
||||
|
48
src/controllers/api/setShipVignetteController.ts
Normal file
48
src/controllers/api/setShipVignetteController.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { addMiscItems, combineInventoryChanges, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getPersonalRooms } from "@/src/services/personalRoomsService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setShipVignetteController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "MiscItems");
|
||||
const personalRooms = await getPersonalRooms(accountId);
|
||||
const body = JSON.parse(String(req.body)) as ISetShipVignetteRequest;
|
||||
personalRooms.Ship.Wallpaper = body.Wallpaper;
|
||||
personalRooms.Ship.Vignette = body.Vignette;
|
||||
personalRooms.Ship.VignetteFish ??= [];
|
||||
const inventoryChanges: IInventoryChanges = {};
|
||||
for (let i = 0; i != body.Fish.length; ++i) {
|
||||
if (body.Fish[i] && !personalRooms.Ship.VignetteFish[i]) {
|
||||
logger.debug(`moving ${body.Fish[i]} from inventory to vignette slot ${i}`);
|
||||
const miscItemsDelta = [{ ItemType: body.Fish[i], ItemCount: -1 }];
|
||||
addMiscItems(inventory, miscItemsDelta);
|
||||
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
|
||||
} else if (personalRooms.Ship.VignetteFish[i] && !body.Fish[i]) {
|
||||
logger.debug(`moving ${personalRooms.Ship.VignetteFish[i]} from vignette slot ${i} to inventory`);
|
||||
const miscItemsDelta = [{ ItemType: personalRooms.Ship.VignetteFish[i], ItemCount: +1 }];
|
||||
addMiscItems(inventory, miscItemsDelta);
|
||||
combineInventoryChanges(inventoryChanges, { MiscItems: miscItemsDelta });
|
||||
}
|
||||
}
|
||||
personalRooms.Ship.VignetteFish = body.Fish;
|
||||
if (body.VignetteDecos.length) {
|
||||
logger.error(`setShipVignette request not fully handled:`, body);
|
||||
}
|
||||
await Promise.all([inventory.save(), personalRooms.save()]);
|
||||
res.json({
|
||||
Wallpaper: body.Wallpaper,
|
||||
Vignette: body.Vignette,
|
||||
VignetteFish: body.Fish,
|
||||
InventoryChanges: inventoryChanges
|
||||
});
|
||||
};
|
||||
|
||||
interface ISetShipVignetteRequest {
|
||||
Wallpaper: string;
|
||||
Vignette: string;
|
||||
Fish: string[];
|
||||
VignetteDecos: unknown[];
|
||||
}
|
@ -11,7 +11,7 @@ import {
|
||||
import { Types } from "mongoose";
|
||||
import { ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
interface IStartDojoRecipeRequest {
|
||||
@ -20,10 +20,10 @@ interface IStartDojoRecipeRequest {
|
||||
}
|
||||
|
||||
export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "GuildId LevelKeys");
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "GuildId LevelKeys");
|
||||
const guild = await getGuildForRequestEx(req, inventory);
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, accountId, GuildPermission.Architect))) {
|
||||
if (!hasAccessToDojo(inventory) || !(await hasGuildPermission(guild, account._id, GuildPermission.Architect))) {
|
||||
res.json({ DojoRequestStatus: -1 });
|
||||
return;
|
||||
}
|
||||
@ -64,5 +64,5 @@ export const startDojoRecipeController: RequestHandler = async (req, res) => {
|
||||
setDojoRoomLogFunded(guild, component);
|
||||
}
|
||||
await guild.save();
|
||||
res.json(await getDojoClient(guild, 0));
|
||||
res.json(await getDojoClient(guild, 0, undefined, account.BuildLabel));
|
||||
};
|
||||
|
@ -41,7 +41,7 @@ export const startRecipeController: RequestHandler = async (req, res) => {
|
||||
];
|
||||
|
||||
for (let i = 0; i != recipe.ingredients.length; ++i) {
|
||||
if (startRecipeRequest.Ids[i]) {
|
||||
if (startRecipeRequest.Ids[i] && startRecipeRequest.Ids[i][0] != "/") {
|
||||
const category = ExportWeapons[recipe.ingredients[i].ItemType].productCategory;
|
||||
if (category != "LongGuns" && category != "Pistols" && category != "Melee") {
|
||||
throw new Error(`unexpected equipment ingredient type: ${category}`);
|
||||
|
@ -1,17 +1,12 @@
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { ExportNightwave, ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||
import { ExportSyndicates, ISyndicateSacrifice } from "warframe-public-export-plus";
|
||||
import { handleStoreItemAcquisition } from "@/src/services/purchaseService";
|
||||
import {
|
||||
addItem,
|
||||
addMiscItems,
|
||||
combineInventoryChanges,
|
||||
getInventory,
|
||||
updateCurrency
|
||||
} from "@/src/services/inventoryService";
|
||||
import { addMiscItems, combineInventoryChanges, getInventory, updateCurrency } from "@/src/services/inventoryService";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { fromStoreItem, isStoreItem } from "@/src/services/itemDataService";
|
||||
import { toStoreItem } from "@/src/services/itemDataService";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
|
||||
export const syndicateSacrificeController: RequestHandler = async (request, response) => {
|
||||
const accountId = await getAccountIdForRequest(request);
|
||||
@ -57,13 +52,6 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
||||
syndicate.Title ??= 0;
|
||||
syndicate.Title += 1;
|
||||
|
||||
if (syndicate.Title > 0 && manifest.favours.length != 0) {
|
||||
syndicate.FreeFavorsEarned ??= [];
|
||||
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
|
||||
syndicate.FreeFavorsEarned.push(syndicate.Title);
|
||||
}
|
||||
}
|
||||
|
||||
if (reward) {
|
||||
combineInventoryChanges(
|
||||
res.InventoryChanges,
|
||||
@ -71,16 +59,36 @@ export const syndicateSacrificeController: RequestHandler = async (request, resp
|
||||
);
|
||||
}
|
||||
|
||||
if (data.AffiliationTag == ExportNightwave.affiliationTag) {
|
||||
const index = syndicate.Title - 1;
|
||||
if (index < ExportNightwave.rewards.length) {
|
||||
// Quacks like a nightwave syndicate?
|
||||
if (manifest.dailyChallenges) {
|
||||
const title = manifest.titles!.find(x => x.level == syndicate.Title);
|
||||
if (title) {
|
||||
res.NewEpisodeReward = true;
|
||||
const reward = ExportNightwave.rewards[index];
|
||||
let rewardType = reward.uniqueName;
|
||||
if (isStoreItem(rewardType)) {
|
||||
rewardType = fromStoreItem(rewardType);
|
||||
let rewardType: string;
|
||||
let rewardCount: number;
|
||||
if (title.storeItemReward) {
|
||||
rewardType = title.storeItemReward;
|
||||
rewardCount = 1;
|
||||
} else {
|
||||
rewardType = toStoreItem(title.reward!.ItemType);
|
||||
rewardCount = title.reward!.ItemCount;
|
||||
}
|
||||
const rewardInventoryChanges = (await handleStoreItemAcquisition(rewardType, inventory, rewardCount))
|
||||
.InventoryChanges;
|
||||
if (Object.keys(rewardInventoryChanges).length == 0) {
|
||||
logger.debug(`nightwave rank up reward did not seem to get added, giving 50 creds instead`);
|
||||
const nightwaveCredsItemType = manifest.titles![0].reward!.ItemType;
|
||||
rewardInventoryChanges.MiscItems = [{ ItemType: nightwaveCredsItemType, ItemCount: 50 }];
|
||||
addMiscItems(inventory, rewardInventoryChanges.MiscItems);
|
||||
}
|
||||
combineInventoryChanges(res.InventoryChanges, rewardInventoryChanges);
|
||||
}
|
||||
} else {
|
||||
if (syndicate.Title > 0 && manifest.favours.find(x => x.rankUpReward && x.requiredLevel == syndicate.Title)) {
|
||||
syndicate.FreeFavorsEarned ??= [];
|
||||
if (!syndicate.FreeFavorsEarned.includes(syndicate.Title)) {
|
||||
syndicate.FreeFavorsEarned.push(syndicate.Title);
|
||||
}
|
||||
combineInventoryChanges(res.InventoryChanges, await addItem(inventory, rewardType, reward.itemCount ?? 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,9 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
addMiscItems,
|
||||
freeUpSlot,
|
||||
getInventory,
|
||||
getStandingLimit,
|
||||
updateStandingLimit
|
||||
} from "@/src/services/inventoryService";
|
||||
import { addMiscItems, addStanding, freeUpSlot, getInventory } from "@/src/services/inventoryService";
|
||||
import { IMiscItem, InventorySlot } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { ExportSyndicates, ExportWeapons } from "warframe-public-export-plus";
|
||||
import { getMaxStanding } from "@/src/helpers/syndicateStandingHelper";
|
||||
import { logger } from "@/src/utils/logger";
|
||||
import { IInventoryChanges } from "@/src/types/purchaseTypes";
|
||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
@ -61,38 +54,13 @@ export const syndicateStandingBonusController: RequestHandler = async (req, res)
|
||||
inventoryChanges[slotBin] = { count: -1, platinum: 0, Slots: 1 };
|
||||
}
|
||||
|
||||
let syndicate = inventory.Affiliations.find(x => x.Tag == request.Operation.AffiliationTag);
|
||||
if (!syndicate) {
|
||||
syndicate =
|
||||
inventory.Affiliations[
|
||||
inventory.Affiliations.push({ Tag: request.Operation.AffiliationTag, Standing: 0 }) - 1
|
||||
];
|
||||
}
|
||||
|
||||
const max = getMaxStanding(syndicateMeta, syndicate.Title ?? 0);
|
||||
if (syndicate.Standing + gainedStanding > max) {
|
||||
gainedStanding = max - syndicate.Standing;
|
||||
}
|
||||
|
||||
if (syndicateMeta.medallionsCappedByDailyLimit) {
|
||||
if (gainedStanding > getStandingLimit(inventory, syndicateMeta.dailyLimitBin)) {
|
||||
gainedStanding = getStandingLimit(inventory, syndicateMeta.dailyLimitBin);
|
||||
}
|
||||
updateStandingLimit(inventory, syndicateMeta.dailyLimitBin, gainedStanding);
|
||||
}
|
||||
|
||||
syndicate.Standing += gainedStanding;
|
||||
const affiliationMod = addStanding(inventory, request.Operation.AffiliationTag, gainedStanding, true);
|
||||
|
||||
await inventory.save();
|
||||
|
||||
res.json({
|
||||
InventoryChanges: inventoryChanges,
|
||||
AffiliationMods: [
|
||||
{
|
||||
Tag: request.Operation.AffiliationTag,
|
||||
Standing: gainedStanding
|
||||
}
|
||||
]
|
||||
AffiliationMods: [affiliationMod]
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,18 +1,26 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getAccountForRequest } from "@/src/services/loginService";
|
||||
import { addChallenges, getInventory } from "@/src/services/inventoryService";
|
||||
import { IChallengeProgress, ISeasonChallenge } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { IAffiliationMods } from "@/src/types/purchaseTypes";
|
||||
|
||||
export const updateChallengeProgressController: RequestHandler = async (req, res) => {
|
||||
const challenges = getJSONfromString<IUpdateChallengeProgressRequest>(String(req.body));
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const account = await getAccountForRequest(req);
|
||||
|
||||
const inventory = await getInventory(accountId, "ChallengeProgress SeasonChallengeHistory Affiliations");
|
||||
const inventory = await getInventory(
|
||||
account._id.toString(),
|
||||
"ChallengeProgress SeasonChallengeHistory Affiliations"
|
||||
);
|
||||
let affiliationMods: IAffiliationMods[] = [];
|
||||
if (challenges.ChallengeProgress) {
|
||||
affiliationMods = addChallenges(inventory, challenges.ChallengeProgress, challenges.SeasonChallengeCompletions);
|
||||
affiliationMods = addChallenges(
|
||||
account,
|
||||
inventory,
|
||||
challenges.ChallengeProgress,
|
||||
challenges.SeasonChallengeCompletions
|
||||
);
|
||||
}
|
||||
if (challenges.SeasonChallengeHistory) {
|
||||
challenges.SeasonChallengeHistory.forEach(({ challenge, id }) => {
|
||||
|
@ -25,7 +25,13 @@ export const upgradesController: RequestHandler = async (req, res) => {
|
||||
operation.UpgradeRequirement == "/Lotus/Types/Items/MiscItems/CustomizationSlotUnlocker"
|
||||
) {
|
||||
updateCurrency(inventory, 10, true);
|
||||
} else if (operation.OperationType != "UOT_ABILITY_OVERRIDE") {
|
||||
} else if (
|
||||
operation.OperationType != "UOT_SWAP_POLARITY" &&
|
||||
operation.OperationType != "UOT_ABILITY_OVERRIDE"
|
||||
) {
|
||||
if (!operation.UpgradeRequirement) {
|
||||
throw new Error(`${operation.OperationType} operation should be free?`);
|
||||
}
|
||||
addMiscItems(inventory, [
|
||||
{
|
||||
ItemType: operation.UpgradeRequirement,
|
||||
|
@ -1,12 +1,16 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { addFusionPoints, getInventory } from "@/src/services/inventoryService";
|
||||
|
||||
export const addCurrencyController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const request = req.body as IAddCurrencyRequest;
|
||||
inventory[request.currency] += request.delta;
|
||||
const inventory = await getInventory(accountId, request.currency);
|
||||
if (request.currency == "FusionPoints") {
|
||||
addFusionPoints(inventory, request.delta);
|
||||
} else {
|
||||
inventory[request.currency] += request.delta;
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ export const addItemsController: RequestHandler = async (req, res) => {
|
||||
const requests = req.body as IAddItemRequest[];
|
||||
const inventory = await getInventory(accountId);
|
||||
for (const request of requests) {
|
||||
await addItem(inventory, request.ItemType, request.ItemCount, true);
|
||||
await addItem(inventory, request.ItemType, request.ItemCount, true, undefined, undefined, true);
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
|
44
src/controllers/custom/addMissingMaxRankModsController.ts
Normal file
44
src/controllers/custom/addMissingMaxRankModsController.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
import { ExportArcanes, ExportUpgrades } from "warframe-public-export-plus";
|
||||
|
||||
export const addMissingMaxRankModsController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId, "Upgrades");
|
||||
|
||||
const maxOwnedRanks: Record<string, number> = {};
|
||||
for (const upgrade of inventory.Upgrades) {
|
||||
const fingerprint = JSON.parse(upgrade.UpgradeFingerprint ?? "{}") as { lvl?: number };
|
||||
if (fingerprint.lvl) {
|
||||
maxOwnedRanks[upgrade.ItemType] ??= 0;
|
||||
if (fingerprint.lvl > maxOwnedRanks[upgrade.ItemType]) {
|
||||
maxOwnedRanks[upgrade.ItemType] = fingerprint.lvl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const [uniqueName, data] of Object.entries(ExportUpgrades)) {
|
||||
if (data.fusionLimit != 0 && data.type != "PARAZON" && maxOwnedRanks[uniqueName] != data.fusionLimit) {
|
||||
inventory.Upgrades.push({
|
||||
ItemType: uniqueName,
|
||||
UpgradeFingerprint: JSON.stringify({ lvl: data.fusionLimit })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [uniqueName, data] of Object.entries(ExportArcanes)) {
|
||||
if (
|
||||
data.name != "/Lotus/Language/Items/GenericCosmeticEnhancerName" &&
|
||||
maxOwnedRanks[uniqueName] != data.fusionLimit
|
||||
) {
|
||||
inventory.Upgrades.push({
|
||||
ItemType: uniqueName,
|
||||
UpgradeFingerprint: JSON.stringify({ lvl: data.fusionLimit })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
@ -1,98 +0,0 @@
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import {
|
||||
getInventory,
|
||||
addEquipment,
|
||||
occupySlot,
|
||||
productCategoryToInventoryBin,
|
||||
applyDefaultUpgrades
|
||||
} from "@/src/services/inventoryService";
|
||||
import { modularWeaponTypes } from "@/src/helpers/modularWeaponHelper";
|
||||
import { getDefaultUpgrades } from "@/src/services/itemDataService";
|
||||
import { IEquipmentDatabase } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { ExportWeapons } from "warframe-public-export-plus";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const addModularEquipmentController: RequestHandler = async (req, res) => {
|
||||
const requiredFields = new Set();
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const request = req.body as IAddModularEquipmentRequest;
|
||||
const category = modularWeaponTypes[request.ItemType];
|
||||
const inventoryBin = productCategoryToInventoryBin(category)!;
|
||||
requiredFields.add(category);
|
||||
requiredFields.add(inventoryBin);
|
||||
|
||||
request.ModularParts.forEach(part => {
|
||||
if (ExportWeapons[part].gunType) {
|
||||
if (category == "LongGuns") {
|
||||
request.ItemType = {
|
||||
GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
|
||||
GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
|
||||
GT_BEAM: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
|
||||
}[ExportWeapons[part].gunType];
|
||||
} else {
|
||||
request.ItemType = {
|
||||
GT_RIFLE: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
|
||||
GT_SHOTGUN: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
|
||||
GT_BEAM: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
|
||||
}[ExportWeapons[part].gunType];
|
||||
}
|
||||
} else if (request.ItemType == "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
|
||||
if (part.includes("ZanukaPetPartHead")) {
|
||||
request.ItemType = {
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
|
||||
}[part]!;
|
||||
}
|
||||
}
|
||||
});
|
||||
const defaultUpgrades = getDefaultUpgrades(request.ModularParts);
|
||||
if (defaultUpgrades) {
|
||||
requiredFields.add("RawUpgrades");
|
||||
}
|
||||
const defaultWeaponsMap: Record<string, string[]> = {
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": [
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponIP"
|
||||
],
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": [
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponIS"
|
||||
],
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": [
|
||||
"/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetMeleeWeaponPS"
|
||||
]
|
||||
};
|
||||
const defaultWeapons = defaultWeaponsMap[request.ItemType] as string[] | undefined;
|
||||
if (defaultWeapons) {
|
||||
for (const defaultWeapon of defaultWeapons) {
|
||||
const category = ExportWeapons[defaultWeapon].productCategory;
|
||||
requiredFields.add(category);
|
||||
requiredFields.add(productCategoryToInventoryBin(category));
|
||||
}
|
||||
}
|
||||
|
||||
const inventory = await getInventory(accountId, Array.from(requiredFields).join(" "));
|
||||
if (defaultWeapons) {
|
||||
for (const defaultWeapon of defaultWeapons) {
|
||||
const category = ExportWeapons[defaultWeapon].productCategory;
|
||||
addEquipment(inventory, category, defaultWeapon);
|
||||
occupySlot(inventory, productCategoryToInventoryBin(category)!, true);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultOverwrites: Partial<IEquipmentDatabase> = {
|
||||
Configs: applyDefaultUpgrades(inventory, defaultUpgrades)
|
||||
};
|
||||
|
||||
addEquipment(inventory, category, request.ItemType, request.ModularParts, undefined, defaultOverwrites);
|
||||
occupySlot(inventory, inventoryBin, true);
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
interface IAddModularEquipmentRequest {
|
||||
ItemType: string;
|
||||
ModularParts: string[];
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { addGearExpByCategory, getInventory } from "@/src/services/inventoryService";
|
||||
import { applyClientEquipmentUpdates, getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { IOid } from "@/src/types/commonTypes";
|
||||
import { IEquipmentClient } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
@ -11,7 +12,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
||||
const request = req.body as IAddXpRequest;
|
||||
for (const [category, gear] of Object.entries(request)) {
|
||||
for (const clientItem of gear) {
|
||||
const dbItem = inventory[category as TEquipmentKey].id(clientItem.ItemId.$oid);
|
||||
const dbItem = inventory[category as TEquipmentKey].id((clientItem.ItemId as IOid).$oid);
|
||||
if (dbItem) {
|
||||
if (dbItem.ItemType in ExportMisc.uniqueLevelCaps) {
|
||||
if ((dbItem.Polarized ?? 0) < 5) {
|
||||
@ -20,7 +21,7 @@ export const addXpController: RequestHandler = async (req, res) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
addGearExpByCategory(inventory, gear, category as TEquipmentKey);
|
||||
applyClientEquipmentUpdates(inventory, gear, category as TEquipmentKey);
|
||||
}
|
||||
await inventory.save();
|
||||
res.end();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RequestHandler } from "express";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { Account } from "@/src/models/loginModel";
|
||||
import { Account, Ignore } from "@/src/models/loginModel";
|
||||
import { Inbox } from "@/src/models/inboxModel";
|
||||
import { Inventory } from "@/src/models/inventoryModels/inventoryModel";
|
||||
import { Loadout } from "@/src/models/inventoryModels/loadoutModel";
|
||||
@ -10,6 +10,7 @@ import { Stats } from "@/src/models/statsModel";
|
||||
import { GuildMember } from "@/src/models/guildModel";
|
||||
import { Leaderboard } from "@/src/models/leaderboardModel";
|
||||
import { deleteGuild } from "@/src/services/guildService";
|
||||
import { Friendship } from "@/src/models/friendModel";
|
||||
|
||||
export const deleteAccountController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
@ -22,7 +23,11 @@ export const deleteAccountController: RequestHandler = async (req, res) => {
|
||||
|
||||
await Promise.all([
|
||||
Account.deleteOne({ _id: accountId }),
|
||||
Friendship.deleteMany({ owner: accountId }),
|
||||
Friendship.deleteMany({ friend: accountId }),
|
||||
GuildMember.deleteMany({ accountId: accountId }),
|
||||
Ignore.deleteMany({ ignorer: accountId }),
|
||||
Ignore.deleteMany({ ignoree: accountId }),
|
||||
Inbox.deleteMany({ ownerId: accountId }),
|
||||
Inventory.deleteOne({ accountOwnerId: accountId }),
|
||||
Leaderboard.deleteMany({ ownerId: accountId }),
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { AllianceMember, Guild, GuildMember } from "@/src/models/guildModel";
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountForRequest, isAdministrator } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const getAccountInfoController: RequestHandler = async (req, res) => {
|
||||
const account = await getAccountForRequest(req);
|
||||
const inventory = await getInventory(account._id.toString(), "QuestKeys");
|
||||
const info: IAccountInfo = {
|
||||
DisplayName: account.DisplayName
|
||||
DisplayName: account.DisplayName,
|
||||
IsAdministrator: isAdministrator(account),
|
||||
CompletedVorsPrize: !!inventory.QuestKeys.find(
|
||||
x => x.ItemType == "/Lotus/Types/Keys/VorsPrize/VorsPrizeQuestKeyChain"
|
||||
)?.Completed
|
||||
};
|
||||
if (isAdministrator(account)) {
|
||||
info.IsAdministrator = true;
|
||||
}
|
||||
const guildMember = await GuildMember.findOne({ accountId: account._id, status: 0 }, "guildId rank");
|
||||
if (guildMember) {
|
||||
const guild = (await Guild.findById(guildMember.guildId, "Ranks AllianceId"))!;
|
||||
@ -31,7 +34,8 @@ export const getAccountInfoController: RequestHandler = async (req, res) => {
|
||||
|
||||
interface IAccountInfo {
|
||||
DisplayName: string;
|
||||
IsAdministrator?: boolean;
|
||||
IsAdministrator: boolean;
|
||||
CompletedVorsPrize: boolean;
|
||||
GuildId?: string;
|
||||
GuildPermissions?: number;
|
||||
GuildRank?: number;
|
||||
|
@ -3,6 +3,7 @@ import { getDict, getItemName, getString } from "@/src/services/itemDataService"
|
||||
import {
|
||||
ExportArcanes,
|
||||
ExportAvionics,
|
||||
ExportCustoms,
|
||||
ExportDrones,
|
||||
ExportGear,
|
||||
ExportKeys,
|
||||
@ -19,6 +20,7 @@ import {
|
||||
TRelicQuality
|
||||
} from "warframe-public-export-plus";
|
||||
import archonCrystalUpgrades from "@/static/fixed_responses/webuiArchonCrystalUpgrades.json";
|
||||
import allIncarnons from "@/static/fixed_responses/allIncarnonList.json";
|
||||
|
||||
interface ListedItem {
|
||||
uniqueName: string;
|
||||
@ -28,6 +30,30 @@ interface ListedItem {
|
||||
badReason?: "starter" | "frivolous" | "notraw";
|
||||
partType?: string;
|
||||
chainLength?: number;
|
||||
parazon?: boolean;
|
||||
}
|
||||
|
||||
interface ItemLists {
|
||||
archonCrystalUpgrades: Record<string, string>;
|
||||
uniqueLevelCaps: Record<string, number>;
|
||||
Suits: ListedItem[];
|
||||
LongGuns: ListedItem[];
|
||||
Melee: ListedItem[];
|
||||
ModularParts: ListedItem[];
|
||||
Pistols: ListedItem[];
|
||||
Sentinels: ListedItem[];
|
||||
SentinelWeapons: ListedItem[];
|
||||
SpaceGuns: ListedItem[];
|
||||
SpaceMelee: ListedItem[];
|
||||
SpaceSuits: ListedItem[];
|
||||
MechSuits: ListedItem[];
|
||||
miscitems: ListedItem[];
|
||||
Syndicates: ListedItem[];
|
||||
OperatorAmps: ListedItem[];
|
||||
QuestKeys: ListedItem[];
|
||||
KubrowPets: ListedItem[];
|
||||
EvolutionProgress: ListedItem[];
|
||||
mods: ListedItem[];
|
||||
}
|
||||
|
||||
const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||
@ -39,22 +65,28 @@ const relicQualitySuffixes: Record<TRelicQuality, string> = {
|
||||
|
||||
const getItemListsController: RequestHandler = (req, response) => {
|
||||
const lang = getDict(typeof req.query.lang == "string" ? req.query.lang : "en");
|
||||
const res: Record<string, ListedItem[]> = {};
|
||||
res.Suits = [];
|
||||
res.LongGuns = [];
|
||||
res.Melee = [];
|
||||
res.ModularParts = [];
|
||||
res.Pistols = [];
|
||||
res.Sentinels = [];
|
||||
res.SentinelWeapons = [];
|
||||
res.SpaceGuns = [];
|
||||
res.SpaceMelee = [];
|
||||
res.SpaceSuits = [];
|
||||
res.MechSuits = [];
|
||||
res.miscitems = [];
|
||||
res.Syndicates = [];
|
||||
res.OperatorAmps = [];
|
||||
res.QuestKeys = [];
|
||||
const res: ItemLists = {
|
||||
archonCrystalUpgrades,
|
||||
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
||||
Suits: [],
|
||||
LongGuns: [],
|
||||
Melee: [],
|
||||
ModularParts: [],
|
||||
Pistols: [],
|
||||
Sentinels: [],
|
||||
SentinelWeapons: [],
|
||||
SpaceGuns: [],
|
||||
SpaceMelee: [],
|
||||
SpaceSuits: [],
|
||||
MechSuits: [],
|
||||
miscitems: [],
|
||||
Syndicates: [],
|
||||
OperatorAmps: [],
|
||||
QuestKeys: [],
|
||||
KubrowPets: [],
|
||||
EvolutionProgress: [],
|
||||
mods: []
|
||||
};
|
||||
for (const [uniqueName, item] of Object.entries(ExportWarframes)) {
|
||||
res[item.productCategory].push({
|
||||
uniqueName,
|
||||
@ -63,20 +95,23 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
});
|
||||
}
|
||||
for (const [uniqueName, item] of Object.entries(ExportSentinels)) {
|
||||
if (item.productCategory == "Sentinels") {
|
||||
if (item.productCategory == "Sentinels" || item.productCategory == "KubrowPets") {
|
||||
res[item.productCategory].push({
|
||||
uniqueName,
|
||||
name: getString(item.name, lang)
|
||||
name: getString(item.name, lang),
|
||||
exalted: item.exalted
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const [uniqueName, item] of Object.entries(ExportWeapons)) {
|
||||
if (item.partType) {
|
||||
res.ModularParts.push({
|
||||
uniqueName,
|
||||
name: getString(item.name, lang),
|
||||
partType: item.partType
|
||||
});
|
||||
if (!uniqueName.startsWith("/Lotus/Types/Items/Deimos/")) {
|
||||
res.ModularParts.push({
|
||||
uniqueName,
|
||||
name: getString(item.name, lang),
|
||||
partType: item.partType
|
||||
});
|
||||
}
|
||||
if (uniqueName.split("/")[5] != "SentTrainingAmplifier") {
|
||||
res.miscitems.push({
|
||||
uniqueName: uniqueName,
|
||||
@ -109,12 +144,28 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
let name = getString(item.name, lang);
|
||||
if ("dissectionParts" in item) {
|
||||
name = getString("/Lotus/Language/Fish/FishDisplayName", lang).split("|FISH_NAME|").join(name);
|
||||
if (uniqueName.indexOf("Large") != -1) {
|
||||
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeLargeAbbrev", lang));
|
||||
} else if (uniqueName.indexOf("Medium") != -1) {
|
||||
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeMediumAbbrev", lang));
|
||||
if (item.syndicateTag == "CetusSyndicate") {
|
||||
if (uniqueName.indexOf("Large") != -1) {
|
||||
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeLargeAbbrev", lang));
|
||||
} else if (uniqueName.indexOf("Medium") != -1) {
|
||||
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeMediumAbbrev", lang));
|
||||
} else {
|
||||
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeSmallAbbrev", lang));
|
||||
}
|
||||
} else {
|
||||
name = name.split("|FISH_SIZE|").join(getString("/Lotus/Language/Fish/FishSizeSmallAbbrev", lang));
|
||||
if (uniqueName.indexOf("Large") != -1) {
|
||||
name = name
|
||||
.split("|FISH_SIZE|")
|
||||
.join(getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryElderAbbrev", lang));
|
||||
} else if (uniqueName.indexOf("Medium") != -1) {
|
||||
name = name
|
||||
.split("|FISH_SIZE|")
|
||||
.join(getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryMatureAbbrev", lang));
|
||||
} else {
|
||||
name = name
|
||||
.split("|FISH_SIZE|")
|
||||
.join(getString("/Lotus/Language/SolarisVenus/RobofishAgeCategoryYoungAbbrev", lang));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (
|
||||
@ -171,8 +222,13 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
name: getString(item.name, lang)
|
||||
});
|
||||
}
|
||||
for (const [uniqueName, item] of Object.entries(ExportCustoms)) {
|
||||
res.miscitems.push({
|
||||
uniqueName: uniqueName,
|
||||
name: getString(item.name, lang)
|
||||
});
|
||||
}
|
||||
|
||||
res.mods = [];
|
||||
for (const [uniqueName, upgrade] of Object.entries(ExportUpgrades)) {
|
||||
const mod: ListedItem = {
|
||||
uniqueName,
|
||||
@ -186,6 +242,9 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
} else if (upgrade.upgradeEntries) {
|
||||
mod.badReason = "notraw";
|
||||
}
|
||||
if (upgrade.type == "PARAZON") {
|
||||
mod.parazon = true;
|
||||
}
|
||||
res.mods.push(mod);
|
||||
}
|
||||
for (const [uniqueName, upgrade] of Object.entries(ExportAvionics)) {
|
||||
@ -227,12 +286,14 @@ const getItemListsController: RequestHandler = (req, response) => {
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const uniqueName of allIncarnons) {
|
||||
res.EvolutionProgress.push({
|
||||
uniqueName,
|
||||
name: getString(getItemName(uniqueName) || "", lang)
|
||||
});
|
||||
}
|
||||
|
||||
response.json({
|
||||
archonCrystalUpgrades,
|
||||
uniqueLevelCaps: ExportMisc.uniqueLevelCaps,
|
||||
...res
|
||||
});
|
||||
response.json(res);
|
||||
};
|
||||
|
||||
export { getItemListsController };
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { EquipmentFeatures } from "@/src/types/inventoryTypes/commonInventoryTypes";
|
||||
import { TEquipmentKey } from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const gildEquipmentController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const request = req.body as IGildEquipmentRequest;
|
||||
const inventory = await getInventory(accountId, request.Category);
|
||||
const weapon = inventory[request.Category].id(request.ItemId);
|
||||
if (weapon) {
|
||||
weapon.Features ??= 0;
|
||||
weapon.Features |= EquipmentFeatures.GILDED;
|
||||
await inventory.save();
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
|
||||
type IGildEquipmentRequest = {
|
||||
ItemId: string;
|
||||
Category: TEquipmentKey;
|
||||
};
|
@ -35,10 +35,8 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
|
||||
|
||||
switch (operation) {
|
||||
case "completeAll": {
|
||||
if (allQuestKeys.includes(questItemType)) {
|
||||
for (const questKey of inventory.QuestKeys) {
|
||||
await completeQuest(inventory, questKey.ItemType);
|
||||
}
|
||||
for (const questKey of inventory.QuestKeys) {
|
||||
await completeQuest(inventory, questKey.ItemType);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -56,15 +54,12 @@ export const manageQuestsController: RequestHandler = async (req, res) => {
|
||||
break;
|
||||
}
|
||||
case "deleteKey": {
|
||||
if (allQuestKeys.includes(questItemType)) {
|
||||
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
|
||||
if (!questKey) {
|
||||
logger.error(`Quest key not found in inventory: ${questItemType}`);
|
||||
break;
|
||||
}
|
||||
|
||||
inventory.QuestKeys.pull({ ItemType: questItemType });
|
||||
const questKey = inventory.QuestKeys.find(key => key.ItemType === questItemType);
|
||||
if (!questKey) {
|
||||
logger.error(`Quest key not found in inventory: ${questItemType}`);
|
||||
break;
|
||||
}
|
||||
inventory.QuestKeys.pull({ ItemType: questItemType });
|
||||
break;
|
||||
}
|
||||
case "completeKey": {
|
||||
|
33
src/controllers/custom/setEvolutionProgressController.ts
Normal file
33
src/controllers/custom/setEvolutionProgressController.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { getInventory } from "@/src/services/inventoryService";
|
||||
import { getAccountIdForRequest } from "@/src/services/loginService";
|
||||
import { RequestHandler } from "express";
|
||||
|
||||
export const setEvolutionProgressController: RequestHandler = async (req, res) => {
|
||||
const accountId = await getAccountIdForRequest(req);
|
||||
const inventory = await getInventory(accountId);
|
||||
const payload = req.body as ISetEvolutionProgressRequest;
|
||||
|
||||
inventory.EvolutionProgress ??= [];
|
||||
payload.forEach(element => {
|
||||
const entry = inventory.EvolutionProgress!.find(entry => entry.ItemType === element.ItemType);
|
||||
|
||||
if (entry) {
|
||||
entry.Progress = 0;
|
||||
entry.Rank = element.Rank;
|
||||
} else {
|
||||
inventory.EvolutionProgress!.push({
|
||||
Progress: 0,
|
||||
Rank: element.Rank,
|
||||
ItemType: element.ItemType
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await inventory.save();
|
||||
res.end();
|
||||
};
|
||||
|
||||
type ISetEvolutionProgressRequest = {
|
||||
ItemType: string;
|
||||
Rank: number;
|
||||
}[];
|
@ -18,64 +18,76 @@ import {
|
||||
ITypeXPItem
|
||||
} from "@/src/types/inventoryTypes/inventoryTypes";
|
||||
import { RequestHandler } from "express";
|
||||
import { catBreadHash } from "@/src/helpers/stringHelpers";
|
||||
import { catBreadHash, getJSONfromString } from "@/src/helpers/stringHelpers";
|
||||
import { ExportCustoms, ExportDojoRecipes } from "warframe-public-export-plus";
|
||||
import { IStatsClient } from "@/src/types/statTypes";
|
||||
import { toStoreItem } from "@/src/services/itemDataService";
|
||||
import { FlattenMaps } from "mongoose";
|
||||
|
||||
export const getProfileViewingDataController: RequestHandler = async (req, res) => {
|
||||
const getProfileViewingDataByPlayerIdImpl = async (playerId: string): Promise<IProfileViewingData | undefined> => {
|
||||
const account = await Account.findById(playerId, "DisplayName");
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
|
||||
|
||||
const result: IPlayerProfileViewingDataResult = {
|
||||
AccountId: toOid(account._id),
|
||||
DisplayName: account.DisplayName,
|
||||
PlayerLevel: inventory.PlayerLevel,
|
||||
LoadOutInventory: {
|
||||
WeaponSkins: [],
|
||||
XPInfo: inventory.XPInfo
|
||||
},
|
||||
PlayerSkills: inventory.PlayerSkills,
|
||||
ChallengeProgress: inventory.ChallengeProgress,
|
||||
DeathMarks: inventory.DeathMarks,
|
||||
Harvestable: inventory.Harvestable,
|
||||
DeathSquadable: inventory.DeathSquadable,
|
||||
Created: toMongoDate(inventory.Created),
|
||||
MigratedToConsole: false,
|
||||
Missions: inventory.Missions,
|
||||
Affiliations: inventory.Affiliations,
|
||||
DailyFocus: inventory.DailyFocus,
|
||||
Wishlist: inventory.Wishlist,
|
||||
Alignment: inventory.Alignment
|
||||
};
|
||||
await populateLoadout(inventory, result);
|
||||
if (inventory.GuildId) {
|
||||
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
|
||||
populateGuild(guild, result);
|
||||
}
|
||||
for (const key of allDailyAffiliationKeys) {
|
||||
result[key] = inventory[key];
|
||||
}
|
||||
|
||||
const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
|
||||
delete stats._id;
|
||||
delete stats.__v;
|
||||
delete stats.accountOwnerId;
|
||||
|
||||
return {
|
||||
Results: [result],
|
||||
TechProjects: [],
|
||||
XpComponents: [],
|
||||
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
|
||||
Stats: stats
|
||||
};
|
||||
};
|
||||
|
||||
export const getProfileViewingDataGetController: RequestHandler = async (req, res) => {
|
||||
if (req.query.playerId) {
|
||||
const account = await Account.findById(req.query.playerId as string, "DisplayName");
|
||||
if (!account) {
|
||||
const data = await getProfileViewingDataByPlayerIdImpl(req.query.playerId as string);
|
||||
if (data) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.status(409).send("Could not find requested account");
|
||||
return;
|
||||
}
|
||||
const inventory = (await Inventory.findOne({ accountOwnerId: account._id }))!;
|
||||
|
||||
const result: IPlayerProfileViewingDataResult = {
|
||||
AccountId: toOid(account._id),
|
||||
DisplayName: account.DisplayName,
|
||||
PlayerLevel: inventory.PlayerLevel,
|
||||
LoadOutInventory: {
|
||||
WeaponSkins: [],
|
||||
XPInfo: inventory.XPInfo
|
||||
},
|
||||
PlayerSkills: inventory.PlayerSkills,
|
||||
ChallengeProgress: inventory.ChallengeProgress,
|
||||
DeathMarks: inventory.DeathMarks,
|
||||
Harvestable: inventory.Harvestable,
|
||||
DeathSquadable: inventory.DeathSquadable,
|
||||
Created: toMongoDate(inventory.Created),
|
||||
MigratedToConsole: false,
|
||||
Missions: inventory.Missions,
|
||||
Affiliations: inventory.Affiliations,
|
||||
DailyFocus: inventory.DailyFocus,
|
||||
Wishlist: inventory.Wishlist,
|
||||
Alignment: inventory.Alignment
|
||||
};
|
||||
await populateLoadout(inventory, result);
|
||||
if (inventory.GuildId) {
|
||||
const guild = (await Guild.findById(inventory.GuildId, "Name Tier XP Class Emblem"))!;
|
||||
populateGuild(guild, result);
|
||||
}
|
||||
for (const key of allDailyAffiliationKeys) {
|
||||
result[key] = inventory[key];
|
||||
}
|
||||
|
||||
const stats = (await Stats.findOne({ accountOwnerId: account._id }))!.toJSON<Partial<TStatsDatabaseDocument>>();
|
||||
delete stats._id;
|
||||
delete stats.__v;
|
||||
delete stats.accountOwnerId;
|
||||
|
||||
res.json({
|
||||
Results: [result],
|
||||
TechProjects: [],
|
||||
XpComponents: [],
|
||||
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
|
||||
Stats: stats
|
||||
});
|
||||
} else if (req.query.guildId) {
|
||||
const guild = await Guild.findById(req.query.guildId, "Name Tier XP Class Emblem TechProjects ClaimedXP");
|
||||
const guild = await Guild.findById(
|
||||
req.query.guildId as string,
|
||||
"Name Tier XP Class Emblem TechProjects ClaimedXP"
|
||||
);
|
||||
if (!guild) {
|
||||
res.status(409).send("Could not find guild");
|
||||
return;
|
||||
@ -170,6 +182,28 @@ export const getProfileViewingDataController: RequestHandler = async (req, res)
|
||||
}
|
||||
};
|
||||
|
||||
// For old versions, this was an authenticated POST request.
|
||||
interface IGetProfileViewingDataRequest {
|
||||
AccountId: string;
|
||||
}
|
||||
export const getProfileViewingDataPostController: RequestHandler = async (req, res) => {
|
||||
const payload = getJSONfromString<IGetProfileViewingDataRequest>(String(req.body));
|
||||
const data = await getProfileViewingDataByPlayerIdImpl(payload.AccountId);
|
||||
if (data) {
|
||||
res.json(data);
|
||||
} else {
|
||||
res.status(409).send("Could not find requested account");
|
||||
}
|
||||
};
|
||||
|
||||
interface IProfileViewingData {
|
||||
Results: IPlayerProfileViewingDataResult[];
|
||||
TechProjects: [];
|
||||
XpComponents: [];
|
||||
//XpCacheExpiryDate, some IMongoDate in the future, no clue what it's for
|
||||
Stats: FlattenMaps<Partial<TStatsDatabaseDocument>>;
|
||||
}
|
||||
|
||||
interface IPlayerProfileViewingDataResult extends Partial<IDailyAffiliations> {
|
||||
AccountId: IOid;
|
||||
DisplayName: string;
|
||||
|
@ -1,611 +1,6 @@
|
||||
import { RequestHandler } from "express";
|
||||
import staticWorldState from "@/static/fixed_responses/worldState/worldState.json";
|
||||
import static1999FallDays from "@/static/fixed_responses/worldState/1999_fall_days.json";
|
||||
import static1999SpringDays from "@/static/fixed_responses/worldState/1999_spring_days.json";
|
||||
import static1999SummerDays from "@/static/fixed_responses/worldState/1999_summer_days.json";
|
||||
import static1999WinterDays from "@/static/fixed_responses/worldState/1999_winter_days.json";
|
||||
import { buildConfig } from "@/src/services/buildConfigService";
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { unixTimesInMs } from "@/src/constants/timeConstants";
|
||||
import { config } from "@/src/services/configService";
|
||||
import { CRng } from "@/src/services/rngService";
|
||||
import { ExportNightwave, ExportRegions } from "warframe-public-export-plus";
|
||||
import {
|
||||
EPOCH,
|
||||
getSortieTime,
|
||||
missionTags,
|
||||
sortieBosses,
|
||||
sortieBossNode,
|
||||
sortieBossToFaction,
|
||||
sortieFactionToFactionIndexes,
|
||||
sortieFactionToSystemIndexes
|
||||
} from "@/src/helpers/worlstateHelper";
|
||||
import { getWorldState } from "@/src/services/worldStateService";
|
||||
|
||||
export const worldStateController: RequestHandler = (req, res) => {
|
||||
const day = Math.trunc((Date.now() - EPOCH) / 86400000);
|
||||
const week = Math.trunc(day / 7);
|
||||
const weekStart = EPOCH + week * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
|
||||
const worldState: IWorldState = {
|
||||
BuildLabel:
|
||||
typeof req.query.buildLabel == "string"
|
||||
? req.query.buildLabel.split(" ").join("+")
|
||||
: buildConfig.buildLabel,
|
||||
Time: config.worldState?.lockTime || Math.round(Date.now() / 1000),
|
||||
Goals: [],
|
||||
GlobalUpgrades: [],
|
||||
Sorties: [],
|
||||
LiteSorties: [],
|
||||
EndlessXpChoices: [],
|
||||
SeasonInfo: {
|
||||
Activation: { $date: { $numberLong: "1715796000000" } },
|
||||
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||
AffiliationTag: "RadioLegionIntermission12Syndicate",
|
||||
Season: 14,
|
||||
Phase: 0,
|
||||
Params: "",
|
||||
ActiveChallenges: [
|
||||
getSeasonDailyChallenge(day - 2),
|
||||
getSeasonDailyChallenge(day - 1),
|
||||
getSeasonDailyChallenge(day - 0),
|
||||
getSeasonWeeklyChallenge(week, 0),
|
||||
getSeasonWeeklyChallenge(week, 1),
|
||||
getSeasonWeeklyHardChallenge(week, 2),
|
||||
getSeasonWeeklyHardChallenge(week, 3),
|
||||
{
|
||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 0).toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||
Challenge:
|
||||
"/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentCompleteMissions" + (week - 12)
|
||||
},
|
||||
{
|
||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 1).toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEximus" + (week - 12)
|
||||
},
|
||||
{
|
||||
_id: { $oid: "67e1b96e9d00cb47" + (week * 7 + 2).toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||
Challenge: "/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanentKillEnemies" + (week - 12)
|
||||
}
|
||||
]
|
||||
},
|
||||
...staticWorldState
|
||||
};
|
||||
|
||||
if (config.worldState?.starDays) {
|
||||
worldState.Goals.push({
|
||||
_id: { $oid: "67a4dcce2a198564d62e1647" },
|
||||
Activation: { $date: { $numberLong: "1738868400000" } },
|
||||
Expiry: { $date: { $numberLong: "2000000000000" } },
|
||||
Count: 0,
|
||||
Goal: 0,
|
||||
Success: 0,
|
||||
Personal: true,
|
||||
Desc: "/Lotus/Language/Events/ValentinesFortunaName",
|
||||
ToolTip: "/Lotus/Language/Events/ValentinesFortunaName",
|
||||
Icon: "/Lotus/Interface/Icons/WorldStatePanel/ValentinesEventIcon.png",
|
||||
Tag: "FortunaValentines",
|
||||
Node: "SolarisUnitedHub1"
|
||||
});
|
||||
}
|
||||
|
||||
// Elite Sanctuary Onslaught cycling every week
|
||||
worldState.NodeOverrides.find(x => x.Node == "SolNode802")!.Seed = week; // unfaithful
|
||||
|
||||
// Holdfast, Cavia, & Hex bounties cycling every 2.5 hours; unfaithful implementation
|
||||
const bountyCycle = Math.trunc(Date.now() / 9000000);
|
||||
const bountyCycleStart = bountyCycle * 9000000;
|
||||
const bountyCycleEnd = bountyCycleStart + 9000000;
|
||||
worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "ZarimanSyndicate")] = {
|
||||
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000029" },
|
||||
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
||||
Tag: "ZarimanSyndicate",
|
||||
Seed: bountyCycle,
|
||||
Nodes: []
|
||||
};
|
||||
worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "EntratiLabSyndicate")] = {
|
||||
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000004" },
|
||||
Activation: { $date: { $numberLong: bountyCycleStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: bountyCycleEnd.toString() } },
|
||||
Tag: "EntratiLabSyndicate",
|
||||
Seed: bountyCycle,
|
||||
Nodes: []
|
||||
};
|
||||
worldState.SyndicateMissions[worldState.SyndicateMissions.findIndex(x => x.Tag == "HexSyndicate")] = {
|
||||
_id: { $oid: Math.trunc(bountyCycleStart / 1000).toString(16) + "0000000000000006" },
|
||||
Activation: { $date: { $numberLong: bountyCycleStart.toString(10) } },
|
||||
Expiry: { $date: { $numberLong: bountyCycleEnd.toString(10) } },
|
||||
Tag: "HexSyndicate",
|
||||
Seed: bountyCycle,
|
||||
Nodes: []
|
||||
};
|
||||
|
||||
if (config.worldState?.creditBoost) {
|
||||
worldState.GlobalUpgrades.push({
|
||||
_id: { $oid: "5b23106f283a555109666672" },
|
||||
Activation: { $date: { $numberLong: "1740164400000" } },
|
||||
ExpiryDate: { $date: { $numberLong: "2000000000000" } },
|
||||
UpgradeType: "GAMEPLAY_MONEY_REWARD_AMOUNT",
|
||||
OperationType: "MULTIPLY",
|
||||
Value: 2,
|
||||
LocalizeTag: "",
|
||||
LocalizeDescTag: ""
|
||||
});
|
||||
}
|
||||
if (config.worldState?.affinityBoost) {
|
||||
worldState.GlobalUpgrades.push({
|
||||
_id: { $oid: "5b23106f283a555109666673" },
|
||||
Activation: { $date: { $numberLong: "1740164400000" } },
|
||||
ExpiryDate: { $date: { $numberLong: "2000000000000" } },
|
||||
UpgradeType: "GAMEPLAY_KILL_XP_AMOUNT",
|
||||
OperationType: "MULTIPLY",
|
||||
Value: 2,
|
||||
LocalizeTag: "",
|
||||
LocalizeDescTag: ""
|
||||
});
|
||||
}
|
||||
if (config.worldState?.resourceBoost) {
|
||||
worldState.GlobalUpgrades.push({
|
||||
_id: { $oid: "5b23106f283a555109666674" },
|
||||
Activation: { $date: { $numberLong: "1740164400000" } },
|
||||
ExpiryDate: { $date: { $numberLong: "2000000000000" } },
|
||||
UpgradeType: "GAMEPLAY_PICKUP_AMOUNT",
|
||||
OperationType: "MULTIPLY",
|
||||
Value: 2,
|
||||
LocalizeTag: "",
|
||||
LocalizeDescTag: ""
|
||||
});
|
||||
}
|
||||
|
||||
// Sortie cycling every day
|
||||
{
|
||||
let genDay;
|
||||
let dayStart;
|
||||
let dayEnd;
|
||||
const sortieRolloverToday = getSortieTime(day);
|
||||
if (Date.now() < sortieRolloverToday) {
|
||||
// Early in the day, generate sortie for `day - 1`, expiring at `sortieRolloverToday`.
|
||||
genDay = day - 1;
|
||||
dayStart = getSortieTime(genDay);
|
||||
dayEnd = sortieRolloverToday;
|
||||
} else {
|
||||
// Late in the day, generate sortie for `day`, expiring at `getSortieTime(day + 1)`.
|
||||
genDay = day;
|
||||
dayStart = sortieRolloverToday;
|
||||
dayEnd = getSortieTime(day + 1);
|
||||
}
|
||||
|
||||
const rng = new CRng(genDay);
|
||||
|
||||
const boss = rng.randomElement(sortieBosses);
|
||||
|
||||
const modifiers = [
|
||||
"SORTIE_MODIFIER_LOW_ENERGY",
|
||||
"SORTIE_MODIFIER_IMPACT",
|
||||
"SORTIE_MODIFIER_SLASH",
|
||||
"SORTIE_MODIFIER_PUNCTURE",
|
||||
"SORTIE_MODIFIER_EXIMUS",
|
||||
"SORTIE_MODIFIER_MAGNETIC",
|
||||
"SORTIE_MODIFIER_CORROSIVE",
|
||||
"SORTIE_MODIFIER_VIRAL",
|
||||
"SORTIE_MODIFIER_ELECTRICITY",
|
||||
"SORTIE_MODIFIER_RADIATION",
|
||||
"SORTIE_MODIFIER_GAS",
|
||||
"SORTIE_MODIFIER_FIRE",
|
||||
"SORTIE_MODIFIER_EXPLOSION",
|
||||
"SORTIE_MODIFIER_FREEZE",
|
||||
"SORTIE_MODIFIER_TOXIN",
|
||||
"SORTIE_MODIFIER_POISON",
|
||||
"SORTIE_MODIFIER_HAZARD_RADIATION",
|
||||
"SORTIE_MODIFIER_HAZARD_MAGNETIC",
|
||||
"SORTIE_MODIFIER_HAZARD_FOG", // TODO: push this if the mission tileset is Grineer Forest
|
||||
"SORTIE_MODIFIER_HAZARD_FIRE", // TODO: push this if the mission tileset is Corpus Ship or Grineer Galleon
|
||||
"SORTIE_MODIFIER_HAZARD_ICE",
|
||||
"SORTIE_MODIFIER_HAZARD_COLD",
|
||||
"SORTIE_MODIFIER_SECONDARY_ONLY",
|
||||
"SORTIE_MODIFIER_SHOTGUN_ONLY",
|
||||
"SORTIE_MODIFIER_SNIPER_ONLY",
|
||||
"SORTIE_MODIFIER_RIFLE_ONLY",
|
||||
"SORTIE_MODIFIER_MELEE_ONLY",
|
||||
"SORTIE_MODIFIER_BOW_ONLY"
|
||||
];
|
||||
|
||||
if (sortieBossToFaction[boss] == "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_SHIELDS");
|
||||
if (sortieBossToFaction[boss] != "FC_CORPUS") modifiers.push("SORTIE_MODIFIER_ARMOR");
|
||||
|
||||
const nodes: string[] = [];
|
||||
const availableMissionIndexes: number[] = [];
|
||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||
if (
|
||||
sortieFactionToSystemIndexes[sortieBossToFaction[boss]].includes(value.systemIndex) &&
|
||||
sortieFactionToFactionIndexes[sortieBossToFaction[boss]].includes(value.factionIndex!) &&
|
||||
value.name.indexOf("Archwing") == -1 &&
|
||||
value.missionIndex != 0 && // Exclude MT_ASSASSINATION
|
||||
value.missionIndex != 5 && // Exclude MT_CAPTURE
|
||||
value.missionIndex != 21 && // Exclude MT_PURIFY
|
||||
value.missionIndex != 23 && // Exclude MT_JUNCTION
|
||||
value.missionIndex <= 28
|
||||
) {
|
||||
if (!availableMissionIndexes.includes(value.missionIndex)) {
|
||||
availableMissionIndexes.push(value.missionIndex);
|
||||
}
|
||||
nodes.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedNodes: { missionType: string; modifierType: string; node: string }[] = [];
|
||||
const missionTypes = new Set();
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const randomIndex = rng.randomInt(0, nodes.length - 1);
|
||||
const node = nodes[randomIndex];
|
||||
let missionIndex = ExportRegions[node].missionIndex;
|
||||
|
||||
if (
|
||||
!["SolNode404", "SolNode411"].includes(node) && // for some reason the game doesn't like missionType changes for these missions
|
||||
missionIndex != 28 &&
|
||||
rng.randomInt(0, 2) == 2
|
||||
) {
|
||||
missionIndex = rng.randomElement(availableMissionIndexes);
|
||||
}
|
||||
|
||||
if (i == 2 && rng.randomInt(0, 2) == 2) {
|
||||
const filteredModifiers = modifiers.filter(mod => mod !== "SORTIE_MODIFIER_MELEE_ONLY");
|
||||
const modifierType = rng.randomElement(filteredModifiers);
|
||||
|
||||
if (boss == "SORTIE_BOSS_PHORID") {
|
||||
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node });
|
||||
nodes.splice(randomIndex, 1);
|
||||
continue;
|
||||
} else if (sortieBossNode[boss]) {
|
||||
selectedNodes.push({ missionType: "MT_ASSASSINATION", modifierType, node: sortieBossNode[boss] });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const missionType = missionTags[missionIndex];
|
||||
|
||||
if (missionTypes.has(missionType)) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
const filteredModifiers =
|
||||
missionType === "MT_TERRITORY"
|
||||
? modifiers.filter(mod => mod != "SORTIE_MODIFIER_HAZARD_RADIATION")
|
||||
: modifiers;
|
||||
|
||||
const modifierType = rng.randomElement(filteredModifiers);
|
||||
|
||||
selectedNodes.push({ missionType, modifierType, node });
|
||||
nodes.splice(randomIndex, 1);
|
||||
missionTypes.add(missionType);
|
||||
}
|
||||
|
||||
worldState.Sorties.push({
|
||||
_id: { $oid: Math.trunc(dayStart / 1000).toString(16) + "d4d932c97c0a3acd" },
|
||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards",
|
||||
Seed: genDay,
|
||||
Boss: boss,
|
||||
Variants: selectedNodes
|
||||
});
|
||||
}
|
||||
|
||||
// Archon Hunt cycling every week
|
||||
{
|
||||
const boss = ["SORTIE_BOSS_AMAR", "SORTIE_BOSS_NIRA", "SORTIE_BOSS_BOREAL"][week % 3];
|
||||
const showdownNode = ["SolNode99", "SolNode53", "SolNode24"][week % 3];
|
||||
const systemIndex = [3, 4, 2][week % 3]; // Mars, Jupiter, Earth
|
||||
|
||||
const nodes: string[] = [];
|
||||
for (const [key, value] of Object.entries(ExportRegions)) {
|
||||
if (
|
||||
value.systemIndex === systemIndex &&
|
||||
value.factionIndex !== undefined &&
|
||||
value.factionIndex < 2 &&
|
||||
value.name.indexOf("Archwing") == -1 &&
|
||||
value.missionIndex != 0 // Exclude MT_ASSASSINATION
|
||||
) {
|
||||
nodes.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
const rng = new CRng(week);
|
||||
const firstNodeIndex = rng.randomInt(0, nodes.length - 1);
|
||||
const firstNode = nodes[firstNodeIndex];
|
||||
nodes.splice(firstNodeIndex, 1);
|
||||
worldState.LiteSorties.push({
|
||||
_id: {
|
||||
$oid: Math.trunc(weekStart / 1000).toString(16) + "5e23a244740a190c"
|
||||
},
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards",
|
||||
Seed: week,
|
||||
Boss: boss,
|
||||
Missions: [
|
||||
{
|
||||
missionType: rng.randomElement([
|
||||
"MT_INTEL",
|
||||
"MT_MOBILE_DEFENSE",
|
||||
"MT_EXTERMINATION",
|
||||
"MT_SABOTAGE",
|
||||
"MT_RESCUE"
|
||||
]),
|
||||
node: firstNode
|
||||
},
|
||||
{
|
||||
missionType: rng.randomElement([
|
||||
"MT_DEFENSE",
|
||||
"MT_TERRITORY",
|
||||
"MT_ARTIFACT",
|
||||
"MT_EXCAVATE",
|
||||
"MT_SURVIVAL"
|
||||
]),
|
||||
node: rng.randomElement(nodes)
|
||||
},
|
||||
{
|
||||
missionType: "MT_ASSASSINATION",
|
||||
node: showdownNode
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// Circuit choices cycling every week
|
||||
worldState.EndlessXpChoices.push({
|
||||
Category: "EXC_NORMAL",
|
||||
Choices: [
|
||||
["Nidus", "Octavia", "Harrow"],
|
||||
["Gara", "Khora", "Revenant"],
|
||||
["Garuda", "Baruuk", "Hildryn"],
|
||||
["Excalibur", "Trinity", "Ember"],
|
||||
["Loki", "Mag", "Rhino"],
|
||||
["Ash", "Frost", "Nyx"],
|
||||
["Saryn", "Vauban", "Nova"],
|
||||
["Nekros", "Valkyr", "Oberon"],
|
||||
["Hydroid", "Mirage", "Limbo"],
|
||||
["Mesa", "Chroma", "Atlas"],
|
||||
["Ivara", "Inaros", "Titania"]
|
||||
][week % 12]
|
||||
});
|
||||
worldState.EndlessXpChoices.push({
|
||||
Category: "EXC_HARD",
|
||||
Choices: [
|
||||
["Boar", "Gammacor", "Angstrum", "Gorgon", "Anku"],
|
||||
["Bo", "Latron", "Furis", "Furax", "Strun"],
|
||||
["Lex", "Magistar", "Boltor", "Bronco", "CeramicDagger"],
|
||||
["Torid", "DualToxocyst", "DualIchor", "Miter", "Atomos"],
|
||||
["AckAndBrunt", "Soma", "Vasto", "NamiSolo", "Burston"],
|
||||
["Zylok", "Sibear", "Dread", "Despair", "Hate"],
|
||||
["Dera", "Sybaris", "Cestra", "Sicarus", "Okina"],
|
||||
["Braton", "Lato", "Skana", "Paris", "Kunai"]
|
||||
][week % 8]
|
||||
});
|
||||
|
||||
// 1999 Calendar Season cycling every week + YearIteration every 4 weeks
|
||||
worldState.KnownCalendarSeasons[0].Activation = { $date: { $numberLong: weekStart.toString() } };
|
||||
worldState.KnownCalendarSeasons[0].Expiry = { $date: { $numberLong: weekEnd.toString() } };
|
||||
worldState.KnownCalendarSeasons[0].Season = ["CST_WINTER", "CST_SPRING", "CST_SUMMER", "CST_FALL"][week % 4];
|
||||
worldState.KnownCalendarSeasons[0].Days = [
|
||||
static1999WinterDays,
|
||||
static1999SpringDays,
|
||||
static1999SummerDays,
|
||||
static1999FallDays
|
||||
][week % 4];
|
||||
worldState.KnownCalendarSeasons[0].YearIteration = Math.trunc(week / 4);
|
||||
|
||||
// Sentient Anomaly cycling every 30 minutes
|
||||
const halfHour = Math.trunc(Date.now() / (unixTimesInMs.hour / 2));
|
||||
const tmp = {
|
||||
cavabegin: "1690761600",
|
||||
PurchasePlatformLockEnabled: true,
|
||||
tcsn: true,
|
||||
pgr: {
|
||||
ts: "1732572900",
|
||||
en: "CUSTOM DECALS @ ZEVILA",
|
||||
fr: "DECALS CUSTOM @ ZEVILA",
|
||||
it: "DECALCOMANIE PERSONALIZZATE @ ZEVILA",
|
||||
de: "AUFKLEBER NACH WUNSCH @ ZEVILA",
|
||||
es: "CALCOMANÍAS PERSONALIZADAS @ ZEVILA",
|
||||
pt: "DECALQUES PERSONALIZADOS NA ZEVILA",
|
||||
ru: "ПОЛЬЗОВАТЕЛЬСКИЕ НАКЛЕЙКИ @ ЗеВиЛа",
|
||||
pl: "NOWE NAKLEJKI @ ZEVILA",
|
||||
uk: "КОРИСТУВАЦЬКІ ДЕКОЛІ @ ЗІВІЛА",
|
||||
tr: "ÖZEL ÇIKARTMALAR @ ZEVILA",
|
||||
ja: "カスタムデカール @ ゼビラ",
|
||||
zh: "定制贴花认准泽威拉",
|
||||
ko: "커스텀 데칼 @ ZEVILA",
|
||||
tc: "自訂貼花 @ ZEVILA",
|
||||
th: "รูปลอกสั่งทำที่ ZEVILA"
|
||||
},
|
||||
ennnd: true,
|
||||
mbrt: true,
|
||||
sfn: [550, 553, 554, 555][halfHour % 4]
|
||||
};
|
||||
worldState.Tmp = JSON.stringify(tmp);
|
||||
|
||||
res.json(worldState);
|
||||
};
|
||||
|
||||
interface IWorldState {
|
||||
Version: number; // for goals
|
||||
BuildLabel: string;
|
||||
Time: number;
|
||||
Goals: IGoal[];
|
||||
SyndicateMissions: ISyndicateMission[];
|
||||
GlobalUpgrades: IGlobalUpgrade[];
|
||||
Sorties: ISortie[];
|
||||
LiteSorties: ILiteSortie[];
|
||||
NodeOverrides: INodeOverride[];
|
||||
EndlessXpChoices: IEndlessXpChoice[];
|
||||
SeasonInfo: {
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
AffiliationTag: string;
|
||||
Season: number;
|
||||
Phase: number;
|
||||
Params: string;
|
||||
ActiveChallenges: ISeasonChallenge[];
|
||||
};
|
||||
KnownCalendarSeasons: ICalendarSeason[];
|
||||
Tmp?: string;
|
||||
}
|
||||
|
||||
interface IGoal {
|
||||
_id: IOid;
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
Count: number;
|
||||
Goal: number;
|
||||
Success: number;
|
||||
Personal: boolean;
|
||||
Desc: string;
|
||||
ToolTip: string;
|
||||
Icon: string;
|
||||
Tag: string;
|
||||
Node: string;
|
||||
}
|
||||
|
||||
interface ISyndicateMission {
|
||||
_id: IOid;
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
Tag: string;
|
||||
Seed: number;
|
||||
Nodes: string[];
|
||||
}
|
||||
|
||||
interface IGlobalUpgrade {
|
||||
_id: IOid;
|
||||
Activation: IMongoDate;
|
||||
ExpiryDate: IMongoDate;
|
||||
UpgradeType: string;
|
||||
OperationType: string;
|
||||
Value: number;
|
||||
LocalizeTag: string;
|
||||
LocalizeDescTag: string;
|
||||
}
|
||||
|
||||
interface INodeOverride {
|
||||
_id: IOid;
|
||||
Activation?: IMongoDate;
|
||||
Expiry?: IMongoDate;
|
||||
Node: string;
|
||||
Hide?: boolean;
|
||||
Seed?: number;
|
||||
LevelOverride?: string;
|
||||
Faction?: string;
|
||||
CustomNpcEncounters?: string;
|
||||
}
|
||||
|
||||
interface ISortie {
|
||||
_id: IOid;
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
Reward: "/Lotus/Types/Game/MissionDecks/SortieRewards";
|
||||
Seed: number;
|
||||
Boss: string;
|
||||
Variants: {
|
||||
missionType: string;
|
||||
modifierType: string;
|
||||
node: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface ILiteSortie {
|
||||
_id: IOid;
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
Reward: "/Lotus/Types/Game/MissionDecks/ArchonSortieRewards";
|
||||
Seed: number;
|
||||
Boss: string; // "SORTIE_BOSS_AMAR" | "SORTIE_BOSS_NIRA" | "SORTIE_BOSS_BOREAL"
|
||||
Missions: {
|
||||
missionType: string;
|
||||
node: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface IEndlessXpChoice {
|
||||
Category: string;
|
||||
Choices: string[];
|
||||
}
|
||||
|
||||
interface ISeasonChallenge {
|
||||
_id: IOid;
|
||||
Daily?: boolean;
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
Challenge: string;
|
||||
}
|
||||
|
||||
interface ICalendarSeason {
|
||||
Activation: IMongoDate;
|
||||
Expiry: IMongoDate;
|
||||
Season: string; // "CST_UNDEFINED" | "CST_WINTER" | "CST_SPRING" | "CST_SUMMER" | "CST_FALL"
|
||||
Days: {
|
||||
day: number;
|
||||
}[];
|
||||
YearIteration: number;
|
||||
}
|
||||
|
||||
const dailyChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
||||
x.startsWith("/Lotus/Types/Challenges/Seasons/Daily/")
|
||||
);
|
||||
|
||||
const getSeasonDailyChallenge = (day: number): ISeasonChallenge => {
|
||||
const dayStart = EPOCH + day * 86400000;
|
||||
const dayEnd = EPOCH + (day + 3) * 86400000;
|
||||
const rng = new CRng(day);
|
||||
return {
|
||||
_id: { $oid: "67e1b5ca9d00cb47" + day.toString().padStart(8, "0") },
|
||||
Daily: true,
|
||||
Activation: { $date: { $numberLong: dayStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: dayEnd.toString() } },
|
||||
Challenge: rng.randomElement(dailyChallenges)
|
||||
};
|
||||
};
|
||||
|
||||
const weeklyChallenges = Object.keys(ExportNightwave.challenges).filter(
|
||||
x =>
|
||||
x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/") &&
|
||||
!x.startsWith("/Lotus/Types/Challenges/Seasons/Weekly/SeasonWeeklyPermanent")
|
||||
);
|
||||
|
||||
const getSeasonWeeklyChallenge = (week: number, id: number): ISeasonChallenge => {
|
||||
const weekStart = EPOCH + week * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
const challengeId = week * 7 + id;
|
||||
const rng = new CRng(challengeId);
|
||||
return {
|
||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||
Challenge: rng.randomElement(weeklyChallenges)
|
||||
};
|
||||
};
|
||||
|
||||
const weeklyHardChallenges = Object.keys(ExportNightwave.challenges).filter(x =>
|
||||
x.startsWith("/Lotus/Types/Challenges/Seasons/WeeklyHard/")
|
||||
);
|
||||
|
||||
const getSeasonWeeklyHardChallenge = (week: number, id: number): ISeasonChallenge => {
|
||||
const weekStart = EPOCH + week * 604800000;
|
||||
const weekEnd = weekStart + 604800000;
|
||||
const challengeId = week * 7 + id;
|
||||
const rng = new CRng(challengeId);
|
||||
return {
|
||||
_id: { $oid: "67e1bb2d9d00cb47" + challengeId.toString().padStart(8, "0") },
|
||||
Activation: { $date: { $numberLong: weekStart.toString() } },
|
||||
Expiry: { $date: { $numberLong: weekEnd.toString() } },
|
||||
Challenge: rng.randomElement(weeklyHardChallenges)
|
||||
};
|
||||
res.json(getWorldState(req.query.buildLabel as string | undefined));
|
||||
};
|
||||
|
@ -27,7 +27,15 @@ const viewController: RequestHandler = async (req, res) => {
|
||||
for (const type of Object.keys(ExportEnemies.avatars)) {
|
||||
if (!scans.has(type)) scans.add(type);
|
||||
}
|
||||
responseJson.Scans ??= [];
|
||||
|
||||
// Take any existing scans and also set them to 9999
|
||||
if (responseJson.Scans) {
|
||||
for (const scan of responseJson.Scans) {
|
||||
scans.add(scan.type);
|
||||
}
|
||||
}
|
||||
responseJson.Scans = [];
|
||||
|
||||
for (const type of scans) {
|
||||
responseJson.Scans.push({ type: type, scans: 9999 });
|
||||
}
|
||||
|
@ -48,7 +48,8 @@ const toDatabaseAccount = (createAccount: IAccountCreation): IDatabaseAccountReq
|
||||
CrossPlatformAllowed: true,
|
||||
ForceLogoutVersion: 0,
|
||||
TrackedSettings: [],
|
||||
Nonce: 0
|
||||
Nonce: 0,
|
||||
LastLogin: new Date()
|
||||
} satisfies IDatabaseAccountRequiredFields;
|
||||
};
|
||||
|
||||
|
@ -1,10 +1,193 @@
|
||||
import { IMongoDate, IOid } from "@/src/types/commonTypes";
|
||||
import { IMongoDate, IOid, IOidWithLegacySupport } from "@/src/types/commonTypes";
|
||||
import { Types } from "mongoose";
|
||||
import { TRarity } from "warframe-public-export-plus";
|
||||
|
||||
export const version_compare = (a: string, b: string): number => {
|
||||
const a_digits = a
|
||||
.split("/")[0]
|
||||
.split(".")
|
||||
.map(x => parseInt(x));
|
||||
const b_digits = b
|
||||
.split("/")[0]
|
||||
.split(".")
|
||||
.map(x => parseInt(x));
|
||||
for (let i = 0; i != a_digits.length; ++i) {
|
||||
if (a_digits[i] != b_digits[i]) {
|
||||
return a_digits[i] > b_digits[i] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const toOid = (objectId: Types.ObjectId): IOid => {
|
||||
return { $oid: objectId.toString() } satisfies IOid;
|
||||
return { $oid: objectId.toString() };
|
||||
};
|
||||
|
||||
export function toOid2(objectId: Types.ObjectId, buildLabel: undefined): IOid;
|
||||
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport;
|
||||
export function toOid2(objectId: Types.ObjectId, buildLabel: string | undefined): IOidWithLegacySupport {
|
||||
if (buildLabel && version_compare(buildLabel, "2016.12.21.19.13") <= 0) {
|
||||
return { $id: objectId.toString() };
|
||||
}
|
||||
return { $oid: objectId.toString() };
|
||||
}
|
||||
|
||||
export const toLegacyOid = (oid: IOidWithLegacySupport): void => {
|
||||
if (!("$id" in oid)) {
|
||||
oid.$id = oid.$oid;
|
||||
delete oid.$oid;
|
||||
}
|
||||
};
|
||||
|
||||
export const fromOid = (oid: IOidWithLegacySupport): string => {
|
||||
return (oid.$oid ?? oid.$id)!;
|
||||
};
|
||||
|
||||
export const toMongoDate = (date: Date): IMongoDate => {
|
||||
return { $date: { $numberLong: date.getTime().toString() } };
|
||||
};
|
||||
|
||||
export const fromMongoDate = (date: IMongoDate): Date => {
|
||||
return new Date(parseInt(date.$date.$numberLong));
|
||||
};
|
||||
|
||||
export const kubrowWeights: Record<TRarity, number> = {
|
||||
COMMON: 6,
|
||||
UNCOMMON: 4,
|
||||
RARE: 2,
|
||||
LEGENDARY: 1
|
||||
};
|
||||
|
||||
export const kubrowFurPatternsWeights: Record<TRarity, number> = {
|
||||
COMMON: 6,
|
||||
UNCOMMON: 5,
|
||||
RARE: 2,
|
||||
LEGENDARY: 1
|
||||
};
|
||||
|
||||
export const catbrowDetails = {
|
||||
Colors: [
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseA", rarity: "COMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseB", rarity: "COMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseC", rarity: "COMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorBaseD", rarity: "COMMON" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryA", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryB", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryC", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorSecondaryD", rarity: "UNCOMMON" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryA", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryB", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryC", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorTertiaryD", rarity: "RARE" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsA", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsB", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsC", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorAccentsD", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
EyeColors: [
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesI", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesJ", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesK", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesL", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesM", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Colors/CatbrowPetColorEyesN", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
FurPatterns: [{ type: "/Lotus/Types/Game/CatbrowPet/Patterns/CatbrowPetPatternA", rarity: "COMMON" as TRarity }],
|
||||
|
||||
BodyTypes: [
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/BodyTypes/CatbrowPetRegularBodyType", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
Heads: [
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadA", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadB", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadC", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Heads/CatbrowHeadD", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
Tails: [
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailA", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailB", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailC", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailD", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/CatbrowPet/Tails/CatbrowTailE", rarity: "LEGENDARY" as TRarity }
|
||||
]
|
||||
};
|
||||
|
||||
export const kubrowDetails = {
|
||||
Colors: [
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneA", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneB", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneC", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneD", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneE", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneF", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneG", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMundaneH", rarity: "UNCOMMON" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidA", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidB", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidC", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidD", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidE", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidF", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidG", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorMidH", rarity: "RARE" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantA", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantB", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantC", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantD", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantE", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantF", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantG", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorVibrantH", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
EyeColors: [
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesA", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesB", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesC", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesD", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesE", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesF", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesG", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesH", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Colors/KubrowPetColorEyesI", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
FurPatterns: [
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternB", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternA", rarity: "UNCOMMON" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternC", rarity: "RARE" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternD", rarity: "RARE" as TRarity },
|
||||
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternE", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/Patterns/KubrowPetPatternF", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
BodyTypes: [
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetRegularBodyType", rarity: "UNCOMMON" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetHeavyBodyType", rarity: "LEGENDARY" as TRarity },
|
||||
{ type: "/Lotus/Types/Game/KubrowPet/BodyTypes/KubrowPetThinBodyType", rarity: "LEGENDARY" as TRarity }
|
||||
],
|
||||
|
||||
Heads: [],
|
||||
|
||||
Tails: []
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user